From ef412d905c4efef69aebd02a87b50bfbf1294da8 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Tue, 1 Aug 2023 18:39:58 +0200 Subject: [PATCH 01/57] Add Singularity support to existing run script --- run-docker.sh | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/run-docker.sh b/run-docker.sh index c24dcec724..5e5ac4401c 100755 --- a/run-docker.sh +++ b/run-docker.sh @@ -97,6 +97,7 @@ SCRIPTPATH=$(dirname "$SCRIPT") : ${OHMYXILINX="${SCRIPTPATH}/deps/oh-my-xilinx"} : ${NVIDIA_VISIBLE_DEVICES=""} : ${DOCKER_BUILDKIT="1"} +: ${FINN_SINGULARITY=""} DOCKER_INTERACTIVE="" @@ -116,8 +117,10 @@ elif [ "$1" = "notebook" ]; then DOCKER_CMD="jupyter notebook --allow-root --no-browser --ip=0.0.0.0 --port $JUPYTER_PORT $JUPYTER_PASSWD_ARG notebooks" FINN_DOCKER_EXTRA+="-e JUPYTER_PORT=$JUPYTER_PORT " FINN_DOCKER_EXTRA+="-e NETRON_PORT=$NETRON_PORT " - FINN_DOCKER_EXTRA+="-p $JUPYTER_PORT:$JUPYTER_PORT " - FINN_DOCKER_EXTRA+="-p $NETRON_PORT:$NETRON_PORT " + if [ -z "$FINN_SINGULARITY" ]; then + FINN_DOCKER_EXTRA+="-p $JUPYTER_PORT:$JUPYTER_PORT " + FINN_DOCKER_EXTRA+="-p $NETRON_PORT:$NETRON_PORT " + fi elif [ "$1" = "build_dataflow" ]; then BUILD_DATAFLOW_DIR=$(readlink -f "$2") FINN_DOCKER_EXTRA+="-v $BUILD_DATAFLOW_DIR:$BUILD_DATAFLOW_DIR " @@ -143,7 +146,7 @@ else fi -if [ "$FINN_DOCKER_GPU" != 0 ];then +if [ "$FINN_DOCKER_GPU" != 0 ] && [ -z "$FINN_SINGULARITY" ];then gecho "nvidia-docker detected, enabling GPUs" if [ ! -z "$NVIDIA_VISIBLE_DEVICES" ];then FINN_DOCKER_EXTRA+="--runtime nvidia -e NVIDIA_VISIBLE_DEVICES=$NVIDIA_VISIBLE_DEVICES " @@ -174,7 +177,7 @@ if [ "$FINN_SKIP_DEP_REPOS" = "0" ]; then fi # Build the FINN Docker image -if [ "$FINN_DOCKER_PREBUILT" = "0" ]; then +if [ "$FINN_DOCKER_PREBUILT" = "0" ] && [ -z "$FINN_SINGULARITY" ]; then # Need to ensure this is done within the finn/ root folder: OLD_PWD=$(pwd) cd $SCRIPTPATH @@ -184,9 +187,8 @@ fi # Launch container with current directory mounted # important to pass the --init flag here for correct Vivado operation, see: # https://stackoverflow.com/questions/55733058/vivado-synthesis-hangs-in-docker-container-spawned-by-jenkins -DOCKER_EXEC="docker run -t --rm $DOCKER_INTERACTIVE --tty --init " -DOCKER_EXEC+="--hostname $DOCKER_INST_NAME " -DOCKER_EXEC+="-e SHELL=/bin/bash " +DOCKER_BASE="docker run -t --rm $DOCKER_INTERACTIVE --tty --init --hostname $DOCKER_INST_NAME " +DOCKER_EXEC="-e SHELL=/bin/bash " DOCKER_EXEC+="-w $SCRIPTPATH " DOCKER_EXEC+="-v $SCRIPTPATH:$SCRIPTPATH " DOCKER_EXEC+="-v $FINN_HOST_BUILD_DIR:$FINN_HOST_BUILD_DIR " @@ -204,7 +206,7 @@ DOCKER_EXEC+="-e NUM_DEFAULT_WORKERS=$NUM_DEFAULT_WORKERS " # Workaround for FlexLM issue, see: # https://community.flexera.com/t5/InstallAnywhere-Forum/Issues-when-running-Xilinx-tools-or-Other-vendor-tools-in-docker/m-p/245820#M10647 DOCKER_EXEC+="-e LD_PRELOAD=/lib/x86_64-linux-gnu/libudev.so.1 " -if [ "$FINN_DOCKER_RUN_AS_ROOT" = "0" ];then +if [ "$FINN_DOCKER_RUN_AS_ROOT" = "0" ] && [ -z "$FINN_SINGULARITY" ];then DOCKER_EXEC+="-v /etc/group:/etc/group:ro " DOCKER_EXEC+="-v /etc/passwd:/etc/passwd:ro " DOCKER_EXEC+="-v /etc/shadow:/etc/shadow:ro " @@ -244,6 +246,16 @@ if [ ! -z "$FINN_XILINX_PATH" ];then fi fi DOCKER_EXEC+="$FINN_DOCKER_EXTRA " -DOCKER_EXEC+="$FINN_DOCKER_TAG $DOCKER_CMD" -$DOCKER_EXEC +if [ -z "$FINN_SINGULARITY" ];then + CMD_TO_RUN="$DOCKER_BASE $DOCKER_EXEC $FINN_DOCKER_TAG $DOCKER_CMD" +else + SINGULARITY_BASE="singularity exec" + # Replace command options for Singularity + SINGULARITY_EXEC="${DOCKER_EXEC//"-e "/"--env "}" + SINGULARITY_EXEC="${SINGULARITY_EXEC//"-v "/"-B "}" + SINGULARITY_EXEC="${SINGULARITY_EXEC//"-w "/"--pwd "}" + CMD_TO_RUN="$SINGULARITY_BASE $SINGULARITY_EXEC $FINN_SINGULARITY /usr/local/bin/finn_entrypoint.sh $DOCKER_CMD" +fi + +$CMD_TO_RUN From 8adee813a9a197546b9977afb8ac8d763a7f54a5 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 3 Aug 2023 18:17:40 +0200 Subject: [PATCH 02/57] Add GHA to build & test singularity container --- .github/workflows/singularity-quicktest.yml | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/singularity-quicktest.yml diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml new file mode 100644 index 0000000000..90f4e8b7f6 --- /dev/null +++ b/.github/workflows/singularity-quicktest.yml @@ -0,0 +1,39 @@ +name: SingularityQuicktest + +on: + pull_request: + branches: [ dev ] + push: + branches: [ dev ] + +jobs: + build_and_test: + runs-on: ubuntu-22.04 + steps: + - name: checkout + uses: actions/checkout@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Set up Singularity + run: | + sudo add-apt-repository -y ppa:apptainer/ppa + sudo apt update + sudo apt install -y apptainer + alias singularity="apptainer" + + - name: Build Docker image + uses: docker/build-push-action@v4 + with: + file: docker/Dockerfile.finn + context: . + tags: finn_docker_export:latest + - name: Build Singularity image + run: singularity build finn_singularity_image.sif docker-daemon://finn_docker_export:latest + + - name: Run quicktest + run: | + export FINN_ROOT=$(pwd) + export FINN_BUILD_DIR=/tmp/finn_gha + export FINN_INST_NAME=finn_gha + export FINN_SINGULARITY=finn_singularity_image.sif + ./run-docker.sh quicktest From 39e88cd1b0f431c9d188fe422d7ca7334af8db26 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 3 Aug 2023 18:37:09 +0200 Subject: [PATCH 03/57] [Singularity] Add documentation --- .github/workflows/singularity-quicktest.yml | 4 +++- docs/finn/getting_started.rst | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml index 90f4e8b7f6..04906a9b70 100644 --- a/.github/workflows/singularity-quicktest.yml +++ b/.github/workflows/singularity-quicktest.yml @@ -8,9 +8,10 @@ on: jobs: build_and_test: + name: Build and test Singularity container runs-on: ubuntu-22.04 steps: - - name: checkout + - name: Checkout uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -26,6 +27,7 @@ jobs: with: file: docker/Dockerfile.finn context: . + load: true tags: finn_docker_export:latest - name: Build Singularity image run: singularity build finn_singularity_image.sif docker-daemon://finn_docker_export:latest diff --git a/docs/finn/getting_started.rst b/docs/finn/getting_started.rst index c575ca7e3b..2edac294d9 100644 --- a/docs/finn/getting_started.rst +++ b/docs/finn/getting_started.rst @@ -116,6 +116,7 @@ These are summarized below: * (optional) ``FINN_SKIP_DEP_REPOS`` (default "0") skips the download of FINN dependency repos (uses the ones already downloaded under deps/. * (optional) ``NVIDIA_VISIBLE_DEVICES`` (default "") specifies specific Nvidia GPUs to use in Docker container. Possible values are a comma-separated list of GPU UUID(s) or index(es) e.g. ``0,1,2``, ``all``, ``none``, or void/empty/unset. * (optional) ``DOCKER_BUILDKIT`` (default "1") enables `Docker BuildKit `_ for faster Docker image rebuilding (recommended). +* (optional) ``FINN_SINGULARITY`` (default "") points to a pre-built Singularity image to use instead of the Docker image. Singularity support is experimental and intended only for systems where Docker is unavailable. Does not support GPUs. General FINN Docker tips ************************ From 7d7d0611f9694f8651bd79a40a8a038d150eb578 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 3 Aug 2023 19:14:13 +0200 Subject: [PATCH 04/57] [Singularity] Fixes for GHA --- .github/workflows/singularity-quicktest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml index 04906a9b70..a8fdc6ab76 100644 --- a/.github/workflows/singularity-quicktest.yml +++ b/.github/workflows/singularity-quicktest.yml @@ -27,7 +27,7 @@ jobs: with: file: docker/Dockerfile.finn context: . - load: true + outputs: type=image,name=finn_docker_export tags: finn_docker_export:latest - name: Build Singularity image run: singularity build finn_singularity_image.sif docker-daemon://finn_docker_export:latest From ed1a325c26f2ce2ce72c89690d9ed08b6988b92e Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 3 Aug 2023 19:43:12 +0200 Subject: [PATCH 05/57] [Singularity] Fixes for GHA --- .github/workflows/singularity-quicktest.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml index a8fdc6ab76..603cfae71b 100644 --- a/.github/workflows/singularity-quicktest.yml +++ b/.github/workflows/singularity-quicktest.yml @@ -13,8 +13,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - name: Set up Docker Buildx + - name: Set up Docker uses: docker/setup-buildx-action@v2 + with: + driver: docker - name: Set up Singularity run: | sudo add-apt-repository -y ppa:apptainer/ppa @@ -27,7 +29,7 @@ jobs: with: file: docker/Dockerfile.finn context: . - outputs: type=image,name=finn_docker_export + load: true tags: finn_docker_export:latest - name: Build Singularity image run: singularity build finn_singularity_image.sif docker-daemon://finn_docker_export:latest From 5aa85d01c199f893b8925573836d71c95993d86d Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 4 Aug 2023 17:06:27 +0200 Subject: [PATCH 06/57] [Singularity] Increase build space for GHA --- .github/workflows/singularity-quicktest.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml index 603cfae71b..7892963081 100644 --- a/.github/workflows/singularity-quicktest.yml +++ b/.github/workflows/singularity-quicktest.yml @@ -11,6 +11,13 @@ jobs: name: Build and test Singularity container runs-on: ubuntu-22.04 steps: + - name: Maximize build space + uses: easimon/maximize-build-space@master + with: + remove-docker-images: 'true' + remove-android: 'true' + remove-haskell: 'true' + remove-codeql: 'true' - name: Checkout uses: actions/checkout@v3 - name: Set up Docker From 71259c5f323656735ff6a697f3df408955f620e5 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 4 Aug 2023 17:32:34 +0200 Subject: [PATCH 07/57] [Singularity] Adjust build space for GHA --- .github/workflows/singularity-quicktest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml index 7892963081..aa748667de 100644 --- a/.github/workflows/singularity-quicktest.yml +++ b/.github/workflows/singularity-quicktest.yml @@ -14,6 +14,7 @@ jobs: - name: Maximize build space uses: easimon/maximize-build-space@master with: + root-reserve-mv: '32768' remove-docker-images: 'true' remove-android: 'true' remove-haskell: 'true' From 3ed04b820a3538be2e5e8041ebc12dfa29f41cd6 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 4 Aug 2023 17:57:06 +0200 Subject: [PATCH 08/57] [Singularity] GHA fixes --- .github/workflows/singularity-quicktest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml index aa748667de..39054ffe2e 100644 --- a/.github/workflows/singularity-quicktest.yml +++ b/.github/workflows/singularity-quicktest.yml @@ -14,7 +14,7 @@ jobs: - name: Maximize build space uses: easimon/maximize-build-space@master with: - root-reserve-mv: '32768' + root-reserve-mb: '32768' remove-docker-images: 'true' remove-android: 'true' remove-haskell: 'true' From 8f263bc9d98d7cf41a9fb13ad78b95aecd72e9b8 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 4 Aug 2023 18:34:56 +0200 Subject: [PATCH 09/57] [Singularity] Adjust GHA --- .github/workflows/singularity-quicktest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml index 39054ffe2e..a185dc5ec2 100644 --- a/.github/workflows/singularity-quicktest.yml +++ b/.github/workflows/singularity-quicktest.yml @@ -14,7 +14,7 @@ jobs: - name: Maximize build space uses: easimon/maximize-build-space@master with: - root-reserve-mb: '32768' + root-reserve-mb: '16384' remove-docker-images: 'true' remove-android: 'true' remove-haskell: 'true' From be61c311c7f87682fee45c831b6e5082f47aeefe Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 4 Aug 2023 19:00:14 +0200 Subject: [PATCH 10/57] [Singularity] Adjust GHA --- .github/workflows/singularity-quicktest.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml index a185dc5ec2..bffdc9ade8 100644 --- a/.github/workflows/singularity-quicktest.yml +++ b/.github/workflows/singularity-quicktest.yml @@ -14,11 +14,13 @@ jobs: - name: Maximize build space uses: easimon/maximize-build-space@master with: - root-reserve-mb: '16384' + root-reserve-mb: '24576' + swap-size-mb: '1024' remove-docker-images: 'true' remove-android: 'true' remove-haskell: 'true' remove-codeql: 'true' + remove-dotnet: 'true' - name: Checkout uses: actions/checkout@v3 - name: Set up Docker From a61ac9b96566eaaa083854b87c8dee5794ddea70 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 4 Aug 2023 19:36:49 +0200 Subject: [PATCH 11/57] [Singularity] Adjust GHA --- .github/workflows/singularity-quicktest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/singularity-quicktest.yml b/.github/workflows/singularity-quicktest.yml index bffdc9ade8..f1efe7666c 100644 --- a/.github/workflows/singularity-quicktest.yml +++ b/.github/workflows/singularity-quicktest.yml @@ -14,8 +14,8 @@ jobs: - name: Maximize build space uses: easimon/maximize-build-space@master with: - root-reserve-mb: '24576' - swap-size-mb: '1024' + root-reserve-mb: '32768' + swap-size-mb: '128' remove-docker-images: 'true' remove-android: 'true' remove-haskell: 'true' From cbf9f4847c1a0581c3cea30dc97da4ea68f9162f Mon Sep 17 00:00:00 2001 From: bwintermann Date: Tue, 15 Aug 2023 10:35:30 +0200 Subject: [PATCH 12/57] Skeleton for C driver generation --- src/finn/builder/build_dataflow_steps.py | 14 +- .../fpgadataflow/get_driver_shapes.py | 123 +++++++++++++++ .../{make_pynq_driver.py => make_driver.py} | 143 ++++-------------- tests/end2end/test_end2end_bnn_pynq.py | 2 +- 4 files changed, 169 insertions(+), 113 deletions(-) create mode 100644 src/finn/transformation/fpgadataflow/get_driver_shapes.py rename src/finn/transformation/fpgadataflow/{make_pynq_driver.py => make_driver.py} (57%) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 54ba7e4ea1..b50a63407e 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -87,7 +87,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.make_pynq_driver import MakePYNQDriver +from finn.transformation.fpgadataflow.make_driver import MakePYNQDriver, MakeCDriver from finn.transformation.fpgadataflow.make_zynq_proj import ZynqBuild from finn.transformation.fpgadataflow.minimize_accumulator_width import ( MinimizeAccumulatorWidth, @@ -719,13 +719,23 @@ def step_make_pynq_driver(model: ModelWrapper, cfg: DataflowBuildConfig): accelerator.""" if DataflowOutputType.PYNQ_DRIVER in cfg.generate_outputs: - driver_dir = cfg.output_dir + "/driver" + driver_dir = os.path.join(cfg.output_dir, "driver") model = model.transform(MakePYNQDriver(cfg._resolve_driver_platform())) copy_tree(model.get_metadata_prop("pynq_driver_dir"), driver_dir) print("PYNQ Python driver written into " + driver_dir) return model +def step_make_c_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> ModelWrapper: + if DataflowOutputType.C_DRIVER in cfg.generate_outputs: + driver_dir = os.path.join(cfg.output_dir, "driver") + model = model.transform(MakeCDriver(cfg._resolve_driver_platform())) + + # TODO: Compilation + # TODO: Copying into driver directory + return model + + def step_out_of_context_synthesis(model: ModelWrapper, cfg: DataflowBuildConfig): """Run out-of-context synthesis and generate reports. Depends on the DataflowOutputType.STITCHED_IP output product.""" diff --git a/src/finn/transformation/fpgadataflow/get_driver_shapes.py b/src/finn/transformation/fpgadataflow/get_driver_shapes.py new file mode 100644 index 0000000000..6706a63249 --- /dev/null +++ b/src/finn/transformation/fpgadataflow/get_driver_shapes.py @@ -0,0 +1,123 @@ +from qonnx.core.modelwrapper import ModelWrapper +from qonnx.custom_op.registry import getCustomOp +from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple +import finn.util.data_packing as dpk +from finn.util.data_packing import ( + hexstring2npbytearray, + pack_innermost_dim_as_hex_string, +) +from typing import Dict + + +# TODO: License? + +def to_external_tensor(init, w_dtype): + """Return an appropriately formatted and packed numpy byte array for given + external parameter tensor.""" + + weight_width = init.shape[1] * w_dtype.bitwidth() + weight_width_padded = roundup_to_integer_multiple(weight_width, 4) + hex_init = pack_innermost_dim_as_hex_string(init, w_dtype, weight_width_padded, prefix="0x") + ext_weight = np.array([], dtype=np.uint8) + for line in hex_init: + array_line = [x for x in reversed(hexstring2npbytearray(line, remove_prefix="0x"))] + ext_weight = np.append(ext_weight, array_line) + + return ext_weight + + +def get_driver_shapes(model: ModelWrapper) -> Dict: + idt = [] + idma_names = [] + ishape_normal = [] + ishape_folded = [] + ishape_packed = [] + for idma_ind, graph_in in enumerate(model.graph.input): + i_tensor_name = graph_in.name + # get inp tensor properties + i_tensor_dt = model.get_tensor_datatype(i_tensor_name) + i_tensor_shape_normal = tuple(model.get_tensor_shape(i_tensor_name)) + # go down into dataflow partition to get folded shape info etc + # TODO consider setting these as attributes during dataflow partitioning + i_consumer = model.find_consumer(i_tensor_name) + assert ( + i_consumer.op_type == "StreamingDataflowPartition" + ), """ + Ensure CreateDataflowPartition called before driver creation.""" + first_df_model = ModelWrapper(getCustomOp(i_consumer).get_nodeattr("model")) + assert ( + first_df_model.graph.node[0].op_type == "IODMA" + ), "First partition must hold input IODMA" + successors = model.find_direct_successors(i_consumer) + successor_input_num = list(successors[0].input).index(i_consumer.output[0]) + successor_sdp = getCustomOp(successors[0]) + successor_df_model = ModelWrapper(successor_sdp.get_nodeattr("model")) + first_node = successor_df_model.find_consumer( + successor_df_model.graph.input[successor_input_num].name + ) + i_tensor_shape_folded = tuple(getCustomOp(first_node).get_folded_input_shape()) + # generate dummy folded i/o tensors and their packed versions + i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded) + i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( + i_tensor_dummy_folded, i_tensor_dt + ) + i_tensor_shape_packed = i_tensor_dummy_packed.shape + # append all input tensor info to relevant lists + idt.append("DataType['%s']" % i_tensor_dt.name) + ishape_normal.append(i_tensor_shape_normal) + ishape_folded.append(i_tensor_shape_folded) + ishape_packed.append(i_tensor_shape_packed) + idma_names.append(getCustomOp(i_consumer).get_nodeattr("instance_name")) + + odt = [] + odma_names = [] + oshape_normal = [] + oshape_folded = [] + oshape_packed = [] + for odma_ind, graph_out in enumerate(model.graph.output): + o_tensor_name = graph_out.name + # get inp tensor properties + o_tensor_dt = model.get_tensor_datatype(o_tensor_name) + o_tensor_shape_normal = tuple(model.get_tensor_shape(o_tensor_name)) + # go down into IODMA partition to get folded shape info etc + # TODO consider setting these as attributes during dataflow partitioning + o_producer = model.find_producer(o_tensor_name) + assert ( + o_producer.op_type == "StreamingDataflowPartition" + ), """ + Ensure CreateDataflowPartition called before driver creation.""" + df_model = ModelWrapper(getCustomOp(o_producer).get_nodeattr("model")) + assert df_model.graph.node[-1].op_type == "IODMA", "Partition must hold output IODMA" + predecessors = model.find_direct_predecessors(o_producer) + predecessor_output_num = list(predecessors[0].output).index(o_producer.input[0]) + predecessor_sdp = getCustomOp(predecessors[0]) + predecessor_df_model = ModelWrapper(predecessor_sdp.get_nodeattr("model")) + last_node = predecessor_df_model.find_producer( + predecessor_df_model.graph.output[predecessor_output_num].name + ) + o_tensor_shape_folded = tuple(getCustomOp(last_node).get_folded_output_shape()) + o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded) + o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( + o_tensor_dummy_folded, o_tensor_dt + ) + o_tensor_shape_packed = o_tensor_dummy_packed.shape + # append all output tensor info to relevant lists + odt.append("DataType['%s']" % o_tensor_dt.name) + oshape_normal.append(o_tensor_shape_normal) + oshape_folded.append(o_tensor_shape_folded) + oshape_packed.append(o_tensor_shape_packed) + odma_names.append(getCustomOp(o_producer).get_nodeattr("instance_name")) + + return { + "idt": idt, + "idma_names": idma_names, + "ishape_normal": ishape_normal, + "ishape_folded": ishape_folded, + "ishape_packed": ishape_packed, + + "odt": odt, + "odma_names": odma_names, + "oshape_normal": oshape_normal, + "oshape_folded": oshape_folded, + "oshape_packed": oshape_packed, + } \ No newline at end of file diff --git a/src/finn/transformation/fpgadataflow/make_pynq_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py similarity index 57% rename from src/finn/transformation/fpgadataflow/make_pynq_driver.py rename to src/finn/transformation/fpgadataflow/make_driver.py index 5a0e47c130..cd86ce7c19 100644 --- a/src/finn/transformation/fpgadataflow/make_pynq_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -34,36 +34,37 @@ import qonnx import shutil import warnings +from typing import Dict, List, Tuple from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp from qonnx.transformation.base import Transformation -from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple import finn.util -import finn.util.data_packing as dpk from finn.util.basic import make_build_dir -from finn.util.data_packing import ( - hexstring2npbytearray, - pack_innermost_dim_as_hex_string, -) + +from finn.transformation.fpgadataflow.get_driver_shapes import get_driver_shapes, to_external_tensor from . import template_driver -def to_external_tensor(init, w_dtype): - """Return an appropriately formatted and packed numpy byte array for given - external parameter tensor.""" +class MakeCDriver(Transformation): + def __init__(self, platform: str): + super().__init__() + self.platform: str = platform + + def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: + # TODO: Preparing folders and files + driver_shapes: Dict = get_driver_shapes(model) - weight_width = init.shape[1] * w_dtype.bitwidth() - weight_width_padded = roundup_to_integer_multiple(weight_width, 4) - hex_init = pack_innermost_dim_as_hex_string(init, w_dtype, weight_width_padded, prefix="0x") - ext_weight = np.array([], dtype=np.uint8) - for line in hex_init: - array_line = [x for x in reversed(hexstring2npbytearray(line, remove_prefix="0x"))] - ext_weight = np.append(ext_weight, array_line) + definitions_header: str = "" + for name in driver_shapes.keys(): + # FIXME: Convert to C Arrays instead of python lists + definitions_header += f"#include {name.upper} {driver_shapes[name]}" + definitions_header += "#include EXT_WEIGHT_NUM " + str(ext_weight_dma_cnt) - return ext_weight + # TODO: Generating weight files + return (model, False) class MakePYNQDriver(Transformation): """Create PYNQ Python code to correctly interface the generated @@ -128,88 +129,10 @@ def apply(self, model): ) for src_file, target_file in files_to_copy: shutil.copy(src_file, target_file) - # extract input-output shapes from the graph + + # Extract input-output shapes from the graph # TODO convert this to an analysis pass? - idt = [] - idma_names = [] - ishape_normal = [] - ishape_folded = [] - ishape_packed = [] - for idma_ind, graph_in in enumerate(model.graph.input): - i_tensor_name = graph_in.name - # get inp tensor properties - i_tensor_dt = model.get_tensor_datatype(i_tensor_name) - i_tensor_shape_normal = tuple(model.get_tensor_shape(i_tensor_name)) - # go down into dataflow partition to get folded shape info etc - # TODO consider setting these as attributes during dataflow partitioning - i_consumer = model.find_consumer(i_tensor_name) - assert ( - i_consumer.op_type == "StreamingDataflowPartition" - ), """ - Ensure CreateDataflowPartition called before driver creation.""" - first_df_model = ModelWrapper(getCustomOp(i_consumer).get_nodeattr("model")) - assert ( - first_df_model.graph.node[0].op_type == "IODMA" - ), "First partition must hold input IODMA" - successors = model.find_direct_successors(i_consumer) - successor_input_num = list(successors[0].input).index(i_consumer.output[0]) - successor_sdp = getCustomOp(successors[0]) - successor_df_model = ModelWrapper(successor_sdp.get_nodeattr("model")) - first_node = successor_df_model.find_consumer( - successor_df_model.graph.input[successor_input_num].name - ) - i_tensor_shape_folded = tuple(getCustomOp(first_node).get_folded_input_shape()) - # generate dummy folded i/o tensors and their packed versions - i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded) - i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - i_tensor_dummy_folded, i_tensor_dt - ) - i_tensor_shape_packed = i_tensor_dummy_packed.shape - # append all input tensor info to relevant lists - idt.append("DataType['%s']" % i_tensor_dt.name) - ishape_normal.append(i_tensor_shape_normal) - ishape_folded.append(i_tensor_shape_folded) - ishape_packed.append(i_tensor_shape_packed) - idma_names.append(getCustomOp(i_consumer).get_nodeattr("instance_name")) - - odt = [] - odma_names = [] - oshape_normal = [] - oshape_folded = [] - oshape_packed = [] - for odma_ind, graph_out in enumerate(model.graph.output): - o_tensor_name = graph_out.name - # get inp tensor properties - o_tensor_dt = model.get_tensor_datatype(o_tensor_name) - o_tensor_shape_normal = tuple(model.get_tensor_shape(o_tensor_name)) - # go down into IODMA partition to get folded shape info etc - # TODO consider setting these as attributes during dataflow partitioning - o_producer = model.find_producer(o_tensor_name) - assert ( - o_producer.op_type == "StreamingDataflowPartition" - ), """ - Ensure CreateDataflowPartition called before driver creation.""" - df_model = ModelWrapper(getCustomOp(o_producer).get_nodeattr("model")) - assert df_model.graph.node[-1].op_type == "IODMA", "Partition must hold output IODMA" - predecessors = model.find_direct_predecessors(o_producer) - predecessor_output_num = list(predecessors[0].output).index(o_producer.input[0]) - predecessor_sdp = getCustomOp(predecessors[0]) - predecessor_df_model = ModelWrapper(predecessor_sdp.get_nodeattr("model")) - last_node = predecessor_df_model.find_producer( - predecessor_df_model.graph.output[predecessor_output_num].name - ) - o_tensor_shape_folded = tuple(getCustomOp(last_node).get_folded_output_shape()) - o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded) - o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - o_tensor_dummy_folded, o_tensor_dt - ) - o_tensor_shape_packed = o_tensor_dummy_packed.shape - # append all output tensor info to relevant lists - odt.append("DataType['%s']" % o_tensor_dt.name) - oshape_normal.append(o_tensor_shape_normal) - oshape_folded.append(o_tensor_shape_folded) - oshape_packed.append(o_tensor_shape_packed) - odma_names.append(getCustomOp(o_producer).get_nodeattr("instance_name")) + driver_shapes: Dict = get_driver_shapes(model) # generate external weights npy files weights_dir = pynq_driver_dir + "/runtime_weights" @@ -249,18 +172,18 @@ def apply(self, model): driver = template_driver.pynq_driver_template driver = driver.replace("$PLATFORM$", self.platform) - driver = driver.replace("$INPUT_FINN_DATATYPE$", str(idt).replace('"', "")) - driver = driver.replace("$INPUT_SHAPE_NORMAL$", str(ishape_normal)) - driver = driver.replace("$INPUT_SHAPE_FOLDED$", str(ishape_folded)) - driver = driver.replace("$INPUT_SHAPE_PACKED$", str(ishape_packed)) - driver = driver.replace("$OUTPUT_FINN_DATATYPE$", str(odt).replace('"', "")) - driver = driver.replace("$OUTPUT_SHAPE_NORMAL$", str(oshape_normal)) - driver = driver.replace("$OUTPUT_SHAPE_FOLDED$", str(oshape_folded)) - driver = driver.replace("$OUTPUT_SHAPE_PACKED$", str(oshape_packed)) - driver = driver.replace("$INPUT_DMA_NAME$", "%s" % str(idma_names)) - driver = driver.replace("$OUTPUT_DMA_NAME$", "%s" % str(odma_names)) - driver = driver.replace("$NUM_INPUTS$", str(len(idma_names))) - driver = driver.replace("$NUM_OUTPUTS$", str(len(odma_names))) + driver = driver.replace("$INPUT_FINN_DATATYPE$", str(driver_shapes["idt"]).replace('"', "")) + driver = driver.replace("$INPUT_SHAPE_NORMAL$", str(driver_shapes["ishape_normal"])) + driver = driver.replace("$INPUT_SHAPE_FOLDED$", str(driver_shapes["ishape_folded"])) + driver = driver.replace("$INPUT_SHAPE_PACKED$", str(driver_shapes["ishape_packed"])) + driver = driver.replace("$OUTPUT_FINN_DATATYPE$", str(driver_shapes["odt"]).replace('"', "")) + driver = driver.replace("$OUTPUT_SHAPE_NORMAL$", str(driver_shapes["oshape_normal"])) + driver = driver.replace("$OUTPUT_SHAPE_FOLDED$", str(driver_shapes["oshape_folded"])) + driver = driver.replace("$OUTPUT_SHAPE_PACKED$", str(driver_shapes["oshape_packed"])) + driver = driver.replace("$INPUT_DMA_NAME$", "%s" % str(driver_shapes["idma_names"])) + driver = driver.replace("$OUTPUT_DMA_NAME$", "%s" % str(driver_shapes["odma_names"])) + driver = driver.replace("$NUM_INPUTS$", str(len(driver_shapes["idma_names"]))) + driver = driver.replace("$NUM_OUTPUTS$", str(len(driver_shapes["odma_names"]))) driver = driver.replace("$EXT_WEIGHT_NUM$", str(ext_weight_dma_cnt)) with open(driver_py, "w") as f: diff --git a/tests/end2end/test_end2end_bnn_pynq.py b/tests/end2end/test_end2end_bnn_pynq.py index 87c1d6005c..00bc277534 100644 --- a/tests/end2end/test_end2end_bnn_pynq.py +++ b/tests/end2end/test_end2end_bnn_pynq.py @@ -71,7 +71,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_pynq_driver import MakePYNQDriver +from finn.transformation.fpgadataflow.make_driver import MakePYNQDriver from finn.transformation.fpgadataflow.minimize_accumulator_width import ( MinimizeAccumulatorWidth, ) From f0bdea2a87f11f8e53703455c249538450311e09 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Tue, 15 Aug 2023 11:42:07 +0200 Subject: [PATCH 13/57] Creating templates for C and C++ drivers --- .../fpgadataflow/make_driver.py | 24 +++++++++++++++++-- .../fpgadataflow/template_driver.c | 11 +++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/finn/transformation/fpgadataflow/template_driver.c diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index cd86ce7c19..25f3fd8657 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -53,19 +53,39 @@ def __init__(self, platform: str): self.platform: str = platform def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: + # Define location for the driver files + c_driver_dir = make_build_dir(prefix="c_driver_") + model.set_metadata_prop("c_driver_dir", c_driver_dir) + # TODO: Preparing folders and files driver_shapes: Dict = get_driver_shapes(model) + # Writer header with shape data definitions_header: str = "" for name in driver_shapes.keys(): # FIXME: Convert to C Arrays instead of python lists - definitions_header += f"#include {name.upper} {driver_shapes[name]}" - definitions_header += "#include EXT_WEIGHT_NUM " + str(ext_weight_dma_cnt) + definitions_header += f"#define {name.upper} {driver_shapes[name]}\n" + definitions_header += "#define EXT_WEIGHT_NUM " + str(ext_weight_dma_cnt) + "\n" + + with open("finn_shape_data.h", "w+") as f: + f.write(definitions_header) # TODO: Generating weight files return (model, False) + +class MakeCPPDriver(Transformation): + def __init__(self, platform: str): + super().__init__() + self.platform: str = platform + + def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: + raise NotImplementedError + return model, False + + + class MakePYNQDriver(Transformation): """Create PYNQ Python code to correctly interface the generated accelerator, including data packing/unpacking. Should be called diff --git a/src/finn/transformation/fpgadataflow/template_driver.c b/src/finn/transformation/fpgadataflow/template_driver.c new file mode 100644 index 0000000000..b59e091a5d --- /dev/null +++ b/src/finn/transformation/fpgadataflow/template_driver.c @@ -0,0 +1,11 @@ +#include +#include "finn_shape_definitions.h" + + +// XRT +#include "xrt/xrt_bo.h" +#include "xrt/xrt_device.h" + +int main() { + +} \ No newline at end of file From 634ef9fa8236c03a550ef897b7488ba61566c2c7 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Tue, 15 Aug 2023 15:07:05 +0200 Subject: [PATCH 14/57] C++ kernel wip --- src/finn/builder/build_dataflow_config.py | 12 +++++++++ src/finn/builder/build_dataflow_steps.py | 9 ++++--- .../fpgadataflow/make_driver.py | 23 +++++++--------- .../fpgadataflow/template_driver.c | 11 -------- .../fpgadataflow/template_driver.cpp | 26 +++++++++++++++++++ 5 files changed, 52 insertions(+), 29 deletions(-) delete mode 100644 src/finn/transformation/fpgadataflow/template_driver.c create mode 100644 src/finn/transformation/fpgadataflow/template_driver.cpp diff --git a/src/finn/builder/build_dataflow_config.py b/src/finn/builder/build_dataflow_config.py index e4fed05731..ad75b8952e 100644 --- a/src/finn/builder/build_dataflow_config.py +++ b/src/finn/builder/build_dataflow_config.py @@ -61,6 +61,7 @@ class DataflowOutputType(str, Enum): RTLSIM_PERFORMANCE = "rtlsim_performance" BITFILE = "bitfile" PYNQ_DRIVER = "pynq_driver" + CPP_DRIVER = "cpp_driver" DEPLOYMENT_PACKAGE = "deployment_package" @@ -93,6 +94,12 @@ class LargeFIFOMemStyle(str, Enum): URAM = "ultra" +class CPPDriverTransferType(str, Enum): + """A stream transfer directly """ + STREAM = "stream" + MEMORY_BUFFERED = "memory_buffered" + + class VerificationStepType(str, Enum): "Steps at which FINN ONNX execution can be launched for verification." @@ -351,6 +358,11 @@ class DataflowBuildConfig: #: rtlsim, otherwise they will be replaced by HLS implementations. rtlsim_use_vivado_comps: Optional[bool] = True + # Determine which type of data transfer the driver should use. + # Stream streams the data directly into the pipeline, memory_buffered writes to board memory first + cpp_driver_transfer_type : Optional[CPPDriverTransferType] = None + + def _resolve_hls_clk_period(self): if self.hls_clk_period_ns is None: # use same clk for synth and hls if not explicitly specified diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index b50a63407e..a14d5797f6 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -87,7 +87,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.make_driver import MakePYNQDriver, MakeCDriver +from finn.transformation.fpgadataflow.make_driver import MakePYNQDriver, MakeCPPDriver from finn.transformation.fpgadataflow.make_zynq_proj import ZynqBuild from finn.transformation.fpgadataflow.minimize_accumulator_width import ( MinimizeAccumulatorWidth, @@ -726,10 +726,10 @@ def step_make_pynq_driver(model: ModelWrapper, cfg: DataflowBuildConfig): return model -def step_make_c_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> ModelWrapper: - if DataflowOutputType.C_DRIVER in cfg.generate_outputs: +def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> ModelWrapper: + if DataflowOutputType.CPP_DRIVER in cfg.generate_outputs: driver_dir = os.path.join(cfg.output_dir, "driver") - model = model.transform(MakeCDriver(cfg._resolve_driver_platform())) + model = model.transform(MakeCPPDriver(cfg._resolve_driver_platform())) # TODO: Compilation # TODO: Copying into driver directory @@ -845,6 +845,7 @@ def step_deployment_package(model: ModelWrapper, cfg: DataflowBuildConfig): "step_create_stitched_ip": step_create_stitched_ip, "step_measure_rtlsim_performance": step_measure_rtlsim_performance, "step_make_pynq_driver": step_make_pynq_driver, + "step_make_cpp_driver": step_make_cpp_driver, "step_out_of_context_synthesis": step_out_of_context_synthesis, "step_synthesize_bitfile": step_synthesize_bitfile, "step_deployment_package": step_deployment_package, diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 25f3fd8657..5ee78c0b81 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -38,6 +38,7 @@ from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp from qonnx.transformation.base import Transformation +from finn.builder.build_dataflow_config import CPPDriverTransferType import finn.util from finn.util.basic import make_build_dir @@ -47,15 +48,16 @@ from . import template_driver -class MakeCDriver(Transformation): - def __init__(self, platform: str): +class MakeCPPDriver(Transformation): + def __init__(self, platform: str, transfer_mode: CPPDriverTransferType): super().__init__() self.platform: str = platform + self.transfer_mode: CPPDriverTransferType = transfer_mode def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: # Define location for the driver files - c_driver_dir = make_build_dir(prefix="c_driver_") - model.set_metadata_prop("c_driver_dir", c_driver_dir) + cpp_driver_dir = make_build_dir(prefix="cpp_driver_") + model.set_metadata_prop("cp_driver_dir", cpp_driver_dir) # TODO: Preparing folders and files driver_shapes: Dict = get_driver_shapes(model) @@ -67,23 +69,16 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: definitions_header += f"#define {name.upper} {driver_shapes[name]}\n" definitions_header += "#define EXT_WEIGHT_NUM " + str(ext_weight_dma_cnt) + "\n" - with open("finn_shape_data.h", "w+") as f: + with open("finn_shape_definitions.h", "w+") as f: f.write(definitions_header) + f.write("#define USE_STREAM_TRANSFER " + str(self.transfer_mode == CPPDriverTransferType.STREAM)) + f.write("#define USE_MEMORY_TRANSFER " + str(self.transfer_mode == CPPDriverTransferType.MEMORY_BUFFERED)) # TODO: Generating weight files return (model, False) -class MakeCPPDriver(Transformation): - def __init__(self, platform: str): - super().__init__() - self.platform: str = platform - - def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: - raise NotImplementedError - return model, False - class MakePYNQDriver(Transformation): diff --git a/src/finn/transformation/fpgadataflow/template_driver.c b/src/finn/transformation/fpgadataflow/template_driver.c deleted file mode 100644 index b59e091a5d..0000000000 --- a/src/finn/transformation/fpgadataflow/template_driver.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include "finn_shape_definitions.h" - - -// XRT -#include "xrt/xrt_bo.h" -#include "xrt/xrt_device.h" - -int main() { - -} \ No newline at end of file diff --git a/src/finn/transformation/fpgadataflow/template_driver.cpp b/src/finn/transformation/fpgadataflow/template_driver.cpp new file mode 100644 index 0000000000..ffda0ec1ab --- /dev/null +++ b/src/finn/transformation/fpgadataflow/template_driver.cpp @@ -0,0 +1,26 @@ +#include +#include "finn_shape_definitions.h" + + +// XRT +#include "xrt/xrt_bo.h" +#include "xrt/xrt_device.h" + + +// TODO: constexpr, cmake, etc. example configuration +int device_index = 0; +std::string binary_file = "finn_accel.xclbin"; + +// TODO: Remove all autos +int main() { + // Load xclbin + auto device = xrt::device(device_index); + auto uuid = device.load_xclbin(binary_file); + + // Setup Kernel + // TODO: Replace palceholder kernel name + auto ip = xrt::ip(device, uuid, "PLACEHOLDER_KERNEL_NAME"); + + // Allocate buffer in global memory if NOT streaming + +} \ No newline at end of file From 24a8066c12e1b37f21e4ed4c52b086e67fc2faed Mon Sep 17 00:00:00 2001 From: bwintermann Date: Tue, 15 Aug 2023 17:11:21 +0200 Subject: [PATCH 15/57] Fixes for C++ driver --- .../fpgadataflow/finn_shape_definitions.hpp | 16 ++++++++++++++ .../fpgadataflow/make_driver.py | 17 ++++++++------ .../fpgadataflow/template_driver.cpp | 22 +++++++++++++++++-- 3 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp diff --git a/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp b/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp new file mode 100644 index 0000000000..262c8867ad --- /dev/null +++ b/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp @@ -0,0 +1,16 @@ +// PLACEHOLDER FOR TESTING +#include +#include + +std::string PLATFORM = "alveo"; +std::string TRANSFER_MODE = "memory_buffered"; + +std::array IDMA_NAMES = {"a", "b", "c"}; +std::array ISHAPE_NORMAL = {1, 2, 3}; +std::array ISHAPE_FOLDED = {1, 2, 3}; +std::array ISHAPE_PACKED = {1, 2, 3}; + +std::array ODMA_NAMES = {"a", "b", "c"}; +std::array OSHAPE_NORMAL = {1, 2, 3}; +std::array OSHAPE_FOLDED = {1, 2, 3}; +std::array OSHAPE_PACKED = {1, 2, 3}; \ No newline at end of file diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 5ee78c0b81..db2c433368 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -63,16 +63,19 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: driver_shapes: Dict = get_driver_shapes(model) # Writer header with shape data - definitions_header: str = "" - for name in driver_shapes.keys(): - # FIXME: Convert to C Arrays instead of python lists - definitions_header += f"#define {name.upper} {driver_shapes[name]}\n" - definitions_header += "#define EXT_WEIGHT_NUM " + str(ext_weight_dma_cnt) + "\n" + make_array = lambda lst: "{" + (", ".join(map(lambda x: f"\"{x}\"", lst))) + "};" + + definitions_header: str = f"#include \n#include \nstd::string PLATFORM = \"{self.platform}\";\nstd::string TRANSFER_MODE = \"{self.transfer_mode.value}\";\n\n" + idma_len = len(driver_shapes["idma_names"]) + odma_len = len(driver_shapes["odma_names"]) + definitions_header += f"std::array IDMA_NAMES = " + make_array(driver_shapes["idma_names"]) + "\n" + definitions_header += f"std::array ODMA_NAMES = " + make_array(driver_shapes["odma_names"]) + "\n" + for name in ["ishape_normal", "ishape_packed", "ishape_folded", "oshape_normal", "oshape_packed", "oshape_folded"]: + definitions_header += "std::array " + name.upper() + " = " + make_array(driver_shapes[name]) + "\n" + definitions_header += "int EXT_WEIGHT_NUMS = " + str(ext_weight_dma_cnt) + ";\n" with open("finn_shape_definitions.h", "w+") as f: f.write(definitions_header) - f.write("#define USE_STREAM_TRANSFER " + str(self.transfer_mode == CPPDriverTransferType.STREAM)) - f.write("#define USE_MEMORY_TRANSFER " + str(self.transfer_mode == CPPDriverTransferType.MEMORY_BUFFERED)) # TODO: Generating weight files diff --git a/src/finn/transformation/fpgadataflow/template_driver.cpp b/src/finn/transformation/fpgadataflow/template_driver.cpp index ffda0ec1ab..6e943a4c93 100644 --- a/src/finn/transformation/fpgadataflow/template_driver.cpp +++ b/src/finn/transformation/fpgadataflow/template_driver.cpp @@ -1,5 +1,5 @@ #include -#include "finn_shape_definitions.h" +#include "finn_shape_definitions.hpp" // XRT @@ -7,10 +7,14 @@ #include "xrt/xrt_device.h" +// TODO: Include matrix libs (TF, Eigen, etc.?) + + // TODO: constexpr, cmake, etc. example configuration int device_index = 0; std::string binary_file = "finn_accel.xclbin"; + // TODO: Remove all autos int main() { // Load xclbin @@ -22,5 +26,19 @@ int main() { auto ip = xrt::ip(device, uuid, "PLACEHOLDER_KERNEL_NAME"); // Allocate buffer in global memory if NOT streaming - + if (TRANSFER_MODE == "memory_buffered") { + std::array idma_bos; + for (int i = 0; i < IDMA_NAMES.size(); i++) { + // FIXME: Is ISHAPE_PACKED a single shape (a,b,c) or a list of shapes? [(a, b), (c, d)] + idma_bos[i] = xrt::bo(device, ISHAPE_PACKED[i], xrt::bo::flags::cacheable); + } + + } else if (TRANSFER_MODE == "stream") { + + // TODO + + } else { + std::cout << "Unknown transfer mode (" << TRANSFER_MODE << "). Please specify a known one in the DataflowBuildConfig!" << std::endl; + return 1; + } } \ No newline at end of file From 201011ace2ddab4d8e4d8ce6116eef2823878d25 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Wed, 16 Aug 2023 10:33:06 +0200 Subject: [PATCH 16/57] Fixes for driver creation --- .../fpgadataflow/make_driver.py | 142 ++++++++++-------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index db2c433368..7dd1159224 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -48,6 +48,72 @@ from . import template_driver +def write_weights(model: ModelWrapper, driver_dir: str) -> Tuple[int, str]: + # Generate external weights npy files + weights_dir = os.path.join(driver_dir, "/runtime_weights") + + os.makedirs(weights_dir) + idma_idx = 0 + ext_weight_dma_cnt = 0 + + for node in model.graph.node: + assert ( + node.op_type == "StreamingDataflowPartition" + ), "CreateDataflowPartition needs to be applied before driver generation" + + if len(node.input) > 0: + producer = model.find_producer(node.input[0]) + init_tensor = model.get_initializer(node.input[0]) + else: + producer = None + init_tensor = None + + if producer is None: # input dma? + sdp_inst = getCustomOp(node) + idma_name = sdp_inst.get_nodeattr("instance_name") + df_model = ModelWrapper(sdp_inst.get_nodeattr("model")) + assert df_model.graph.node[0].op_type == "IODMA" + iodma_node = getCustomOp(df_model.graph.node[0]) + if iodma_node.get_nodeattr("burstMode") == "wrap": # input weights dma? + init_tensor = df_model.get_initializer(iodma_node.onnx_node.input[0]) + ext_weight_dma_cnt += 1 + w_dtype = df_model.get_tensor_datatype(iodma_node.onnx_node.input[0]) + init_external_tensor = to_external_tensor(init_tensor, w_dtype) + np.save(weights_dir + "/" + idma_name + ".npy", init_external_tensor) + idma_idx += 1 + return ext_weight_dma_cnt, weights_dir + + +def generate_runtime_weights(model: ModelWrapper, weights_dir: str): + for sdp_ind, sdp_node in enumerate(model.graph.node): + assert sdp_node.op_type == "StreamingDataflowPartition" + # get dataflow model + sdp_node = getCustomOp(sdp_node) + dataflow_model_filename = sdp_node.get_nodeattr("model") + dataflow_model = ModelWrapper(dataflow_model_filename) + rt_layer_ind = 0 + for node in dataflow_model.graph.node: + if node.op_type in ["MatrixVectorActivation", "Thresholding_Batch"]: + node_inst = getCustomOp(node) + is_rt_weights = node_inst.get_nodeattr("runtime_writeable_weights") + if is_rt_weights == 1: + fcl_w = dataflow_model.get_initializer(node.input[1]) + w_filename = weights_dir + "/%d_%d_%s.dat" % ( + sdp_ind, + rt_layer_ind, + node.name, + ) + node_inst.make_weight_file(fcl_w, "decoupled_runtime", w_filename) + rt_layer_ind += 1 + elif node.op_type == "StreamingDataflowPartition": + warnings.warn( + """Nested StreamingDataflowPartition are not supported + """ + ) + else: + continue + + class MakeCPPDriver(Transformation): def __init__(self, platform: str, transfer_mode: CPPDriverTransferType): super().__init__() @@ -61,6 +127,10 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: # TODO: Preparing folders and files driver_shapes: Dict = get_driver_shapes(model) + ext_weight_dma_cnt: int + weights_dir: str + ext_weight_dma_cnt, weights_dir = write_weights(model, cpp_driver_dir) + # Writer header with shape data make_array = lambda lst: "{" + (", ".join(map(lambda x: f"\"{x}\"", lst))) + "};" @@ -74,10 +144,17 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: definitions_header += "std::array " + name.upper() + " = " + make_array(driver_shapes[name]) + "\n" definitions_header += "int EXT_WEIGHT_NUMS = " + str(ext_weight_dma_cnt) + ";\n" + + # DEBUG: + print("DEFS: ") + print(definitions_header) + + with open("finn_shape_definitions.h", "w+") as f: f.write(definitions_header) # TODO: Generating weight files + generate_runtime_weights(model, weights_dir) return (model, False) @@ -152,38 +229,10 @@ def apply(self, model): # TODO convert this to an analysis pass? driver_shapes: Dict = get_driver_shapes(model) - # generate external weights npy files - weights_dir = pynq_driver_dir + "/runtime_weights" - - os.makedirs(weights_dir) - idma_idx = 0 - ext_weight_dma_cnt = 0 - - for node in model.graph.node: - assert ( - node.op_type == "StreamingDataflowPartition" - ), "CreateDataflowPartition needs to be applied before driver generation" - - if len(node.input) > 0: - producer = model.find_producer(node.input[0]) - init_tensor = model.get_initializer(node.input[0]) - else: - producer = None - init_tensor = None - - if producer is None: # input dma? - sdp_inst = getCustomOp(node) - idma_name = sdp_inst.get_nodeattr("instance_name") - df_model = ModelWrapper(sdp_inst.get_nodeattr("model")) - assert df_model.graph.node[0].op_type == "IODMA" - iodma_node = getCustomOp(df_model.graph.node[0]) - if iodma_node.get_nodeattr("burstMode") == "wrap": # input weights dma? - init_tensor = df_model.get_initializer(iodma_node.onnx_node.input[0]) - ext_weight_dma_cnt += 1 - w_dtype = df_model.get_tensor_datatype(iodma_node.onnx_node.input[0]) - init_external_tensor = to_external_tensor(init_tensor, w_dtype) - np.save(weights_dir + "/" + idma_name + ".npy", init_external_tensor) - idma_idx += 1 + # Write weights + ext_weight_dma_cnt: int + weights_dir: str + ext_weight_dma_cnt, weights_dir = write_weights(model, pynq_driver_dir) # fill in the driver template driver_py = pynq_driver_dir + "/driver.py" @@ -213,33 +262,6 @@ def apply(self, model): shutil.copy(validate_template, validate_py) # generate weight files for runtime-writable layers - - for sdp_ind, sdp_node in enumerate(model.graph.node): - assert sdp_node.op_type == "StreamingDataflowPartition" - # get dataflow model - sdp_node = getCustomOp(sdp_node) - dataflow_model_filename = sdp_node.get_nodeattr("model") - dataflow_model = ModelWrapper(dataflow_model_filename) - rt_layer_ind = 0 - for node in dataflow_model.graph.node: - if node.op_type in ["MatrixVectorActivation", "Thresholding_Batch"]: - node_inst = getCustomOp(node) - is_rt_weights = node_inst.get_nodeattr("runtime_writeable_weights") - if is_rt_weights == 1: - fcl_w = dataflow_model.get_initializer(node.input[1]) - w_filename = weights_dir + "/%d_%d_%s.dat" % ( - sdp_ind, - rt_layer_ind, - node.name, - ) - node_inst.make_weight_file(fcl_w, "decoupled_runtime", w_filename) - rt_layer_ind += 1 - elif node.op_type == "StreamingDataflowPartition": - warnings.warn( - """Nested StreamingDataflowPartition are not supported - """ - ) - else: - continue + generate_runtime_weights(model, weights_dir) return (model, False) From 60640ddb525d37c2715c1f40d9ee93058c62908f Mon Sep 17 00:00:00 2001 From: bwintermann Date: Wed, 16 Aug 2023 15:34:25 +0200 Subject: [PATCH 17/57] C++ driver buffers and memory initialization --- .../fpgadataflow/finn_shape_definitions.hpp | 21 ++++--- .../fpgadataflow/get_driver_shapes.py | 1 + .../fpgadataflow/make_driver.py | 26 +++++--- .../fpgadataflow/template_driver.cpp | 60 ++++++++++++++----- 4 files changed, 78 insertions(+), 30 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp b/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp index 262c8867ad..f71d7e6d1c 100644 --- a/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp +++ b/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp @@ -1,16 +1,19 @@ // PLACEHOLDER FOR TESTING #include -#include +#include std::string PLATFORM = "alveo"; std::string TRANSFER_MODE = "memory_buffered"; -std::array IDMA_NAMES = {"a", "b", "c"}; -std::array ISHAPE_NORMAL = {1, 2, 3}; -std::array ISHAPE_FOLDED = {1, 2, 3}; -std::array ISHAPE_PACKED = {1, 2, 3}; +std::vector INPUT_BYTEWIDTH = {1, 1, 2}; +std::vector OUTPUT_BYTEWIDTH = {1, 1, 2}; -std::array ODMA_NAMES = {"a", "b", "c"}; -std::array OSHAPE_NORMAL = {1, 2, 3}; -std::array OSHAPE_FOLDED = {1, 2, 3}; -std::array OSHAPE_PACKED = {1, 2, 3}; \ No newline at end of file +std::vector IDMA_NAMES = {"a", "b", "c"}; +std::vector> ISHAPE_NORMAL = {{1,2,3}}; +std::vector> ISHAPE_FOLDED = {{1,2,3}}; +std::vector> ISHAPE_PACKED = {{1,2,3}}; + +std::vector ODMA_NAMES = {"a", "b", "c"}; +std::vector> OSHAPE_NORMAL = {{1,2,3}}; +std::vector> OSHAPE_FOLDED = {{1,2,3}}; +std::vector> OSHAPE_PACKED = {{1,2,3}}; \ No newline at end of file diff --git a/src/finn/transformation/fpgadataflow/get_driver_shapes.py b/src/finn/transformation/fpgadataflow/get_driver_shapes.py index 6706a63249..00212eea03 100644 --- a/src/finn/transformation/fpgadataflow/get_driver_shapes.py +++ b/src/finn/transformation/fpgadataflow/get_driver_shapes.py @@ -7,6 +7,7 @@ pack_innermost_dim_as_hex_string, ) from typing import Dict +import numpy as np # TODO: License? diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 7dd1159224..f02626e9dd 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -34,9 +34,11 @@ import qonnx import shutil import warnings +from math import ceil from typing import Dict, List, Tuple from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp +from qonnx.core.datatype import DataType from qonnx.transformation.base import Transformation from finn.builder.build_dataflow_config import CPPDriverTransferType @@ -133,15 +135,25 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: # Writer header with shape data - make_array = lambda lst: "{" + (", ".join(map(lambda x: f"\"{x}\"", lst))) + "};" + make_array = lambda lst: "{" + (", ".join(map(lambda x: f"\"{x}\"", lst))) + "}" - definitions_header: str = f"#include \n#include \nstd::string PLATFORM = \"{self.platform}\";\nstd::string TRANSFER_MODE = \"{self.transfer_mode.value}\";\n\n" - idma_len = len(driver_shapes["idma_names"]) - odma_len = len(driver_shapes["odma_names"]) - definitions_header += f"std::array IDMA_NAMES = " + make_array(driver_shapes["idma_names"]) + "\n" - definitions_header += f"std::array ODMA_NAMES = " + make_array(driver_shapes["odma_names"]) + "\n" + definitions_header: str = f"#include \n#include \nstd::string PLATFORM = \"{self.platform}\";\nstd::string TRANSFER_MODE = \"{self.transfer_mode.value}\";\n\n" + + input_datatypes: List[DataType] = driver_shapes["idt"] + output_datatypes: List[DataType] = driver_shapes["odt"] + + assert all([dt.is_integer() for dt in input_datatypes]), f"One of the datatypes for the input is not an integer! Datatypes: {input_datatypes}" + assert all([dt.is_integer() for dt in output_datatypes]), f"One of the datatypes for the output is not an integer! Datatypes: {output_datatypes}" + + definitions_header += "std::vector INPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in input_datatypes]) + "};\n" + definitions_header += "std::vector ONPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in output_datatypes]) + "};\n" + + definitions_header += f"std::vector IDMA_NAMES = " + make_array(driver_shapes["idma_names"]) + ";\n" + definitions_header += f"std::vector ODMA_NAMES = " + make_array(driver_shapes["odma_names"]) + ";\n" for name in ["ishape_normal", "ishape_packed", "ishape_folded", "oshape_normal", "oshape_packed", "oshape_folded"]: - definitions_header += "std::array " + name.upper() + " = " + make_array(driver_shapes[name]) + "\n" + definitions_header += "std::vector> " + name.upper() + " = {\n" + definitions_header += ",\n".join([make_array(shape) for shape in driver_shapes[name]]) + definitions_header += "}\n" definitions_header += "int EXT_WEIGHT_NUMS = " + str(ext_weight_dma_cnt) + ";\n" diff --git a/src/finn/transformation/fpgadataflow/template_driver.cpp b/src/finn/transformation/fpgadataflow/template_driver.cpp index 6e943a4c93..1271647c87 100644 --- a/src/finn/transformation/fpgadataflow/template_driver.cpp +++ b/src/finn/transformation/fpgadataflow/template_driver.cpp @@ -1,42 +1,74 @@ #include +#include +#include + +// Created by FINN during compilation #include "finn_shape_definitions.hpp" +// Helpers +#include "include/mdspan/mdspan.hpp" // XRT #include "xrt/xrt_bo.h" #include "xrt/xrt_device.h" -// TODO: Include matrix libs (TF, Eigen, etc.?) +enum DriverMode { + EXECUTE = "execute", + THROUGHPUT_TEST = "throughput_test" +} -// TODO: constexpr, cmake, etc. example configuration +// TODO: Make into command line arguments int device_index = 0; std::string binary_file = "finn_accel.xclbin"; +DriverMode driver_mode = DriverMode::THROUGHPUT_TEST; + + + +// Create buffers, one buffer per given shape, and bytewidth +std::vector create_io_buffers(xrt::device device, std::vector widths, std::vector> shape) { + std::vector buffers; + int elements; + for (int i = 0; i < widths.size(); i++) { + elements = std::accumulate(std::begin(shape[i]), std::end(shape[i]), 1, std::multiplies()); + buffers.push_back(xrt::bo(device, widths[i] * elements, xrt::bo::flags::cacheable, 1)); // TODO: Correct memory group setting missing, assuming 1 here + } + return buffers; +} + + +// Create mappings of the given datatype for the given buffers +template +std::vector create_memory_maps(std::vector buffers) { + std::vector maps; + for (xrt::bo buffer : buffers) { + maps.push_back(buffer.map()); + } + return maps; +} -// TODO: Remove all autos int main() { // Load xclbin - auto device = xrt::device(device_index); + xrt::device device = xrt::device(device_index); auto uuid = device.load_xclbin(binary_file); // Setup Kernel // TODO: Replace palceholder kernel name - auto ip = xrt::ip(device, uuid, "PLACEHOLDER_KERNEL_NAME"); + xrt::xclbin::ip ip = xrt::ip(device, uuid, "PLACEHOLDER_KERNEL_NAME"); - // Allocate buffer in global memory if NOT streaming if (TRANSFER_MODE == "memory_buffered") { - std::array idma_bos; - for (int i = 0; i < IDMA_NAMES.size(); i++) { - // FIXME: Is ISHAPE_PACKED a single shape (a,b,c) or a list of shapes? [(a, b), (c, d)] - idma_bos[i] = xrt::bo(device, ISHAPE_PACKED[i], xrt::bo::flags::cacheable); - } - + // Create IO buffers + std::vector input_buffers = create_io_buffers(device, INPUT_BYTEWIDTH, ISHAPE_PACKED); + std::vector output_buffers = create_io_buffers(device, OUTPUT_BYTEWIDTH, OSHAPE_PACKED); + + // Create memory maps + std::vector input_buffer_maps = create_memory_maps(input_buffers); + std::vector onput_buffer_maps = create_memory_maps(output_buffers); + } else if (TRANSFER_MODE == "stream") { - // TODO - } else { std::cout << "Unknown transfer mode (" << TRANSFER_MODE << "). Please specify a known one in the DataflowBuildConfig!" << std::endl; return 1; From c0b390c75e8c7e3e3ec8d450c5f41841ca428234 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Wed, 16 Aug 2023 15:44:54 +0200 Subject: [PATCH 18/57] Compilation fix --- src/finn/builder/build_dataflow_steps.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index a14d5797f6..b062e92979 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -121,6 +121,7 @@ get_rtlsim_trace_depth, pyverilate_get_liveness_threshold_cycles, ) +from finn.builder.build_dataflow_config import CPPDriverTransferType from finn.util.pyverilator import verilator_fifosim from finn.util.test import execute_parent @@ -729,7 +730,11 @@ def step_make_pynq_driver(model: ModelWrapper, cfg: DataflowBuildConfig): def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> ModelWrapper: if DataflowOutputType.CPP_DRIVER in cfg.generate_outputs: driver_dir = os.path.join(cfg.output_dir, "driver") - model = model.transform(MakeCPPDriver(cfg._resolve_driver_platform())) + + # TODO: REMOVE DEBUG + cfg.cpp_driver_transfer_type = CPPDriverTransferType.MEMORY_BUFFERED + + model = model.transform(MakeCPPDriver(cfg._resolve_driver_platform()), cfg.cpp_driver_transfer_type) # TODO: Compilation # TODO: Copying into driver directory From 89e3ff3585042c3f7e82c384d80c8d4faee2029b Mon Sep 17 00:00:00 2001 From: bwintermann Date: Thu, 17 Aug 2023 16:12:28 +0200 Subject: [PATCH 19/57] Inclusion of cpp drivers per submodule. Fixes for MakeCPPDriver --- .gitmodules | 3 + .../fpgadataflow/finn-cpp-driver | 1 + .../fpgadataflow/finn_shape_definitions.hpp | 19 ----- .../fpgadataflow/make_driver.py | 16 +++- .../fpgadataflow/template_driver.cpp | 76 ------------------- 5 files changed, 17 insertions(+), 98 deletions(-) create mode 100644 .gitmodules create mode 160000 src/finn/transformation/fpgadataflow/finn-cpp-driver delete mode 100644 src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp delete mode 100644 src/finn/transformation/fpgadataflow/template_driver.cpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..e19fe624b3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/finn/transformation/fpgadataflow/finn-cpp-driver"] + path = src/finn/transformation/fpgadataflow/finn-cpp-driver + url = git@github.com:eki-project/finn-cpp-driver.git diff --git a/src/finn/transformation/fpgadataflow/finn-cpp-driver b/src/finn/transformation/fpgadataflow/finn-cpp-driver new file mode 160000 index 0000000000..02a6a0ebef --- /dev/null +++ b/src/finn/transformation/fpgadataflow/finn-cpp-driver @@ -0,0 +1 @@ +Subproject commit 02a6a0ebef1def59afee7e1f419e0dc49e8fe2fc diff --git a/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp b/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp deleted file mode 100644 index f71d7e6d1c..0000000000 --- a/src/finn/transformation/fpgadataflow/finn_shape_definitions.hpp +++ /dev/null @@ -1,19 +0,0 @@ -// PLACEHOLDER FOR TESTING -#include -#include - -std::string PLATFORM = "alveo"; -std::string TRANSFER_MODE = "memory_buffered"; - -std::vector INPUT_BYTEWIDTH = {1, 1, 2}; -std::vector OUTPUT_BYTEWIDTH = {1, 1, 2}; - -std::vector IDMA_NAMES = {"a", "b", "c"}; -std::vector> ISHAPE_NORMAL = {{1,2,3}}; -std::vector> ISHAPE_FOLDED = {{1,2,3}}; -std::vector> ISHAPE_PACKED = {{1,2,3}}; - -std::vector ODMA_NAMES = {"a", "b", "c"}; -std::vector> OSHAPE_NORMAL = {{1,2,3}}; -std::vector> OSHAPE_FOLDED = {{1,2,3}}; -std::vector> OSHAPE_PACKED = {{1,2,3}}; \ No newline at end of file diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index f02626e9dd..0accab29d0 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -34,6 +34,8 @@ import qonnx import shutil import warnings +import subprocess +from shutil import which from math import ceil from typing import Dict, List, Tuple from qonnx.core.modelwrapper import ModelWrapper @@ -137,7 +139,7 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: # Writer header with shape data make_array = lambda lst: "{" + (", ".join(map(lambda x: f"\"{x}\"", lst))) + "}" - definitions_header: str = f"#include \n#include \nstd::string PLATFORM = \"{self.platform}\";\nstd::string TRANSFER_MODE = \"{self.transfer_mode.value}\";\n\n" + definitions_header: str = f"#include \n#include \nstd::string platform = \"{self.platform}\";\nstd::string transferMode = \"{self.transfer_mode.value}\";\n\n" input_datatypes: List[DataType] = driver_shapes["idt"] output_datatypes: List[DataType] = driver_shapes["odt"] @@ -161,10 +163,18 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: print("DEFS: ") print(definitions_header) - - with open("finn_shape_definitions.h", "w+") as f: + # TODO(bwintermann): Move compilation somewhere else / Include header file from relative path from cpp submodule? + with open(os.path.join("finn-cpp-driver", "src", "template_driver.hpp"), "w+") as f: f.write(definitions_header) + # Compilation + assert which("cmake") is not None, "cmake not found! Please install it or add it to path!" + assert which("make") is not None, "make not found! Please install it or add it to path!" + os.chdir(os.path.join(self.cpp_template_dir, "build")) + subprocess.run("cmake --build .", shell=True) + subprocess.run("make -j4", shell=True) + + # TODO: Generating weight files generate_runtime_weights(model, weights_dir) diff --git a/src/finn/transformation/fpgadataflow/template_driver.cpp b/src/finn/transformation/fpgadataflow/template_driver.cpp deleted file mode 100644 index 1271647c87..0000000000 --- a/src/finn/transformation/fpgadataflow/template_driver.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include -#include - -// Created by FINN during compilation -#include "finn_shape_definitions.hpp" - -// Helpers -#include "include/mdspan/mdspan.hpp" - -// XRT -#include "xrt/xrt_bo.h" -#include "xrt/xrt_device.h" - - -enum DriverMode { - EXECUTE = "execute", - THROUGHPUT_TEST = "throughput_test" -} - - -// TODO: Make into command line arguments -int device_index = 0; -std::string binary_file = "finn_accel.xclbin"; -DriverMode driver_mode = DriverMode::THROUGHPUT_TEST; - - - -// Create buffers, one buffer per given shape, and bytewidth -std::vector create_io_buffers(xrt::device device, std::vector widths, std::vector> shape) { - std::vector buffers; - int elements; - for (int i = 0; i < widths.size(); i++) { - elements = std::accumulate(std::begin(shape[i]), std::end(shape[i]), 1, std::multiplies()); - buffers.push_back(xrt::bo(device, widths[i] * elements, xrt::bo::flags::cacheable, 1)); // TODO: Correct memory group setting missing, assuming 1 here - } - return buffers; -} - - -// Create mappings of the given datatype for the given buffers -template -std::vector create_memory_maps(std::vector buffers) { - std::vector maps; - for (xrt::bo buffer : buffers) { - maps.push_back(buffer.map()); - } - return maps; -} - - -int main() { - // Load xclbin - xrt::device device = xrt::device(device_index); - auto uuid = device.load_xclbin(binary_file); - - // Setup Kernel - // TODO: Replace palceholder kernel name - xrt::xclbin::ip ip = xrt::ip(device, uuid, "PLACEHOLDER_KERNEL_NAME"); - - if (TRANSFER_MODE == "memory_buffered") { - // Create IO buffers - std::vector input_buffers = create_io_buffers(device, INPUT_BYTEWIDTH, ISHAPE_PACKED); - std::vector output_buffers = create_io_buffers(device, OUTPUT_BYTEWIDTH, OSHAPE_PACKED); - - // Create memory maps - std::vector input_buffer_maps = create_memory_maps(input_buffers); - std::vector onput_buffer_maps = create_memory_maps(output_buffers); - - } else if (TRANSFER_MODE == "stream") { - // TODO - } else { - std::cout << "Unknown transfer mode (" << TRANSFER_MODE << "). Please specify a known one in the DataflowBuildConfig!" << std::endl; - return 1; - } -} \ No newline at end of file From 820e8f86823f8267f3f7eac000cdc799025c65f5 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Thu, 17 Aug 2023 16:39:53 +0200 Subject: [PATCH 20/57] Fixes for c++ driver generation --- src/finn/builder/build_dataflow_config.py | 1 + src/finn/builder/build_dataflow_steps.py | 10 +++++++++- src/finn/transformation/fpgadataflow/make_driver.py | 8 +++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/finn/builder/build_dataflow_config.py b/src/finn/builder/build_dataflow_config.py index ad75b8952e..d9f8664de6 100644 --- a/src/finn/builder/build_dataflow_config.py +++ b/src/finn/builder/build_dataflow_config.py @@ -136,6 +136,7 @@ class VerificationStepType(str, Enum): "step_out_of_context_synthesis", "step_synthesize_bitfile", "step_make_pynq_driver", + "step_make_cpp_driver", "step_deployment_package", ] diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index b062e92979..4d9cd4f684 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -734,10 +734,18 @@ def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> Model # TODO: REMOVE DEBUG cfg.cpp_driver_transfer_type = CPPDriverTransferType.MEMORY_BUFFERED - model = model.transform(MakeCPPDriver(cfg._resolve_driver_platform()), cfg.cpp_driver_transfer_type) + model = model.transform( + MakeCPPDriver( + cfg._resolve_driver_platform(), + cfg.cpp_driver_transfer_type, + cpp_template_dir="finn-cpp-driver" + ) + ) # TODO: Compilation # TODO: Copying into driver directory + else: + print("WARNING: The step step_make_cpp_driver is in the build list but will not be executed since CPP_DRIVER is not in generate_outputs in your build.py file!") return model diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 0accab29d0..c061f1e636 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -42,7 +42,8 @@ from qonnx.custom_op.registry import getCustomOp from qonnx.core.datatype import DataType from qonnx.transformation.base import Transformation -from finn.builder.build_dataflow_config import CPPDriverTransferType +from finn.builder.build_dataflow_config import CPPDriverTransferType, DataflowBuildConfig, DataflowOutputType + import finn.util from finn.util.basic import make_build_dir @@ -119,10 +120,11 @@ def generate_runtime_weights(model: ModelWrapper, weights_dir: str): class MakeCPPDriver(Transformation): - def __init__(self, platform: str, transfer_mode: CPPDriverTransferType): + def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, cpp_template_dir: str): super().__init__() self.platform: str = platform self.transfer_mode: CPPDriverTransferType = transfer_mode + self.cpp_template_dir = cpp_template_dir def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: # Define location for the driver files @@ -164,7 +166,7 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: print(definitions_header) # TODO(bwintermann): Move compilation somewhere else / Include header file from relative path from cpp submodule? - with open(os.path.join("finn-cpp-driver", "src", "template_driver.hpp"), "w+") as f: + with open(os.path.join(self.cpp_template_dir, "src", "template_driver.hpp"), "w+") as f: f.write(definitions_header) # Compilation From ea554b5a98226d9a9d702530e4c3124401be8d23 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Fri, 18 Aug 2023 14:30:15 +0200 Subject: [PATCH 21/57] Fixed driver export --- src/finn/builder/build_dataflow_steps.py | 2 +- .../fpgadataflow/make_driver.py | 24 ++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 4d9cd4f684..e3f37697ca 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -737,7 +737,7 @@ def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> Model model = model.transform( MakeCPPDriver( cfg._resolve_driver_platform(), - cfg.cpp_driver_transfer_type, + transfer_mode=cfg.cpp_driver_transfer_type, cpp_template_dir="finn-cpp-driver" ) ) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index c061f1e636..84b23a1d02 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -55,7 +55,7 @@ def write_weights(model: ModelWrapper, driver_dir: str) -> Tuple[int, str]: # Generate external weights npy files - weights_dir = os.path.join(driver_dir, "/runtime_weights") + weights_dir = os.path.join(driver_dir, "runtime_weights") os.makedirs(weights_dir) idma_idx = 0 @@ -140,6 +140,15 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: # Writer header with shape data make_array = lambda lst: "{" + (", ".join(map(lambda x: f"\"{x}\"", lst))) + "}" + def resolve_dt_name(s: str) -> str: + if "ap_int" in s: + return "INT" + elif "ap_uint" in s: + return "UINT" + elif "ap_fixed" in s: + return "FLOAT" + else: + return "UNKNOWN" definitions_header: str = f"#include \n#include \nstd::string platform = \"{self.platform}\";\nstd::string transferMode = \"{self.transfer_mode.value}\";\n\n" @@ -149,13 +158,16 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: assert all([dt.is_integer() for dt in input_datatypes]), f"One of the datatypes for the input is not an integer! Datatypes: {input_datatypes}" assert all([dt.is_integer() for dt in output_datatypes]), f"One of the datatypes for the output is not an integer! Datatypes: {output_datatypes}" - definitions_header += "std::vector INPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in input_datatypes]) + "};\n" - definitions_header += "std::vector ONPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in output_datatypes]) + "};\n" + definitions_header += "std::initializer_list INPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in input_datatypes]) + "};\n" + definitions_header += "std::initializer_list ONPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in output_datatypes]) + "};\n" + + definitions_header += "std::initializer_list> INPUT_DATATYPE = {" + ", ".join([resolve_dt_name(dt.get_hls_datatype_str()) for dt in input_datatypes] + "};\n") + definitions_header += "std::initializer_list> OUTPUT_DATATYPE = {" + ", ".join([resolve_dt_name(dt.get_hls_datatype_str()) for dt in output_datatypes] + "};\n") - definitions_header += f"std::vector IDMA_NAMES = " + make_array(driver_shapes["idma_names"]) + ";\n" - definitions_header += f"std::vector ODMA_NAMES = " + make_array(driver_shapes["odma_names"]) + ";\n" + definitions_header += f"std::initializer_list IDMA_NAMES = " + make_array(driver_shapes["idma_names"]) + ";\n" + definitions_header += f"std::initializer_list ODMA_NAMES = " + make_array(driver_shapes["odma_names"]) + ";\n" for name in ["ishape_normal", "ishape_packed", "ishape_folded", "oshape_normal", "oshape_packed", "oshape_folded"]: - definitions_header += "std::vector> " + name.upper() + " = {\n" + definitions_header += "std::initializer_list " + name.upper() + " = {\n" definitions_header += ",\n".join([make_array(shape) for shape in driver_shapes[name]]) definitions_header += "}\n" definitions_header += "int EXT_WEIGHT_NUMS = " + str(ext_weight_dma_cnt) + ";\n" From c5225e1cdf4ccda192ea0c06206a4bb6e30e5b5f Mon Sep 17 00:00:00 2001 From: bwintermann Date: Wed, 6 Sep 2023 15:07:13 +0200 Subject: [PATCH 22/57] Temporary debugging print statements --- src/finn/transformation/fpgadataflow/make_driver.py | 6 +++--- src/finn/transformation/fpgadataflow/vitis_build.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 84b23a1d02..515e5e1501 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -155,8 +155,8 @@ def resolve_dt_name(s: str) -> str: input_datatypes: List[DataType] = driver_shapes["idt"] output_datatypes: List[DataType] = driver_shapes["odt"] - assert all([dt.is_integer() for dt in input_datatypes]), f"One of the datatypes for the input is not an integer! Datatypes: {input_datatypes}" - assert all([dt.is_integer() for dt in output_datatypes]), f"One of the datatypes for the output is not an integer! Datatypes: {output_datatypes}" + #assert all([dt.is_integer() for dt in input_datatypes]), f"One of the datatypes for the input is not an integer! Datatypes: {input_datatypes}" + #assert all([dt.is_integer() for dt in output_datatypes]), f"One of the datatypes for the output is not an integer! Datatypes: {output_datatypes}" definitions_header += "std::initializer_list INPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in input_datatypes]) + "};\n" definitions_header += "std::initializer_list ONPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in output_datatypes]) + "};\n" @@ -167,7 +167,7 @@ def resolve_dt_name(s: str) -> str: definitions_header += f"std::initializer_list IDMA_NAMES = " + make_array(driver_shapes["idma_names"]) + ";\n" definitions_header += f"std::initializer_list ODMA_NAMES = " + make_array(driver_shapes["odma_names"]) + ";\n" for name in ["ishape_normal", "ishape_packed", "ishape_folded", "oshape_normal", "oshape_packed", "oshape_folded"]: - definitions_header += "std::initializer_list " + name.upper() + " = {\n" + definitions_header += "std::initializer_list> " + name.upper() + " = {\n" definitions_header += ",\n".join([make_array(shape) for shape in driver_shapes[name]]) definitions_header += "}\n" definitions_header += "int EXT_WEIGHT_NUMS = " + str(ext_weight_dma_cnt) + ";\n" diff --git a/src/finn/transformation/fpgadataflow/vitis_build.py b/src/finn/transformation/fpgadataflow/vitis_build.py index 2fc0b2f3bb..d5c5c4cb8f 100644 --- a/src/finn/transformation/fpgadataflow/vitis_build.py +++ b/src/finn/transformation/fpgadataflow/vitis_build.py @@ -84,11 +84,13 @@ class CreateVitisXO(Transformation): def __init__(self, ip_name="finn_design"): super().__init__() self.ip_name = ip_name + print(f"[DBG HELP] Calling CreateVitisXO with ip_name {ip_name}") def apply(self, model): _check_vitis_envvars() vivado_proj_dir = model.get_metadata_prop("vivado_stitch_proj") stitched_ip_dir = vivado_proj_dir + "/ip" + print(f"[DBG HELP] vivado stitched id proj dir: {stitched_ip_dir}") interfaces = json.loads(model.get_metadata_prop("vivado_stitch_ifnames")) args_string = [] arg_id = 0 @@ -131,6 +133,7 @@ def apply(self, model): xo_name = self.ip_name + ".xo" xo_path = vivado_proj_dir + "/" + xo_name model.set_metadata_prop("vitis_xo", xo_path) + print(f"[DBG HELP] Creating Vitis XO file with the name {xo_name} at path {xo_path}") # generate the package_xo command in a tcl script package_xo_string = "package_xo -force -xo_path %s -kernel_name %s -ip_directory %s" % ( @@ -194,6 +197,7 @@ def apply(self, model): dataflow_model_filename = sdp_node.get_nodeattr("model") kernel_model = ModelWrapper(dataflow_model_filename) kernel_xo = kernel_model.get_metadata_prop("vitis_xo") + print(f"[DBG HELP LINK] Looking at node {node.name} with vitis_xo attribute field value {kernel_xo}") object_files.append(kernel_xo) # gather info on connectivity # assume each node connected to outputs/inputs is DMA: @@ -212,13 +216,16 @@ def apply(self, model): # check top-level in/out list instead if producer is None: instance_names[node.name] = "idma" + str(idma_idx) + print(f"[DBG HELP LINK] Renaming node {node.name} into {instance_names[node.name]} for the instance!") config.append("nk=%s:1:%s" % (node.name, instance_names[node.name])) idma_idx += 1 elif consumer == []: instance_names[node.name] = "odma" + str(odma_idx) + print(f"[DBG HELP LINK] Renaming node {node.name} into {instance_names[node.name]} for the instance!") config.append("nk=%s:1:%s" % (node.name, instance_names[node.name])) odma_idx += 1 else: + print(f"[DBG HELP LINK] Node name {node.name} keeps its name as an instance!") instance_names[node.name] = node.name config.append("nk=%s:1:%s" % (node.name, instance_names[node.name])) sdp_node.set_nodeattr("instance_name", instance_names[node.name]) @@ -267,8 +274,11 @@ def apply(self, model): ) ) + print(f"[DBG HELP LINK] Linking together object files: {object_files}") + # create a temporary folder for the project link_dir = make_build_dir(prefix="vitis_link_proj_") + print(f"[DBG HELP LINK] Setting link directory for vitis_link_proj to {link_dir}") model.set_metadata_prop("vitis_link_proj", link_dir) # add Vivado physopt directives if desired @@ -398,6 +408,7 @@ def apply(self, model): # Build each kernel individually sdp_nodes = model.get_nodes_by_op_type("StreamingDataflowPartition") for sdp_node in sdp_nodes: + print(f"[DBG HELP BUILD] Creating a stitched ip for the node {sdp_node.name}") prefix = sdp_node.name + "_" sdp_node = getCustomOp(sdp_node) dataflow_model_filename = sdp_node.get_nodeattr("model") From 50b04d281805af7f5da2883776e154e5c4134fea Mon Sep 17 00:00:00 2001 From: bwintermann Date: Wed, 4 Oct 2023 16:41:16 +0200 Subject: [PATCH 23/57] Updated to write out header and config files for updated cpp driver --- .../fpgadataflow/finn-cpp-driver | 2 +- .../fpgadataflow/make_driver.py | 168 +++++++++++++----- 2 files changed, 126 insertions(+), 44 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/finn-cpp-driver b/src/finn/transformation/fpgadataflow/finn-cpp-driver index 02a6a0ebef..ddab23f0d3 160000 --- a/src/finn/transformation/fpgadataflow/finn-cpp-driver +++ b/src/finn/transformation/fpgadataflow/finn-cpp-driver @@ -1 +1 @@ -Subproject commit 02a6a0ebef1def59afee7e1f419e0dc49e8fe2fc +Subproject commit ddab23f0d3453a37f1687609d259072af19549ee diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 515e5e1501..69f5ea803f 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -120,8 +120,9 @@ def generate_runtime_weights(model: ModelWrapper, weights_dir: str): class MakeCPPDriver(Transformation): - def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, cpp_template_dir: str): + def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, cpp_template_dir: str, run_name: str = "RUN_ID"): super().__init__() + self.run_name = run_name self.platform: str = platform self.transfer_mode: CPPDriverTransferType = transfer_mode self.cpp_template_dir = cpp_template_dir @@ -138,55 +139,136 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: ext_weight_dma_cnt, weights_dir = write_weights(model, cpp_driver_dir) - # Writer header with shape data - make_array = lambda lst: "{" + (", ".join(map(lambda x: f"\"{x}\"", lst))) + "}" - def resolve_dt_name(s: str) -> str: - if "ap_int" in s: - return "INT" - elif "ap_uint" in s: - return "UINT" - elif "ap_fixed" in s: - return "FLOAT" - else: - return "UNKNOWN" - - definitions_header: str = f"#include \n#include \nstd::string platform = \"{self.platform}\";\nstd::string transferMode = \"{self.transfer_mode.value}\";\n\n" - - input_datatypes: List[DataType] = driver_shapes["idt"] - output_datatypes: List[DataType] = driver_shapes["odt"] - - #assert all([dt.is_integer() for dt in input_datatypes]), f"One of the datatypes for the input is not an integer! Datatypes: {input_datatypes}" - #assert all([dt.is_integer() for dt in output_datatypes]), f"One of the datatypes for the output is not an integer! Datatypes: {output_datatypes}" + #* Setting up compilation + if not os.path.isdir(os.path.join(self.cpp_template_dir, "build")): + os.mkdir(os.path.join(self.cpp_template_dir, "build")) + + #* Setting Filepaths for compilation of the C++ driver + # By default the structure is + # finn-cpp-driver + # --- build + # ------ src + # --------- finn (exec) + # --------- finn-accel.xclbin + # --- src + # ------ config + # --------- header.h + # --------- config.json + # + # Here config.json specifies the location of the xclbin (from BUILD_PATH), and the two compiler macros point to the header location (from self.cpp_template_dir + "/src/") and the config json (from self.cpp_template_dir + "/unittests/core/") [all "froms" if the path is relative! Should be absolute!] + # Due to the complex structure its best to pass every path as absolute + + # EXEC/BUILD + BUILD_PATH = os.path.abspath(os.path.join(self.cpp_template_dir, "build")) + + # HEADER + CPP_CONFIG_DIR = os.path.join(self.cpp_template_dir, "src", "config") + HEADER_NAME = f"FDTT_Header_Compiled_{self.run_name}.h" + HEADER_PATH = os.path.join(CPP_CONFIG_DIR, HEADER_NAME) + CMAKE_FINN_HEADER_LOCATION = os.path.abspath(HEADER_PATH) - definitions_header += "std::initializer_list INPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in input_datatypes]) + "};\n" - definitions_header += "std::initializer_list ONPUT_BYTEWIDTH = {" + ", ".join([ceil(dt.bitwidth()/8) for dt in output_datatypes]) + "};\n" + # CONFIG + JSON_NAME = f"config_{self.run_name}.json" + JSON_PATH = os.path.join(CPP_CONFIG_DIR, JSON_NAME) + CMAKE_FINN_CUSTOM_UNITTEST_CONFIG = os.path.abspath(JSON_PATH) - definitions_header += "std::initializer_list> INPUT_DATATYPE = {" + ", ".join([resolve_dt_name(dt.get_hls_datatype_str()) for dt in input_datatypes] + "};\n") - definitions_header += "std::initializer_list> OUTPUT_DATATYPE = {" + ", ".join([resolve_dt_name(dt.get_hls_datatype_str()) for dt in output_datatypes] + "};\n") - definitions_header += f"std::initializer_list IDMA_NAMES = " + make_array(driver_shapes["idma_names"]) + ";\n" - definitions_header += f"std::initializer_list ODMA_NAMES = " + make_array(driver_shapes["odma_names"]) + ";\n" - for name in ["ishape_normal", "ishape_packed", "ishape_folded", "oshape_normal", "oshape_packed", "oshape_folded"]: - definitions_header += "std::initializer_list> " + name.upper() + " = {\n" - definitions_header += ",\n".join([make_array(shape) for shape in driver_shapes[name]]) - definitions_header += "}\n" - definitions_header += "int EXT_WEIGHT_NUMS = " + str(ext_weight_dma_cnt) + ";\n" + #* Writing the header file + # TODO: Enable multiple input types! Now only assumes the first one + def resolve_dt_name(s: str) -> str: + if s in ["BINARY", "TERNARY", "BIPOLAR"]: + return "Datatype" + s[0] + s[1:].lower() + elif "INT" in s: + if s.startswith("U"): + return "DatatypeUint<" + s.replace("UINT", "") + ">" + else: + return "DatatypeInt<" + s.replace("INT", "") + ">" + elif "FLOAT" in s: + return "DatatypeFloat<" + s.replace("FLOAT", "") + ">" + elif "FIXED" in s: + return "DatatypeFixed" + s.replace("FIXED", "") + else: + return "UNKNOWN_DATATYPE_ERROR_BY_FINN_COMPILER" + inputDatatype: str = resolve_dt_name(driver_shapes["idt"].get_canonical_name()) + outputDatatype: str = resolve_dt_name(driver_shapes["odt"].get_canonical_name()) + print(f"Writing input header file for run with name {self.run_name}. Used datatypes will be {inputDatatype} and {outputDatatype}!") + with open(HEADER_PATH, 'w+') as f: + f.write("//! THIS FILE IS AUTOGENERATED BY THE FINN COMPILER\n") + f.write("#include \"../utils/FinnDatatypes.hpp\"\n#include \"../core/BaseDriver.hpp\"\n\n") + f.write(f"using InputFinnType = Finn::{inputDatatype};\n") + f.write(f"using OutputFinnType = Finn::{outputDatatype};\n") + f.write(f"namespace Finn {{ using Driver = Finn::BaseDriver; }} // namespace Finn\n") - # DEBUG: - print("DEFS: ") - print(definitions_header) - # TODO(bwintermann): Move compilation somewhere else / Include header file from relative path from cpp submodule? - with open(os.path.join(self.cpp_template_dir, "src", "template_driver.hpp"), "w+") as f: - f.write(definitions_header) + #* Writing the json file + # TODO: Update this for multi-fpga usage (more than one device!) + + # Path of the xclbin in the finn compiler project + xclbin_finn_path = model.get_metadata_prop("bitfile") + + # Path of the xclbin in the instantiated finn driver build directory, where the finn driver executable gets placed + #! Because the json is read at RUNTIME, the path to the xclbin has to either be given as absolute or relative to the location of the finn exec!! + xclbin_cppdriver_path = os.path.abspath(os.path.join(self.cpp_template_dir, "build", "src", "finn-accel.xclbin")) # TODO: Check + + # Copying finn-accel bitstream to the build folder of the cpp driver + import shutil + shutil.copy(xclbin_finn_path, xclbin_cppdriver_path) + + # Get kernel names using xclbinutil + import subprocess + import json + assert shutil.which("xclbinutil") is not None, "xclbinutil not in PATH or not installed. Required to read kernel names for driver config!" + subprocess.run(f"xclbinutil -i {xclbin_finn_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", shell=True) + ips = None + with open("ip_layout.json") as f: + ips = json.loads(f.read())["ip_layout"]["m_ip_data"] + + # Get only ips that are kernels + isIO = lambda x: x["m_type"] == "IP_KERNEL" and x["m_base_address"] != "not_used" and ("idma" in x["m_name"] or "odma" in x["m_name"]) + idmas = [x["m_name"] for x in ips if isIO(x) and "idma" in x["m_name"]] + odmas = [x["m_name"] for x in ips if isIO(x) and "odma" in x["m_name"]] + + # Create idma and odma entries + jsonIdmas = [] + jsonOdmas = [] + for i in range(len(driver_shapes["idma_names"])): + jsonIdmas.append({ + "kernelName": [name for name in idmas if driver_shapes["idma_names"][i] in name][0], + "normalShape": driver_shapes["ishape_normal"][i], + "foldedShape": driver_shapes["ishape_folded"][i], + "packedShape": driver_shapes["ishape_packed"][i] + }) + for i in range(len(driver_shapes["odma_names"])): + jsonOdmas.append({ + "kernelName": [name for name in odmas if driver_shapes["odma_names"][i] in name][0], + "normalShape": driver_shapes["oshape_normal"][i], + "foldedShape": driver_shapes["oshape_folded"][i], + "packedShape": driver_shapes["oshape_packed"][i] + }) + + data = [] + data.append({ + "xrtDeviceIndex": 0, + + #! XCLBIN must be in the same directory as the finn executable! + # TODO: This script has to move the xclbin into the build/src folder of the cpp driver + # TODO: For that the script must know where it is, and where the xclbin isnt. + "xclbinPath":xclbin_cppdriver_path, + + "name": "MainDevice", + "idmas": jsonIdmas, + "odmas": jsonOdmas + }) + with open(JSON_PATH, 'w+') as f: + f.write(json.dumps(data, indent=4)) + + #* Compilation + assert os.path.isfile(CMAKE_FINN_HEADER_LOCATION) and os.path.isfile(CMAKE_FINN_CUSTOM_UNITTEST_CONFIG) and os.path.isdir(BUILD_PATH), "Header, configjson or build folder missing. Cannot compile C++ driver!" + compile_result = subprocess.run(f"cd {BUILD_PATH};cmake -DCMAKE_BUILD_TYPE=Release -DFINN_HEADER_LOCATION=\"{CMAKE_FINN_HEADER_LOCATION}\" -DFINN_CUSTOM_UNITTEST_CONFIG=\"{CMAKE_FINN_CUSTOM_UNITTEST_CONFIG}\" .;cmake --build . --target finn", stdout=subprocess.PIPE, shell=True) + assert compile_result.returncode == 0, "[MakeCPPDriver - Transformation] Compilation failed!" + print("Compiled C++ driver successfully.") - # Compilation - assert which("cmake") is not None, "cmake not found! Please install it or add it to path!" - assert which("make") is not None, "make not found! Please install it or add it to path!" - os.chdir(os.path.join(self.cpp_template_dir, "build")) - subprocess.run("cmake --build .", shell=True) - subprocess.run("make -j4", shell=True) # TODO: Generating weight files From 2145749043d95392e0e0aeab6e04c5cb5a66c1cb Mon Sep 17 00:00:00 2001 From: bwintermann Date: Wed, 4 Oct 2023 16:44:56 +0200 Subject: [PATCH 24/57] Updated finn-cpp-driver submodule to track dev branch --- src/finn/transformation/fpgadataflow/finn-cpp-driver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finn/transformation/fpgadataflow/finn-cpp-driver b/src/finn/transformation/fpgadataflow/finn-cpp-driver index ddab23f0d3..339e213589 160000 --- a/src/finn/transformation/fpgadataflow/finn-cpp-driver +++ b/src/finn/transformation/fpgadataflow/finn-cpp-driver @@ -1 +1 @@ -Subproject commit ddab23f0d3453a37f1687609d259072af19549ee +Subproject commit 339e213589298262b0a0ae5dad96c63065fbd8f2 From b5aaa2da84321492e7f81c88070c9102cd459790 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Fri, 6 Oct 2023 18:17:51 +0200 Subject: [PATCH 25/57] Some path fixing --- src/finn/builder/build_dataflow_steps.py | 6 ++--- .../fpgadataflow/make_driver.py | 22 ++++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index e3f37697ca..3786cc96b2 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -738,12 +738,10 @@ def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> Model MakeCPPDriver( cfg._resolve_driver_platform(), transfer_mode=cfg.cpp_driver_transfer_type, - cpp_template_dir="finn-cpp-driver" + cpp_template_dir=os.path.join(cfg.output_dir, "cppdriver") + ) ) - - # TODO: Compilation - # TODO: Copying into driver directory else: print("WARNING: The step step_make_cpp_driver is in the build list but will not be executed since CPP_DRIVER is not in generate_outputs in your build.py file!") return model diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 69f5ea803f..e9a18ab925 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -120,24 +120,36 @@ def generate_runtime_weights(model: ModelWrapper, weights_dir: str): class MakeCPPDriver(Transformation): + #! This is likely the wrong path because this module is likely not called from the transformation/fpgadatflow path. + # TODO: Correct this later on + CPP_DRIVER_TEMPLATE_LOCATION: str = "finn-cpp-driver" + def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, cpp_template_dir: str, run_name: str = "RUN_ID"): super().__init__() self.run_name = run_name self.platform: str = platform self.transfer_mode: CPPDriverTransferType = transfer_mode + + # CPP Template dir is the directory where the build should happen, this must + # not necessarily be the finn folder itself, but can also be the projects build folder + # TODO: Find out where the location of the project folder is stored (where driver, bitfile, logs etc. are placed) self.cpp_template_dir = cpp_template_dir def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: # Define location for the driver files - cpp_driver_dir = make_build_dir(prefix="cpp_driver_") - model.set_metadata_prop("cp_driver_dir", cpp_driver_dir) + #cpp_driver_dir = make_build_dir(prefix="cpp_driver_") + #model.set_metadata_prop("cpp_driver_dir", cpp_driver_dir) # TODO: Preparing folders and files driver_shapes: Dict = get_driver_shapes(model) ext_weight_dma_cnt: int weights_dir: str - ext_weight_dma_cnt, weights_dir = write_weights(model, cpp_driver_dir) +# ext_weight_dma_cnt, weights_dir = write_weights(model, cpp_driver_dir) + #* Copying the template c++ driver dir to the specified location + assert os.path.isdir(self.cpp_template_dir), "CPP Driver location dir not found: " + self.cpp_template_dir + shutil.copy(MakeCPPDriver.CPP_DRIVER_TEMPLATE_LOCATION, os.path.join(self.cpp_template_dir, "finn-cpp-driver")) + self.cpp_template_dir = os.path.join(self.cpp_template_dir, "finn-cpp-driver") #* Setting up compilation if not os.path.isdir(os.path.join(self.cpp_template_dir, "build")): @@ -190,8 +202,8 @@ def resolve_dt_name(s: str) -> str: else: return "UNKNOWN_DATATYPE_ERROR_BY_FINN_COMPILER" - inputDatatype: str = resolve_dt_name(driver_shapes["idt"].get_canonical_name()) - outputDatatype: str = resolve_dt_name(driver_shapes["odt"].get_canonical_name()) + inputDatatype: str = resolve_dt_name(driver_shapes["idt"][0].get_canonical_name()) + outputDatatype: str = resolve_dt_name(driver_shapes["odt"][0].get_canonical_name()) print(f"Writing input header file for run with name {self.run_name}. Used datatypes will be {inputDatatype} and {outputDatatype}!") with open(HEADER_PATH, 'w+') as f: f.write("//! THIS FILE IS AUTOGENERATED BY THE FINN COMPILER\n") From cacbd55be7043d28b43d40dd4fb445d859fab30a Mon Sep 17 00:00:00 2001 From: bwintermann Date: Tue, 17 Oct 2023 10:04:56 +0200 Subject: [PATCH 26/57] Transfer run-docker update for singularity from PR --- run-docker.sh | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/run-docker.sh b/run-docker.sh index c24dcec724..99c8a214e8 100755 --- a/run-docker.sh +++ b/run-docker.sh @@ -97,6 +97,7 @@ SCRIPTPATH=$(dirname "$SCRIPT") : ${OHMYXILINX="${SCRIPTPATH}/deps/oh-my-xilinx"} : ${NVIDIA_VISIBLE_DEVICES=""} : ${DOCKER_BUILDKIT="1"} +: ${FINN_SINGULARITY=""} DOCKER_INTERACTIVE="" @@ -116,8 +117,10 @@ elif [ "$1" = "notebook" ]; then DOCKER_CMD="jupyter notebook --allow-root --no-browser --ip=0.0.0.0 --port $JUPYTER_PORT $JUPYTER_PASSWD_ARG notebooks" FINN_DOCKER_EXTRA+="-e JUPYTER_PORT=$JUPYTER_PORT " FINN_DOCKER_EXTRA+="-e NETRON_PORT=$NETRON_PORT " - FINN_DOCKER_EXTRA+="-p $JUPYTER_PORT:$JUPYTER_PORT " - FINN_DOCKER_EXTRA+="-p $NETRON_PORT:$NETRON_PORT " + if [ -z "$FINN_SINGULARITY" ]; then + FINN_DOCKER_EXTRA+="-p $JUPYTER_PORT:$JUPYTER_PORT " + FINN_DOCKER_EXTRA+="-p $NETRON_PORT:$NETRON_PORT " + fi elif [ "$1" = "build_dataflow" ]; then BUILD_DATAFLOW_DIR=$(readlink -f "$2") FINN_DOCKER_EXTRA+="-v $BUILD_DATAFLOW_DIR:$BUILD_DATAFLOW_DIR " @@ -143,7 +146,7 @@ else fi -if [ "$FINN_DOCKER_GPU" != 0 ];then +if [ "$FINN_DOCKER_GPU" != 0 ] && [ -z "$FINN_SINGULARITY" ];then gecho "nvidia-docker detected, enabling GPUs" if [ ! -z "$NVIDIA_VISIBLE_DEVICES" ];then FINN_DOCKER_EXTRA+="--runtime nvidia -e NVIDIA_VISIBLE_DEVICES=$NVIDIA_VISIBLE_DEVICES " @@ -174,7 +177,7 @@ if [ "$FINN_SKIP_DEP_REPOS" = "0" ]; then fi # Build the FINN Docker image -if [ "$FINN_DOCKER_PREBUILT" = "0" ]; then +if [ "$FINN_DOCKER_PREBUILT" = "0" ] && [ -z "$FINN_SINGULARITY" ]; then # Need to ensure this is done within the finn/ root folder: OLD_PWD=$(pwd) cd $SCRIPTPATH @@ -184,9 +187,8 @@ fi # Launch container with current directory mounted # important to pass the --init flag here for correct Vivado operation, see: # https://stackoverflow.com/questions/55733058/vivado-synthesis-hangs-in-docker-container-spawned-by-jenkins -DOCKER_EXEC="docker run -t --rm $DOCKER_INTERACTIVE --tty --init " -DOCKER_EXEC+="--hostname $DOCKER_INST_NAME " -DOCKER_EXEC+="-e SHELL=/bin/bash " +DOCKER_BASE="docker run -t --rm $DOCKER_INTERACTIVE --tty --init --hostname $DOCKER_INST_NAME " +DOCKER_EXEC="-e SHELL=/bin/bash " DOCKER_EXEC+="-w $SCRIPTPATH " DOCKER_EXEC+="-v $SCRIPTPATH:$SCRIPTPATH " DOCKER_EXEC+="-v $FINN_HOST_BUILD_DIR:$FINN_HOST_BUILD_DIR " @@ -204,7 +206,7 @@ DOCKER_EXEC+="-e NUM_DEFAULT_WORKERS=$NUM_DEFAULT_WORKERS " # Workaround for FlexLM issue, see: # https://community.flexera.com/t5/InstallAnywhere-Forum/Issues-when-running-Xilinx-tools-or-Other-vendor-tools-in-docker/m-p/245820#M10647 DOCKER_EXEC+="-e LD_PRELOAD=/lib/x86_64-linux-gnu/libudev.so.1 " -if [ "$FINN_DOCKER_RUN_AS_ROOT" = "0" ];then +if [ "$FINN_DOCKER_RUN_AS_ROOT" = "0" ] && [ -z "$FINN_SINGULARITY" ];then DOCKER_EXEC+="-v /etc/group:/etc/group:ro " DOCKER_EXEC+="-v /etc/passwd:/etc/passwd:ro " DOCKER_EXEC+="-v /etc/shadow:/etc/shadow:ro " @@ -244,6 +246,17 @@ if [ ! -z "$FINN_XILINX_PATH" ];then fi fi DOCKER_EXEC+="$FINN_DOCKER_EXTRA " -DOCKER_EXEC+="$FINN_DOCKER_TAG $DOCKER_CMD" -$DOCKER_EXEC +if [ -z "$FINN_SINGULARITY" ];then + CMD_TO_RUN="$DOCKER_BASE $DOCKER_EXEC $FINN_DOCKER_TAG $DOCKER_CMD" +else + SINGULARITY_BASE="singularity exec" + # Replace command options for Singularity + SINGULARITY_EXEC="${DOCKER_EXEC//"-e "/"--env "}" + SINGULARITY_EXEC="${SINGULARITY_EXEC//"-v "/"-B "}" + SINGULARITY_EXEC="${SINGULARITY_EXEC//"-w "/"--pwd "}" + CMD_TO_RUN="$SINGULARITY_BASE $SINGULARITY_EXEC $FINN_SINGULARITY /usr/local/bin/finn_entrypoint.sh $DOCKER_CMD" +fi + +$CMD_TO_RUN + From 7c39ad5bf8711afd06f51020a6722cc6b8d79b37 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Wed, 18 Oct 2023 14:52:47 +0200 Subject: [PATCH 27/57] Fixed path errors during cpp driver step --- src/finn/builder/build_dataflow_steps.py | 4 +- .../fpgadataflow/make_driver.py | 136 ++++++------------ 2 files changed, 44 insertions(+), 96 deletions(-) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 3786cc96b2..01a4d26ac1 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -738,8 +738,8 @@ def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> Model MakeCPPDriver( cfg._resolve_driver_platform(), transfer_mode=cfg.cpp_driver_transfer_type, - cpp_template_dir=os.path.join(cfg.output_dir, "cppdriver") - + cpp_template_dir=os.path.join(os.path.dirname(__file__), "finn-cpp-driver"), + output_dir = cfg.output_dir ) ) else: diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index e9a18ab925..cffd2a2aea 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -29,6 +29,7 @@ import pkg_resources as pk +import json import numpy as np import os import qonnx @@ -120,92 +121,53 @@ def generate_runtime_weights(model: ModelWrapper, weights_dir: str): class MakeCPPDriver(Transformation): - #! This is likely the wrong path because this module is likely not called from the transformation/fpgadatflow path. - # TODO: Correct this later on - CPP_DRIVER_TEMPLATE_LOCATION: str = "finn-cpp-driver" + # TODO: Enable multiple input types! Now only assumes the first one + def resolve_dt_name(s: str) -> str: + if s in ["BINARY", "TERNARY", "BIPOLAR"]: + return "Datatype" + s[0] + s[1:].lower() + elif "INT" in s: + if s.startswith("U"): + return "DatatypeUint<" + s.replace("UINT", "") + ">" + else: + return "DatatypeInt<" + s.replace("INT", "") + ">" + elif "FLOAT" in s: + return "DatatypeFloat<" + s.replace("FLOAT", "") + ">" + elif "FIXED" in s: + return "DatatypeFixed" + s.replace("FIXED", "") + else: + return "UNKNOWN_DATATYPE_ERROR_BY_FINN_COMPILER" - def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, cpp_template_dir: str, run_name: str = "RUN_ID"): + def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, cpp_template_dir: str, output_dir: str, run_name: str = "RUN_ID"): super().__init__() self.run_name = run_name self.platform: str = platform self.transfer_mode: CPPDriverTransferType = transfer_mode - - # CPP Template dir is the directory where the build should happen, this must - # not necessarily be the finn folder itself, but can also be the projects build folder - # TODO: Find out where the location of the project folder is stored (where driver, bitfile, logs etc. are placed) self.cpp_template_dir = cpp_template_dir + self.output_dir = output_dir - def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: - # Define location for the driver files - #cpp_driver_dir = make_build_dir(prefix="cpp_driver_") - #model.set_metadata_prop("cpp_driver_dir", cpp_driver_dir) + # Locations of files + self.xclbin_path = os.path.join(output_dir, "bitfile", "finn-accel.xclbin") + self.template_target_dir = os.path.join(output_dir, "finn-cpp-driver") + self.json_path = os.path.join(output_dir, "driver", "cppdconfig.json") + self.header_path = os.path.join(self.template_target_dir, "src", "config", "FinnDriverUsedDatatypes.h") + self.finn_driver_exec_path = os.path.join(output_dir, "driver", "finn") - # TODO: Preparing folders and files + def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: driver_shapes: Dict = get_driver_shapes(model) ext_weight_dma_cnt: int weights_dir: str -# ext_weight_dma_cnt, weights_dir = write_weights(model, cpp_driver_dir) - - #* Copying the template c++ driver dir to the specified location - assert os.path.isdir(self.cpp_template_dir), "CPP Driver location dir not found: " + self.cpp_template_dir - shutil.copy(MakeCPPDriver.CPP_DRIVER_TEMPLATE_LOCATION, os.path.join(self.cpp_template_dir, "finn-cpp-driver")) - self.cpp_template_dir = os.path.join(self.cpp_template_dir, "finn-cpp-driver") - - #* Setting up compilation - if not os.path.isdir(os.path.join(self.cpp_template_dir, "build")): - os.mkdir(os.path.join(self.cpp_template_dir, "build")) - - #* Setting Filepaths for compilation of the C++ driver - # By default the structure is - # finn-cpp-driver - # --- build - # ------ src - # --------- finn (exec) - # --------- finn-accel.xclbin - # --- src - # ------ config - # --------- header.h - # --------- config.json - # - # Here config.json specifies the location of the xclbin (from BUILD_PATH), and the two compiler macros point to the header location (from self.cpp_template_dir + "/src/") and the config json (from self.cpp_template_dir + "/unittests/core/") [all "froms" if the path is relative! Should be absolute!] - # Due to the complex structure its best to pass every path as absolute - - # EXEC/BUILD - BUILD_PATH = os.path.abspath(os.path.join(self.cpp_template_dir, "build")) - - # HEADER - CPP_CONFIG_DIR = os.path.join(self.cpp_template_dir, "src", "config") - HEADER_NAME = f"FDTT_Header_Compiled_{self.run_name}.h" - HEADER_PATH = os.path.join(CPP_CONFIG_DIR, HEADER_NAME) - CMAKE_FINN_HEADER_LOCATION = os.path.abspath(HEADER_PATH) - - # CONFIG - JSON_NAME = f"config_{self.run_name}.json" - JSON_PATH = os.path.join(CPP_CONFIG_DIR, JSON_NAME) - CMAKE_FINN_CUSTOM_UNITTEST_CONFIG = os.path.abspath(JSON_PATH) + # ext_weight_dma_cnt, weights_dir = write_weights(model, cpp_driver_dir) + #* Copying the finn-cpp-driver into the output folder to have a clean template every run + if os.path.isdir(self.template_target_dir): + subprocess.run("rm -rf " + self.template_target_dir, shell=True, stdout=subprocess.PIPE) + subprocess.run(f"cp -r {self.cpp_template_dir} {self.template_target_dir}", shell=True, stdout=subprocess.PIPE) #* Writing the header file - # TODO: Enable multiple input types! Now only assumes the first one - def resolve_dt_name(s: str) -> str: - if s in ["BINARY", "TERNARY", "BIPOLAR"]: - return "Datatype" + s[0] + s[1:].lower() - elif "INT" in s: - if s.startswith("U"): - return "DatatypeUint<" + s.replace("UINT", "") + ">" - else: - return "DatatypeInt<" + s.replace("INT", "") + ">" - elif "FLOAT" in s: - return "DatatypeFloat<" + s.replace("FLOAT", "") + ">" - elif "FIXED" in s: - return "DatatypeFixed" + s.replace("FIXED", "") - else: - return "UNKNOWN_DATATYPE_ERROR_BY_FINN_COMPILER" - - inputDatatype: str = resolve_dt_name(driver_shapes["idt"][0].get_canonical_name()) - outputDatatype: str = resolve_dt_name(driver_shapes["odt"][0].get_canonical_name()) + inputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["idt"][0].get_canonical_name()) + outputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["odt"][0].get_canonical_name()) print(f"Writing input header file for run with name {self.run_name}. Used datatypes will be {inputDatatype} and {outputDatatype}!") - with open(HEADER_PATH, 'w+') as f: + with open(self.header_path, 'w+') as f: f.write("//! THIS FILE IS AUTOGENERATED BY THE FINN COMPILER\n") f.write("#include \"../utils/FinnDatatypes.hpp\"\n#include \"../core/BaseDriver.hpp\"\n\n") f.write(f"using InputFinnType = Finn::{inputDatatype};\n") @@ -215,23 +177,10 @@ def resolve_dt_name(s: str) -> str: #* Writing the json file # TODO: Update this for multi-fpga usage (more than one device!) - # Path of the xclbin in the finn compiler project - xclbin_finn_path = model.get_metadata_prop("bitfile") - - # Path of the xclbin in the instantiated finn driver build directory, where the finn driver executable gets placed - #! Because the json is read at RUNTIME, the path to the xclbin has to either be given as absolute or relative to the location of the finn exec!! - xclbin_cppdriver_path = os.path.abspath(os.path.join(self.cpp_template_dir, "build", "src", "finn-accel.xclbin")) # TODO: Check - - # Copying finn-accel bitstream to the build folder of the cpp driver - import shutil - shutil.copy(xclbin_finn_path, xclbin_cppdriver_path) - # Get kernel names using xclbinutil - import subprocess - import json assert shutil.which("xclbinutil") is not None, "xclbinutil not in PATH or not installed. Required to read kernel names for driver config!" - subprocess.run(f"xclbinutil -i {xclbin_finn_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", shell=True) + subprocess.run(f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", shell=True) ips = None with open("ip_layout.json") as f: ips = json.loads(f.read())["ip_layout"]["m_ip_data"] @@ -262,26 +211,25 @@ def resolve_dt_name(s: str) -> str: data = [] data.append({ "xrtDeviceIndex": 0, - - #! XCLBIN must be in the same directory as the finn executable! - # TODO: This script has to move the xclbin into the build/src folder of the cpp driver - # TODO: For that the script must know where it is, and where the xclbin isnt. - "xclbinPath":xclbin_cppdriver_path, + "xclbinPath":os.path.abspath(self.xclbin_path), "name": "MainDevice", "idmas": jsonIdmas, "odmas": jsonOdmas }) - with open(JSON_PATH, 'w+') as f: + with open(self.json_path, 'w+') as f: f.write(json.dumps(data, indent=4)) #* Compilation - assert os.path.isfile(CMAKE_FINN_HEADER_LOCATION) and os.path.isfile(CMAKE_FINN_CUSTOM_UNITTEST_CONFIG) and os.path.isdir(BUILD_PATH), "Header, configjson or build folder missing. Cannot compile C++ driver!" - compile_result = subprocess.run(f"cd {BUILD_PATH};cmake -DCMAKE_BUILD_TYPE=Release -DFINN_HEADER_LOCATION=\"{CMAKE_FINN_HEADER_LOCATION}\" -DFINN_CUSTOM_UNITTEST_CONFIG=\"{CMAKE_FINN_CUSTOM_UNITTEST_CONFIG}\" .;cmake --build . --target finn", stdout=subprocess.PIPE, shell=True) + build_path = os.path.join(self.template_target_dir, "build") + if not os.path.isdir(build_path): + os.mkdir(build_path) + compile_result = subprocess.run(f"cmake -DCMAKE_BUILD_TYPE=Release -DFINN_HEADER_LOCATION=\"{self.header_path}\" .;cmake --build . --target finn", stdout=subprocess.PIPE, shell=True, cwd=build_path) assert compile_result.returncode == 0, "[MakeCPPDriver - Transformation] Compilation failed!" print("Compiled C++ driver successfully.") - + #* Copy exec back + shutil.copy(os.join(build_path, "src", "finn"), self.finn_driver_exec_path) # TODO: Generating weight files generate_runtime_weights(model, weights_dir) From 9b521f53dcdd228a9ec6b6d7f419e1103738e1e1 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Thu, 19 Oct 2023 13:21:47 +0200 Subject: [PATCH 28/57] Fixed pathing issues and type name conversion --- src/finn/builder/build_dataflow_steps.py | 2 +- .../fpgadataflow/make_driver.py | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 01a4d26ac1..f5beedae15 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -738,7 +738,7 @@ def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> Model MakeCPPDriver( cfg._resolve_driver_platform(), transfer_mode=cfg.cpp_driver_transfer_type, - cpp_template_dir=os.path.join(os.path.dirname(__file__), "finn-cpp-driver"), + cpp_template_dir=os.path.join(os.path.dirname(__file__), "..", "transformation", "fpgadataflow", "finn-cpp-driver"), output_dir = cfg.output_dir ) ) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index cffd2a2aea..ddb30014e4 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -123,13 +123,13 @@ def generate_runtime_weights(model: ModelWrapper, weights_dir: str): class MakeCPPDriver(Transformation): # TODO: Enable multiple input types! Now only assumes the first one def resolve_dt_name(s: str) -> str: + print("Converting tensor datatype " + str(s)) if s in ["BINARY", "TERNARY", "BIPOLAR"]: return "Datatype" + s[0] + s[1:].lower() - elif "INT" in s: - if s.startswith("U"): - return "DatatypeUint<" + s.replace("UINT", "") + ">" - else: - return "DatatypeInt<" + s.replace("INT", "") + ">" + elif s.startswith("U"): + return "DatatypeUint<" + s.replace("U", "") + ">" + elif s.startswith("I"): + return "DatatypeInt<" + s.replace("I", "") + ">" elif "FLOAT" in s: return "DatatypeFloat<" + s.replace("FLOAT", "") + ">" elif "FIXED" in s: @@ -158,14 +158,20 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: weights_dir: str # ext_weight_dma_cnt, weights_dir = write_weights(model, cpp_driver_dir) + #* Creating the driver dir if it doesnt exist yet + driver_dir = os.path.join(self.output_dir, "driver") + if not os.path.isdir(driver_dir): + os.mkdir(driver_dir) + #* Copying the finn-cpp-driver into the output folder to have a clean template every run if os.path.isdir(self.template_target_dir): subprocess.run("rm -rf " + self.template_target_dir, shell=True, stdout=subprocess.PIPE) + print("Copying finn-cpp-driver from " + self.cpp_template_dir + " to " + self.template_target_dir) subprocess.run(f"cp -r {self.cpp_template_dir} {self.template_target_dir}", shell=True, stdout=subprocess.PIPE) #* Writing the header file - inputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["idt"][0].get_canonical_name()) - outputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["odt"][0].get_canonical_name()) + inputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["idt"][0].replace("'", ""))#.get_canonical_name()) + outputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["odt"][0].replace("'", ""))#.get_canonical_name()) print(f"Writing input header file for run with name {self.run_name}. Used datatypes will be {inputDatatype} and {outputDatatype}!") with open(self.header_path, 'w+') as f: f.write("//! THIS FILE IS AUTOGENERATED BY THE FINN COMPILER\n") From 1e2aea6a2b80065796dd1459a95d8f441b4620b2 Mon Sep 17 00:00:00 2001 From: bwintermann Date: Thu, 19 Oct 2023 16:17:42 +0200 Subject: [PATCH 29/57] Datatype parser fix --- src/finn/transformation/fpgadataflow/make_driver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index ddb30014e4..723052ef86 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -123,6 +123,7 @@ def generate_runtime_weights(model: ModelWrapper, weights_dir: str): class MakeCPPDriver(Transformation): # TODO: Enable multiple input types! Now only assumes the first one def resolve_dt_name(s: str) -> str: + s = s.replace("Datatype[", "").replace("]", "") print("Converting tensor datatype " + str(s)) if s in ["BINARY", "TERNARY", "BIPOLAR"]: return "Datatype" + s[0] + s[1:].lower() From da51f97bdefd892f2eed1231d315fd5d7c240aef Mon Sep 17 00:00:00 2001 From: Linus Jungemann Date: Mon, 30 Oct 2023 16:08:53 +0100 Subject: [PATCH 30/57] Add support for U55C to FINN --- .../qnn-data/templates/driver/driver_base.py | 5 +-- .../fpgadataflow/template_driver.py | 7 ++-- .../fpgadataflow/vitis_build.py | 2 +- src/finn/util/basic.py | 2 ++ src/finn/util/platforms.py | 34 +++++++++++++++++++ 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/finn/qnn-data/templates/driver/driver_base.py b/src/finn/qnn-data/templates/driver/driver_base.py index f701122885..52a28ef062 100644 --- a/src/finn/qnn-data/templates/driver/driver_base.py +++ b/src/finn/qnn-data/templates/driver/driver_base.py @@ -78,6 +78,7 @@ def __init__( Path to runtime weights folder. """ super().__init__(bitfile_name, download=download, device=device) + self.device = device self.runtime_weight_dir = runtime_weight_dir self._io_shape_dict = io_shape_dict self.ibuf_packed_device = None @@ -262,12 +263,12 @@ def batch_size(self, value): self.obuf_packed = [] for i in range(self.num_inputs): new_packed_ibuf = allocate( - shape=self.ishape_packed(i), dtype=np.uint8, cacheable=cacheable + shape=self.ishape_packed(i), dtype=np.uint8, cacheable=cacheable, target = self.device ) self.ibuf_packed_device.append(new_packed_ibuf) for o in range(self.num_outputs): new_packed_obuf = allocate( - shape=self.oshape_packed(o), dtype=np.uint8, cacheable=cacheable + shape=self.oshape_packed(o), dtype=np.uint8, cacheable=cacheable, target = self.device ) self.obuf_packed_device.append(new_packed_obuf) self.obuf_packed.append(np.empty_like(new_packed_obuf)) diff --git a/src/finn/transformation/fpgadataflow/template_driver.py b/src/finn/transformation/fpgadataflow/template_driver.py index 158825191e..4cefdfd98e 100644 --- a/src/finn/transformation/fpgadataflow/template_driver.py +++ b/src/finn/transformation/fpgadataflow/template_driver.py @@ -88,8 +88,9 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description='Execute FINN-generated accelerator on numpy inputs, or run throughput test') parser.add_argument('--exec_mode', help='Please select functional verification ("execute") or throughput test ("throughput_test")', default="execute") - parser.add_argument('--platform', help='Target platform: zynq-iodma alveo', default="$PLATFORM$") + parser.add_argument('--platform', help='Target platform: zynq-iodma alveo', default="alveo") parser.add_argument('--batchsize', help='number of samples for inference', 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 (i.e. "resizer.bit")', default="resizer.bit") parser.add_argument('--inputfile', help='name(s) of input npy file(s) (i.e. "input.npy")', nargs="*", type=str, default=["input.npy"]) parser.add_argument('--outputfile', help='name(s) of output npy file(s) (i.e. "output.npy")', nargs="*", type=str, default=["output.npy"]) @@ -103,12 +104,14 @@ inputfile = args.inputfile outputfile = args.outputfile runtime_weight_dir = args.runtime_weight_dir + devID = args.device + device = Device.devices[devID] # instantiate FINN accelerator driver and pass batchsize and bitfile accel = FINNExampleOverlay( bitfile_name = bitfile, platform = platform, io_shape_dict = io_shape_dict, batch_size = batch_size, - runtime_weight_dir = runtime_weight_dir + runtime_weight_dir = runtime_weight_dir, device=device ) # for the remote execution the data from the input npy file has to be loaded, diff --git a/src/finn/transformation/fpgadataflow/vitis_build.py b/src/finn/transformation/fpgadataflow/vitis_build.py index 2fc0b2f3bb..a102660001 100644 --- a/src/finn/transformation/fpgadataflow/vitis_build.py +++ b/src/finn/transformation/fpgadataflow/vitis_build.py @@ -231,7 +231,7 @@ def apply(self, model): node_mem_port = sdp_node.get_nodeattr("mem_port") if node_mem_port == "": # configure good defaults based on board - if "u50" in self.platform or "u280" in self.platform: + if "u50" in self.platform or "u280" in self.platform or "u55c" in self.platform: # Use HBM where available (also U50 does not have DDR) mem_type = "HBM" mem_idx = 0 diff --git a/src/finn/util/basic.py b/src/finn/util/basic.py index 05f748d3bb..86a06b0453 100644 --- a/src/finn/util/basic.py +++ b/src/finn/util/basic.py @@ -60,12 +60,14 @@ alveo_part_map["U200"] = "xcu200-fsgd2104-2-e" alveo_part_map["U250"] = "xcu250-figd2104-2L-e" alveo_part_map["U280"] = "xcu280-fsvh2892-2L-e" +alveo_part_map["U55C"] = "xcu55c-fsvh2892-2L-e" alveo_default_platform = dict() alveo_default_platform["U50"] = "xilinx_u50_gen3x16_xdma_5_202210_1" alveo_default_platform["U200"] = "xilinx_u200_gen3x16_xdma_2_202110_1" alveo_default_platform["U250"] = "xilinx_u250_gen3x16_xdma_4_1_202210_1" alveo_default_platform["U280"] = "xilinx_u280_gen3x16_xdma_1_202211_1" +alveo_default_platform["U55C"] = "xilinx_u55c_gen3x16_xdma_3_202210_1" def get_rtlsim_trace_depth(): diff --git a/src/finn/util/platforms.py b/src/finn/util/platforms.py index 77dc591445..4a944d59ef 100644 --- a/src/finn/util/platforms.py +++ b/src/finn/util/platforms.py @@ -459,6 +459,39 @@ def compute_resources(self): [382080, 2 * 382080, 2 * 576, 320, 2880], [380640, 2 * 380640, 2 * 576, 320, 2880], ] + +class Alveo_NxU55C_Platform(Platform): + def __init__( + self, + ndevices=1, + limits=DEFAULT_RES_LIMITS, + avg_constraints=DEFAULT_AVG_CONSTRAINTS, + ): + sll_counts = [[0, 5000, 0], [5000, 0, 5000], [0, 5000, 0]] + super(Alveo_NxU50_Platform, self).__init__( + nslr=2, + ndevices=ndevices, + sll_count=sll_counts, + ddr_slr=[], + hbm_slr=0, + eth_slr=2, + eth_gbps=100, + limits=limits, + avg_constraints=avg_constraints, + ) + + @property + def compute_resources(self): + # according to UG1120 + # return [[369000, 746000, 2*507, 320, 2733], + # [333000, 675000, 2*468, 320, 2877], + # [367000, 729000, 2*512, 320, 2880]] + # observed from Vivado: + return [ + [400800, 2 * 400800, 2 * 600, 320, 2736], + [382080, 2 * 382080, 2 * 576, 320, 2880], + [380640, 2 * 380640, 2 * 576, 320, 2880], + ] platforms = dict() @@ -466,6 +499,7 @@ def compute_resources(self): platforms["U200"] = Alveo_NxU200_Platform platforms["U250"] = Alveo_NxU250_Platform platforms["U280"] = Alveo_NxU280_Platform +platforms["U55C"] = Alveo_NxU55C_Platform platforms["Pynq-Z1"] = Zynq7020_Platform platforms["Pynq-Z2"] = Zynq7020_Platform platforms["Ultra96"] = ZU3EG_Platform From dcc0f14409870f694b7e9818a2876b7f9ebd6ab0 Mon Sep 17 00:00:00 2001 From: Linus Jungemann Date: Mon, 30 Oct 2023 16:11:35 +0100 Subject: [PATCH 31/57] Update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index be61378730..a9785da9cd 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ __pycache__/* .cache/* .*.swp *.ipynb_checkpoints* +*.sif # Project files .vscode @@ -65,6 +66,7 @@ coverage.xml .pytest_cache/ # Build and docs folder/files +finn_build_single_job.sh build/* dist/* sdist/* From f4552e89ae4ebbdfde7fefad07ad957b18d37a33 Mon Sep 17 00:00:00 2001 From: Linus Jungemann Date: Tue, 16 Jan 2024 09:59:55 +0100 Subject: [PATCH 32/57] Change HBM bank allocation to use different banks for input and output --- src/finn/transformation/fpgadataflow/vitis_build.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/vitis_build.py b/src/finn/transformation/fpgadataflow/vitis_build.py index a102660001..d897bd0ff6 100644 --- a/src/finn/transformation/fpgadataflow/vitis_build.py +++ b/src/finn/transformation/fpgadataflow/vitis_build.py @@ -187,6 +187,7 @@ def apply(self, model): object_files = [] idma_idx = 0 odma_idx = 0 + mem_idx = 0 instance_names = {} for node in model.graph.node: assert node.op_type == "StreamingDataflowPartition", "Invalid link graph" @@ -227,18 +228,20 @@ def apply(self, model): if node_slr != -1: config.append("slr=%s:SLR%d" % (instance_names[node.name], node_slr)) # assign memory banks - if producer is None or consumer is None: + if producer is None or consumer is None or consumer == []: node_mem_port = sdp_node.get_nodeattr("mem_port") if node_mem_port == "": # configure good defaults based on board if "u50" in self.platform or "u280" in self.platform or "u55c" in self.platform: # Use HBM where available (also U50 does not have DDR) mem_type = "HBM" - mem_idx = 0 + node_mem_port = "%s[%d]" % (mem_type, mem_idx) + mem_idx += 1 elif "u200" in self.platform: # Use DDR controller in static region of U200 mem_type = "DDR" mem_idx = 1 + node_mem_port = "%s[%d]" % (mem_type, mem_idx) elif "u250" in self.platform: # Use DDR controller on the node's SLR if set, otherwise 0 mem_type = "DDR" @@ -246,10 +249,11 @@ def apply(self, model): mem_idx = 0 else: mem_idx = node_slr + node_mem_port = "%s[%d]" % (mem_type, mem_idx) else: mem_type = "DDR" mem_idx = 1 - node_mem_port = "%s[%d]" % (mem_type, mem_idx) + node_mem_port = "%s[%d]" % (mem_type, mem_idx) config.append("sp=%s.m_axi_gmem0:%s" % (instance_names[node.name], node_mem_port)) # connect streams if producer is not None: From 9e65299f93dfa3baa3aa45082c77631368188a75 Mon Sep 17 00:00:00 2001 From: Linus Jungemann Date: Thu, 1 Feb 2024 10:14:14 +0100 Subject: [PATCH 33/57] Update finn integration of C++ driver --- .gitmodules | 2 +- src/finn/builder/build_dataflow_config.py | 8 +- src/finn/builder/build_dataflow_steps.py | 6 +- .../fpgadataflow/finn-cpp-driver | 2 +- .../fpgadataflow/make_driver.py | 97 ++++-- .../fpgadataflow/make_pynq_driver.py | 303 ------------------ 6 files changed, 87 insertions(+), 331 deletions(-) delete mode 100644 src/finn/transformation/fpgadataflow/make_pynq_driver.py diff --git a/.gitmodules b/.gitmodules index e19fe624b3..dd79b21b3f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "src/finn/transformation/fpgadataflow/finn-cpp-driver"] path = src/finn/transformation/fpgadataflow/finn-cpp-driver - url = git@github.com:eki-project/finn-cpp-driver.git + url = https://github.com/eki-project/finn-cpp-driver.git diff --git a/src/finn/builder/build_dataflow_config.py b/src/finn/builder/build_dataflow_config.py index d9f8664de6..e6a39f9d82 100644 --- a/src/finn/builder/build_dataflow_config.py +++ b/src/finn/builder/build_dataflow_config.py @@ -359,9 +359,13 @@ class DataflowBuildConfig: #: rtlsim, otherwise they will be replaced by HLS implementations. rtlsim_use_vivado_comps: Optional[bool] = True - # Determine which type of data transfer the driver should use. - # Stream streams the data directly into the pipeline, memory_buffered writes to board memory first + #: Determine which type of data transfer the driver should use. + #: Stream streams the data directly into the pipeline, memory_buffered writes to board memory first cpp_driver_transfer_type : Optional[CPPDriverTransferType] = None + + #: If set to True, the C++ Driver will be build inside the container during synthesis + #: Otherwise, building the C++ driver will be skipped + cpp_driver_build_during_synthesis : Optional[bool] = False def _resolve_hls_clk_period(self): diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index f5beedae15..c113619f88 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -732,12 +732,16 @@ def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> Model driver_dir = os.path.join(cfg.output_dir, "driver") # TODO: REMOVE DEBUG - cfg.cpp_driver_transfer_type = CPPDriverTransferType.MEMORY_BUFFERED + if cfg.cpp_driver_transfer_type == None: + cfg.cpp_driver_transfer_type = CPPDriverTransferType.MEMORY_BUFFERED + if cfg.cpp_driver_build_during_synthesis == None: + cfg.cpp_driver_build_during_synthesis = False model = model.transform( MakeCPPDriver( cfg._resolve_driver_platform(), transfer_mode=cfg.cpp_driver_transfer_type, + build_driver=cfg.cpp_driver_build_during_synthesis, cpp_template_dir=os.path.join(os.path.dirname(__file__), "..", "transformation", "fpgadataflow", "finn-cpp-driver"), output_dir = cfg.output_dir ) diff --git a/src/finn/transformation/fpgadataflow/finn-cpp-driver b/src/finn/transformation/fpgadataflow/finn-cpp-driver index 339e213589..1469722634 160000 --- a/src/finn/transformation/fpgadataflow/finn-cpp-driver +++ b/src/finn/transformation/fpgadataflow/finn-cpp-driver @@ -1 +1 @@ -Subproject commit 339e213589298262b0a0ae5dad96c63065fbd8f2 +Subproject commit 1469722634609bffbbfdd56204e2df820e92bf96 diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index d8147f9cef..2e74ba2097 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -31,11 +31,18 @@ import qonnx import shutil import warnings +import subprocess +import json +from string import Template +from typing import Dict, List, Tuple +from multiprocessing import cpu_count from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp from qonnx.transformation.base import Transformation from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple +from finn.builder.build_dataflow_config import CPPDriverTransferType +from finn.transformation.fpgadataflow.get_driver_shapes import get_driver_shapes import finn.util import finn.util.data_packing as dpk from finn.util.basic import make_build_dir @@ -63,14 +70,14 @@ def to_external_tensor(init, w_dtype): class MakeCPPDriver(Transformation): # TODO: Enable multiple input types! Now only assumes the first one def resolve_dt_name(s: str) -> str: - s = s.replace("Datatype[", "").replace("]", "") + s = s.replace("DataType[", "").replace("]", "") print("Converting tensor datatype " + str(s)) if s in ["BINARY", "TERNARY", "BIPOLAR"]: return "Datatype" + s[0] + s[1:].lower() elif s.startswith("U"): - return "DatatypeUint<" + s.replace("U", "") + ">" + return "DatatypeUint<" + s.replace("UINT", "") + ">" elif s.startswith("I"): - return "DatatypeInt<" + s.replace("I", "") + ">" + return "DatatypeInt<" + s.replace("INT", "") + ">" elif "FLOAT" in s: return "DatatypeFloat<" + s.replace("FLOAT", "") + ">" elif "FIXED" in s: @@ -78,11 +85,12 @@ def resolve_dt_name(s: str) -> str: else: return "UNKNOWN_DATATYPE_ERROR_BY_FINN_COMPILER" - def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, cpp_template_dir: str, output_dir: str, run_name: str = "RUN_ID"): + def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, build_driver: bool, cpp_template_dir: str, output_dir: str, run_name: str = "RUN_ID"): super().__init__() self.run_name = run_name self.platform: str = platform self.transfer_mode: CPPDriverTransferType = transfer_mode + self.build_driver: bool = build_driver self.cpp_template_dir = cpp_template_dir self.output_dir = output_dir @@ -90,7 +98,7 @@ def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, cpp_temp self.xclbin_path = os.path.join(output_dir, "bitfile", "finn-accel.xclbin") self.template_target_dir = os.path.join(output_dir, "finn-cpp-driver") self.json_path = os.path.join(output_dir, "driver", "cppdconfig.json") - self.header_path = os.path.join(self.template_target_dir, "src", "config", "FinnDriverUsedDatatypes.h") + self.header_path = os.path.join(output_dir, "driver", "FinnDriverUsedDatatypes.h") self.finn_driver_exec_path = os.path.join(output_dir, "driver", "finn") def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: @@ -114,12 +122,14 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: inputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["idt"][0].replace("'", ""))#.get_canonical_name()) outputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["odt"][0].replace("'", ""))#.get_canonical_name()) print(f"Writing input header file for run with name {self.run_name}. Used datatypes will be {inputDatatype} and {outputDatatype}!") - with open(self.header_path, 'w+') as f: - f.write("//! THIS FILE IS AUTOGENERATED BY THE FINN COMPILER\n") - f.write("#include \"../utils/FinnDatatypes.hpp\"\n#include \"../core/BaseDriver.hpp\"\n\n") - f.write(f"using InputFinnType = Finn::{inputDatatype};\n") - f.write(f"using OutputFinnType = Finn::{outputDatatype};\n") - f.write(f"namespace Finn {{ using Driver = Finn::BaseDriver; }} // namespace Finn\n") + with open(os.path.join(self.cpp_template_dir, "src", "FINNCppDriver", "config", "FinnDriverUsedDatatypes.h.in"), 'r') as f_in: + header = f_in.read() + template_handler = Template(header) + templated_str = template_handler.substitute(inputDatatype=inputDatatype,outputDatatype=outputDatatype) + with open(self.header_path, 'w+') as f: + f.write(templated_str) + + print("Successfully created config header file.") #* Writing the json file @@ -136,20 +146,24 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: isIO = lambda x: x["m_type"] == "IP_KERNEL" and x["m_base_address"] != "not_used" and ("idma" in x["m_name"] or "odma" in x["m_name"]) idmas = [x["m_name"] for x in ips if isIO(x) and "idma" in x["m_name"]] odmas = [x["m_name"] for x in ips if isIO(x) and "odma" in x["m_name"]] + + def formatKernelName(kname:str): + kparts = kname.split(":") + return kparts[0]+":{"+kparts[1]+"}" # Create idma and odma entries jsonIdmas = [] jsonOdmas = [] for i in range(len(driver_shapes["idma_names"])): jsonIdmas.append({ - "kernelName": [name for name in idmas if driver_shapes["idma_names"][i] in name][0], + "kernelName": [formatKernelName(name) for name in idmas if driver_shapes["idma_names"][i] in name][0], "normalShape": driver_shapes["ishape_normal"][i], "foldedShape": driver_shapes["ishape_folded"][i], "packedShape": driver_shapes["ishape_packed"][i] }) for i in range(len(driver_shapes["odma_names"])): jsonOdmas.append({ - "kernelName": [name for name in odmas if driver_shapes["odma_names"][i] in name][0], + "kernelName": [formatKernelName(name) for name in odmas if driver_shapes["odma_names"][i] in name][0], "normalShape": driver_shapes["oshape_normal"][i], "foldedShape": driver_shapes["oshape_folded"][i], "packedShape": driver_shapes["oshape_packed"][i] @@ -166,20 +180,57 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: }) with open(self.json_path, 'w+') as f: f.write(json.dumps(data, indent=4)) + + print("Created runtime json config file") #* Compilation - build_path = os.path.join(self.template_target_dir, "build") - if not os.path.isdir(build_path): - os.mkdir(build_path) - compile_result = subprocess.run(f"cmake -DCMAKE_BUILD_TYPE=Release -DFINN_HEADER_LOCATION=\"{self.header_path}\" .;cmake --build . --target finn", stdout=subprocess.PIPE, shell=True, cwd=build_path) - assert compile_result.returncode == 0, "[MakeCPPDriver - Transformation] Compilation failed!" - print("Compiled C++ driver successfully.") - - #* Copy exec back - shutil.copy(os.join(build_path, "src", "finn"), self.finn_driver_exec_path) + if(self.build_driver == True): + #TODO: build dependencies + build_path = os.path.join(self.template_target_dir, "build") + if not os.path.isdir(build_path): + os.mkdir(build_path) + n_procs = cpu_count() + compile_result = subprocess.run(f"cmake -DCMAKE_BUILD_TYPE=Release -DFINN_HEADER_LOCATION=\"{self.header_path}\" -DFINNC_ENABLE_SANITIZERS=Off ..;make -j{n_procs}", shell=True, cwd=build_path, capture_output=True) + print(compile_result.stdout.decode('utf-8'),flush=True) + print(compile_result.stderr.decode('utf-8'),flush=True) + assert compile_result.returncode == 0, "[MakeCPPDriver - Transformation] Compilation failed!" + print("Compiled C++ driver successfully.") + + #* Copy exec back + shutil.copy(os.path.join(build_path, "src", "finn"), self.finn_driver_exec_path) # TODO: Generating weight files - generate_runtime_weights(model, weights_dir) + # weights_dir = output_dir + "/runtime_weights" + + # os.makedirs(weights_dir) + # idma_idx = 0 + # ext_weight_dma_cnt = 0 + + # for node in model.graph.node: + # assert ( + # node.op_type == "StreamingDataflowPartition" + # ), "CreateDataflowPartition needs to be applied before driver generation" + + # if len(node.input) > 0: + # producer = model.find_producer(node.input[0]) + # init_tensor = model.get_initializer(node.input[0]) + # else: + # producer = None + # init_tensor = None + + # if producer is None: # input dma? + # sdp_inst = getCustomOp(node) + # idma_name = sdp_inst.get_nodeattr("instance_name") + # df_model = ModelWrapper(sdp_inst.get_nodeattr("model")) + # assert df_model.graph.node[0].op_type == "IODMA" + # iodma_node = getCustomOp(df_model.graph.node[0]) + # if iodma_node.get_nodeattr("burstMode") == "wrap": # input weights dma? + # init_tensor = df_model.get_initializer(iodma_node.onnx_node.input[0]) + # ext_weight_dma_cnt += 1 + # w_dtype = df_model.get_tensor_datatype(iodma_node.onnx_node.input[0]) + # init_external_tensor = to_external_tensor(init_tensor, w_dtype) + # np.save(weights_dir + "/" + idma_name + ".npy", init_external_tensor) + # idma_idx += 1 return (model, False) diff --git a/src/finn/transformation/fpgadataflow/make_pynq_driver.py b/src/finn/transformation/fpgadataflow/make_pynq_driver.py deleted file mode 100644 index 6d1fa290b4..0000000000 --- a/src/finn/transformation/fpgadataflow/make_pynq_driver.py +++ /dev/null @@ -1,303 +0,0 @@ -# Copyright (c) 2020, Xilinx -# 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 -import qonnx -import shutil -import warnings -from qonnx.core.modelwrapper import ModelWrapper -from qonnx.custom_op.registry import getCustomOp -from qonnx.transformation.base import Transformation -from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple - -import finn.util -import finn.util.data_packing as dpk -from finn.util.basic import make_build_dir -from finn.util.data_packing import ( - hexstring2npbytearray, - pack_innermost_dim_as_hex_string, -) - -from . import template_driver - - -def to_external_tensor(init, w_dtype): - """Return an appropriately formatted and packed numpy byte array for given - external parameter tensor.""" - - weight_width = init.shape[1] * w_dtype.bitwidth() - weight_width_padded = roundup_to_integer_multiple(weight_width, 4) - hex_init = pack_innermost_dim_as_hex_string(init, w_dtype, weight_width_padded, prefix="0x") - ext_weight = np.array([], dtype=np.uint8) - for line in hex_init: - array_line = [x for x in reversed(hexstring2npbytearray(line, remove_prefix="0x"))] - ext_weight = np.append(ext_weight, array_line) - - return ext_weight - - -class MakePYNQDriver(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 - dataflow partitions for correct operation. - - platform: one of ["zynq-iodma", "alveo"] - - Outcome if successful: sets the pynq_driver_dir attribute in the ONNX - ModelProto's metadata_props field, with the created driver dir as the - value. If any layers use runtime-writable parameters, those will be gathered - under the runtime_weights/ subfolder of the pynq_driver_dir. - """ - - def __init__(self, platform): - super().__init__() - self.platform = platform - - def apply(self, model): - # 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 the base FINN driver -- same for all accels - driver_base_template = ( - os.environ["FINN_ROOT"] + "/src/finn/qnn-data/templates/driver/driver_base.py" - ) - driver_base_py = pynq_driver_dir + "/driver_base.py" - shutil.copy(driver_base_template, driver_base_py) - # driver depends on qonnx and finn packages - # extract individual source files and copy to driver folder - qonnx_target_path = pynq_driver_dir + "/qonnx" - finn_target_path = pynq_driver_dir + "/finn" - os.makedirs(qonnx_target_path + "/core", exist_ok=True) - os.makedirs(qonnx_target_path + "/util", exist_ok=True) - os.makedirs(finn_target_path + "/util", exist_ok=True) - qonnx_path = qonnx.__path__[0] - finn_util_path = finn.util.__path__[0] - files_to_copy = [] - files_to_copy.append( - (qonnx_path + "/core/datatype.py", qonnx_target_path + "/core/datatype.py") - ) - files_to_copy.append( - (qonnx_path + "/core/__init__.py", qonnx_target_path + "/core/__init__.py") - ) - files_to_copy.append((qonnx_path + "/util/basic.py", qonnx_target_path + "/util/basic.py")) - files_to_copy.append( - (qonnx_path + "/util/__init__.py", qonnx_target_path + "/util/__init__.py") - ) - files_to_copy.append( - ( - finn_util_path + "/data_packing.py", - finn_target_path + "/util/data_packing.py", - ) - ) - files_to_copy.append( - ( - finn_util_path + "/__init__.py", - finn_target_path + "/util/__init__.py", - ) - ) - for src_file, target_file in files_to_copy: - shutil.copy(src_file, target_file) - # extract input-output shapes from the graph - # TODO convert this to an analysis pass? - idt = [] - idma_names = [] - ishape_normal = [] - ishape_folded = [] - ishape_packed = [] - for idma_ind, graph_in in enumerate(model.graph.input): - i_tensor_name = graph_in.name - # get inp tensor properties - i_tensor_dt = model.get_tensor_datatype(i_tensor_name) - i_tensor_shape_normal = tuple(model.get_tensor_shape(i_tensor_name)) - # go down into dataflow partition to get folded shape info etc - # TODO consider setting these as attributes during dataflow partitioning - i_consumer = model.find_consumer(i_tensor_name) - assert ( - i_consumer.op_type == "StreamingDataflowPartition" - ), """ - Ensure CreateDataflowPartition called before driver creation.""" - first_df_model = ModelWrapper(getCustomOp(i_consumer).get_nodeattr("model")) - assert ( - first_df_model.graph.node[0].op_type == "IODMA" - ), "First partition must hold input IODMA" - successors = model.find_direct_successors(i_consumer) - successor_input_num = list(successors[0].input).index(i_consumer.output[0]) - successor_sdp = getCustomOp(successors[0]) - successor_df_model = ModelWrapper(successor_sdp.get_nodeattr("model")) - first_node = successor_df_model.find_consumer( - successor_df_model.graph.input[successor_input_num].name - ) - i_tensor_shape_folded = tuple(getCustomOp(first_node).get_folded_input_shape()) - # generate dummy folded i/o tensors and their packed versions - i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded) - i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - i_tensor_dummy_folded, i_tensor_dt - ) - i_tensor_shape_packed = i_tensor_dummy_packed.shape - # append all input tensor info to relevant lists - idt.append("DataType['%s']" % i_tensor_dt.name) - ishape_normal.append(i_tensor_shape_normal) - ishape_folded.append(i_tensor_shape_folded) - ishape_packed.append(i_tensor_shape_packed) - idma_names.append(getCustomOp(i_consumer).get_nodeattr("instance_name")) - - odt = [] - odma_names = [] - oshape_normal = [] - oshape_folded = [] - oshape_packed = [] - for odma_ind, graph_out in enumerate(model.graph.output): - o_tensor_name = graph_out.name - # get inp tensor properties - o_tensor_dt = model.get_tensor_datatype(o_tensor_name) - o_tensor_shape_normal = tuple(model.get_tensor_shape(o_tensor_name)) - # go down into IODMA partition to get folded shape info etc - # TODO consider setting these as attributes during dataflow partitioning - o_producer = model.find_producer(o_tensor_name) - assert ( - o_producer.op_type == "StreamingDataflowPartition" - ), """ - Ensure CreateDataflowPartition called before driver creation.""" - df_model = ModelWrapper(getCustomOp(o_producer).get_nodeattr("model")) - assert df_model.graph.node[-1].op_type == "IODMA", "Partition must hold output IODMA" - predecessors = model.find_direct_predecessors(o_producer) - predecessor_output_num = list(predecessors[0].output).index(o_producer.input[0]) - predecessor_sdp = getCustomOp(predecessors[0]) - predecessor_df_model = ModelWrapper(predecessor_sdp.get_nodeattr("model")) - last_node = predecessor_df_model.find_producer( - predecessor_df_model.graph.output[predecessor_output_num].name - ) - o_tensor_shape_folded = tuple(getCustomOp(last_node).get_folded_output_shape()) - o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded) - o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - o_tensor_dummy_folded, o_tensor_dt - ) - o_tensor_shape_packed = o_tensor_dummy_packed.shape - # append all output tensor info to relevant lists - odt.append("DataType['%s']" % o_tensor_dt.name) - oshape_normal.append(o_tensor_shape_normal) - oshape_folded.append(o_tensor_shape_folded) - oshape_packed.append(o_tensor_shape_packed) - odma_names.append(getCustomOp(o_producer).get_nodeattr("instance_name")) - - # generate external weights npy files - weights_dir = pynq_driver_dir + "/runtime_weights" - - os.makedirs(weights_dir) - idma_idx = 0 - ext_weight_dma_cnt = 0 - - for node in model.graph.node: - assert ( - node.op_type == "StreamingDataflowPartition" - ), "CreateDataflowPartition needs to be applied before driver generation" - - if len(node.input) > 0: - producer = model.find_producer(node.input[0]) - init_tensor = model.get_initializer(node.input[0]) - else: - producer = None - init_tensor = None - - if producer is None: # input dma? - sdp_inst = getCustomOp(node) - idma_name = sdp_inst.get_nodeattr("instance_name") - df_model = ModelWrapper(sdp_inst.get_nodeattr("model")) - assert df_model.graph.node[0].op_type == "IODMA" - iodma_node = getCustomOp(df_model.graph.node[0]) - if iodma_node.get_nodeattr("burstMode") == "wrap": # input weights dma? - init_tensor = df_model.get_initializer(iodma_node.onnx_node.input[0]) - ext_weight_dma_cnt += 1 - w_dtype = df_model.get_tensor_datatype(iodma_node.onnx_node.input[0]) - init_external_tensor = to_external_tensor(init_tensor, w_dtype) - np.save(weights_dir + "/" + idma_name + ".npy", init_external_tensor) - idma_idx += 1 - - # fill in the driver template - driver_py = pynq_driver_dir + "/driver.py" - driver = template_driver.pynq_driver_template - - driver = driver.replace("$PLATFORM$", self.platform) - driver = driver.replace("$INPUT_FINN_DATATYPE$", str(idt).replace('"', "")) - driver = driver.replace("$INPUT_SHAPE_NORMAL$", str(ishape_normal)) - driver = driver.replace("$INPUT_SHAPE_FOLDED$", str(ishape_folded)) - driver = driver.replace("$INPUT_SHAPE_PACKED$", str(ishape_packed)) - driver = driver.replace("$OUTPUT_FINN_DATATYPE$", str(odt).replace('"', "")) - driver = driver.replace("$OUTPUT_SHAPE_NORMAL$", str(oshape_normal)) - driver = driver.replace("$OUTPUT_SHAPE_FOLDED$", str(oshape_folded)) - driver = driver.replace("$OUTPUT_SHAPE_PACKED$", str(oshape_packed)) - driver = driver.replace("$INPUT_DMA_NAME$", "%s" % str(idma_names)) - driver = driver.replace("$OUTPUT_DMA_NAME$", "%s" % str(odma_names)) - driver = driver.replace("$NUM_INPUTS$", str(len(idma_names))) - driver = driver.replace("$NUM_OUTPUTS$", str(len(odma_names))) - driver = driver.replace("$EXT_WEIGHT_NUM$", str(ext_weight_dma_cnt)) - - with open(driver_py, "w") as f: - f.write(driver) - - # add validate.py to run full top-1 test (only for suitable networks) - validate_py = pynq_driver_dir + "/validate.py" - validate_template = ( - os.environ["FINN_ROOT"] + "/src/finn/qnn-data/templates/driver/validate.py" - ) - shutil.copy(validate_template, validate_py) - - # generate weight files for runtime-writable layers - - for sdp_ind, sdp_node in enumerate(model.graph.node): - assert sdp_node.op_type == "StreamingDataflowPartition" - # get dataflow model - sdp_node = getCustomOp(sdp_node) - dataflow_model_filename = sdp_node.get_nodeattr("model") - dataflow_model = ModelWrapper(dataflow_model_filename) - rt_layer_ind = 0 - for node in dataflow_model.graph.node: - if node.op_type in ["MatrixVectorActivation", "Thresholding_Batch"]: - node_inst = getCustomOp(node) - is_rt_weights = node_inst.get_nodeattr("runtime_writeable_weights") - if is_rt_weights == 1: - fcl_w = dataflow_model.get_initializer(node.input[1]) - w_filename = weights_dir + "/%d_%d_%s.dat" % ( - sdp_ind, - rt_layer_ind, - node.name, - ) - node_inst.make_weight_file(fcl_w, "decoupled_runtime", w_filename) - rt_layer_ind += 1 - elif node.op_type == "StreamingDataflowPartition": - warnings.warn( - """Nested StreamingDataflowPartition are not supported - """ - ) - else: - continue - - return (model, False) From cd2e82f36d82eed0c91b230a3b6e7d746d48b9e5 Mon Sep 17 00:00:00 2001 From: Linus Jungemann Date: Wed, 21 Feb 2024 14:32:46 +0100 Subject: [PATCH 34/57] Remove some merge artifacts# --- .gitignore | 1 - .../qnn-data/templates/driver/driver_base.py | 1 - .../fpgadataflow/template_driver.py | 2 +- .../fpgadataflow/vitis_build.py | 6 +--- src/finn/util/platforms.py | 33 ------------------- 5 files changed, 2 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index a9785da9cd..bc350d3aad 100644 --- a/.gitignore +++ b/.gitignore @@ -66,7 +66,6 @@ coverage.xml .pytest_cache/ # Build and docs folder/files -finn_build_single_job.sh build/* dist/* sdist/* diff --git a/src/finn/qnn-data/templates/driver/driver_base.py b/src/finn/qnn-data/templates/driver/driver_base.py index 466fadbda1..aa54f84733 100644 --- a/src/finn/qnn-data/templates/driver/driver_base.py +++ b/src/finn/qnn-data/templates/driver/driver_base.py @@ -78,7 +78,6 @@ def __init__( Path to runtime weights folder. """ super().__init__(bitfile_name, download=download, device=device) - self.device = device self.runtime_weight_dir = runtime_weight_dir self._io_shape_dict = io_shape_dict self.ibuf_packed_device = None diff --git a/src/finn/transformation/fpgadataflow/template_driver.py b/src/finn/transformation/fpgadataflow/template_driver.py index e7eff22a2a..a65e060ed9 100644 --- a/src/finn/transformation/fpgadataflow/template_driver.py +++ b/src/finn/transformation/fpgadataflow/template_driver.py @@ -89,7 +89,7 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description='Execute FINN-generated accelerator on numpy inputs, or run throughput test') parser.add_argument('--exec_mode', help='Please select functional verification ("execute") or throughput test ("throughput_test")', default="execute") - parser.add_argument('--platform', help='Target platform: zynq-iodma alveo', default="alveo") + parser.add_argument('--platform', help='Target platform: zynq-iodma alveo', default="$PLATFORM$") parser.add_argument('--batchsize', help='number of samples for inference', 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 (i.e. "resizer.bit")', default="resizer.bit") diff --git a/src/finn/transformation/fpgadataflow/vitis_build.py b/src/finn/transformation/fpgadataflow/vitis_build.py index 360f47c81d..c246515b43 100644 --- a/src/finn/transformation/fpgadataflow/vitis_build.py +++ b/src/finn/transformation/fpgadataflow/vitis_build.py @@ -198,7 +198,6 @@ def apply(self, model): dataflow_model_filename = sdp_node.get_nodeattr("model") kernel_model = ModelWrapper(dataflow_model_filename) kernel_xo = kernel_model.get_metadata_prop("vitis_xo") - print(f"[DBG HELP LINK] Looking at node {node.name} with vitis_xo attribute field value {kernel_xo}") object_files.append(kernel_xo) # gather info on connectivity # assume each node connected to outputs/inputs is DMA: @@ -217,16 +216,13 @@ def apply(self, model): # check top-level in/out list instead if producer is None: instance_names[node.name] = "idma" + str(idma_idx) - print(f"[DBG HELP LINK] Renaming node {node.name} into {instance_names[node.name]} for the instance!") config.append("nk=%s:1:%s" % (node.name, instance_names[node.name])) idma_idx += 1 elif consumer == []: instance_names[node.name] = "odma" + str(odma_idx) - print(f"[DBG HELP LINK] Renaming node {node.name} into {instance_names[node.name]} for the instance!") config.append("nk=%s:1:%s" % (node.name, instance_names[node.name])) odma_idx += 1 else: - print(f"[DBG HELP LINK] Node name {node.name} keeps its name as an instance!") instance_names[node.name] = node.name config.append("nk=%s:1:%s" % (node.name, instance_names[node.name])) sdp_node.set_nodeattr("instance_name", instance_names[node.name]) @@ -243,7 +239,7 @@ def apply(self, model): # Use HBM where available (also U50 does not have DDR) mem_type = "HBM" node_mem_port = "%s[%d]" % (mem_type, mem_idx) - mem_idx += 1 + # mem_idx += 1 elif "u200" in self.platform: # Use DDR controller in static region of U200 mem_type = "DDR" diff --git a/src/finn/util/platforms.py b/src/finn/util/platforms.py index 8d5882174d..8856ce0ab8 100644 --- a/src/finn/util/platforms.py +++ b/src/finn/util/platforms.py @@ -459,39 +459,6 @@ def compute_resources(self): [382080, 2 * 382080, 2 * 576, 320, 2880], [380640, 2 * 380640, 2 * 576, 320, 2880], ] - -class Alveo_NxU55C_Platform(Platform): - def __init__( - self, - ndevices=1, - limits=DEFAULT_RES_LIMITS, - avg_constraints=DEFAULT_AVG_CONSTRAINTS, - ): - sll_counts = [[0, 5000, 0], [5000, 0, 5000], [0, 5000, 0]] - super(Alveo_NxU50_Platform, self).__init__( - nslr=2, - ndevices=ndevices, - sll_count=sll_counts, - ddr_slr=[], - hbm_slr=0, - eth_slr=2, - eth_gbps=100, - limits=limits, - avg_constraints=avg_constraints, - ) - - @property - def compute_resources(self): - # according to UG1120 - # return [[369000, 746000, 2*507, 320, 2733], - # [333000, 675000, 2*468, 320, 2877], - # [367000, 729000, 2*512, 320, 2880]] - # observed from Vivado: - return [ - [400800, 2 * 400800, 2 * 600, 320, 2736], - [382080, 2 * 382080, 2 * 576, 320, 2880], - [380640, 2 * 380640, 2 * 576, 320, 2880], - ] class Alveo_NxU55C_Platform(Platform): From 8bf469e1af9bb9e9aa073980b86ca471b4b66ea6 Mon Sep 17 00:00:00 2001 From: Linus Jungemann Date: Wed, 21 Feb 2024 15:33:11 +0100 Subject: [PATCH 35/57] Update documentation --- src/finn/builder/build_dataflow_config.py | 16 ++++++++------ src/finn/builder/build_dataflow_steps.py | 26 ++++++++++++++--------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/finn/builder/build_dataflow_config.py b/src/finn/builder/build_dataflow_config.py index e6a39f9d82..c0165e905d 100644 --- a/src/finn/builder/build_dataflow_config.py +++ b/src/finn/builder/build_dataflow_config.py @@ -95,7 +95,8 @@ class LargeFIFOMemStyle(str, Enum): class CPPDriverTransferType(str, Enum): - """A stream transfer directly """ + """A stream transfer directly""" + STREAM = "stream" MEMORY_BUFFERED = "memory_buffered" @@ -359,14 +360,15 @@ class DataflowBuildConfig: #: rtlsim, otherwise they will be replaced by HLS implementations. rtlsim_use_vivado_comps: Optional[bool] = True - #: Determine which type of data transfer the driver should use. - #: Stream streams the data directly into the pipeline, memory_buffered writes to board memory first - cpp_driver_transfer_type : Optional[CPPDriverTransferType] = None - + #: Determine which type of data transfer the driver should use. + #: Stream streams the data directly into the pipeline, memory_buffered writes + #: to board memory first + cpp_driver_transfer_type: Optional[CPPDriverTransferType] = None + #: If set to True, the C++ Driver will be build inside the container during synthesis #: Otherwise, building the C++ driver will be skipped - cpp_driver_build_during_synthesis : Optional[bool] = False - + # TODO: Change container to make build during synthesis possible + cpp_driver_build_during_synthesis: Optional[bool] = False def _resolve_hls_clk_period(self): if self.hls_clk_period_ns is None: diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index c113619f88..525db34d7a 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -66,6 +66,7 @@ res_estimation_complete, ) from finn.builder.build_dataflow_config import ( + CPPDriverTransferType, DataflowBuildConfig, DataflowOutputType, ShellFlowType, @@ -87,7 +88,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.make_driver import MakePYNQDriver, MakeCPPDriver +from finn.transformation.fpgadataflow.make_driver import MakeCPPDriver, MakePYNQDriver from finn.transformation.fpgadataflow.make_zynq_proj import ZynqBuild from finn.transformation.fpgadataflow.minimize_accumulator_width import ( MinimizeAccumulatorWidth, @@ -121,7 +122,6 @@ get_rtlsim_trace_depth, pyverilate_get_liveness_threshold_cycles, ) -from finn.builder.build_dataflow_config import CPPDriverTransferType from finn.util.pyverilator import verilator_fifosim from finn.util.test import execute_parent @@ -729,12 +729,9 @@ def step_make_pynq_driver(model: ModelWrapper, cfg: DataflowBuildConfig): def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> ModelWrapper: if DataflowOutputType.CPP_DRIVER in cfg.generate_outputs: - driver_dir = os.path.join(cfg.output_dir, "driver") - - # TODO: REMOVE DEBUG - if cfg.cpp_driver_transfer_type == None: + if cfg.cpp_driver_transfer_type is None: cfg.cpp_driver_transfer_type = CPPDriverTransferType.MEMORY_BUFFERED - if cfg.cpp_driver_build_during_synthesis == None: + if cfg.cpp_driver_build_during_synthesis is None: cfg.cpp_driver_build_during_synthesis = False model = model.transform( @@ -742,12 +739,21 @@ def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> Model cfg._resolve_driver_platform(), transfer_mode=cfg.cpp_driver_transfer_type, build_driver=cfg.cpp_driver_build_during_synthesis, - cpp_template_dir=os.path.join(os.path.dirname(__file__), "..", "transformation", "fpgadataflow", "finn-cpp-driver"), - output_dir = cfg.output_dir + cpp_template_dir=os.path.join( + os.path.dirname(__file__), + "..", + "transformation", + "fpgadataflow", + "finn-cpp-driver", + ), + output_dir=cfg.output_dir, ) ) else: - print("WARNING: The step step_make_cpp_driver is in the build list but will not be executed since CPP_DRIVER is not in generate_outputs in your build.py file!") + print( + "WARNING: The step step_make_cpp_driver is in the build list but will not be executed" + + " since CPP_DRIVER is not in generate_outputs in your build.py file!" + ) return model From fa7cef910ee420d1bd67f43b1647a255b5c9e886 Mon Sep 17 00:00:00 2001 From: Linus Jungemann Date: Wed, 21 Feb 2024 15:37:04 +0100 Subject: [PATCH 36/57] Remove debug printing --- src/finn/transformation/fpgadataflow/vitis_build.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/vitis_build.py b/src/finn/transformation/fpgadataflow/vitis_build.py index c246515b43..24e84512c9 100644 --- a/src/finn/transformation/fpgadataflow/vitis_build.py +++ b/src/finn/transformation/fpgadataflow/vitis_build.py @@ -84,13 +84,11 @@ class CreateVitisXO(Transformation): def __init__(self, ip_name="finn_design"): super().__init__() self.ip_name = ip_name - print(f"[DBG HELP] Calling CreateVitisXO with ip_name {ip_name}") def apply(self, model): _check_vitis_envvars() vivado_proj_dir = model.get_metadata_prop("vivado_stitch_proj") stitched_ip_dir = vivado_proj_dir + "/ip" - print(f"[DBG HELP] vivado stitched id proj dir: {stitched_ip_dir}") interfaces = json.loads(model.get_metadata_prop("vivado_stitch_ifnames")) args_string = [] arg_id = 0 @@ -133,7 +131,6 @@ def apply(self, model): xo_name = self.ip_name + ".xo" xo_path = vivado_proj_dir + "/" + xo_name model.set_metadata_prop("vitis_xo", xo_path) - print(f"[DBG HELP] Creating Vitis XO file with the name {xo_name} at path {xo_path}") # generate the package_xo command in a tcl script package_xo_string = "package_xo -force -xo_path %s -kernel_name %s -ip_directory %s" % ( @@ -274,11 +271,8 @@ def apply(self, model): ) ) - print(f"[DBG HELP LINK] Linking together object files: {object_files}") - # create a temporary folder for the project link_dir = make_build_dir(prefix="vitis_link_proj_") - print(f"[DBG HELP LINK] Setting link directory for vitis_link_proj to {link_dir}") model.set_metadata_prop("vitis_link_proj", link_dir) # add Vivado physopt directives if desired @@ -408,7 +402,6 @@ def apply(self, model): # Build each kernel individually sdp_nodes = model.get_nodes_by_op_type("StreamingDataflowPartition") for sdp_node in sdp_nodes: - print(f"[DBG HELP BUILD] Creating a stitched ip for the node {sdp_node.name}") prefix = sdp_node.name + "_" sdp_node = getCustomOp(sdp_node) dataflow_model_filename = sdp_node.get_nodeattr("model") From 6dbe116f48c7d80a195ebd00876226f7fd474312 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:30:45 +0200 Subject: [PATCH 37/57] Move high performance driver version to v1.1 --- src/finn/transformation/fpgadataflow/finn-cpp-driver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finn/transformation/fpgadataflow/finn-cpp-driver b/src/finn/transformation/fpgadataflow/finn-cpp-driver index 21d7ae89dd..c3327c3695 160000 --- a/src/finn/transformation/fpgadataflow/finn-cpp-driver +++ b/src/finn/transformation/fpgadataflow/finn-cpp-driver @@ -1 +1 @@ -Subproject commit 21d7ae89ddad99689f3227b8f49abfb25481d6bd +Subproject commit c3327c3695b33b8b6d1dbde84668557b42679f6b From faba00a28671b461eaf7c141aaf809dee5174c60 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Fri, 14 Mar 2025 14:53:46 +0100 Subject: [PATCH 38/57] Unify Python and C++ driver generation --- src/finn/builder/build_dataflow_config.py | 14 +- src/finn/builder/build_dataflow_steps.py | 40 ++- .../fpgadataflow/make_driver.py | 229 +++++++++++------- 3 files changed, 160 insertions(+), 123 deletions(-) diff --git a/src/finn/builder/build_dataflow_config.py b/src/finn/builder/build_dataflow_config.py index 276fe81a12..33f45e5fcd 100644 --- a/src/finn/builder/build_dataflow_config.py +++ b/src/finn/builder/build_dataflow_config.py @@ -131,8 +131,7 @@ class VerificationStepType(str, Enum): "step_measure_rtlsim_performance", "step_out_of_context_synthesis", "step_synthesize_bitfile", - "step_make_pynq_driver", - "step_make_cpp_driver", + "step_make_driver", "step_deployment_package", ] @@ -361,15 +360,16 @@ class DataflowBuildConfig: #: rtlsim, otherwise they will be replaced by RTL implementations. rtlsim_use_vivado_comps: Optional[bool] = True + # TODO: This should be unified with the host/HBM memory selection process! #: Determine which type of data transfer the driver should use. #: Stream streams the data directly into the pipeline, memory_buffered writes #: to board memory first - cpp_driver_transfer_type: Optional[CPPDriverTransferType] = None + cpp_driver_transfer_type: Optional[CPPDriverTransferType] = "memory_buffered" - #: If set to True, the C++ Driver will be build inside the container during synthesis - #: Otherwise, building the C++ driver will be skipped - # TODO: Change container to make build during synthesis possible - cpp_driver_build_during_synthesis: Optional[bool] = False + #: Determine if the C++ driver should be generated instead of the PYNQ driver + #: If set to latest newest version will be used + #: If set to commit hash specified version will be used + cpp_driver_version: Optional[str] = "latest" def _resolve_hls_clk_period(self): if self.hls_clk_period_ns is None: diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 60e9fff617..d473dd1714 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -756,44 +756,35 @@ def step_measure_rtlsim_performance(model: ModelWrapper, cfg: DataflowBuildConfi return model -def step_make_pynq_driver(model: ModelWrapper, cfg: DataflowBuildConfig): - """Create a PYNQ Python driver that can be used to interface the generated - accelerator.""" +def step_make_driver(model: ModelWrapper, cfg: DataflowBuildConfig): + """Create a driver that can be used to interface the generated accelerator. + Use DataflowBuildConfig to select PYNQ Python or C++ driver.""" + driver_dir = os.path.join(cfg.output_dir, "driver") if DataflowOutputType.PYNQ_DRIVER in cfg.generate_outputs: - driver_dir = os.path.join(cfg.output_dir, "driver") + # generate PYNQ driver model = model.transform(MakePYNQDriver(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 - - -def step_make_cpp_driver(model: ModelWrapper, cfg: DataflowBuildConfig) -> ModelWrapper: - if DataflowOutputType.CPP_DRIVER in cfg.generate_outputs: + elif DataflowOutputType.CPP_DRIVER in cfg.generate_outputs: + # generate C++ Driver if cfg.cpp_driver_transfer_type is None: cfg.cpp_driver_transfer_type = CPPDriverTransferType.MEMORY_BUFFERED - if cfg.cpp_driver_build_during_synthesis is None: - cfg.cpp_driver_build_during_synthesis = False model = model.transform( MakeCPPDriver( cfg._resolve_driver_platform(), transfer_mode=cfg.cpp_driver_transfer_type, - build_driver=cfg.cpp_driver_build_during_synthesis, - cpp_template_dir=os.path.join( - os.path.dirname(__file__), - "..", - "transformation", - "fpgadataflow", - "finn-cpp-driver", - ), - output_dir=cfg.output_dir, + build_dir=cfg.output_dir, + version=cfg.cpp_driver_version, + driver_dir=driver_dir, ) ) + print("C++ driver written into " + driver_dir) else: - print( - "WARNING: The step step_make_cpp_driver is in the build list but will not be executed" - + " since CPP_DRIVER is not in generate_outputs in your build.py file!" + warnings.warn( + "The step step_make_driver is in the build list but will not be executed" + + " since no driver is selected in generate_outputs in your build.py file!" ) return model @@ -916,8 +907,7 @@ def step_deployment_package(model: ModelWrapper, cfg: DataflowBuildConfig): "step_set_fifo_depths": step_set_fifo_depths, "step_create_stitched_ip": step_create_stitched_ip, "step_measure_rtlsim_performance": step_measure_rtlsim_performance, - "step_make_pynq_driver": step_make_pynq_driver, - "step_make_cpp_driver": step_make_cpp_driver, + "step_make_driver": step_make_driver, "step_out_of_context_synthesis": step_out_of_context_synthesis, "step_synthesize_bitfile": step_synthesize_bitfile, "step_deployment_package": step_deployment_package, diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 56c3c51208..4ef6af771e 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -26,25 +26,25 @@ # 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 qonnx +import shlex import shutil -import warnings import subprocess -import json -from string import Template -from typing import Dict, List, Tuple -from multiprocessing import cpu_count +import warnings from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp from qonnx.transformation.base import Transformation from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple +from string import Template +from typing import Dict, Tuple -from finn.builder.build_dataflow_config import CPPDriverTransferType -from finn.transformation.fpgadataflow.get_driver_shapes import get_driver_shapes import finn.util import finn.util.data_packing as dpk +from finn.builder.build_dataflow_config import CPPDriverTransferType +from finn.transformation.fpgadataflow.get_driver_shapes import get_driver_shapes from finn.util.basic import make_build_dir from finn.util.data_packing import ( hexstring2npbytearray, @@ -53,6 +53,7 @@ from . import template_driver + def to_external_tensor(init, w_dtype): """Return an appropriately formatted and packed numpy byte array for given external parameter tensor.""" @@ -67,137 +68,183 @@ def to_external_tensor(init, w_dtype): return ext_weight + class MakeCPPDriver(Transformation): # TODO: Enable multiple input types! Now only assumes the first one def resolve_dt_name(s: str) -> str: s = s.replace("DataType[", "").replace("]", "") - print("Converting tensor datatype " + str(s)) + print(f"Converting tensor datatype {s}") if s in ["BINARY", "TERNARY", "BIPOLAR"]: return "Datatype" + s[0] + s[1:].lower() elif s.startswith("U"): - return "DatatypeUint<" + s.replace("UINT", "") + ">" + return "DatatypeUint<" + s.replace("UINT", "") + ">" elif s.startswith("I"): - return "DatatypeInt<" + s.replace("INT", "") + ">" + return "DatatypeInt<" + s.replace("INT", "") + ">" elif "FLOAT" in s: return "DatatypeFloat<" + s.replace("FLOAT", "") + ">" elif "FIXED" in s: return "DatatypeFixed" + s.replace("FIXED", "") else: - return "UNKNOWN_DATATYPE_ERROR_BY_FINN_COMPILER" - - def __init__(self, platform: str, transfer_mode: CPPDriverTransferType, build_driver: bool, cpp_template_dir: str, output_dir: str, run_name: str = "RUN_ID"): + raise RuntimeError(f"Unknown datatype for C++ Driver:{s}") + + def __init__( + self, + platform: str, + transfer_mode: CPPDriverTransferType, + build_dir: str, + version: str, + driver_dir, + ): super().__init__() - self.run_name = run_name self.platform: str = platform self.transfer_mode: CPPDriverTransferType = transfer_mode - self.build_driver: bool = build_driver - self.cpp_template_dir = cpp_template_dir - self.output_dir = output_dir + self.build_dir = build_dir + self.version = version + self.driver_dir = driver_dir + + # Define variables for the repository URL and commit hash + self.repository_url = "https://github.com/eki-project/finn-cpp-driver.git" + if version == "latest" or version is None: + self.commit_hash = "HEAD" + else: + self.commit_hash = version # Locations of files - self.xclbin_path = os.path.join(output_dir, "bitfile", "finn-accel.xclbin") - self.template_target_dir = os.path.join(output_dir, "finn-cpp-driver") - self.json_path = os.path.join(output_dir, "driver", "cppdconfig.json") - self.header_path = os.path.join(output_dir, "driver", "FinnDriverUsedDatatypes.h") - self.finn_driver_exec_path = os.path.join(output_dir, "driver", "finn") + self.xclbin_path = os.path.join(self.build_dir, "bitfile", "finn-accel.xclbin") + self.json_path = os.path.join(self.driver_dir, "acceleratorconfig.json") + self.header_path = os.path.join(self.driver_dir, "AcceleratorDatatypes.h") def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: driver_shapes: Dict = get_driver_shapes(model) - ext_weight_dma_cnt: int - weights_dir: str + ext_weight_dma_cnt: int # noqa + weights_dir: str # noqa # ext_weight_dma_cnt, weights_dir = write_weights(model, cpp_driver_dir) - #* Creating the driver dir if it doesnt exist yet - driver_dir = os.path.join(self.output_dir, "driver") - if not os.path.isdir(driver_dir): - os.mkdir(driver_dir) - - #* Copying the finn-cpp-driver into the output folder to have a clean template every run - if os.path.isdir(self.template_target_dir): - subprocess.run("rm -rf " + self.template_target_dir, shell=True, stdout=subprocess.PIPE) - print("Copying finn-cpp-driver from " + self.cpp_template_dir + " to " + self.template_target_dir) - subprocess.run(f"cp -r {self.cpp_template_dir} {self.template_target_dir}", shell=True, stdout=subprocess.PIPE) - - #* Writing the header file - inputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["idt"][0].replace("'", ""))#.get_canonical_name()) - outputDatatype: str = MakeCPPDriver.resolve_dt_name(driver_shapes["odt"][0].replace("'", ""))#.get_canonical_name()) - print(f"Writing input header file for run with name {self.run_name}. Used datatypes will be {inputDatatype} and {outputDatatype}!") - with open(os.path.join(self.cpp_template_dir, "src", "FINNCppDriver", "config", "FinnDriverUsedDatatypes.h.in"), 'r') as f_in: + # * Creating the driver dir if it doesnt exist yet + if not os.path.isdir(self.driver_dir): + os.mkdir(self.driver_dir) + + # Get the base C++ driver repo + def run_command(command, cwd=None): + try: + result = subprocess.run( + shlex.split(command), cwd=cwd, check=True, text=True, capture_output=True + ) + print(result.stdout) # Print the output for debugging purposes + except subprocess.CalledProcessError as e: + print(f"Error running command: {' '.join(command)}") + raise e + + # Step-by-step equivalent of the provided bash script + run_command("git init", cwd=self.driver_dir) + run_command(f"git remote add origin {self.repository_url}", cwd=self.driver_dir) + run_command(f"git fetch origin {self.commit_hash} --depth=1", cwd=self.driver_dir) + run_command("git checkout FETCH_HEAD", cwd=self.driver_dir) + run_command("git submodule update --init --recursive", cwd=self.driver_dir) + run_command("./buildDependencies.sh", cwd=self.driver_dir) + + # * Writing the header file + inputDatatype: str = MakeCPPDriver.resolve_dt_name( + driver_shapes["idt"][0].replace("'", "") + ) # .get_canonical_name()) + outputDatatype: str = MakeCPPDriver.resolve_dt_name( + driver_shapes["odt"][0].replace("'", "") + ) # .get_canonical_name()) + print( + f"Writing input header file: Used datatypes\ + will be {inputDatatype} and {outputDatatype}!" + ) + with open( + os.path.join( + self.driver_dir, "src", "FINNCppDriver", "config", "FinnDriverUsedDatatypes.h.in" + ), + "r", + ) as f_in: header = f_in.read() template_handler = Template(header) - templated_str = template_handler.substitute(inputDatatype=inputDatatype,outputDatatype=outputDatatype) - with open(self.header_path, 'w+') as f: + templated_str = template_handler.substitute( + inputDatatype=inputDatatype, outputDatatype=outputDatatype + ) + with open(self.header_path, "w+") as f: f.write(templated_str) - - print("Successfully created config header file.") + print("Successfully created config header file.") - #* Writing the json file + # * Writing the json file # TODO: Update this for multi-fpga usage (more than one device!) # Path of the xclbin in the finn compiler project # Get kernel names using xclbinutil - assert shutil.which("xclbinutil") is not None, "xclbinutil not in PATH or not installed. Required to read kernel names for driver config!" - subprocess.run(f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", shell=True) + + if shutil.which("xclbinutil") is None: + raise RuntimeError( + "xclbinutil not in PATH or not installed.\ + Required to read kernel names for driver config!" + ) + subprocess.run( + f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", + shell=True, + ) ips = None with open("ip_layout.json") as f: ips = json.loads(f.read())["ip_layout"]["m_ip_data"] # Get only ips that are kernels - isIO = lambda x: x["m_type"] == "IP_KERNEL" and x["m_base_address"] != "not_used" and ("idma" in x["m_name"] or "odma" in x["m_name"]) + isIO = ( + lambda x: x["m_type"] == "IP_KERNEL" + and x["m_base_address"] != "not_used" + and ("idma" in x["m_name"] or "odma" in x["m_name"]) + ) idmas = [x["m_name"] for x in ips if isIO(x) and "idma" in x["m_name"]] odmas = [x["m_name"] for x in ips if isIO(x) and "odma" in x["m_name"]] - - def formatKernelName(kname:str): + + def formatKernelName(kname: str): kparts = kname.split(":") - return kparts[0]+":{"+kparts[1]+"}" + return kparts[0] + ":{" + kparts[1] + "}" # Create idma and odma entries jsonIdmas = [] jsonOdmas = [] for i in range(len(driver_shapes["idma_names"])): - jsonIdmas.append({ - "kernelName": [formatKernelName(name) for name in idmas if driver_shapes["idma_names"][i] in name][0], - "normalShape": driver_shapes["ishape_normal"][i], - "foldedShape": driver_shapes["ishape_folded"][i], - "packedShape": driver_shapes["ishape_packed"][i] - }) + jsonIdmas.append( + { + "kernelName": [ + formatKernelName(name) + for name in idmas + if driver_shapes["idma_names"][i] in name + ][0], + "normalShape": driver_shapes["ishape_normal"][i], + "foldedShape": driver_shapes["ishape_folded"][i], + "packedShape": driver_shapes["ishape_packed"][i], + } + ) for i in range(len(driver_shapes["odma_names"])): - jsonOdmas.append({ - "kernelName": [formatKernelName(name) for name in odmas if driver_shapes["odma_names"][i] in name][0], - "normalShape": driver_shapes["oshape_normal"][i], - "foldedShape": driver_shapes["oshape_folded"][i], - "packedShape": driver_shapes["oshape_packed"][i] - }) - - data = [] - data.append({ - "xrtDeviceIndex": 0, - "xclbinPath":os.path.abspath(self.xclbin_path), - - "name": "MainDevice", - "idmas": jsonIdmas, - "odmas": jsonOdmas - }) - with open(self.json_path, 'w+') as f: + jsonOdmas.append( + { + "kernelName": [ + formatKernelName(name) + for name in odmas + if driver_shapes["odma_names"][i] in name + ][0], + "normalShape": driver_shapes["oshape_normal"][i], + "foldedShape": driver_shapes["oshape_folded"][i], + "packedShape": driver_shapes["oshape_packed"][i], + } + ) + + data = [] + data.append( + { + "xrtDeviceIndex": 0, + "xclbinPath": os.path.abspath(self.xclbin_path), + "name": "MainDevice", + "idmas": jsonIdmas, + "odmas": jsonOdmas, + } + ) + with open(self.json_path, "w+") as f: f.write(json.dumps(data, indent=4)) - - print("Created runtime json config file") - #* Compilation - if(self.build_driver == True): - #TODO: build dependencies - build_path = os.path.join(self.template_target_dir, "build") - if not os.path.isdir(build_path): - os.mkdir(build_path) - n_procs = cpu_count() - compile_result = subprocess.run(f"cmake -DCMAKE_BUILD_TYPE=Release -DFINN_HEADER_LOCATION=\"{self.header_path}\" -DFINNC_ENABLE_SANITIZERS=Off ..;make -j{n_procs}", shell=True, cwd=build_path, capture_output=True) - print(compile_result.stdout.decode('utf-8'),flush=True) - print(compile_result.stderr.decode('utf-8'),flush=True) - assert compile_result.returncode == 0, "[MakeCPPDriver - Transformation] Compilation failed!" - print("Compiled C++ driver successfully.") - - #* Copy exec back - shutil.copy(os.path.join(build_path, "src", "finn"), self.finn_driver_exec_path) + print("Created runtime json config file") # TODO: Generating weight files # weights_dir = output_dir + "/runtime_weights" From ac6e2ed821e3c27627332e85f7ed3e2aff1a5156 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Fri, 14 Mar 2025 16:05:39 +0100 Subject: [PATCH 39/57] Update repo checkout and dependency building --- src/finn/transformation/fpgadataflow/make_driver.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 4ef6af771e..221eb941e4 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -123,14 +123,22 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: # * Creating the driver dir if it doesnt exist yet if not os.path.isdir(self.driver_dir): os.mkdir(self.driver_dir) + else: + try: + shutil.rmtree(self.driver_dir) + except Exception as e: + print(f"Failed to delete {self.driver_dir}. Reason: {e}") + raise e + os.mkdir(self.driver_dir) # Get the base C++ driver repo - def run_command(command, cwd=None): + def run_command(command, cwd=None, debug=False): try: result = subprocess.run( shlex.split(command), cwd=cwd, check=True, text=True, capture_output=True ) - print(result.stdout) # Print the output for debugging purposes + if debug: + print(result.stdout) # Print the output for debugging purposes except subprocess.CalledProcessError as e: print(f"Error running command: {' '.join(command)}") raise e From 8b4c26dfda986988bb6eda7cf1ce3df7c24c7319 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Fri, 14 Mar 2025 16:19:33 +0100 Subject: [PATCH 40/57] Remove C++ driver as a submodule, because it is now cloned during runtime --- .gitmodules | 3 --- src/finn/transformation/fpgadataflow/finn-cpp-driver | 1 - 2 files changed, 4 deletions(-) delete mode 160000 src/finn/transformation/fpgadataflow/finn-cpp-driver diff --git a/.gitmodules b/.gitmodules index dd79b21b3f..e69de29bb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "src/finn/transformation/fpgadataflow/finn-cpp-driver"] - path = src/finn/transformation/fpgadataflow/finn-cpp-driver - url = https://github.com/eki-project/finn-cpp-driver.git diff --git a/src/finn/transformation/fpgadataflow/finn-cpp-driver b/src/finn/transformation/fpgadataflow/finn-cpp-driver deleted file mode 160000 index c3327c3695..0000000000 --- a/src/finn/transformation/fpgadataflow/finn-cpp-driver +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c3327c3695b33b8b6d1dbde84668557b42679f6b From 113b8ea5726155d60cf1360fc5af4e82ad691654 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Fri, 28 Mar 2025 09:49:29 +0100 Subject: [PATCH 41/57] Remove finn-plus code that is unneccesary for finn --- src/finn/builder/build_dataflow_config.py | 13 ------------- src/finn/builder/build_dataflow_steps.py | 4 ---- src/finn/transformation/fpgadataflow/make_driver.py | 3 --- 3 files changed, 20 deletions(-) diff --git a/src/finn/builder/build_dataflow_config.py b/src/finn/builder/build_dataflow_config.py index 33f45e5fcd..18fe0a3a47 100644 --- a/src/finn/builder/build_dataflow_config.py +++ b/src/finn/builder/build_dataflow_config.py @@ -86,13 +86,6 @@ class LargeFIFOMemStyle(str, Enum): URAM = "ultra" -class CPPDriverTransferType(str, Enum): - """A stream transfer directly""" - - STREAM = "stream" - MEMORY_BUFFERED = "memory_buffered" - - class VerificationStepType(str, Enum): "Steps at which FINN ONNX execution can be launched for verification." @@ -360,12 +353,6 @@ class DataflowBuildConfig: #: rtlsim, otherwise they will be replaced by RTL implementations. rtlsim_use_vivado_comps: Optional[bool] = True - # TODO: This should be unified with the host/HBM memory selection process! - #: Determine which type of data transfer the driver should use. - #: Stream streams the data directly into the pipeline, memory_buffered writes - #: to board memory first - cpp_driver_transfer_type: Optional[CPPDriverTransferType] = "memory_buffered" - #: Determine if the C++ driver should be generated instead of the PYNQ driver #: If set to latest newest version will be used #: If set to commit hash specified version will be used diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index d473dd1714..6ea861104e 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -68,7 +68,6 @@ res_estimation_complete, ) from finn.builder.build_dataflow_config import ( - CPPDriverTransferType, DataflowBuildConfig, DataflowOutputType, ShellFlowType, @@ -768,13 +767,10 @@ def step_make_driver(model: ModelWrapper, cfg: DataflowBuildConfig): print("PYNQ Python driver written into " + driver_dir) elif DataflowOutputType.CPP_DRIVER in cfg.generate_outputs: # generate C++ Driver - if cfg.cpp_driver_transfer_type is None: - cfg.cpp_driver_transfer_type = CPPDriverTransferType.MEMORY_BUFFERED model = model.transform( MakeCPPDriver( cfg._resolve_driver_platform(), - transfer_mode=cfg.cpp_driver_transfer_type, build_dir=cfg.output_dir, version=cfg.cpp_driver_version, driver_dir=driver_dir, diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 221eb941e4..d9a3a0baa6 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -43,7 +43,6 @@ import finn.util import finn.util.data_packing as dpk -from finn.builder.build_dataflow_config import CPPDriverTransferType from finn.transformation.fpgadataflow.get_driver_shapes import get_driver_shapes from finn.util.basic import make_build_dir from finn.util.data_packing import ( @@ -90,14 +89,12 @@ def resolve_dt_name(s: str) -> str: def __init__( self, platform: str, - transfer_mode: CPPDriverTransferType, build_dir: str, version: str, driver_dir, ): super().__init__() self.platform: str = platform - self.transfer_mode: CPPDriverTransferType = transfer_mode self.build_dir = build_dir self.version = version self.driver_dir = driver_dir From 9c9b19565f589c9768e7f088d8a274b0cbaff080 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Mon, 7 Apr 2025 13:11:29 +0200 Subject: [PATCH 42/57] Fix small issues --- src/finn/transformation/fpgadataflow/get_driver_shapes.py | 4 ++-- src/finn/transformation/fpgadataflow/make_driver.py | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/get_driver_shapes.py b/src/finn/transformation/fpgadataflow/get_driver_shapes.py index 00212eea03..57cc4545e3 100644 --- a/src/finn/transformation/fpgadataflow/get_driver_shapes.py +++ b/src/finn/transformation/fpgadataflow/get_driver_shapes.py @@ -47,7 +47,7 @@ def get_driver_shapes(model: ModelWrapper) -> Dict: Ensure CreateDataflowPartition called before driver creation.""" first_df_model = ModelWrapper(getCustomOp(i_consumer).get_nodeattr("model")) assert ( - first_df_model.graph.node[0].op_type == "IODMA" + first_df_model.graph.node[0].op_type == "IODMA_hls" ), "First partition must hold input IODMA" successors = model.find_direct_successors(i_consumer) successor_input_num = list(successors[0].input).index(i_consumer.output[0]) @@ -88,7 +88,7 @@ def get_driver_shapes(model: ModelWrapper) -> Dict: ), """ Ensure CreateDataflowPartition called before driver creation.""" df_model = ModelWrapper(getCustomOp(o_producer).get_nodeattr("model")) - assert df_model.graph.node[-1].op_type == "IODMA", "Partition must hold output IODMA" + assert df_model.graph.node[-1].op_type == "IODMA_hls", "Partition must hold output IODMA" predecessors = model.find_direct_predecessors(o_producer) predecessor_output_num = list(predecessors[0].output).index(o_producer.input[0]) predecessor_sdp = getCustomOp(predecessors[0]) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index d9a3a0baa6..34ced7f3a1 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -185,10 +185,7 @@ def run_command(command, cwd=None, debug=False): "xclbinutil not in PATH or not installed.\ Required to read kernel names for driver config!" ) - subprocess.run( - f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", - shell=True, - ) + run_command(f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json") ips = None with open("ip_layout.json") as f: ips = json.loads(f.read())["ip_layout"]["m_ip_data"] From 8ed6263b40481c2a6f8a29afe1e6628dd85eb0ab Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:49:18 +0200 Subject: [PATCH 43/57] Fix output to shell --- src/finn/transformation/fpgadataflow/make_driver.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 34ced7f3a1..046af2bcb0 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -137,7 +137,8 @@ def run_command(command, cwd=None, debug=False): if debug: print(result.stdout) # Print the output for debugging purposes except subprocess.CalledProcessError as e: - print(f"Error running command: {' '.join(command)}") + print(f"Error running command: {command}") + print(f"Output:{e.stdout}; Error:{e.stderr}") raise e # Step-by-step equivalent of the provided bash script @@ -185,7 +186,7 @@ def run_command(command, cwd=None, debug=False): "xclbinutil not in PATH or not installed.\ Required to read kernel names for driver config!" ) - run_command(f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json") + run_command(f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", cwd=os.path.join(self.build_dir, "..")) ips = None with open("ip_layout.json") as f: ips = json.loads(f.read())["ip_layout"]["m_ip_data"] From 7e1d6ced7b40d8910a8ce15a776298083cc807d0 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:52:55 +0200 Subject: [PATCH 44/57] Fix linting --- .../3-build-accelerator-with-finn.ipynb | 249 +++++++++++++++--- .../fpgadataflow/get_driver_shapes.py | 20 +- .../fpgadataflow/make_driver.py | 5 +- 3 files changed, 228 insertions(+), 46 deletions(-) diff --git a/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb b/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb index 28702d0286..173f47195d 100644 --- a/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb +++ b/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb @@ -106,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -129,10 +129,11 @@ "cfg_estimates = build.DataflowBuildConfig(\n", " output_dir = estimates_output_dir,\n", " mvau_wwidth_max = 80,\n", - " target_fps = 1000000,\n", + " target_fps = 1000,\n", " synth_clk_period_ns = 10.0,\n", - " fpga_part = \"xc7z020clg400-1\",\n", + " fpga_part = \"xilinx_u55c_gen3x16_xdma_3_202210_1\",\n", " steps = build_cfg.estimate_only_dataflow_steps,\n", + " board = \"U55C\",\n", " generate_outputs=[\n", " build_cfg.DataflowOutputType.ESTIMATE_REPORTS,\n", " ]\n", @@ -141,9 +142,43 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Building dataflow accelerator from /home/linusjun/git/finn/notebooks/end2end_example/cybersecurity/cybsec-mlp-ready.onnx\n", + "Intermediate outputs will be generated in /tmp/finn_dev_linusjun\n", + "Final outputs will be generated in output_estimates_only\n", + "Build log is at output_estimates_only/build_dataflow.log\n", + "Running step: step_qonnx_to_finn [1/10]\n", + "Running step: step_tidy_up [2/10]\n", + "Running step: step_streamline [3/10]\n", + "Running step: step_convert_to_hw [4/10]\n", + "Running step: step_create_dataflow_partition [5/10]\n", + "Running step: step_specialize_layers [6/10]\n", + "Running step: step_target_fps_parallelization [7/10]\n", + "Running step: step_apply_folding_config [8/10]\n", + "Running step: step_minimize_bit_width [9/10]\n", + "Running step: step_generate_estimate_reports [10/10]\n", + "Completed successfully\n", + "CPU times: user 1.06 s, sys: 984 ms, total: 2.05 s\n", + "Wall time: 673 ms\n" + ] + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%time\n", "build.build_dataflow_cfg(model_file, cfg_estimates)" @@ -151,7 +186,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -167,18 +202,38 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "auto_folding_config.json report\n", + "build_dataflow.log\t template_specialize_layers_config.json\n", + "intermediate_models\t time_per_step.json\n" + ] + } + ], "source": [ "! ls {estimates_output_dir}" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "estimate_layer_config_alternatives.json estimate_network_performance.json\n", + "estimate_layer_cycles.json\t\t op_and_param_counts.json\n", + "estimate_layer_resources.json\n" + ] + } + ], "source": [ "! ls {estimates_output_dir}/report" ] @@ -192,9 +247,23 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"critical_path_cycles\": 8256,\n", + " \"max_cycles\": 4096,\n", + " \"max_cycles_node_name\": \"MVAU_hls_0\",\n", + " \"estimated_throughput_fps\": 24414.0625,\n", + " \"estimated_latency_ns\": 82560.0\n", + "}" + ] + } + ], "source": [ "! cat {estimates_output_dir}/report/estimate_network_performance.json" ] @@ -208,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -221,9 +290,20 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'MVAU_hls_0': 4096, 'MVAU_hls_1': 4096, 'MVAU_hls_2': 64}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "read_json_dict(estimates_output_dir + \"/report/estimate_layer_cycles.json\")" ] @@ -239,9 +319,38 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'MVAU_hls_0': {'BRAM_18K': 1,\n", + " 'BRAM_efficiency': 0.4444444444444444,\n", + " 'LUT': 317,\n", + " 'URAM': 0,\n", + " 'URAM_efficiency': 1,\n", + " 'DSP': 0},\n", + " 'MVAU_hls_1': {'BRAM_18K': 1,\n", + " 'BRAM_efficiency': 0.4444444444444444,\n", + " 'LUT': 316,\n", + " 'URAM': 0,\n", + " 'URAM_efficiency': 1,\n", + " 'DSP': 0},\n", + " 'MVAU_hls_2': {'BRAM_18K': 1,\n", + " 'BRAM_efficiency': 0.006944444444444444,\n", + " 'LUT': 316,\n", + " 'URAM': 0,\n", + " 'URAM_efficiency': 1,\n", + " 'DSP': 0},\n", + " 'total': {'BRAM_18K': 3.0, 'LUT': 949.0, 'URAM': 0.0, 'DSP': 0.0}}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "read_json_dict(estimates_output_dir + \"/report/estimate_layer_resources.json\")" ] @@ -294,9 +403,10 @@ "cfg_stitched_ip = build.DataflowBuildConfig(\n", " output_dir = rtlsim_output_dir,\n", " mvau_wwidth_max = 80,\n", - " target_fps = 1000000,\n", + " target_fps = 1000,\n", " synth_clk_period_ns = 10.0,\n", - " fpga_part = \"xc7z020clg400-1\",\n", + " fpga_part = \"xilinx_u55c_gen3x16_xdma_3_202210_1\",\n", + " board = \"U55C\",\n", " generate_outputs=[\n", " build_cfg.DataflowOutputType.STITCHED_IP,\n", " build_cfg.DataflowOutputType.RTLSIM_PERFORMANCE,\n", @@ -424,7 +534,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -445,13 +555,14 @@ "cfg = build.DataflowBuildConfig(\n", " output_dir = final_output_dir,\n", " mvau_wwidth_max = 80,\n", - " target_fps = 1000000,\n", + " target_fps = 1000,\n", " synth_clk_period_ns = 10.0,\n", - " board = \"Pynq-Z1\",\n", - " shell_flow_type = build_cfg.ShellFlowType.VIVADO_ZYNQ,\n", + " fpga_part = \"xilinx_u55c_gen3x16_xdma_3_202210_1\",\n", + " board = \"U55C\",\n", + " shell_flow_type = build_cfg.ShellFlowType.VITIS_ALVEO,\n", " generate_outputs=[\n", " build_cfg.DataflowOutputType.BITFILE,\n", - " build_cfg.DataflowOutputType.PYNQ_DRIVER,\n", + " build_cfg.DataflowOutputType.CPP_DRIVER,\n", " build_cfg.DataflowOutputType.DEPLOYMENT_PACKAGE,\n", " ]\n", ")" @@ -461,10 +572,82 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Building dataflow accelerator from /home/linusjun/git/finn/notebooks/end2end_example/cybersecurity/cybsec-mlp-ready.onnx\n", + "Intermediate outputs will be generated in /tmp/finn_dev_linusjun\n", + "Final outputs will be generated in output_final\n", + "Build log is at output_final/build_dataflow.log\n", + "Running step: step_qonnx_to_finn [1/19]\n", + "Running step: step_tidy_up [2/19]\n", + "Running step: step_streamline [3/19]\n", + "Running step: step_convert_to_hw [4/19]\n", + "Running step: step_create_dataflow_partition [5/19]\n", + "Running step: step_specialize_layers [6/19]\n", + "Running step: step_target_fps_parallelization [7/19]\n", + "Running step: step_apply_folding_config [8/19]\n", + "Running step: step_minimize_bit_width [9/19]\n", + "Running step: step_generate_estimate_reports [10/19]\n", + "Running step: step_hw_codegen [11/19]\n", + "Running step: step_hw_ipgen [12/19]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "multiprocessing.pool.RemoteTraceback: \n", + "\"\"\"\n", + "Traceback (most recent call last):\n", + " File \"/usr/lib/python3.10/multiprocessing/pool.py\", line 125, in worker\n", + " result = (True, func(*args, **kwds))\n", + " File \"/usr/lib/python3.10/multiprocessing/pool.py\", line 48, in mapstar\n", + " return list(map(*args))\n", + " File \"/home/linusjun/git/finn/src/finn/transformation/fpgadataflow/hlssynth_ip.py\", line 72, in applyNodeLocal\n", + " inst.ipgen_singlenode_code()\n", + " File \"/home/linusjun/git/finn/src/finn/custom_op/fpgadataflow/hlsbackend.py\", line 188, in ipgen_singlenode_code\n", + " assert os.path.isdir(ip_path), \"IPGen failed: %s not found. Check log under %s\" % (\n", + "AssertionError: IPGen failed: /tmp/finn_dev_linusjun/code_gen_ipgen_MVAU_hls_0_jkparhik/project_MVAU_hls_0/sol1/impl/ip not found. Check log under /tmp/finn_dev_linusjun/code_gen_ipgen_MVAU_hls_0_jkparhik\n", + "\"\"\"\n", + "\n", + "The above exception was the direct cause of the following exception:\n", + "\n", + "Traceback (most recent call last):\n", + " File \"/home/linusjun/git/finn/src/finn/builder/build_dataflow.py\", line 158, in build_dataflow_cfg\n", + " model = transform_step(model, cfg)\n", + " File \"/home/linusjun/git/finn/src/finn/builder/build_dataflow_steps.py\", line 524, in step_hw_ipgen\n", + " model = model.transform(HLSSynthIP())\n", + " File \"/home/linusjun/git/finn/deps/qonnx/src/qonnx/core/modelwrapper.py\", line 140, in transform\n", + " (transformed_model, model_was_changed) = transformation.apply(transformed_model)\n", + " File \"/home/linusjun/git/finn/deps/qonnx/src/qonnx/transformation/base.py\", line 112, in apply\n", + " new_nodes_and_bool = p.map(self.applyNodeLocal, old_nodes, chunksize=1)\n", + " File \"/usr/lib/python3.10/multiprocessing/pool.py\", line 367, in map\n", + " return self._map_async(func, iterable, mapstar, chunksize).get()\n", + " File \"/usr/lib/python3.10/multiprocessing/pool.py\", line 774, in get\n", + " raise self._value\n", + "AssertionError: IPGen failed: /tmp/finn_dev_linusjun/code_gen_ipgen_MVAU_hls_0_jkparhik/project_MVAU_hls_0/sol1/impl/ip not found. Check log under /tmp/finn_dev_linusjun/code_gen_ipgen_MVAU_hls_0_jkparhik\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> \u001b[0;32m/usr/lib/python3.10/multiprocessing/pool.py\u001b[0m(774)\u001b[0;36mget\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 772 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_value\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 773 \u001b[0;31m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m--> 774 \u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_value\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 775 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 776 \u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0m_set\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n" + ] + } + ], "source": [ - "#%%time\n", - "#build.build_dataflow_cfg(model_file, cfg)" + "%%time\n", + "build.build_dataflow_cfg(model_file, cfg)" ] }, { diff --git a/src/finn/transformation/fpgadataflow/get_driver_shapes.py b/src/finn/transformation/fpgadataflow/get_driver_shapes.py index 57cc4545e3..f11b5959f1 100644 --- a/src/finn/transformation/fpgadataflow/get_driver_shapes.py +++ b/src/finn/transformation/fpgadataflow/get_driver_shapes.py @@ -1,17 +1,18 @@ +import numpy as np from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple +from typing import Dict + import finn.util.data_packing as dpk from finn.util.data_packing import ( hexstring2npbytearray, pack_innermost_dim_as_hex_string, ) -from typing import Dict -import numpy as np - # TODO: License? + def to_external_tensor(init, w_dtype): """Return an appropriately formatted and packed numpy byte array for given external parameter tensor.""" @@ -59,9 +60,7 @@ def get_driver_shapes(model: ModelWrapper) -> Dict: i_tensor_shape_folded = tuple(getCustomOp(first_node).get_folded_input_shape()) # generate dummy folded i/o tensors and their packed versions i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded) - i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - i_tensor_dummy_folded, i_tensor_dt - ) + i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray(i_tensor_dummy_folded, i_tensor_dt) i_tensor_shape_packed = i_tensor_dummy_packed.shape # append all input tensor info to relevant lists idt.append("DataType['%s']" % i_tensor_dt.name) @@ -98,9 +97,7 @@ def get_driver_shapes(model: ModelWrapper) -> Dict: ) o_tensor_shape_folded = tuple(getCustomOp(last_node).get_folded_output_shape()) o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded) - o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - o_tensor_dummy_folded, o_tensor_dt - ) + o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray(o_tensor_dummy_folded, o_tensor_dt) o_tensor_shape_packed = o_tensor_dummy_packed.shape # append all output tensor info to relevant lists odt.append("DataType['%s']" % o_tensor_dt.name) @@ -108,17 +105,16 @@ def get_driver_shapes(model: ModelWrapper) -> Dict: oshape_folded.append(o_tensor_shape_folded) oshape_packed.append(o_tensor_shape_packed) odma_names.append(getCustomOp(o_producer).get_nodeattr("instance_name")) - + return { "idt": idt, "idma_names": idma_names, "ishape_normal": ishape_normal, "ishape_folded": ishape_folded, "ishape_packed": ishape_packed, - "odt": odt, "odma_names": odma_names, "oshape_normal": oshape_normal, "oshape_folded": oshape_folded, "oshape_packed": oshape_packed, - } \ No newline at end of file + } diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 046af2bcb0..ead79ec331 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -186,7 +186,10 @@ def run_command(command, cwd=None, debug=False): "xclbinutil not in PATH or not installed.\ Required to read kernel names for driver config!" ) - run_command(f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", cwd=os.path.join(self.build_dir, "..")) + run_command( + f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", + cwd=os.path.join(self.build_dir, ".."), + ) ips = None with open("ip_layout.json") as f: ips = json.loads(f.read())["ip_layout"]["m_ip_data"] From c70977758a3808eed606145df651122e0eedc13b Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:52:55 +0200 Subject: [PATCH 45/57] Fix linting --- .../bnn-pynq/cnv_end2end_example.ipynb | 5 +++-- .../bnn-pynq/tfc_end2end_example.ipynb | 5 +++-- .../fpgadataflow/get_driver_shapes.py | 20 ++++++++----------- .../fpgadataflow/make_driver.py | 5 ++++- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb index 507b1022e6..8b8cff8ee9 100644 --- a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb @@ -484,7 +484,8 @@ "metadata": {}, "outputs": [], "source": [ - "from shutil import copy, copytree\n", + "from shutil import copy\n", + "from distutils.dir_util import copy_tree\n", "\n", "# create directory for deployment files\n", "deployment_dir = make_build_dir(prefix=\"pynq_deployment_\")\n", @@ -502,7 +503,7 @@ "\n", "# driver.py and python libraries\n", "pynq_driver_dir = model.get_metadata_prop(\"pynq_driver_dir\")\n", - "copytree(pynq_driver_dir, deployment_dir, dirs_exist_ok=True)" + "copy_tree(pynq_driver_dir, deployment_dir)" ] }, { diff --git a/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb b/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb index bb5e357b66..675ba23d2d 100644 --- a/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb @@ -895,7 +895,8 @@ "metadata": {}, "outputs": [], "source": [ - "from shutil import copy, copytree\n", + "from shutil import copy\n", + "from distutils.dir_util import copy_tree\n", "\n", "# create directory for deployment files\n", "deployment_dir = make_build_dir(prefix=\"pynq_deployment_\")\n", @@ -913,7 +914,7 @@ "\n", "# driver.py and python libraries\n", "pynq_driver_dir = model.get_metadata_prop(\"pynq_driver_dir\")\n", - "copytree(pynq_driver_dir, deployment_dir, dirs_exist_ok=True)" + "copy_tree(pynq_driver_dir, deployment_dir)" ] }, { diff --git a/src/finn/transformation/fpgadataflow/get_driver_shapes.py b/src/finn/transformation/fpgadataflow/get_driver_shapes.py index 57cc4545e3..f11b5959f1 100644 --- a/src/finn/transformation/fpgadataflow/get_driver_shapes.py +++ b/src/finn/transformation/fpgadataflow/get_driver_shapes.py @@ -1,17 +1,18 @@ +import numpy as np from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple +from typing import Dict + import finn.util.data_packing as dpk from finn.util.data_packing import ( hexstring2npbytearray, pack_innermost_dim_as_hex_string, ) -from typing import Dict -import numpy as np - # TODO: License? + def to_external_tensor(init, w_dtype): """Return an appropriately formatted and packed numpy byte array for given external parameter tensor.""" @@ -59,9 +60,7 @@ def get_driver_shapes(model: ModelWrapper) -> Dict: i_tensor_shape_folded = tuple(getCustomOp(first_node).get_folded_input_shape()) # generate dummy folded i/o tensors and their packed versions i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded) - i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - i_tensor_dummy_folded, i_tensor_dt - ) + i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray(i_tensor_dummy_folded, i_tensor_dt) i_tensor_shape_packed = i_tensor_dummy_packed.shape # append all input tensor info to relevant lists idt.append("DataType['%s']" % i_tensor_dt.name) @@ -98,9 +97,7 @@ def get_driver_shapes(model: ModelWrapper) -> Dict: ) o_tensor_shape_folded = tuple(getCustomOp(last_node).get_folded_output_shape()) o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded) - o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - o_tensor_dummy_folded, o_tensor_dt - ) + o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray(o_tensor_dummy_folded, o_tensor_dt) o_tensor_shape_packed = o_tensor_dummy_packed.shape # append all output tensor info to relevant lists odt.append("DataType['%s']" % o_tensor_dt.name) @@ -108,17 +105,16 @@ def get_driver_shapes(model: ModelWrapper) -> Dict: oshape_folded.append(o_tensor_shape_folded) oshape_packed.append(o_tensor_shape_packed) odma_names.append(getCustomOp(o_producer).get_nodeattr("instance_name")) - + return { "idt": idt, "idma_names": idma_names, "ishape_normal": ishape_normal, "ishape_folded": ishape_folded, "ishape_packed": ishape_packed, - "odt": odt, "odma_names": odma_names, "oshape_normal": oshape_normal, "oshape_folded": oshape_folded, "oshape_packed": oshape_packed, - } \ No newline at end of file + } diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 046af2bcb0..ead79ec331 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -186,7 +186,10 @@ def run_command(command, cwd=None, debug=False): "xclbinutil not in PATH or not installed.\ Required to read kernel names for driver config!" ) - run_command(f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", cwd=os.path.join(self.build_dir, "..")) + run_command( + f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", + cwd=os.path.join(self.build_dir, ".."), + ) ips = None with open("ip_layout.json") as f: ips = json.loads(f.read())["ip_layout"]["m_ip_data"] From 5b63111f461399235e5af60a397efec9b2b69a45 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:04:35 +0200 Subject: [PATCH 46/57] Revert changes to notebooks --- .../3-build-accelerator-with-finn.ipynb | 249 +++--------------- 1 file changed, 33 insertions(+), 216 deletions(-) diff --git a/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb b/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb index 173f47195d..28702d0286 100644 --- a/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb +++ b/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb @@ -106,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -129,11 +129,10 @@ "cfg_estimates = build.DataflowBuildConfig(\n", " output_dir = estimates_output_dir,\n", " mvau_wwidth_max = 80,\n", - " target_fps = 1000,\n", + " target_fps = 1000000,\n", " synth_clk_period_ns = 10.0,\n", - " fpga_part = \"xilinx_u55c_gen3x16_xdma_3_202210_1\",\n", + " fpga_part = \"xc7z020clg400-1\",\n", " steps = build_cfg.estimate_only_dataflow_steps,\n", - " board = \"U55C\",\n", " generate_outputs=[\n", " build_cfg.DataflowOutputType.ESTIMATE_REPORTS,\n", " ]\n", @@ -142,43 +141,9 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Building dataflow accelerator from /home/linusjun/git/finn/notebooks/end2end_example/cybersecurity/cybsec-mlp-ready.onnx\n", - "Intermediate outputs will be generated in /tmp/finn_dev_linusjun\n", - "Final outputs will be generated in output_estimates_only\n", - "Build log is at output_estimates_only/build_dataflow.log\n", - "Running step: step_qonnx_to_finn [1/10]\n", - "Running step: step_tidy_up [2/10]\n", - "Running step: step_streamline [3/10]\n", - "Running step: step_convert_to_hw [4/10]\n", - "Running step: step_create_dataflow_partition [5/10]\n", - "Running step: step_specialize_layers [6/10]\n", - "Running step: step_target_fps_parallelization [7/10]\n", - "Running step: step_apply_folding_config [8/10]\n", - "Running step: step_minimize_bit_width [9/10]\n", - "Running step: step_generate_estimate_reports [10/10]\n", - "Completed successfully\n", - "CPU times: user 1.06 s, sys: 984 ms, total: 2.05 s\n", - "Wall time: 673 ms\n" - ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "%%time\n", "build.build_dataflow_cfg(model_file, cfg_estimates)" @@ -186,7 +151,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -202,38 +167,18 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "auto_folding_config.json report\n", - "build_dataflow.log\t template_specialize_layers_config.json\n", - "intermediate_models\t time_per_step.json\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "! ls {estimates_output_dir}" ] }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "estimate_layer_config_alternatives.json estimate_network_performance.json\n", - "estimate_layer_cycles.json\t\t op_and_param_counts.json\n", - "estimate_layer_resources.json\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "! ls {estimates_output_dir}/report" ] @@ -247,23 +192,9 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"critical_path_cycles\": 8256,\n", - " \"max_cycles\": 4096,\n", - " \"max_cycles_node_name\": \"MVAU_hls_0\",\n", - " \"estimated_throughput_fps\": 24414.0625,\n", - " \"estimated_latency_ns\": 82560.0\n", - "}" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "! cat {estimates_output_dir}/report/estimate_network_performance.json" ] @@ -277,7 +208,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -290,20 +221,9 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'MVAU_hls_0': 4096, 'MVAU_hls_1': 4096, 'MVAU_hls_2': 64}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "read_json_dict(estimates_output_dir + \"/report/estimate_layer_cycles.json\")" ] @@ -319,38 +239,9 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'MVAU_hls_0': {'BRAM_18K': 1,\n", - " 'BRAM_efficiency': 0.4444444444444444,\n", - " 'LUT': 317,\n", - " 'URAM': 0,\n", - " 'URAM_efficiency': 1,\n", - " 'DSP': 0},\n", - " 'MVAU_hls_1': {'BRAM_18K': 1,\n", - " 'BRAM_efficiency': 0.4444444444444444,\n", - " 'LUT': 316,\n", - " 'URAM': 0,\n", - " 'URAM_efficiency': 1,\n", - " 'DSP': 0},\n", - " 'MVAU_hls_2': {'BRAM_18K': 1,\n", - " 'BRAM_efficiency': 0.006944444444444444,\n", - " 'LUT': 316,\n", - " 'URAM': 0,\n", - " 'URAM_efficiency': 1,\n", - " 'DSP': 0},\n", - " 'total': {'BRAM_18K': 3.0, 'LUT': 949.0, 'URAM': 0.0, 'DSP': 0.0}}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "read_json_dict(estimates_output_dir + \"/report/estimate_layer_resources.json\")" ] @@ -403,10 +294,9 @@ "cfg_stitched_ip = build.DataflowBuildConfig(\n", " output_dir = rtlsim_output_dir,\n", " mvau_wwidth_max = 80,\n", - " target_fps = 1000,\n", + " target_fps = 1000000,\n", " synth_clk_period_ns = 10.0,\n", - " fpga_part = \"xilinx_u55c_gen3x16_xdma_3_202210_1\",\n", - " board = \"U55C\",\n", + " fpga_part = \"xc7z020clg400-1\",\n", " generate_outputs=[\n", " build_cfg.DataflowOutputType.STITCHED_IP,\n", " build_cfg.DataflowOutputType.RTLSIM_PERFORMANCE,\n", @@ -534,7 +424,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -555,14 +445,13 @@ "cfg = build.DataflowBuildConfig(\n", " output_dir = final_output_dir,\n", " mvau_wwidth_max = 80,\n", - " target_fps = 1000,\n", + " target_fps = 1000000,\n", " synth_clk_period_ns = 10.0,\n", - " fpga_part = \"xilinx_u55c_gen3x16_xdma_3_202210_1\",\n", - " board = \"U55C\",\n", - " shell_flow_type = build_cfg.ShellFlowType.VITIS_ALVEO,\n", + " board = \"Pynq-Z1\",\n", + " shell_flow_type = build_cfg.ShellFlowType.VIVADO_ZYNQ,\n", " generate_outputs=[\n", " build_cfg.DataflowOutputType.BITFILE,\n", - " build_cfg.DataflowOutputType.CPP_DRIVER,\n", + " build_cfg.DataflowOutputType.PYNQ_DRIVER,\n", " build_cfg.DataflowOutputType.DEPLOYMENT_PACKAGE,\n", " ]\n", ")" @@ -572,82 +461,10 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Building dataflow accelerator from /home/linusjun/git/finn/notebooks/end2end_example/cybersecurity/cybsec-mlp-ready.onnx\n", - "Intermediate outputs will be generated in /tmp/finn_dev_linusjun\n", - "Final outputs will be generated in output_final\n", - "Build log is at output_final/build_dataflow.log\n", - "Running step: step_qonnx_to_finn [1/19]\n", - "Running step: step_tidy_up [2/19]\n", - "Running step: step_streamline [3/19]\n", - "Running step: step_convert_to_hw [4/19]\n", - "Running step: step_create_dataflow_partition [5/19]\n", - "Running step: step_specialize_layers [6/19]\n", - "Running step: step_target_fps_parallelization [7/19]\n", - "Running step: step_apply_folding_config [8/19]\n", - "Running step: step_minimize_bit_width [9/19]\n", - "Running step: step_generate_estimate_reports [10/19]\n", - "Running step: step_hw_codegen [11/19]\n", - "Running step: step_hw_ipgen [12/19]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "multiprocessing.pool.RemoteTraceback: \n", - "\"\"\"\n", - "Traceback (most recent call last):\n", - " File \"/usr/lib/python3.10/multiprocessing/pool.py\", line 125, in worker\n", - " result = (True, func(*args, **kwds))\n", - " File \"/usr/lib/python3.10/multiprocessing/pool.py\", line 48, in mapstar\n", - " return list(map(*args))\n", - " File \"/home/linusjun/git/finn/src/finn/transformation/fpgadataflow/hlssynth_ip.py\", line 72, in applyNodeLocal\n", - " inst.ipgen_singlenode_code()\n", - " File \"/home/linusjun/git/finn/src/finn/custom_op/fpgadataflow/hlsbackend.py\", line 188, in ipgen_singlenode_code\n", - " assert os.path.isdir(ip_path), \"IPGen failed: %s not found. Check log under %s\" % (\n", - "AssertionError: IPGen failed: /tmp/finn_dev_linusjun/code_gen_ipgen_MVAU_hls_0_jkparhik/project_MVAU_hls_0/sol1/impl/ip not found. Check log under /tmp/finn_dev_linusjun/code_gen_ipgen_MVAU_hls_0_jkparhik\n", - "\"\"\"\n", - "\n", - "The above exception was the direct cause of the following exception:\n", - "\n", - "Traceback (most recent call last):\n", - " File \"/home/linusjun/git/finn/src/finn/builder/build_dataflow.py\", line 158, in build_dataflow_cfg\n", - " model = transform_step(model, cfg)\n", - " File \"/home/linusjun/git/finn/src/finn/builder/build_dataflow_steps.py\", line 524, in step_hw_ipgen\n", - " model = model.transform(HLSSynthIP())\n", - " File \"/home/linusjun/git/finn/deps/qonnx/src/qonnx/core/modelwrapper.py\", line 140, in transform\n", - " (transformed_model, model_was_changed) = transformation.apply(transformed_model)\n", - " File \"/home/linusjun/git/finn/deps/qonnx/src/qonnx/transformation/base.py\", line 112, in apply\n", - " new_nodes_and_bool = p.map(self.applyNodeLocal, old_nodes, chunksize=1)\n", - " File \"/usr/lib/python3.10/multiprocessing/pool.py\", line 367, in map\n", - " return self._map_async(func, iterable, mapstar, chunksize).get()\n", - " File \"/usr/lib/python3.10/multiprocessing/pool.py\", line 774, in get\n", - " raise self._value\n", - "AssertionError: IPGen failed: /tmp/finn_dev_linusjun/code_gen_ipgen_MVAU_hls_0_jkparhik/project_MVAU_hls_0/sol1/impl/ip not found. Check log under /tmp/finn_dev_linusjun/code_gen_ipgen_MVAU_hls_0_jkparhik\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "> \u001b[0;32m/usr/lib/python3.10/multiprocessing/pool.py\u001b[0m(774)\u001b[0;36mget\u001b[0;34m()\u001b[0m\n", - "\u001b[0;32m 772 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_value\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 773 \u001b[0;31m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m--> 774 \u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_value\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 775 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 776 \u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0m_set\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\n" - ] - } - ], + "outputs": [], "source": [ - "%%time\n", - "build.build_dataflow_cfg(model_file, cfg)" + "#%%time\n", + "#build.build_dataflow_cfg(model_file, cfg)" ] }, { From 2c2a33ac24c1501516ce09d90276472140549a26 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:22:30 +0200 Subject: [PATCH 47/57] Remove replicated code --- .../bnn-pynq/cnv_end2end_example.ipynb | 5 +- .../bnn-pynq/tfc_end2end_example.ipynb | 5 +- .../fpgadataflow/make_driver.py | 115 +++--------------- 3 files changed, 21 insertions(+), 104 deletions(-) diff --git a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb index 61b69da1e2..55e6f9b7aa 100644 --- a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb @@ -484,8 +484,7 @@ "metadata": {}, "outputs": [], "source": [ - "from shutil import copy\n", - "from distutils.dir_util import copy_tree\n", + "from shutil import copy, copytree\n", "\n", "# create directory for deployment files\n", "deployment_dir = make_build_dir(prefix=\"pynq_deployment_\")\n", @@ -502,7 +501,7 @@ "\n", "# driver.py and python libraries\n", "pynq_driver_dir = model.get_metadata_prop(\"pynq_driver_dir\")\n", - "copy_tree(pynq_driver_dir, deployment_dir)" + "copytree(pynq_driver_dir, deployment_dir, dirs_exist_ok=True)" ] }, { diff --git a/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb b/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb index 2cd0eb6ed0..974e513f58 100644 --- a/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb @@ -895,8 +895,7 @@ "metadata": {}, "outputs": [], "source": [ - "from shutil import copy\n", - "from distutils.dir_util import copy_tree\n", + "from shutil import copy, copytree\n", "\n", "# create directory for deployment files\n", "deployment_dir = make_build_dir(prefix=\"pynq_deployment_\")\n", @@ -913,7 +912,7 @@ "\n", "# driver.py and python libraries\n", "pynq_driver_dir = model.get_metadata_prop(\"pynq_driver_dir\")\n", - "copy_tree(pynq_driver_dir, deployment_dir)" + "copytree(pynq_driver_dir, deployment_dir, dirs_exist_ok=True)" ] }, { diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index ead79ec331..5afc05ed71 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -37,12 +37,11 @@ from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp from qonnx.transformation.base import Transformation -from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple +from qonnx.util.basic import roundup_to_integer_multiple from string import Template from typing import Dict, Tuple import finn.util -import finn.util.data_packing as dpk from finn.transformation.fpgadataflow.get_driver_shapes import get_driver_shapes from finn.util.basic import make_build_dir from finn.util.data_packing import ( @@ -351,90 +350,8 @@ def apply(self, model): ) for src_file, target_file in files_to_copy: shutil.copy(src_file, target_file) - # extract input-output shapes from the graph - # TODO convert this to an analysis pass? - idt = [] - idma_names = [] - ishape_normal = [] - ishape_folded = [] - ishape_packed = [] - for idma_ind, graph_in in enumerate(model.graph.input): - i_tensor_name = graph_in.name - # get inp tensor properties - i_tensor_dt = model.get_tensor_datatype(i_tensor_name) - i_tensor_shape_normal = tuple(model.get_tensor_shape(i_tensor_name)) - # go down into dataflow partition to get folded shape info etc - # TODO consider setting these as attributes during dataflow partitioning - i_consumer = model.find_consumer(i_tensor_name) - assert ( - i_consumer.op_type == "StreamingDataflowPartition" - ), """ - Ensure CreateDataflowPartition called before driver creation.""" - first_df_model = ModelWrapper(getCustomOp(i_consumer).get_nodeattr("model")) - assert ( - first_df_model.graph.node[0].op_type == "IODMA_hls" - ), "First partition must hold input IODMA" - successors = model.find_direct_successors(i_consumer) - successor_input_num = list(successors[0].input).index(i_consumer.output[0]) - successor_sdp = getCustomOp(successors[0]) - successor_df_model = ModelWrapper(successor_sdp.get_nodeattr("model")) - first_node = successor_df_model.find_consumer( - successor_df_model.graph.input[successor_input_num].name - ) - i_tensor_shape_folded = tuple(getCustomOp(first_node).get_folded_input_shape()) - # generate dummy folded i/o tensors and their packed versions - i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded) - i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - i_tensor_dummy_folded, i_tensor_dt - ) - i_tensor_shape_packed = i_tensor_dummy_packed.shape - # append all input tensor info to relevant lists - idt.append("DataType['%s']" % i_tensor_dt.name) - ishape_normal.append(i_tensor_shape_normal) - ishape_folded.append(i_tensor_shape_folded) - ishape_packed.append(i_tensor_shape_packed) - idma_names.append(getCustomOp(i_consumer).get_nodeattr("instance_name")) - - odt = [] - odma_names = [] - oshape_normal = [] - oshape_folded = [] - oshape_packed = [] - for odma_ind, graph_out in enumerate(model.graph.output): - o_tensor_name = graph_out.name - # get inp tensor properties - o_tensor_dt = model.get_tensor_datatype(o_tensor_name) - o_tensor_shape_normal = tuple(model.get_tensor_shape(o_tensor_name)) - # go down into IODMA partition to get folded shape info etc - # TODO consider setting these as attributes during dataflow partitioning - o_producer = model.find_producer(o_tensor_name) - assert ( - o_producer.op_type == "StreamingDataflowPartition" - ), """ - Ensure CreateDataflowPartition called before driver creation.""" - df_model = ModelWrapper(getCustomOp(o_producer).get_nodeattr("model")) - assert ( - df_model.graph.node[-1].op_type == "IODMA_hls" - ), "Partition must hold output IODMA" - predecessors = model.find_direct_predecessors(o_producer) - predecessor_output_num = list(predecessors[0].output).index(o_producer.input[0]) - predecessor_sdp = getCustomOp(predecessors[0]) - predecessor_df_model = ModelWrapper(predecessor_sdp.get_nodeattr("model")) - last_node = predecessor_df_model.find_producer( - predecessor_df_model.graph.output[predecessor_output_num].name - ) - o_tensor_shape_folded = tuple(getCustomOp(last_node).get_folded_output_shape()) - o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded) - o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( - o_tensor_dummy_folded, o_tensor_dt - ) - o_tensor_shape_packed = o_tensor_dummy_packed.shape - # append all output tensor info to relevant lists - odt.append("DataType['%s']" % o_tensor_dt.name) - oshape_normal.append(o_tensor_shape_normal) - oshape_folded.append(o_tensor_shape_folded) - oshape_packed.append(o_tensor_shape_packed) - odma_names.append(getCustomOp(o_producer).get_nodeattr("instance_name")) + + driver_shapes: Dict = get_driver_shapes(model) # generate external weights npy files weights_dir = pynq_driver_dir + "/runtime_weights" @@ -474,18 +391,20 @@ def apply(self, model): driver = template_driver.pynq_driver_template driver = driver.replace("$PLATFORM$", self.platform) - driver = driver.replace("$INPUT_FINN_DATATYPE$", str(idt).replace('"', "")) - driver = driver.replace("$INPUT_SHAPE_NORMAL$", str(ishape_normal)) - driver = driver.replace("$INPUT_SHAPE_FOLDED$", str(ishape_folded)) - driver = driver.replace("$INPUT_SHAPE_PACKED$", str(ishape_packed)) - driver = driver.replace("$OUTPUT_FINN_DATATYPE$", str(odt).replace('"', "")) - driver = driver.replace("$OUTPUT_SHAPE_NORMAL$", str(oshape_normal)) - driver = driver.replace("$OUTPUT_SHAPE_FOLDED$", str(oshape_folded)) - driver = driver.replace("$OUTPUT_SHAPE_PACKED$", str(oshape_packed)) - driver = driver.replace("$INPUT_DMA_NAME$", "%s" % str(idma_names)) - driver = driver.replace("$OUTPUT_DMA_NAME$", "%s" % str(odma_names)) - driver = driver.replace("$NUM_INPUTS$", str(len(idma_names))) - driver = driver.replace("$NUM_OUTPUTS$", str(len(odma_names))) + driver = driver.replace("$INPUT_FINN_DATATYPE$", str(driver_shapes["idt"]).replace('"', "")) + driver = driver.replace("$INPUT_SHAPE_NORMAL$", str(driver_shapes["ishape_normal"])) + driver = driver.replace("$INPUT_SHAPE_FOLDED$", str(driver_shapes["ishape_folded"])) + driver = driver.replace("$INPUT_SHAPE_PACKED$", str(driver_shapes["ishape_packed"])) + driver = driver.replace( + "$OUTPUT_FINN_DATATYPE$", str(driver_shapes["odt"]).replace('"', "") + ) + driver = driver.replace("$OUTPUT_SHAPE_NORMAL$", str(driver_shapes["oshape_normal"])) + driver = driver.replace("$OUTPUT_SHAPE_FOLDED$", str(driver_shapes["oshape_folded"])) + driver = driver.replace("$OUTPUT_SHAPE_PACKED$", str(driver_shapes["oshape_packed"])) + driver = driver.replace("$INPUT_DMA_NAME$", "%s" % str(driver_shapes["idma_names"])) + driver = driver.replace("$OUTPUT_DMA_NAME$", "%s" % str(driver_shapes["odma_names"])) + driver = driver.replace("$NUM_INPUTS$", str(len(driver_shapes["idma_names"]))) + driver = driver.replace("$NUM_OUTPUTS$", str(len(driver_shapes["odma_names"]))) driver = driver.replace("$EXT_WEIGHT_NUM$", str(ext_weight_dma_cnt)) with open(driver_py, "w") as f: From aec60dba40ddf215546cd2dba85b3ce1d2a090a4 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Thu, 17 Apr 2025 10:10:58 +0200 Subject: [PATCH 48/57] Add missing import --- src/finn/builder/build_dataflow_steps.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 675baf132c..6bc63b0d36 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -31,6 +31,7 @@ import numpy as np import os import shutil +import warnings from copy import deepcopy from functools import partial from qonnx.core.modelwrapper import ModelWrapper From 808798a4a3fe4d919411c0d46f053056923c5bc3 Mon Sep 17 00:00:00 2001 From: Linus Jungemann <38974033+LinusJungemann@users.noreply.github.com> Date: Thu, 17 Apr 2025 10:54:22 +0200 Subject: [PATCH 49/57] Add license --- .../fpgadataflow/get_driver_shapes.py | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/get_driver_shapes.py b/src/finn/transformation/fpgadataflow/get_driver_shapes.py index f11b5959f1..0b66e579d3 100644 --- a/src/finn/transformation/fpgadataflow/get_driver_shapes.py +++ b/src/finn/transformation/fpgadataflow/get_driver_shapes.py @@ -1,3 +1,31 @@ +# Copyright (C) 2025, 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 from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp @@ -10,8 +38,6 @@ pack_innermost_dim_as_hex_string, ) -# TODO: License? - def to_external_tensor(init, w_dtype): """Return an appropriately formatted and packed numpy byte array for given From 030feeb7dc332b07328ce42d9c245af28d822c61 Mon Sep 17 00:00:00 2001 From: auphelia Date: Wed, 21 May 2025 08:15:51 +0100 Subject: [PATCH 50/57] Remove unsused gitmodules files --- .gitmodules | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb2..0000000000 From d06d17e89d59364e025d5f3e59f6fe0a16e9d12b Mon Sep 17 00:00:00 2001 From: auphelia Date: Wed, 21 May 2025 18:58:06 +0100 Subject: [PATCH 51/57] [MakeCPPDriver] Small changes to input arguments and model metadata settings --- src/finn/builder/build_dataflow_steps.py | 4 +- .../fpgadataflow/make_driver.py | 59 ++++++------------- tests/end2end/test_end2end_bnn_pynq.py | 18 ++++-- 3 files changed, 33 insertions(+), 48 deletions(-) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 6bc63b0d36..d0fd9636a5 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -745,15 +745,13 @@ def step_make_driver(model: ModelWrapper, cfg: DataflowBuildConfig): print("PYNQ Python driver written into " + driver_dir) elif DataflowOutputType.CPP_DRIVER in cfg.generate_outputs: # generate C++ Driver - model = model.transform( MakeCPPDriver( cfg._resolve_driver_platform(), - build_dir=cfg.output_dir, version=cfg.cpp_driver_version, - driver_dir=driver_dir, ) ) + shutil.copytree(model.get_metadata_prop("cpp_driver_dir"), driver_dir, dirs_exist_ok=True) print("C++ driver written into " + driver_dir) else: warnings.warn( diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 5afc05ed71..92374a2849 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -71,7 +71,6 @@ class MakeCPPDriver(Transformation): # TODO: Enable multiple input types! Now only assumes the first one def resolve_dt_name(s: str) -> str: s = s.replace("DataType[", "").replace("]", "") - print(f"Converting tensor datatype {s}") if s in ["BINARY", "TERNARY", "BIPOLAR"]: return "Datatype" + s[0] + s[1:].lower() elif s.startswith("U"): @@ -88,15 +87,11 @@ def resolve_dt_name(s: str) -> str: def __init__( self, platform: str, - build_dir: str, version: str, - driver_dir, ): super().__init__() self.platform: str = platform - self.build_dir = build_dir self.version = version - self.driver_dir = driver_dir # Define variables for the repository URL and commit hash self.repository_url = "https://github.com/eki-project/finn-cpp-driver.git" @@ -105,11 +100,6 @@ def __init__( else: self.commit_hash = version - # Locations of files - self.xclbin_path = os.path.join(self.build_dir, "bitfile", "finn-accel.xclbin") - self.json_path = os.path.join(self.driver_dir, "acceleratorconfig.json") - self.header_path = os.path.join(self.driver_dir, "AcceleratorDatatypes.h") - def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: driver_shapes: Dict = get_driver_shapes(model) ext_weight_dma_cnt: int # noqa @@ -117,15 +107,12 @@ def apply(self, model: ModelWrapper) -> Tuple[ModelWrapper, bool]: # ext_weight_dma_cnt, weights_dir = write_weights(model, cpp_driver_dir) # * Creating the driver dir if it doesnt exist yet - if not os.path.isdir(self.driver_dir): - os.mkdir(self.driver_dir) - else: - try: - shutil.rmtree(self.driver_dir) - except Exception as e: - print(f"Failed to delete {self.driver_dir}. Reason: {e}") - raise e - os.mkdir(self.driver_dir) + # create a temporary folder for the generated driver + cpp_driver_dir = make_build_dir(prefix="cpp_driver_") + model.set_metadata_prop("cpp_driver_dir", cpp_driver_dir) + xclbin_path = model.get_metadata_prop("bitfile") + json_path = os.path.join(cpp_driver_dir, "acceleratorconfig.json") + header_path = os.path.join(cpp_driver_dir, "AcceleratorDatatypes.h") # Get the base C++ driver repo def run_command(command, cwd=None, debug=False): @@ -141,12 +128,12 @@ def run_command(command, cwd=None, debug=False): raise e # Step-by-step equivalent of the provided bash script - run_command("git init", cwd=self.driver_dir) - run_command(f"git remote add origin {self.repository_url}", cwd=self.driver_dir) - run_command(f"git fetch origin {self.commit_hash} --depth=1", cwd=self.driver_dir) - run_command("git checkout FETCH_HEAD", cwd=self.driver_dir) - run_command("git submodule update --init --recursive", cwd=self.driver_dir) - run_command("./buildDependencies.sh", cwd=self.driver_dir) + run_command("git init", cwd=cpp_driver_dir) + run_command(f"git remote add origin {self.repository_url}", cwd=cpp_driver_dir) + run_command(f"git fetch origin {self.commit_hash} --depth=1", cwd=cpp_driver_dir) + run_command("git checkout FETCH_HEAD", cwd=cpp_driver_dir) + run_command("git submodule update --init --recursive", cwd=cpp_driver_dir) + run_command("./buildDependencies.sh", cwd=cpp_driver_dir) # * Writing the header file inputDatatype: str = MakeCPPDriver.resolve_dt_name( @@ -155,13 +142,9 @@ def run_command(command, cwd=None, debug=False): outputDatatype: str = MakeCPPDriver.resolve_dt_name( driver_shapes["odt"][0].replace("'", "") ) # .get_canonical_name()) - print( - f"Writing input header file: Used datatypes\ - will be {inputDatatype} and {outputDatatype}!" - ) with open( os.path.join( - self.driver_dir, "src", "FINNCppDriver", "config", "FinnDriverUsedDatatypes.h.in" + cpp_driver_dir, "src", "FINNCppDriver", "config", "FinnDriverUsedDatatypes.h.in" ), "r", ) as f_in: @@ -170,11 +153,9 @@ def run_command(command, cwd=None, debug=False): templated_str = template_handler.substitute( inputDatatype=inputDatatype, outputDatatype=outputDatatype ) - with open(self.header_path, "w+") as f: + with open(header_path, "w+") as f: f.write(templated_str) - print("Successfully created config header file.") - # * Writing the json file # TODO: Update this for multi-fpga usage (more than one device!) # Path of the xclbin in the finn compiler project @@ -186,11 +167,11 @@ def run_command(command, cwd=None, debug=False): Required to read kernel names for driver config!" ) run_command( - f"xclbinutil -i {self.xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", - cwd=os.path.join(self.build_dir, ".."), + f"xclbinutil -i {xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", + cwd=os.path.dirname(xclbin_path), ) ips = None - with open("ip_layout.json") as f: + with open(os.path.join(os.path.dirname(xclbin_path), "ip_layout.json")) as f: ips = json.loads(f.read())["ip_layout"]["m_ip_data"] # Get only ips that are kernels @@ -240,17 +221,15 @@ def formatKernelName(kname: str): data.append( { "xrtDeviceIndex": 0, - "xclbinPath": os.path.abspath(self.xclbin_path), + "xclbinPath": os.path.abspath(xclbin_path), "name": "MainDevice", "idmas": jsonIdmas, "odmas": jsonOdmas, } ) - with open(self.json_path, "w+") as f: + with open(json_path, "w+") as f: f.write(json.dumps(data, indent=4)) - print("Created runtime json config file") - # TODO: Generating weight files # weights_dir = output_dir + "/runtime_weights" diff --git a/tests/end2end/test_end2end_bnn_pynq.py b/tests/end2end/test_end2end_bnn_pynq.py index 1cc2789be6..0b28e2d20b 100644 --- a/tests/end2end/test_end2end_bnn_pynq.py +++ b/tests/end2end/test_end2end_bnn_pynq.py @@ -74,7 +74,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 MakeCPPDriver, MakePYNQDriver from finn.transformation.fpgadataflow.minimize_accumulator_width import ( MinimizeAccumulatorWidth, ) @@ -356,8 +356,13 @@ def deploy_based_on_board(model, model_title, topology, wbits, abits, board): # driver.py and python libraries pynq_driver_dir = model.get_metadata_prop("pynq_driver_dir") - copytree(pynq_driver_dir, deployment_dir, dirs_exist_ok=True) - model.set_metadata_prop("pynq_deploy_dir", deployment_dir) + if not None: + copytree(pynq_driver_dir, deployment_dir, dirs_exist_ok=True) + model.set_metadata_prop("pynq_deploy_dir", deployment_dir) + else: + cpp_driver_dir = model.get_metadata_prop("cpp_driver_dir") + copytree(cpp_driver_dir, deployment_dir, dirs_exist_ok=True) + model.set_metadata_prop("cpp_deploy_dir", deployment_dir) # parameters that make up inputs to test case(s) @@ -811,14 +816,17 @@ def test_build(self, topology, wbits, abits, board): @pytest.mark.slow @pytest.mark.vivado @pytest.mark.vitis - def test_make_pynq_driver(self, topology, wbits, abits, board): + def test_make_driver(self, topology, wbits, abits, board): build_data = get_build_env(board, target_clk_ns) if build_data["kind"] == "alveo" and ("VITIS_PATH" not in os.environ): pytest.skip("VITIS_PATH not set") 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)) + if build_data["kind"] == "alveo" and topology == "tfc": + model = model.transform(MakeCPPDriver(board_to_driver_platform, version="latest")) + else: + model = model.transform(MakePYNQDriver(board_to_driver_platform)) model.save(get_checkpoint_name(board, topology, wbits, abits, "driver")) def test_deploy(self, topology, wbits, abits, board): From d131907c2bbf04ab40ee34f0dfd453536778cc75 Mon Sep 17 00:00:00 2001 From: auphelia Date: Mon, 26 May 2025 09:50:16 +0100 Subject: [PATCH 52/57] [Deps] Remove obsolete ending from gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index bc350d3aad..be61378730 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,6 @@ __pycache__/* .cache/* .*.swp *.ipynb_checkpoints* -*.sif # Project files .vscode From 398be021e954f5edc924addde48d318d92f7a54a Mon Sep 17 00:00:00 2001 From: auphelia Date: Mon, 26 May 2025 09:52:41 +0100 Subject: [PATCH 53/57] [Transform] Update copyright header --- src/finn/transformation/fpgadataflow/make_driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 92374a2849..c0c3a05087 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -1,4 +1,5 @@ -# Copyright (c) 2020, Xilinx +# Copyright (C) 2020, Xilinx, Inc. +# Copyright (C) 2025, Advanced Micro Devices, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without From 369f003de53e787044a0807ab735733c100b6aa2 Mon Sep 17 00:00:00 2001 From: auphelia Date: Mon, 26 May 2025 13:02:39 +0100 Subject: [PATCH 54/57] [Driver] Move util functions --- .../fpgadataflow/get_driver_shapes.py | 146 ------------------ .../fpgadataflow/make_driver.py | 22 +-- src/finn/util/data_packing.py | 115 +++++++++++++- 3 files changed, 114 insertions(+), 169 deletions(-) delete mode 100644 src/finn/transformation/fpgadataflow/get_driver_shapes.py diff --git a/src/finn/transformation/fpgadataflow/get_driver_shapes.py b/src/finn/transformation/fpgadataflow/get_driver_shapes.py deleted file mode 100644 index 0b66e579d3..0000000000 --- a/src/finn/transformation/fpgadataflow/get_driver_shapes.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright (C) 2025, 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 -from qonnx.core.modelwrapper import ModelWrapper -from qonnx.custom_op.registry import getCustomOp -from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple -from typing import Dict - -import finn.util.data_packing as dpk -from finn.util.data_packing import ( - hexstring2npbytearray, - pack_innermost_dim_as_hex_string, -) - - -def to_external_tensor(init, w_dtype): - """Return an appropriately formatted and packed numpy byte array for given - external parameter tensor.""" - - weight_width = init.shape[1] * w_dtype.bitwidth() - weight_width_padded = roundup_to_integer_multiple(weight_width, 4) - hex_init = pack_innermost_dim_as_hex_string(init, w_dtype, weight_width_padded, prefix="0x") - ext_weight = np.array([], dtype=np.uint8) - for line in hex_init: - array_line = [x for x in reversed(hexstring2npbytearray(line, remove_prefix="0x"))] - ext_weight = np.append(ext_weight, array_line) - - return ext_weight - - -def get_driver_shapes(model: ModelWrapper) -> Dict: - idt = [] - idma_names = [] - ishape_normal = [] - ishape_folded = [] - ishape_packed = [] - for idma_ind, graph_in in enumerate(model.graph.input): - i_tensor_name = graph_in.name - # get inp tensor properties - i_tensor_dt = model.get_tensor_datatype(i_tensor_name) - i_tensor_shape_normal = tuple(model.get_tensor_shape(i_tensor_name)) - # go down into dataflow partition to get folded shape info etc - # TODO consider setting these as attributes during dataflow partitioning - i_consumer = model.find_consumer(i_tensor_name) - assert ( - i_consumer.op_type == "StreamingDataflowPartition" - ), """ - Ensure CreateDataflowPartition called before driver creation.""" - first_df_model = ModelWrapper(getCustomOp(i_consumer).get_nodeattr("model")) - assert ( - first_df_model.graph.node[0].op_type == "IODMA_hls" - ), "First partition must hold input IODMA" - successors = model.find_direct_successors(i_consumer) - successor_input_num = list(successors[0].input).index(i_consumer.output[0]) - successor_sdp = getCustomOp(successors[0]) - successor_df_model = ModelWrapper(successor_sdp.get_nodeattr("model")) - first_node = successor_df_model.find_consumer( - successor_df_model.graph.input[successor_input_num].name - ) - i_tensor_shape_folded = tuple(getCustomOp(first_node).get_folded_input_shape()) - # generate dummy folded i/o tensors and their packed versions - i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded) - i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray(i_tensor_dummy_folded, i_tensor_dt) - i_tensor_shape_packed = i_tensor_dummy_packed.shape - # append all input tensor info to relevant lists - idt.append("DataType['%s']" % i_tensor_dt.name) - ishape_normal.append(i_tensor_shape_normal) - ishape_folded.append(i_tensor_shape_folded) - ishape_packed.append(i_tensor_shape_packed) - idma_names.append(getCustomOp(i_consumer).get_nodeattr("instance_name")) - - odt = [] - odma_names = [] - oshape_normal = [] - oshape_folded = [] - oshape_packed = [] - for odma_ind, graph_out in enumerate(model.graph.output): - o_tensor_name = graph_out.name - # get inp tensor properties - o_tensor_dt = model.get_tensor_datatype(o_tensor_name) - o_tensor_shape_normal = tuple(model.get_tensor_shape(o_tensor_name)) - # go down into IODMA partition to get folded shape info etc - # TODO consider setting these as attributes during dataflow partitioning - o_producer = model.find_producer(o_tensor_name) - assert ( - o_producer.op_type == "StreamingDataflowPartition" - ), """ - Ensure CreateDataflowPartition called before driver creation.""" - df_model = ModelWrapper(getCustomOp(o_producer).get_nodeattr("model")) - assert df_model.graph.node[-1].op_type == "IODMA_hls", "Partition must hold output IODMA" - predecessors = model.find_direct_predecessors(o_producer) - predecessor_output_num = list(predecessors[0].output).index(o_producer.input[0]) - predecessor_sdp = getCustomOp(predecessors[0]) - predecessor_df_model = ModelWrapper(predecessor_sdp.get_nodeattr("model")) - last_node = predecessor_df_model.find_producer( - predecessor_df_model.graph.output[predecessor_output_num].name - ) - o_tensor_shape_folded = tuple(getCustomOp(last_node).get_folded_output_shape()) - o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded) - o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray(o_tensor_dummy_folded, o_tensor_dt) - o_tensor_shape_packed = o_tensor_dummy_packed.shape - # append all output tensor info to relevant lists - odt.append("DataType['%s']" % o_tensor_dt.name) - oshape_normal.append(o_tensor_shape_normal) - oshape_folded.append(o_tensor_shape_folded) - oshape_packed.append(o_tensor_shape_packed) - odma_names.append(getCustomOp(o_producer).get_nodeattr("instance_name")) - - return { - "idt": idt, - "idma_names": idma_names, - "ishape_normal": ishape_normal, - "ishape_folded": ishape_folded, - "ishape_packed": ishape_packed, - "odt": odt, - "odma_names": odma_names, - "oshape_normal": oshape_normal, - "oshape_folded": oshape_folded, - "oshape_packed": oshape_packed, - } diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index c0c3a05087..84713346ba 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -38,36 +38,16 @@ from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp from qonnx.transformation.base import Transformation -from qonnx.util.basic import roundup_to_integer_multiple from string import Template from typing import Dict, Tuple import finn.util -from finn.transformation.fpgadataflow.get_driver_shapes import get_driver_shapes from finn.util.basic import make_build_dir -from finn.util.data_packing import ( - hexstring2npbytearray, - pack_innermost_dim_as_hex_string, -) +from finn.util.data_packing import get_driver_shapes, to_external_tensor from . import template_driver -def to_external_tensor(init, w_dtype): - """Return an appropriately formatted and packed numpy byte array for given - external parameter tensor.""" - - weight_width = init.shape[1] * w_dtype.bitwidth() - weight_width_padded = roundup_to_integer_multiple(weight_width, 4) - hex_init = pack_innermost_dim_as_hex_string(init, w_dtype, weight_width_padded, prefix="0x") - ext_weight = np.array([], dtype=np.uint8) - for line in hex_init: - array_line = [x for x in reversed(hexstring2npbytearray(line, remove_prefix="0x"))] - ext_weight = np.append(ext_weight, array_line) - - return ext_weight - - class MakeCPPDriver(Transformation): # TODO: Enable multiple input types! Now only assumes the first one def resolve_dt_name(s: str) -> str: diff --git a/src/finn/util/data_packing.py b/src/finn/util/data_packing.py index 6a72d38058..61773d29b4 100644 --- a/src/finn/util/data_packing.py +++ b/src/finn/util/data_packing.py @@ -1,4 +1,5 @@ -# Copyright (c) 2020 Xilinx, Inc. +# Copyright (C) 2020 Xilinx, Inc. +# Copyright (C) 2025, Advanced Micro Devices, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,7 +33,10 @@ import sys from bitstring import BitArray from qonnx.core.datatype import DataType -from qonnx.util.basic import roundup_to_integer_multiple +from qonnx.core.modelwrapper import ModelWrapper +from qonnx.custom_op.registry import getCustomOp +from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple +from typing import Dict def array2hexstring(array, dtype, pad_to_nbits, prefix="0x", reverse=False): @@ -449,3 +453,110 @@ def packed_bytearray_to_finnpy( ) return ret + + +def to_external_tensor(init, w_dtype): + """Return an appropriately formatted and packed numpy byte array for given + external parameter tensor.""" + + weight_width = init.shape[1] * w_dtype.bitwidth() + weight_width_padded = roundup_to_integer_multiple(weight_width, 4) + hex_init = pack_innermost_dim_as_hex_string(init, w_dtype, weight_width_padded, prefix="0x") + ext_weight = np.array([], dtype=np.uint8) + for line in hex_init: + array_line = [x for x in reversed(hexstring2npbytearray(line, remove_prefix="0x"))] + ext_weight = np.append(ext_weight, array_line) + + return ext_weight + + +def get_driver_shapes(model: ModelWrapper) -> Dict: + idt = [] + idma_names = [] + ishape_normal = [] + ishape_folded = [] + ishape_packed = [] + for idma_ind, graph_in in enumerate(model.graph.input): + i_tensor_name = graph_in.name + # get inp tensor properties + i_tensor_dt = model.get_tensor_datatype(i_tensor_name) + i_tensor_shape_normal = tuple(model.get_tensor_shape(i_tensor_name)) + # go down into dataflow partition to get folded shape info etc + # TODO consider setting these as attributes during dataflow partitioning + i_consumer = model.find_consumer(i_tensor_name) + assert ( + i_consumer.op_type == "StreamingDataflowPartition" + ), """ + Ensure CreateDataflowPartition called before driver creation.""" + first_df_model = ModelWrapper(getCustomOp(i_consumer).get_nodeattr("model")) + assert ( + first_df_model.graph.node[0].op_type == "IODMA_hls" + ), "First partition must hold input IODMA" + successors = model.find_direct_successors(i_consumer) + successor_input_num = list(successors[0].input).index(i_consumer.output[0]) + successor_sdp = getCustomOp(successors[0]) + successor_df_model = ModelWrapper(successor_sdp.get_nodeattr("model")) + first_node = successor_df_model.find_consumer( + successor_df_model.graph.input[successor_input_num].name + ) + i_tensor_shape_folded = tuple(getCustomOp(first_node).get_folded_input_shape()) + # generate dummy folded i/o tensors and their packed versions + i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded) + i_tensor_dummy_packed = finnpy_to_packed_bytearray(i_tensor_dummy_folded, i_tensor_dt) + i_tensor_shape_packed = i_tensor_dummy_packed.shape + # append all input tensor info to relevant lists + idt.append("DataType['%s']" % i_tensor_dt.name) + ishape_normal.append(i_tensor_shape_normal) + ishape_folded.append(i_tensor_shape_folded) + ishape_packed.append(i_tensor_shape_packed) + idma_names.append(getCustomOp(i_consumer).get_nodeattr("instance_name")) + + odt = [] + odma_names = [] + oshape_normal = [] + oshape_folded = [] + oshape_packed = [] + for odma_ind, graph_out in enumerate(model.graph.output): + o_tensor_name = graph_out.name + # get inp tensor properties + o_tensor_dt = model.get_tensor_datatype(o_tensor_name) + o_tensor_shape_normal = tuple(model.get_tensor_shape(o_tensor_name)) + # go down into IODMA partition to get folded shape info etc + # TODO consider setting these as attributes during dataflow partitioning + o_producer = model.find_producer(o_tensor_name) + assert ( + o_producer.op_type == "StreamingDataflowPartition" + ), """ + Ensure CreateDataflowPartition called before driver creation.""" + df_model = ModelWrapper(getCustomOp(o_producer).get_nodeattr("model")) + assert df_model.graph.node[-1].op_type == "IODMA_hls", "Partition must hold output IODMA" + predecessors = model.find_direct_predecessors(o_producer) + predecessor_output_num = list(predecessors[0].output).index(o_producer.input[0]) + predecessor_sdp = getCustomOp(predecessors[0]) + predecessor_df_model = ModelWrapper(predecessor_sdp.get_nodeattr("model")) + last_node = predecessor_df_model.find_producer( + predecessor_df_model.graph.output[predecessor_output_num].name + ) + o_tensor_shape_folded = tuple(getCustomOp(last_node).get_folded_output_shape()) + o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded) + o_tensor_dummy_packed = finnpy_to_packed_bytearray(o_tensor_dummy_folded, o_tensor_dt) + o_tensor_shape_packed = o_tensor_dummy_packed.shape + # append all output tensor info to relevant lists + odt.append("DataType['%s']" % o_tensor_dt.name) + oshape_normal.append(o_tensor_shape_normal) + oshape_folded.append(o_tensor_shape_folded) + oshape_packed.append(o_tensor_shape_packed) + odma_names.append(getCustomOp(o_producer).get_nodeattr("instance_name")) + + return { + "idt": idt, + "idma_names": idma_names, + "ishape_normal": ishape_normal, + "ishape_folded": ishape_folded, + "ishape_packed": ishape_packed, + "odt": odt, + "odma_names": odma_names, + "oshape_normal": oshape_normal, + "oshape_folded": oshape_folded, + "oshape_packed": oshape_packed, + } From 3949169c971ee77400b30534ab1c36089a3d81ba Mon Sep 17 00:00:00 2001 From: auphelia Date: Mon, 26 May 2025 14:31:17 +0100 Subject: [PATCH 55/57] [MakeDriver] Add docstring and check for platform --- .../fpgadataflow/make_driver.py | 49 ++++++------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index 84713346ba..dc0cd2a518 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -49,6 +49,17 @@ class MakeCPPDriver(Transformation): + """Create CPP code to correctly interface the generated + accelerator, including data packing/unpacking. Should be called + after conversion to HLS layers, folding and the creation of + dataflow partitions for correct operation. + platform: has to be "alveo", otherwise an error is thrown + Outcome if successful: sets the cpp_driver_dir attribute in the ONNX + ModelProto's metadata_props field, with the created driver dir as the + value. + runtime writeable weights not yet supported. + """ + # TODO: Enable multiple input types! Now only assumes the first one def resolve_dt_name(s: str) -> str: s = s.replace("DataType[", "").replace("]", "") @@ -72,6 +83,9 @@ def __init__( ): super().__init__() self.platform: str = platform + assert ( + platform == "alveo" + ), "CPP driver only supported for Alveo devices, please use PYNQ driver instead." self.version = version # Define variables for the repository URL and commit hash @@ -148,7 +162,7 @@ def run_command(command, cwd=None, debug=False): Required to read kernel names for driver config!" ) run_command( - f"xclbinutil -i {xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json", + f"xclbinutil -i {xclbin_path} --dump-section IP_LAYOUT:JSON:ip_layout.json --force", cwd=os.path.dirname(xclbin_path), ) ips = None @@ -211,39 +225,6 @@ def formatKernelName(kname: str): with open(json_path, "w+") as f: f.write(json.dumps(data, indent=4)) - # TODO: Generating weight files - # weights_dir = output_dir + "/runtime_weights" - - # os.makedirs(weights_dir) - # idma_idx = 0 - # ext_weight_dma_cnt = 0 - - # for node in model.graph.node: - # assert ( - # node.op_type == "StreamingDataflowPartition" - # ), "CreateDataflowPartition needs to be applied before driver generation" - - # if len(node.input) > 0: - # producer = model.find_producer(node.input[0]) - # init_tensor = model.get_initializer(node.input[0]) - # else: - # producer = None - # init_tensor = None - - # if producer is None: # input dma? - # sdp_inst = getCustomOp(node) - # idma_name = sdp_inst.get_nodeattr("instance_name") - # df_model = ModelWrapper(sdp_inst.get_nodeattr("model")) - # assert df_model.graph.node[0].op_type == "IODMA" - # iodma_node = getCustomOp(df_model.graph.node[0]) - # if iodma_node.get_nodeattr("burstMode") == "wrap": # input weights dma? - # init_tensor = df_model.get_initializer(iodma_node.onnx_node.input[0]) - # ext_weight_dma_cnt += 1 - # w_dtype = df_model.get_tensor_datatype(iodma_node.onnx_node.input[0]) - # init_external_tensor = to_external_tensor(init_tensor, w_dtype) - # np.save(weights_dir + "/" + idma_name + ".npy", init_external_tensor) - # idma_idx += 1 - return (model, False) From ced9f8283340937b92000cccf0dcc738b6426e96 Mon Sep 17 00:00:00 2001 From: auphelia Date: Mon, 26 May 2025 14:54:14 +0100 Subject: [PATCH 56/57] [NBs] Change last occurrences of pynq driver import --- docs/finn/hw_build.rst | 2 +- docs/finn/source_code/finn.transformation.fpgadataflow.rst | 2 +- notebooks/advanced/4_advanced_builder_settings.ipynb | 4 ++-- notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb | 2 +- notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/finn/hw_build.rst b/docs/finn/hw_build.rst index 39c39eb7df..5e1f67e9f2 100644 --- a/docs/finn/hw_build.rst +++ b/docs/finn/hw_build.rst @@ -35,7 +35,7 @@ To rapidly test the generated design on PYNQ platforms, FINN is capable of generating a Python driver for the given design. This driver packs/unpacks the input/output tensors in the expected format, then uses PYNQ APIs to initiate data movement and transfer back the results to the host CPU. The generation of -the driver is done by transformation pass :py:mod:`finn.transformation.fpgadataflow.make_pynq_driver.MakePYNQDriver`. +the driver is done by transformation pass :py:mod:`finn.transformation.fpgadataflow.make_driver.MakePYNQDriver`. DMA and DWC Node Insertion --------------------------- diff --git a/docs/finn/source_code/finn.transformation.fpgadataflow.rst b/docs/finn/source_code/finn.transformation.fpgadataflow.rst index f56b5fcf01..d72dd467a2 100644 --- a/docs/finn/source_code/finn.transformation.fpgadataflow.rst +++ b/docs/finn/source_code/finn.transformation.fpgadataflow.rst @@ -149,7 +149,7 @@ finn.transformation.fpgadataflow.insert\_tlastmarker finn.transformation.fpgadataflow.make\_pynq\_driver ---------------------------------------------------------- -.. automodule:: finn.transformation.fpgadataflow.make_pynq_driver +.. automodule:: finn.transformation.fpgadataflow.make_driver :members: :undoc-members: :show-inheritance: diff --git a/notebooks/advanced/4_advanced_builder_settings.ipynb b/notebooks/advanced/4_advanced_builder_settings.ipynb index e9e37459c8..db85d59552 100644 --- a/notebooks/advanced/4_advanced_builder_settings.ipynb +++ b/notebooks/advanced/4_advanced_builder_settings.ipynb @@ -1565,7 +1565,7 @@ "metadata": {}, "source": [ "You can see that after the generation of the estimate reports, the code generation and the ip generation is invoked (`step_hw_codegen` and `step_hw_ipgen`). The FIFO depths are determined and the FIFOs are inserted in the network (`step_set_fifo_depths`), we can then create an IP design of our whole network by stitching the IPs from each layer together (`step_create_stitched_ip`). At this point we have an implementation of the neural network that we can integrate within a bigger FPGA design, we can run performance measurements using simulation (`step_measure_rtlsim_performance`) and out-of-context synthesis (`step_out_of_context_synthesis`) for it.\n", - "The FINN builder also provides automatic system integration for Zynq and Alveo devices, this can be invoked by running `step_synthesize_bitfile`, `step_make_pynq_driver` and `step_deployment_package`." + "The FINN builder also provides automatic system integration for Zynq and Alveo devices, this can be invoked by running `step_synthesize_bitfile`, `step_make_driver` and `step_deployment_package`." ] }, { @@ -1782,7 +1782,7 @@ " \"step_measure_rtlsim_performance\",\n", " \"step_out_of_context_synthesis\",\n", " \"step_synthesize_bitfile\",\n", - " \"step_make_pynq_driver\",\n", + " \"step_make_driver\",\n", " \"step_deployment_package\",\n", "]\n", "\n", diff --git a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb index 55e6f9b7aa..2b01f24557 100644 --- a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb @@ -456,7 +456,7 @@ "metadata": {}, "outputs": [], "source": [ - "from finn.transformation.fpgadataflow.make_pynq_driver import MakePYNQDriver\n", + "from finn.transformation.fpgadataflow.make_driver import MakePYNQDriver\n", "model = model.transform(MakePYNQDriver(\"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 974e513f58..b0510b0fdb 100644 --- a/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb @@ -751,7 +751,7 @@ "metadata": {}, "outputs": [], "source": [ - "from finn.transformation.fpgadataflow.make_pynq_driver import MakePYNQDriver\n", + "from finn.transformation.fpgadataflow.make_driver import MakePYNQDriver\n", "model = model.transform(MakePYNQDriver(\"zynq-iodma\"))" ] }, From 52e230cd2d09c2d6eb33f38563a094049aba8825 Mon Sep 17 00:00:00 2001 From: auphelia Date: Mon, 26 May 2025 15:20:28 +0100 Subject: [PATCH 57/57] [Docs] Change make_pynq_driver to make_driver --- docs/finn/command_line.rst | 2 +- tutorials/fpga_flow/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/finn/command_line.rst b/docs/finn/command_line.rst index 07fb3b094c..9dced6255d 100644 --- a/docs/finn/command_line.rst +++ b/docs/finn/command_line.rst @@ -81,7 +81,7 @@ as it goes through numerous steps: Running step: step_measure_rtlsim_performance [15/19] Running step: step_out_of_context_synthesis [16/19] Running step: step_synthesize_bitfile [17/19] - Running step: step_make_pynq_driver [18/19] + Running step: step_make_driver [18/19] Running step: step_deployment_package [19/19] diff --git a/tutorials/fpga_flow/README.md b/tutorials/fpga_flow/README.md index 71f2a2a625..1fcda7f265 100644 --- a/tutorials/fpga_flow/README.md +++ b/tutorials/fpga_flow/README.md @@ -57,7 +57,7 @@ The build should finish in about 10 minutes, and the FINN docker will close on s Running step: step_measure_rtlsim_performance [13/18] Running step: step_out_of_context_synthesis [14/18] Running step: step_synthesize_bitfile [15/18] - Running step: step_make_pynq_driver [16/18] + Running step: step_make_driver [16/18] Running step: step_deployment_package [17/18] Running step: custom_step_gen_tb_and_io [18/18] Completed successfully