diff --git a/Cortex-M0/nanosoc/systems/mcu/fpga_imp/pynq_export/pz104/jupyter_notebooks/soclabs/nanosoc-iotest.ipynb b/Cortex-M0/nanosoc/systems/mcu/fpga_imp/pynq_export/pz104/jupyter_notebooks/soclabs/nanosoc-iotest.ipynb
new file mode 100755
index 0000000000000000000000000000000000000000..15a9d8985625cada4d7dfb655cf4cd17ba07fc2b
--- /dev/null
+++ b/Cortex-M0/nanosoc/systems/mcu/fpga_imp/pynq_export/pz104/jupyter_notebooks/soclabs/nanosoc-iotest.ipynb
@@ -0,0 +1,708 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Downloading Overlays\n",
+    "\n",
+    "This notebook demonstrates how to download an FPGA overlay and examine programmable logic state.  \n",
+    "\n",
+    "## 1. Instantiating an overlay\n",
+    "With the following overlay bundle present in the `overlays` folder, users can instantiate the overlay easily.\n",
+    "\n",
+    "*  A bitstream file (\\*.bit).\n",
+    "*  An hwh file (\\*.hwh).\n",
+    "*  A python class (\\*.py).\n",
+    "\n",
+    "For example, an overlay called `base` can be loaded by:\n",
+    "```python\n",
+    "from pynq.overlays.base import BaseOverlay\n",
+    "overlay = BaseOverlay(\"base.bit\")\n",
+    "```\n",
+    "Users can also use the absolute file path of the bitstream to instantiate the overlay.\n",
+    "\n",
+    "In this notebook, we get the current bitstream loaded on PL, and try to download it multiple times."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/javascript": [
+       "\n",
+       "try {\n",
+       "require(['notebook/js/codecell'], function(codecell) {\n",
+       "  codecell.CodeCell.options_default.highlight_modes[\n",
+       "      'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n",
+       "  Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n",
+       "      Jupyter.notebook.get_cells().map(function(cell){\n",
+       "          if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n",
+       "  });\n",
+       "});\n",
+       "} catch (e) {};\n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/javascript": [
+       "\n",
+       "try {\n",
+       "require(['notebook/js/codecell'], function(codecell) {\n",
+       "  codecell.CodeCell.options_default.highlight_modes[\n",
+       "      'magic_text/x-csrc'] = {'reg':[/^%%pybind11/]};\n",
+       "  Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n",
+       "      Jupyter.notebook.get_cells().map(function(cell){\n",
+       "          if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n",
+       "  });\n",
+       "});\n",
+       "} catch (e) {};\n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "import os, warnings\n",
+    "from pynq import PL\n",
+    "from pynq import Overlay\n",
+    "\n",
+    "ol = Overlay(\"/home/xilinx/pynq/overlays/soclabs/design_1.bit\")\n",
+    "\n",
+    "if not os.path.exists(PL.bitfile_name):\n",
+    "    warnings.warn('There is no overlay loaded after boot.', UserWarning)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "**Note**: If you see a warning message in the above cell, it means that no overlay\n",
+    "has been loaded after boot, hence the PL server is not aware of the \n",
+    "current status of the PL. In that case you won't be able to run this notebook\n",
+    "until you manually load an overlay at least once using:\n",
+    "\n",
+    "```python\n",
+    "from pynq import Overlay\n",
+    "ol = Overlay('your_overlay.bit')\n",
+    "```\n",
+    "\n",
+    "If you do not see any warning message, you can safely proceed."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ol = Overlay(PL.bitfile_name)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now we can check the download timestamp for this overlay."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'2023/2/20 17:44:40 +479858'"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "ol.download()\n",
+    "ol.timestamp"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pynq import Overlay\n",
+    "from pynq import MMIO\n",
+    "import time\n",
+    "from time import sleep, time\n",
+    "\n",
+    "#ADP_address = 0x80020000 # nanosoc FT1248 com channel\n",
+    "#ADP_address = 0x80030000 # streamio TX/RX loopback (unbuffered)\n",
+    "#ADP_address = 0x80040000 # streamio TX/RX loopback (FIFO buffered)\n",
+    "ADP_address  = 0x80050000 # local test ADP controller COM channel\n",
+    "UART2_address = 0x80060000 # nanosoc UART2 com serial (9600 baud?)\n",
+    "#ADP_address = 0x80070000 # uart TX/RX loopback\n",
+    "\n",
+    "# HARDWARE CONSTANTS\n",
+    "RX_FIFO = 0x00\n",
+    "TX_FIFO = 0x04\n",
+    "# Status Reg\n",
+    "STAT_REG = 0x08\n",
+    "RX_VALID = 0\n",
+    "RX_FULL = 1\n",
+    "TX_EMPTY = 2\n",
+    "TX_FULL = 3\n",
+    "IS_INTR = 4\n",
+    "\n",
+    "# Ctrl Reg\n",
+    "CTRL_REG = 0x0C\n",
+    "RST_TX = 0\n",
+    "RST_RX = 1\n",
+    "INTR_EN = 4\n",
+    "\n",
+    "class ADPIO:\n",
+    "    def __init__(self, address):\n",
+    "        # Setup axi core\n",
+    "        self.uart = MMIO(address, 0x10000, debug=False)\n",
+    "        self.address = address\n",
+    "\n",
+    "    def setupCtrlReg(self):\n",
+    "#        # Reset FIFOs, disable interrupts\n",
+    "#        self.uart.write(CTRL_REG, 1 << RST_TX | 1 << RST_RX)\n",
+    "#        sleep(1)\n",
+    "        self.uart.write(CTRL_REG, 0)\n",
+    "        sleep(1)\n",
+    "        self.uart.write(TX_FIFO, 0x1b)\n",
+    "        sleep(1)\n",
+    "\n",
+    "    def read(self, count, timeout=1):\n",
+    "        # status = currentStatus(uart) bad idea\n",
+    "        buf = \"\"\n",
+    "        stop_time = time() + timeout\n",
+    "        for i in range(count):\n",
+    "            # Wait till RX fifo has valid data, stop waiting if timeoutpasses\n",
+    "            while (not (self.uart.read(STAT_REG) & 1 << RX_VALID)) and (time() < stop_time):\n",
+    "                pass\n",
+    "            if time() >= stop_time:\n",
+    "                break\n",
+    "            buf += chr(self.uart.read(RX_FIFO))\n",
+    "        return buf\n",
+    "    \n",
+    "    def write(self, buf, timeout=1):\n",
+    "        # Write bytes via UART\n",
+    "        stop_time = time() + timeout\n",
+    "        wr_count = 0\n",
+    "        for i in buf:\n",
+    "            # Wait while TX FIFO is Full, stop waiting if timeout passes\n",
+    "            while (self.uart.read(STAT_REG) & 1 << TX_FULL) and (time() < stop_time):\n",
+    "                pass\n",
+    "            # Check timeout\n",
+    "            if time() > stop_time:\n",
+    "                wr_count = -1\n",
+    "                break\n",
+    "            self.uart.write(TX_FIFO, ord(i))\n",
+    "            wr_count += 1\n",
+    "        return wr_count\n",
+    "\n",
+    "    "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Inspect the ADP banner after reset (0x50CLAB02 expected)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "' 0x50c1ab02\\n\\r\\n\\r]'"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp = ADPIO(ADP_address)\n",
+    "# Setup AXI UART register\n",
+    "adp.setupCtrlReg()\n",
+    "adp.read(20)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Also check the UART2 RX channel for any boot message"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "''"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "uart = ADPIO(UART2_address)\n",
+    "# Setup AXI UART register\n",
+    "uart.setupCtrlReg()\n",
+    "uart.read(50)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now check adp Address pointer reets to zero"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'A 0x00000000\\n\\r]'"
+      ]
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('A\\n')\n",
+    "adp.read(20)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "And do a couple of 32-bit reads"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'R 0x00000000\\n\\r]'"
+      ]
+     },
+     "execution_count": 10,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('R\\n')\n",
+    "adp.read(15)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'R 0x00000000\\n\\r]'"
+      ]
+     },
+     "execution_count": 11,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('R\\n')\n",
+    "adp.read(15)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Verify the address pointer has auto-incremented two words"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'A 0x00000008\\n\\r]'"
+      ]
+     },
+     "execution_count": 12,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('A\\n')\n",
+    "adp.read(15)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now set adp address to high memory and write a couple of patterns"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'A 0xc0000000\\n\\r]'"
+      ]
+     },
+     "execution_count": 13,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('A c0000000\\n')\n",
+    "adp.read(20)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'W 0x11111111\\n\\r]'"
+      ]
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('W 111111111\\n')\n",
+    "adp.read(15)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'W 0x22222222\\n\\r]'"
+      ]
+     },
+     "execution_count": 15,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('W 22222222\\n')\n",
+    "adp.read(15)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Reset the address pointer and verify same patterns read back"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'A 0xc0000000\\n\\r]'"
+      ]
+     },
+     "execution_count": 16,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('A c0000000\\n')\n",
+    "adp.read(20)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'R 0x11111111\\n\\r]'"
+      ]
+     },
+     "execution_count": 17,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('R\\n')\n",
+    "adp.read(15)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'R 0x22222222\\n\\r]'"
+      ]
+     },
+     "execution_count": 18,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "adp.write('R\\n')\n",
+    "adp.read(15)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "\n",
+    "## ol.download()\n",
+    "ol.timestamp\n",
+    "ol.reset()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ip_info = {'trace_cntrl':\"ft1248tb_i/ila_0\"}\n",
+    "class pynq.logictools.trace_analyzer.TraceAnalyzer(ip_info)\n",
+    "\n",
+    "                "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "#PL.ip_dict"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gpio_0.register_map\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 2. Examining the PL state\n",
+    "\n",
+    "While there can be multiple overlay instances in Python, there is only one bitstream that is currently loaded onto the programmable logic (PL). \n",
+    "\n",
+    "This bitstream state is held in the singleton class, PL, and is available for user queries."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "PL.bitfile_name"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "PL.timestamp\n",
+    "\n",
+    "PL.ip_dict\n",
+    "\n",
+    "leds_address = PL.ip_dict['led_4bits']['phys_addr']\n",
+    "                                               \n",
+    "from pynq import MMIO\n",
+    "mmio_buttons = MMIO(0xa0000000, 16)\n",
+    "help (mmio_buttons)\n",
+    "\n",
+    "#print(hex(mmio_buttons))\n",
+    "#print(hex(mmio_buttons.read(0)))\n",
+    "\n",
+    "hex(pl.ip_dict[\"axi_gpio_1\"][\"phys_addr\"])\n",
+    "\n",
+    "\n",
+    "#buttons_address = ssc_dpram.ip_dict['push_button_4bits']['phys_addr']\n",
+    "#switches_address = ssc_dpram.ip_dict['dip_switch_4bits']['phys_addr']\n",
+    "#leds_address = ssc_dpram.ip_dict['led_4bitss']['phys_addr']"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Users can verify whether an overlay instance is currently loaded using the Overlay is_loaded() method"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ol.is_loaded()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 3. Overlay downloading overhead\n",
+    "\n",
+    "Finally, using Python, we can see the bitstream download time over 50 downloads.  "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import time\n",
+    "import matplotlib.pyplot as plt\n",
+    "\n",
+    "length = 50\n",
+    "time_log = []\n",
+    "for i in range(length):\n",
+    "    start = time.time()\n",
+    "    ol.download()\n",
+    "    end = time.time()\n",
+    "    time_log.append((end-start)*1000)\n",
+    "\n",
+    "%matplotlib inline\n",
+    "plt.plot(range(length), time_log, 'ro')\n",
+    "plt.title('Bitstream loading time (ms)')\n",
+    "plt.axis([0, length, 0, 1000])\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}