VitisUnified
The VitisUnified backend provides an end-to-end workflow for AMD SoC boards and data-center cards, from an ML model to a design that is ready to deploy. It is inherited from the Vitis backend. We use the new Vitis Unified software, which can automatically link the HLS kernel to the system hardware. SoC boards are deployed with a PYNQ Python driver, data-center cards with an XRT one.
It is the recommended flow for AMD SoC boards with Vitis 2023.2 or newer. Models with io_parallel or with ap_fixed interface types stay with the VivadoAccelerator backend.
Currently hls4ml officially supports the following boards and tool versions:
zcu102 (Vitis and Vivado 2023.2)
kv260 (Vitis and Vivado 2023.2 and 2025.2)
alveo-u55c (Vitis and Vivado 2024.2 with the
xilinx_u55c_gen3x16_xdma_3_202210_1platform,axi_masterwithdriver='xrt')alveo-u50andalveo-u280(not tested on hardware, nokernel_slr)
If you use another board, another Vivado version, or want to optimize the system design for your own workload, you can build your own platform. The steps are covered in the platform setup tutorial in the accelerator backend section of the hls4ml-tutorial repository.
System Flow
The figure below shows the flow of the backend, from the generated HLS files to the files that are ready to ship to the board.
AXI interface modes
The backend supports two ways to move data between the PS and the kernel, selected with axi_mode.
In both modes the CPU controls the kernel through AXI-Lite and receives an interrupt when the kernel is done.
axi_master(default)The kernel reads its input and writes its output in DDR by itself through an AXI master port. The driver allocates one DDR buffer per model input and output, so models with several inputs or outputs are supported. It takes the offsets of the pointer and batch-size registers from the hardware handoff file through PYNQ, so it does not depend on the register layout.
axi_streamThe kernel has one AXI-Stream input and one AXI-Stream output. An AXI DMA in the platform moves the data between DDR and the kernel. Only models with one input and one output are supported. The driver expects the DMA instance to be called
axi_dma_0; another name can be passed with itsdma_nameargument.
The stream interface follows this contract:
Each beat carries one element. The data field uses the
input_typeformat, 32 bits forfloatand 64 bits fordouble. The platforms built by the shipped Tcl scripts have a 32-bit DMA, sodoubleis rejected with them and needs your own platform.For each kernel start the kernel reads exactly
batch_size × N_INinput beats and writes exactlybatch_size × N_OUToutput beats.N_INandN_OUTare the flattened input and output sizes of the model.TLASTis set only on the last output beat of the batch.TLASTon the input is ignored, so a transfer with fewer beats than expected makes the kernel wait.TKEEPis driven all-ones on every output beat. It is required by the AXI DMA, which never completes a transfer without it.TKEEPon the input is not checked; every beat is taken as a full element.
Data-center cards
A card is linked against an installed card platform instead of one built by Vivado, and it is driven by XRT over PCIe instead of by PYNQ. Three things follow from that, and all are handled by the board and driver options:
The platform is looked up under
PLATFORM_REPO_PATHS, which has to be set whenbitfile=Trueruns the link.The kernel pointers are assigned to memory banks explicitly. The banks of the board entry are split evenly over the pointer arguments, one contiguous slice each, and the generated driver allocates every buffer in the banks of its own kernel argument. For one input and one output on a card with 32 HBM banks that gives
HBM[0:15]andHBM[16:31].The kernel is placed in the SLR given by
kernel_slr(from the board entry,SLR2on the U55C), so v++ pipelines the path to its memory banks. Passkernel_slr=Falsefor a model that does not fit in one SLR.
Only axi_master is supported on a card. Instead of the raw bitstream and hardware handoff file that PYNQ needs, the .xclbin is copied to export/.
The kernel runs on the card’s scalable clock. Vitis implements it against the platform’s default kernel frequency, 300 MHz on the U55C, and when routing misses that it lowers the clock in the .xclbin to the highest frequency that meets timing. A [clock] entry would not change this, so a card’s link_system.cfg has none. build(bitfile=True) prints the kernel clock read from the .xclbin and returns it as KernelClockMHz in its report. clock_period still sets the HLS schedule, and since the HLS estimate leaves out routing, a short period pays off. A 16-64-32-32-5 jet tagger at ReuseFactor 1 ships at 112 MHz with clock_period=6.66 and at 237 MHz with clock_period=3.333. Adding in_stream_buf_size=2 and the options below to link_system.cfg after write() gives 256 MHz:
[vivado]
prop=run.impl_1.STEPS.PHYS_OPT_DESIGN.ARGS.DIRECTIVE=AggressiveFanoutOpt
prop=run.impl_1.STEPS.POST_ROUTE_PHYS_OPT_DESIGN.IS_ENABLED=true
prop=run.impl_1.STEPS.POST_ROUTE_PHYS_OPT_DESIGN.ARGS.DIRECTIVE=AggressiveExplore
With Vivado 2024.2, run.impl_1.strategy=Performance_Explore crashes the placer when kernel_slr is set.
hls_model = hls4ml.converters.convert_from_keras_model(model,
hls_config=config,
output_dir='hls4ml_prj_u55c',
backend='VitisUnified',
board='alveo-u55c',
driver='xrt',
clock_period=3.333)
The generated export/axi_master_driver.py loads the .xclbin from the path it is given, relative to the working directory. The shapes it is constructed with must share one batch size and match the number of values per sample of each model input and output, and the data type defaults to the interface type (np.float32 for float, np.float64 for double). predict takes an input of exactly that shape:
from axi_master_driver import NeuralNetworkAccelerator
accel = NeuralNetworkAccelerator('myproject.xclbin', X.shape, (len(X), n_out))
y = accel.predict(X)
Configuration options
The options below are passed as keyword arguments to the converter (for example convert_from_keras_model).
They are stored under VitisUnifiedConfig in the model configuration.
Option |
Default |
Description |
|---|---|---|
|
|
Target board.
It selects the FPGA part, the platform, and the Python driver template.
The current version only supports the boards in
supported_boards.json (zcu102, kv260, alveo-u55c, alveo-u50 and alveo-u280).Any other board name is rejected with an error, unless
platform and part are given.You can use your own board: build its platform by following the platform setup tutorial in the hls4ml-tutorial repository and pass it with
platform. |
|
from board |
FPGA part name.
If not given, it is taken from the board entry in
supported_boards.json. |
|
|
Path to your own platform file,
.xpfm or .xsa.When given, the platform of the board entry is not used, so a board that is not in
supported_boards.json works together with part.The driver does not depend on the platform, only on the linked design. For
axi_master any Vitis embedded platform with a PS, DDR, and an interrupt input works.For
axi_stream the platform must expose the two AXI-Stream ports of an AXI DMA with the tags DMA_MM2S and DMA_S2MM, and the DMA’s s2mm_introut must reach the PS. The shipped Tcl scripts show how. |
|
|
Kernel clock period in ns.
The same clock is used when the kernel is linked to the platform.
|
|
|
Clock uncertainty passed to Vitis HLS. The default is the same as in the Vitis backend.
|
|
|
hls4ml I/O type of the model.
The current version only supports
io_stream. |
|
|
Interface between the PS and the kernel:
axi_master or axi_stream.See AXI interface modes.
|
|
|
Type of driver generated for the board.
python is the PYNQ driver for SoC boards, xrt the PCIe driver for data-center cards. |
|
|
Data type of the model input on the AXI interface.
The current version only supports
float and double.The PYNQ driver uses the matching NumPy type.
|
|
|
Data type of the model output on the AXI interface.
The current version only supports
float and double.It must be the same as
input_type. |
|
|
Depth of the FIFO between the wrapper input (AXI master read or AXI-Stream) and the HLS model. Used in both AXI modes.
The unit is one entry of the model input stream. One entry holds the last dimension of the input shape: the channels of one pixel for an image, the whole vector for a flat input.
One sample takes
N_IN / channels entries. For a 4x4x1 input an entry is one element and the default holds 8 samples; for a 32x32x3 input an entry is 3 elements and the default holds 128 of the 1024 entries of one sample. |
|
|
Depth of the FIFO between the HLS model and the wrapper output (AXI master write or AXI-Stream). Used in both AXI modes.
The unit is one entry of the model output stream. One entry holds the last dimension of the output shape, and one sample takes
N_OUT / channels entries, the same rule as for the input. |
|
from board |
SLR the kernel is placed in on a data-center card, for example
'SLR1'.False leaves the placement to Vivado. |
Example:
hls_model = hls4ml.converters.convert_from_keras_model(model,
hls_config=config,
output_dir='hls4ml_prj_unified',
backend='VitisUnified',
board='kv260',
axi_mode='axi_stream',
clock_period=10,
in_stream_buf_size=256,
out_stream_buf_size=256)
The version argument of the converter (default 1.0.0) sets package.ip.version of the kernel, and the generated driver binds to xilinx.com:hls:<top>:<major.minor>.
Output directory layout
All paths inside the generated files are relative, so the output directory can be moved or copied to another machine.
<output_dir>/
├── firmware/ HLS sources: model, AXI wrapper, weights
├── tb_data/ testbench input and reference output
├── <project_name>_test.cpp C testbench of the AXI wrapper
├── <project_name>_bridge.cpp bridge used by hls_model.predict()
├── build_lib.sh builds the shared library for predict()
├── vivado_synth.tcl Vivado synthesis, and place and route, of the kernel (vsynth=True)
├── vivado_synth.xdc clock constraint written by vivado_synth.tcl
├── vivado_synth*.rpt, vivado_impl*.rpt reports of vsynth=True and pnr=True
├── .srcs/, .gen/ AMD IP cores of the kernel, such as floating-point operators, made by vivado_synth.tcl
├── hls4ml_config.yml
├── fifo_depths.json with FIFO depth optimization only
├── <step>_stdout.log, <step>_stderr.log with log_to_stdout=False only
├── vitis_workspace/
│ ├── <project_name>/
│ │ ├── vitis-comp.json Vitis Unified component
│ │ ├── hls_kernel_config_csim.cfg Vitis HLS config for csynth, package, and csim
│ │ ├── hls_kernel_config_cosim.cfg the same for cosim
│ │ ├── hls_kernel_config_cosim_fifo_sizing.cfg cosim with FIFO sizing on (vitis_fifo_sizing=True)
│ │ └── vitis_unified_project/ hls/, logs/, reports/, <project_name>_axi_*.xo
│ ├── system_link/
│ │ ├── link_system.cfg, link_system.sh
│ │ ├── <project_name>.xclbin link output (bitfile=True)
│ │ └── _x/ Vivado project of the system link
│ └── <board>/
│ └── tcl_scripts/ create_xsa.tcl, platform tcl, output/<board>_*.xsa
├── export/
│ ├── system.bit bitstream (bitfile=True, PYNQ driver only)
│ ├── system.hwh hardware handoff (bitfile=True, PYNQ driver only)
│ ├── <project_name>.xclbin linked design (bitfile=True, XRT driver only)
│ └── axi_master_driver.py or axi_stream_driver.py
└── final_reports/ timing, utilization, power, link summary, hls_compile.rpt
Build options
hls_model.build() runs the Vitis tools on the written project. Each step is selected with a keyword argument.
Argument |
Effect |
|---|---|
|
Runs C synthesis with |
|
Runs the C simulation of the AXI wrapper with the generated test bench. |
|
Runs RTL co-simulation. It turns on |
|
Runs the FIFO depth optimization. It turns on |
|
Uses the FIFO sizing feature of Vitis HLS during co-simulation. It turns on |
|
Synthesizes the kernel alone with Vivado, out of context, and writes |
|
Also places and routes the kernel after Vivado synthesis and writes |
|
Links the packaged kernel to the board platform and writes the |
|
Writes the output of each step to |
|
Deletes the Vitis HLS project, the link work directory and the reports of Vivado synthesis and of the bitfile build before the selected steps run. |
|
Accepted for compatibility with the other backends. They are ignored with a warning. The kernel is always packaged by |
build() returns a dictionary with the same keys as the Vitis backend: CSynthesisReport after synth=True, CosimReport after cosim=True, VivadoSynthReport after vsynth=True and VivadoImplReport after pnr=True.
After bitfile=True it also has BitfileReport: the resources of the kernel in the linked design, and the slack of the whole design and the power of the whole device, which include the platform.
The raw reports are under vitis_workspace/<project_name>/vitis_unified_project/ and, after bitfile=True, under final_reports/.
Limitations
The following are not supported in this version:
io_parallelmodels. Onlyio_streamis supported.Fixed-point interface types.
input_typeandoutput_typemust befloatordouble, and they must be the same.Weights that become external BRAM ports through
BramFactor. The conversion stops with an error.Models with several inputs or outputs in
axi_streammode. Useaxi_masterfor them.doubleinaxi_streammode with the shipped platforms. Their DMA is 32 bits wide, so pass your own platform with a 64-bit DMA.Multigraph models.
A C or C++ host driver. Only Python drivers are generated, PYNQ for SoC boards and XRT for cards.
axi_streamon a data-center card. Its platform has no AXI DMA for the kernel to connect to.Cards other than the ones in
supported_boards.json. Another card works by passing itsplatformandpart, but then the bank assignment has to be added tolink_system.cfgby hand.
Tutorial
A step-by-step tutorial with notebooks is available in the accelerator backend section of the hls4ml-tutorial repository. It covers prediction, C simulation, co-simulation, FIFO depth optimization, bitstream generation, and how to build your own platform.