Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions 5_accelerator_backends/vitis_unified/5_vu_a_predict.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "cell-0",
"metadata": {},
"source": [
"# Part 5a: The Vitis Unified backend\n",
"\n",
"The previous parts of this tutorial stop at the HLS IP: a piece of RTL that still has to be integrated into a full system by hand. The **VitisUnified** backend of hls4ml goes further. It wraps the hls4ml kernel with an AXI interface, links it into a SoC platform (processing system, interconnect, interrupt controller, DMA) with `v++`, and runs place and route, so that the output is a bitstream plus a PYNQ driver ready to run on a board.\n",
"\n",
"Part 5 walks through the full flow:\n",
"\n",
"| Notebook | What it does |\n",
"|----------|--------------|\n",
"| **5a** (this one) | Converts a small U-Net with both the `VitisUnified` and the `Vitis` backends and checks that the software predictions are identical |\n",
"| 5b | Runs **C simulation** (`csim`) and compares it with the software prediction. If this passes, the generated C++ is correct |\n",
"| 5c | Runs **RTL co-simulation** (`cosim`): the C++ is synthesized to RTL and simulated |\n",
"| 5d | Enables the **FIFO depth optimization** flow, which profiles the FIFOs during co-simulation and writes `fifo_depths.json` |\n",
"| 5e | Generates the **bitfile**: synthesis, system link, place and route. Writes `system.bit`, `system.hwh` and the PYNQ driver |\n",
"| 5f | A guide to building your own SoC platform in Vivado and adding a new board to the backend |\n",
"\n",
"## Prerequisites\n",
"\n",
"- Vitis and Vivado **2023.2** (tested). The `kv260` platform also supports 2025.2.\n",
"- A supported board: **zcu102** or **kv260**. The backend ships a platform for both, so you do not need to build one yourself.\n",
"- hls4ml with the Vitis Unified backend. The backend is not merged into the main branch of hls4ml yet; install it from pull request [#1376](https://github.com/fastmachinelearning/hls4ml/pull/1376):\n",
"\n",
"```bash\n",
"pip install git+https://github.com/fastmachinelearning/hls4ml.git@refs/pull/1376/head\n",
"```\n",
"\n",
"In this notebook we only use the C++ bridge (`hls_model.predict`), so no Vitis run is needed yet, but the backend still needs `XILINX_VITIS` to locate the platform."
]
},
{
"cell_type": "markdown",
"id": "cell-1",
"metadata": {},
"source": [
"## Settings\n",
"\n",
"Set the target board here. The backend picks the platform (`.xpfm` or `.xsa`) from its `supported_boards.json` using the board and the AXI mode, so nothing else needs to be configured. To add another board, see `5_vu_f_platform_setup.md`.\n",
"\n",
"| Variable | Meaning |\n",
"|----------|---------|\n",
"| `BOARD` | `'zcu102'` or `'kv260'` |\n",
"| `AXI_MODE` | `'axi_master'` (default) or `'axi_stream'`; see the *AXI interface modes* page of the hls4ml docs |\n",
"\n",
"The Vitis tools are located through the `XILINX_VITIS` environment variable, as in the other parts of this tutorial."
]
},
{
"cell_type": "code",
"id": "cell-2",
"metadata": {},
"source": [
"import os\n",
"\n",
"os.environ['KERAS_BACKEND'] = 'tensorflow'\n",
"\n",
"import numpy as np\n",
"import sys\n",
"\n",
"sys.path.append('../..')\n",
"from models import create_simple_unet\n",
"import hls4ml\n",
"\n",
"os.environ['PATH'] = os.environ['XILINX_VITIS'] + '/bin:' + os.environ['PATH']\n",
"\n",
"BOARD = 'zcu102' # or 'kv260'\n",
"AXI_MODE = 'axi_master' # or 'axi_stream'\n",
"\n",
"DATA_DIR = '../../data/vitis_unified'\n",
"os.makedirs(DATA_DIR, exist_ok=True)\n",
"np.random.seed(0)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "cell-3",
"metadata": {},
"source": [
"## Create the model and some test data\n",
"\n",
"We use a tiny U-Net with a single skip connection, defined in `models.py`. The weights are left untrained on purpose: the goal of this part is to check that every stage of the accelerator flow reproduces the same numbers, not to reach a good accuracy. A `Concatenate` skip connection is a good stress test for the streaming interface, because it forces two branches to be merged again."
]
},
{
"cell_type": "code",
"id": "cell-4",
"metadata": {},
"source": [
"X = np.random.rand(10, 4, 4, 1).astype(np.float32)\n",
"np.save(f'{DATA_DIR}/X_part5a.npy', X)\n",
"print(f'Input shape: {X.shape}')\n",
"\n",
"model = create_simple_unet()\n",
"model.summary()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "cell-5",
"metadata": {},
"source": [
"## Convert with both backends\n",
"\n",
"We convert the same model twice: once with the `VitisUnified` backend and once with the plain `Vitis` backend as a reference. Both use `io_stream`, which is required for the CNN layers.\n",
"\n",
"The `VitisUnified` backend takes the same arguments as the `Vitis` backend, plus:\n",
"\n",
"| Argument | Meaning |\n",
"|----------|---------|\n",
"| `board` | the target board; the FPGA part and the platform are looked up from it |\n",
"| `axi_mode` | `'axi_master'` or `'axi_stream'`, the interface between the PS and the kernel |\n",
"| `input_type`, `output_type` | the data type on the AXI interface (`'float'` here, so the driver can send `float32` NumPy arrays directly) |"
]
},
{
"cell_type": "code",
"id": "cell-6",
"metadata": {},
"source": [
"config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend='Vitis')\n",
"\n",
"hls_model_unified = hls4ml.converters.convert_from_keras_model(\n",
" model,\n",
" hls_config=config,\n",
" backend='VitisUnified',\n",
" io_type='io_stream',\n",
" output_dir=os.path.abspath(f'../../hls4ml_prjs/hls4ml_prj_part5a_unified_{AXI_MODE}'),\n",
" board=BOARD,\n",
" axi_mode=AXI_MODE,\n",
" input_type='float',\n",
" output_type='float',\n",
" clock_period=10,\n",
")\n",
"hls_model_unified.compile()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"id": "cell-7",
"metadata": {},
"source": [
"hls_model_vitis = hls4ml.converters.convert_from_keras_model(\n",
" model,\n",
" hls_config=config,\n",
" backend='Vitis',\n",
" io_type='io_stream',\n",
" output_dir='../../hls4ml_prjs/hls4ml_prj_part5a_vitis',\n",
" part='xczu9eg-ffvb1156-2-e',\n",
" clock_period=10,\n",
")\n",
"hls_model_vitis.compile()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "cell-8",
"metadata": {},
"source": [
"## Predict and compare\n",
"\n",
"The AXI wrapper added by the `VitisUnified` backend must not change the arithmetic: the fixed-point results of the two backends should be bit-for-bit identical."
]
},
{
"cell_type": "code",
"id": "cell-9",
"metadata": {},
"source": [
"y_unified = hls_model_unified.predict(X)\n",
"y_vitis = hls_model_vitis.predict(X)\n",
"\n",
"print(f'VitisUnified output shape: {np.array(y_unified).shape}')\n",
"print(f'Vitis output shape: {np.array(y_vitis).shape}')\n",
"\n",
"assert np.array_equal(y_unified, y_vitis), 'The results from VitisUnified and Vitis are NOT equal!'\n",
"print('Backend prediction comparison passed: both are equal')"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "cell-10",
"metadata": {},
"source": [
"Continue with `5_vu_b_csim.ipynb` to check that the generated C++ behaves the same when compiled by the Vitis HLS compiler."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "hls4ml-tutorial",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.16"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading