diff --git a/notebooks/9-FINN-EndToEndFlow.ipynb b/notebooks/9-FINN-EndToEndFlow.ipynb
index 48a87d2bdeab38f32fcf376c7398684abbbf11de..73049e0967c10c107bdd1f374013cdd12df9e567 100644
--- a/notebooks/9-FINN-EndToEndFlow.ipynb
+++ b/notebooks/9-FINN-EndToEndFlow.ipynb
@@ -13,7 +13,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -82,7 +82,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": 2,
    "metadata": {},
    "outputs": [
     {
@@ -116,7 +116,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [
     {
@@ -134,7 +134,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
@@ -262,7 +262,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 24,
    "metadata": {},
    "outputs": [
     {
@@ -282,7 +282,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 25,
    "metadata": {},
    "outputs": [
     {
@@ -315,7 +315,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [
     {
@@ -367,16 +367,18 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "\n",
-      "Stopping http://0.0.0.0:8081\n",
-      "Serving 'lfc_w1_a1.onnx' at http://0.0.0.0:8081\n"
+     "ename": "NameError",
+     "evalue": "name 'netron' is not defined",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
+      "\u001b[0;32m<ipython-input-7-9a67441e65ab>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mStreamline\u001b[0m\u001b[0;34m(\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[1;32m      2\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msave\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"lfc_w1_a1.onnx\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mnetron\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"lfc_w1_a1.onnx\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mport\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m8081\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhost\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"0.0.0.0\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+      "\u001b[0;31mNameError\u001b[0m: name 'netron' is not defined"
      ]
     }
    ],
@@ -388,7 +390,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 28,
    "metadata": {},
    "outputs": [
     {
@@ -421,7 +423,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 8,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -447,16 +449,18 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 9,
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "\n",
-      "Stopping http://0.0.0.0:8081\n",
-      "Serving 'lfc_w1_a1.onnx' at http://0.0.0.0:8081\n"
+     "ename": "NameError",
+     "evalue": "name 'netron' is not defined",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
+      "\u001b[0;32m<ipython-input-9-c6c083e4118c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mto_hls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mInferBinaryStreamingFCLayer\u001b[0m\u001b[0;34m(\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[1;32m      3\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msave\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"lfc_w1_a1.onnx\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mnetron\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"lfc_w1_a1.onnx\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mport\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m8081\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhost\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"0.0.0.0\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+      "\u001b[0;31mNameError\u001b[0m: name 'netron' is not defined"
      ]
     }
    ],
@@ -469,7 +473,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 31,
    "metadata": {
     "scrolled": true
    },
@@ -511,7 +515,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 10,
    "metadata": {},
    "outputs": [
     {
@@ -540,37 +544,87 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Now we can use the [HLSCustomOp](https://github.com/Xilinx/finn/blob/dev/src/finn/custom_op/fpgadataflow/__init__.py) class to create a [StreamingFCLayer_Batch](https://github.com/Xilinx/finn/blob/dev/src/finn/custom_op/fpgadataflow/streamingfclayer_batch.py) object for each node to set PE and SIMD. This procedure is identical for each node. For more details about custom ops, see Jupyter notebook [7-FINN-CustomOps](7-FINN-CustomOps.ipynb)."
+    "We can use the higher-level [HLSCustomOp](https://github.com/Xilinx/finn/blob/dev/src/finn/custom_op/fpgadataflow/__init__.py) wrappers for these nodes. These wrappers provide easy access to specific properties of these nodes, such as the folding factors (PE and SIMD). For more details about custom ops, see Jupyter notebook [7-FINN-CustomOps](7-FINN-CustomOps.ipynb). Let's have a look at which node attributes are defined by the CustomOp wrapper, and adjust the SIMD and PE attributes."
    ]
   },
   {
    "cell_type": "code",
    "execution_count": 15,
    "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CustomOp wrapper is of class StreamingFCLayer_Batch\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "{'PE': ('i', True, 0),\n",
+       " 'SIMD': ('i', True, 0),\n",
+       " 'MW': ('i', True, 0),\n",
+       " 'MH': ('i', True, 0),\n",
+       " 'resType': ('s', True, ''),\n",
+       " 'ActVal': ('i', False, 0),\n",
+       " 'inputDataType': ('s', True, ''),\n",
+       " 'weightDataType': ('s', True, ''),\n",
+       " 'outputDataType': ('s', True, ''),\n",
+       " 'binaryXnorMode': ('i', False, 0),\n",
+       " 'noActivation': ('i', False, 0),\n",
+       " 'backend': ('s', True, 'fpgadataflow'),\n",
+       " 'code_gen_dir_npysim': ('s', False, ''),\n",
+       " 'code_gen_dir_ipgen': ('s', False, ''),\n",
+       " 'executable_path': ('s', False, ''),\n",
+       " 'ipgen_path': ('s', False, ''),\n",
+       " 'sim_mode': ('s', False, ''),\n",
+       " 'sim_cycles': ('i', False, 0)}"
+      ]
+     },
+     "execution_count": 15,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from finn.custom_op.registry import getCustomOp\n",
+    "\n",
+    "fc0w = getCustomOp(fc0)\n",
+    "fc1w = getCustomOp(fc1)\n",
+    "fc2w = getCustomOp(fc2)\n",
+    "fc3w = getCustomOp(fc3)\n",
+    "\n",
+    "print(\"CustomOp wrapper is of class \" + fc0w.__class__.__name__)\n",
+    "\n",
+    "fc0w.get_nodeattr_types()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
    "outputs": [],
    "source": [
-    "from finn.custom_op.fpgadataflow.streamingfclayer_batch import StreamingFCLayer_Batch\n",
+    "# SIMD controls the folding over the input vector\n",
+    "# PE controls the folding over the output vector\n",
     "\n",
-    "fc0w = StreamingFCLayer_Batch(fc0)\n",
     "fc0w.set_nodeattr(\"SIMD\", 16)\n",
     "fc0w.set_nodeattr(\"PE\", 32)\n",
     "\n",
-    "fc1w = StreamingFCLayer_Batch(fc1)\n",
-    "fc1w.set_nodeattr(\"SIMD\", 16)\n",
+    "fc1w.set_nodeattr(\"SIMD\", 32)\n",
     "fc1w.set_nodeattr(\"PE\", 32)\n",
     "\n",
-    "fc2w = StreamingFCLayer_Batch(fc2)\n",
-    "fc2w.set_nodeattr(\"SIMD\", 16)\n",
+    "fc2w.set_nodeattr(\"SIMD\", 32)\n",
     "fc2w.set_nodeattr(\"PE\", 32)\n",
     "\n",
-    "fc3w = StreamingFCLayer_Batch(fc3)\n",
-    "fc3w.set_nodeattr(\"SIMD\", 16)\n",
-    "fc3w.set_nodeattr(\"PE\", 10)\n"
+    "fc3w.set_nodeattr(\"SIMD\", 32)\n",
+    "fc3w.set_nodeattr(\"PE\", 10)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
+   "execution_count": 18,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -590,17 +644,31 @@
    "metadata": {},
    "source": [
     "## 3. Vivado HLS and Vivado synthesis <a id='vivado'></a>\n",
-    "* [HLS IP per layer](#hls_per_layer)\n",
+    "* [Generating HLS Code](#hls_per_layer)\n",
+    "* [Synthesizing HLS to IP Blocks](#hls_synth)\n",
     "* [Creation of stitched design](#stitched_design)\n",
     "* [PYNQ shell project](#pynq_shell)\n",
-    "* [Synthesis, place and route](#synth_pl_ro)"
+    "* [Synthesis, place and route](#synth_pl_ro)\n",
+    "\n",
+    "As we will be performing FPGA synthesis in these tasks, we'll define two helper variables that describe the Xilinx FPGA part name and the PYNQ board name that we are targeting."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "fpga_part = \"xczu3eg-sbva484-1-e\"\n",
+    "pynq_board = \"Ultra96\"\n",
+    "target_clk_ns = 5"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### HLS IP per layer <a id='hls_per_layer'></a>\n",
+    "### Generating HLS Code <a id='hls_per_layer'></a>\n",
     "This section deals with the generation of an IP block from the different layers. These can then be stitched to a block design that corresponds to the complete model. The single conversion into IP blocks allows a good transparency and we can check the functionality of each IP block and compare it with the behaviour of the corresponding ONNX node. The emulation of such an IP block is performed with PyVerilator and is described in detail in section [Emulation (rtlsim) using Pyverilator](#rtlsim)."
    ]
   },
@@ -609,41 +677,125 @@
    "metadata": {},
    "source": [
     "Two transformations are required to generate HLS IP blocks for each layer: \n",
-    "* `CodeGen_ipgen` which generates the C++ code for the node and a tcl-script which starts the HLS synthesis and exports the design as IP. \n",
-    "* `HLSSynth_IPGen` which passes the tcl-script to Vivado and thus performs the actual IP generation. \n",
+    "* `CodeGen_ipgen` which generates the HLS C++ code for the node and a tcl-script which starts the HLS synthesis and exports the design as IP. \n",
+    "* `HLSSynth_IPGen` which passes the tcl-script to Vivado HLS and thus performs the actual IP generation. \n",
     "\n",
-    "First the basic transformation `GiveUniqueNodeNames` is applied and then the two transformations necessary for the IP block creation are performed. This will take some time as Vivado is called and for each StreamingFCLayer_Batch node in the design HLS synthesis is performed and the node is exported as IP block. `CodeGen_ipgen` gets as arguments an FPGA part as string and the clock in ns as integer."
+    "We start off by giving unique node names using the basic transformation `GiveUniqueNodeNames`, and then proceed with the HLS C++ code generation with `CodeGen_ipgen`."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 21,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "model = model.transform(GiveUniqueNodeNames())\n",
+    "\n",
+    "from finn.transformation.fpgadataflow.codegen_ipgen import CodeGen_ipgen\n",
+    "model = model.transform(CodeGen_ipgen(fpga_part, target_clk_ns))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Each `fpgadataflow` node will have its own code generation directory, which we can examine as follows:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
    "metadata": {},
    "outputs": [
     {
-     "ename": "KeyboardInterrupt",
-     "evalue": "",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
-      "\u001b[0;32m<ipython-input-17-8f990c5295da>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      6\u001b[0m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mCodeGen_ipgen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"xc7z020clg400-1\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5\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[0;32m----> 7\u001b[0;31m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mHLSSynth_IPGen\u001b[0m\u001b[0;34m(\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",
-      "\u001b[0;32m/workspace/finn/src/finn/core/modelwrapper.py\u001b[0m in \u001b[0;36mtransform\u001b[0;34m(self, transformation, make_deepcopy)\u001b[0m\n\u001b[1;32m     66\u001b[0m         \u001b[0;32mwhile\u001b[0m \u001b[0mmodel_was_changed\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     67\u001b[0m             (transformed_model, model_was_changed) = transformation.apply(\n\u001b[0;32m---> 68\u001b[0;31m                 \u001b[0mtransformed_model\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     69\u001b[0m             )\n\u001b[1;32m     70\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0mtransformed_model\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/workspace/finn/src/finn/transformation/fpgadataflow/hlssynth_ipgen.py\u001b[0m in \u001b[0;36mapply\u001b[0;34m(self, model)\u001b[0m\n\u001b[1;32m     25\u001b[0m                         \u001b[0;32massert\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_nodeattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"code_gen_dir_ipgen\"\u001b[0m\u001b[0;34m)\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[1;32m     26\u001b[0m                         \u001b[0;31m# call the compilation function for this node\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 27\u001b[0;31m                         \u001b[0minst\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mipgen_singlenode_code\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\u001b[1;32m     28\u001b[0m                         \u001b[0;31m# ensure that executable path is now set\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     29\u001b[0m                         \u001b[0;32massert\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_nodeattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"ipgen_path\"\u001b[0m\u001b[0;34m)\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[0;32m/workspace/finn/src/finn/custom_op/fpgadataflow/__init__.py\u001b[0m in \u001b[0;36mipgen_singlenode_code\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    100\u001b[0m         \u001b[0mbuilder\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend_tcl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcode_gen_dir\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\"/hls_syn_{}.tcl\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnode\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\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[1;32m    101\u001b[0m         \u001b[0mbuilder\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_ipgen_path\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcode_gen_dir\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\"/project_{}\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnode\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\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[0;32m--> 102\u001b[0;31m         \u001b[0mbuilder\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuild\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcode_gen_dir\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    103\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_nodeattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"ipgen_path\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbuilder\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mipgen_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    104\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/workspace/finn/src/finn/core/utils.py\u001b[0m in \u001b[0;36mbuild\u001b[0;34m(self, code_gen_dir)\u001b[0m\n\u001b[1;32m    320\u001b[0m         \u001b[0mbash_command\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m\"bash\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mipgen_script\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    321\u001b[0m         \u001b[0mprocess_compile\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msubprocess\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbash_command\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstdout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0msubprocess\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPIPE\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 322\u001b[0;31m         \u001b[0mprocess_compile\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcommunicate\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",
-      "\u001b[0;32m/opt/conda/lib/python3.6/subprocess.py\u001b[0m in \u001b[0;36mcommunicate\u001b[0;34m(self, input, timeout)\u001b[0m\n\u001b[1;32m    848\u001b[0m                 \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_stdin_write\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minput\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    849\u001b[0m             \u001b[0;32melif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstdout\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 850\u001b[0;31m                 \u001b[0mstdout\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstdout\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\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\u001b[1;32m    851\u001b[0m                 \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstdout\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\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[1;32m    852\u001b[0m             \u001b[0;32melif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstderr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "hls_syn_StreamingFCLayer_Batch_0.tcl  thresh.h\r\n",
+      "params.h\t\t\t      top_StreamingFCLayer_Batch_0.cpp\r\n"
      ]
     }
    ],
    "source": [
-    "model = model.transform(GiveUniqueNodeNames())\n",
+    "fc0w = getCustomOp(model.graph.node[2])\n",
+    "codegen_dir = fc0w.get_nodeattr(\"code_gen_dir_ipgen\")\n",
+    "! ls {codegen_dir}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can see the various generated files. In particular, the `top*.cpp` will contain the Vivado HLS function call that instantiates the correct `finn-hlslib` library component for this node:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\r\n",
+      "#include \"bnn-library.h\"\r\n",
+      "// includes for network parameters\r\n",
+      "#include \"weights.hpp\"\r\n",
+      "#include \"activations.hpp\"\r\n",
+      "#include \"params.h\"\r\n",
+      "#include \"thresh.h\"\r\n",
+      "\r\n",
+      "// defines for network parameters\r\n",
+      "#define MW1 784\r\n",
+      " #define MH1 1024\r\n",
+      " #define SIMD1 16\r\n",
+      "\r\n",
+      "            #define PE1 32\r\n",
+      " #define WMEM1 1568\r\n",
+      " #define TMEM1 32\r\n",
+      "\r\n",
+      "            #define numReps 1\r\n",
+      "#define PRAGMA_SUB(x) _Pragma (#x)\r\n",
+      "#define DO_PRAGMA(x) PRAGMA_SUB(x)\r\n",
+      "\r\n",
+      "void StreamingFCLayer_Batch_0(hls::stream<ap_uint<16>> &in0,\r\n",
+      "                hls::stream<ap_uint<32>> &out\r\n",
+      "                )\r\n",
+      "{\r\n",
+      "#pragma HLS INTERFACE axis port=in0\r\n",
+      "#pragma HLS INTERFACE axis port=out\r\n",
+      "#pragma HLS INTERFACE ap_ctrl_none port=return\r\n",
+      "DO_PRAGMA(HLS ARRAY_PARTITION variable=weights.m_weights complete dim=1)\r\n",
+      "DO_PRAGMA(HLS ARRAY_PARTITION variable=threshs.m_thresholds complete dim=1)\r\n",
+      "DO_PRAGMA(HLS ARRAY_PARTITION variable=threshs.m_thresholds complete dim=3)\r\n",
+      "StreamingFCLayer_Batch<MW1, MH1, SIMD1, PE1, Recast<XnorMul>, Identity, Identity>\r\n",
+      "            (in0, out, weights, threshs, numReps, ap_resource_lut());\r\n",
+      "}\r\n"
+     ]
+    }
+   ],
+   "source": [
+    "! cat {codegen_dir}/top_StreamingFCLayer_Batch_0.cpp"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Synthesizing HLS to IP Blocks <a id='hls_synth'></a>\n",
     "\n",
-    "from finn.transformation.fpgadataflow.codegen_ipgen import CodeGen_ipgen\n",
+    "Now that we have generated the HLS code for each layer, we can call the `HLSSynth_IPGen` transformation to convert the generated HLS into Vivado IP blocks. As this involves calling HLS synthesis, this transformation will run for some time."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
     "from finn.transformation.fpgadataflow.hlssynth_ipgen import HLSSynth_IPGen\n",
     "\n",
-    "model = model.transform(CodeGen_ipgen(\"xc7z020clg400-1\", 5))\n",
     "model = model.transform(HLSSynth_IPGen())"
    ]
   },