Skip to content

Vitis Unified Backend - #1376

Merged
vloncar merged 160 commits into
fastmachinelearning:mainfrom
Tanawin1701d:VitisUnifiedClean
Sep 20, 2026
Merged

vloncar merged 160 commits into
fastmachinelearning:mainfrom
Tanawin1701d:VitisUnifiedClean

Conversation

@Tanawin1701d

@Tanawin1701d Tanawin1701d commented Sep 2, 2025 •

Copy link
Copy Markdown
Contributor

Description

VitisUnified backend

Motivation

The Vitis backend stops at the HLS project. This backend takes an hls4ml model to a file set that is ready for a PYNQ board: bitstream, hardware handoff and Python driver, built with the Vitis Unified flow (v++ compile and package, v++ link against a platform). It is meant as the flow for AMD SoC boards with Vitis 2023.2 or newer; models with io_parallel or ap_fixed interfaces stay with VivadoAccelerator.

Features

  • axi_mode: axi_master (kernel accesses DDR itself, multiple inputs and outputs) or axi_stream (AXI DMA in the platform, one input and one output).

    axi_master mode axi_stream mode

  • One batch per kernel start, interrupt driven; one PYNQ driver per AXI mode with register offsets from the hardware handoff.

  • build() with the parent keywords: synth, csim, cosim, fifo_opt, vitis_fifo_sizing, bitfile; returns the Vitis report dictionary.

  • Boards as data: supported_boards.json (zcu102, kv260) or your own platform via platform= and part=.

  • Configuration validated at conversion; predict() accepts any numpy dtype.

  • Documentation page docs/backend/vitis_unified.rst.

Limitations

io_stream only; float/double interfaces only, both the same; no BramFactor weights (rejected at conversion); axi_stream with one input and one output; no multigraph; Python driver only; double on axi_stream needs a platform with a 64-bit DMA (the shipped ones are 32 bits).

Type of change

  • New feature (non-breaking change which adds functionality)

Tests

test/pytest/test_vitis_unified.py, 41 tests in six sections ordered by flow depth:

  1. conversion: invalid configurations and BRAM weights rejected before anything is written
  2. write: config files, custom project name, version, driver contents, own platform, name-length guard
  3. predict: parity with the Vitis backend for axi_mode x float/double x numpy dtype, writer options forwarded
  4. build: report dictionary and reset (fake reports, no Vitis needed)
  5. Vitis HLS (RUN_SYNTHESIS=true): csim, cosim, FIFO depth optimization, both AXI modes
  6. bitstream (RUN_VITIS_UNIFIED_BITSTREAM=true): kv260, axi_stream, axi_master, 2-input/2-output axi_master

Results: without Vitis 32 passed, 9 skipped. With Vitis 2023.2 all 41 pass.

Hardware, KV260 with PYNQ 3.0.1, generated drivers, batch of 10, compared with the software prediction saved by the test (test/board/vitis_unified_hw_test.py):

Bitstream AXI mode Ports max abs diff hw vs sw Second run identical
simple U-Net axi_stream 1 in, 1 out 0.0 yes
simple U-Net axi_master 1 in, 1 out 0.0 yes
2-in / 2-out CNN axi_master 2 in, 2 out 0.0 yes

Test reproduce

cd test/pytest
pytest test_vitis_unified.py                                                   # no Vitis needed
RUN_SYNTHESIS=true pytest test_vitis_unified.py                                # + csim, cosim, FIFO (v++, vitis-run on PATH)
RUN_SYNTHESIS=true RUN_VITIS_UNIFIED_BITSTREAM=true pytest test_vitis_unified.py   # + kv260 bitstreams (vivado, xclbinutil, XILINX_VITIS)

build(log_to_stdout=False) writes <step>_stdout.log and <step>_stderr.log in the output directory instead of printing.

Checklist

  • I have read the guidelines for contributing.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation.
  • My changes generate no new warnings. (see the section below)
  • I have installed and run pre-commit on the files I edited or added.
  • I have added tests that prove my fix is effective or that my feature works.

AI assistance disclosure

Claude Code (Claude Fable 5.1) was used in this PR; commits from March 2026 onward may contain code, tests or documentation written with it, including all fixes after the first review. Every change was reviewed, and all numbers above come from runs on my machine and my KV260.

Implementation detail

VitisUnified backend flow

Three stages produce the files to ship:

  • file generation: HLS sources, AXI wrapper, test bench, bridge, Vitis config files, driver
  • kernel synthesis: v++ -c --mode hls and vitis-run --package produce the .xo
  • link: link_system.sh runs v++ -l against the platform, then extracts system.bit and system.hwh from the .xclbin

Template structure

hls4ml/templates/vitis_unified:

├── ap_types/                       ap_axi_sdata.h for simulation without Vitis
├── build_lib.sh                    builds the shared library for predict()
├── drivers/
│   ├── axi_master_driver.py.hls4ml
│   └── axi_stream_driver.py.hls4ml
├── hls_kernel_config.cfg           Vitis HLS config, one copy per step is written
├── myproject_axi_master.cpp / .h   AXI-master wrapper
├── myproject_axi_stream.cpp / .h   AXI-stream wrapper
├── myproject_bridge.cpp            bridge for predict()
├── myproject_test.cpp              test bench for csim and cosim
├── nnet_utils/nnet_helpers_axi.h   stream helpers for simulation
├── vitis_workspace/
│   ├── kernel_project/vitis-comp.json
│   └── system_link/link_system.cfg, link_system.sh
├── kv260/tcl_scripts/              platform generation
└── zcu102/tcl_scripts/

Output structure

<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
├── 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, open this folder in the IDE
│   │   ├── hls_kernel_config_csim.cfg
│   │   ├── hls_kernel_config_cosim.cfg
│   │   ├── hls_kernel_config_cosim_fifo_sizing.cfg
│   │   └── 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/           platform generation, output/<board>_*.xsa
├── export/
│   ├── system.bit, system.hwh         bitfile=True
│   └── axi_master_driver.py or axi_stream_driver.py
└── final_reports/                     timing, utilization, power, link summary, hls_compile.rpt

All paths in the generated files are relative, so the output directory can be moved or copied to another machine.

Configuration

Keyword arguments of the converter:

board='zcu102',              # zcu102 or kv260 from supported_boards.json; any name together with platform and part
part=None,                   # taken from the board entry if not given
platform=None,               # your own .xpfm or .xsa; the board entry is then only used for part
clock_period=5,
clock_uncertainty=None,      # Vitis backend default, 27%
io_type='io_stream',         # only io_stream
driver='python',             # only python
input_type='float',          # float or double, must equal output_type
output_type='float',
in_stream_buf_size=128,      # FIFO depth between wrapper input and model, in stream entries (last dim of the input shape)
out_stream_buf_size=128,     # same for the output
axi_mode='axi_master',       # axi_master or axi_stream
**kwargs,                    # Vitis/Vivado writer options: namespace, write_tar, ...

version (default 1.0.0) sets package.ip.version and the driver's bindto.

Notes

  • Open vitis_workspace/<project_name> in the Vitis Unified IDE to debug the kernel; the component points at the csim config.
  • The m_axi depth in the AXI-master wrapper is for simulation only, one sample; the test bench sends one sample per kernel start.
  • The Vivado project of the link is under vitis_workspace/system_link/_x/link/vivado/vpl/prj.
  • Tutorial: the accelerator backend section of hls4ml-tutorial (prediction, simulation, bitstream, own platform).

Generated warnings

From the U-Net model used in the tests, not from the backend:

WARNING:absl:Skipping variable loading for optimizer 'Adam', because it has 17 variables whereas the saved optimizer has 1 variables.
WARNING: Config parameter "algorithm" overwrites an existing attribute in layer "up_sampling2d" (Resize)

Kernel synthesis prints the usual Vitis HLS warnings (unused parameter, deprecated pragma).

…i wrapper for vitisUnified partial backend and build the skeleton code for other generation section
…ks in axi_stream

- add axi_stream double to test_predict_any_numpy_dtype (review U2)
…e wrapper build

- add vitisunified:ip default flow with validate_bram_weights, test and doc note (review U3)
…ame and invalid configs

- cover items (d) and (e) of review U21; they flip to passing when U4, U12 and U7 land
…nd limitations

- unknown boards are rejected, buffer size unit, positioning against VivadoAccelerator (review U22)
…nd support namespace in the wrapper

- namespace, write_tar and the other writer options are no longer dropped (review U4)
…lement reset

- add parse_vitis_unified_report reusing the Vivado csynth and cosim parsers, CI entries and a test (review U5)
…eter

- VitisUnified calls super().__init__ instead of bypassing two parents (review U6)
…te time

- checks in create_initial_config and a validate_config pass, asserts replaced by exceptions (review U7)
…r edits generated files

- three cfg variants, vitis-comp.json points at the csim one, test added (review U9)
…lative paths

- the generated project builds after a move; the test moves it and checks every path (review U11)
…gument, no hard-coded names or tool paths

- one driver per AXI mode, project-named test bench, IP version from the config, XILINX_VITIS from the environment (review U12, U13, U14)
… scheduled by the flow

- VitisUnified inherits the split and only overrides the HLS project path; test with a faked co-simulation (review U10)
…tcl, and write the tar once

- documented no-op overrides, the tar is made at the end of write_hls (review U8)
…moved, explicit stream contract

- guard message computes the allowed length, TKEEP set on every packet, sim-only depth comment (review U15 to U20)
…ions

- fixtures, conversion, write, compile, Vitis HLS, bitstream; bodies unchanged
- their AXI DMA is 32 bits wide; use float, or a user platform with a 64-bit DMA
- drop test_fifo_depth_passes, covered by test_fifo_depth, and comment the config tests
- the AXI DMA never completes a transfer without TKEEP, so the review's drop-TKEEP option is not possible
…e saved prediction

- verified on kv260 for axi_stream, axi_master and a 2-in/2-out axi_master model
- one entry holds the last dimension of the tensor, so a sample takes N / channels entries
@Tanawin1701d

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. All 22 findings are addressed in the 20 commits after the reviewed head 41b4837, now merged into the PR branch. Status per item below, grouped as in section 5 of the review. "Note" marks where I did something different from the proposed fix, or something worth knowing.

Required before merge

U Status Note
U1 Fixed Both bridge entry points are filled; predict() converts at the boundary for any numpy dtype (3e349ab).
U2 Fixed AXI helpers templated on the packet type (d52a136). New: double on axi_stream is rejected with the shipped platforms, their AXI DMA is 32 bits wide; a user platform with a 64-bit DMA is still accepted (6cdc412).
U3 Fixed Chose rejection: BramFactor weights stop at conversion with a clear error, ports are not forwarded (c36e3f1).
U21 Fixed 41 tests in six sections by flow depth: config rejection, BRAM rejection, custom name/namespace/tar, driver contents, custom platform, dtype parity vs the Vitis backend, report parsing; csim/cosim/FIFO and bitstream tests stay opt-in (70334c2, 6910ae9).
U22 Fixed Unknown-board paragraph matches the code, build() options table, stream contract, buffer sizes, own-platform option, limitations (cbb5e2e). Positioning sentence is on the VitisUnified page; accelerator.rst is untouched, out of scope of this PR. The PR description now shows the platform argument; the old xpfmPath / XPFM_PATH text is gone.

Needed for it to be a credible default

U Status Note
U4 Fixed Named backend parameters plus **kwargs forwarded to the parent (4b89553).
U6 Fixed name parameter on the Vivado and Vitis constructors, super().__init__(name='VitisUnified') (8a7d812). VivadoAccelerator and Coyote constructors untouched.
U7 Fixed Graph-free checks in create_initial_config (vitis_unified_validation.py), graph checks in a pass on the default flow; exceptions instead of asserts (0683968).
U5 Fixed Parent keyword set, unsupported ones warn, reset implemented, cosim implies synth, report dict returned, CI helper entries (940307b).
U9 Fixed Three cfg files written at write time, vitis-comp.json points at the csim one, build() never edits generated files (e9761d9).
U11 Fixed All paths relative to the component directory; a moved project builds (bab531f).
U12 Fixed <project>_test, XILINX_VITIS from the environment with a clear error, .hwh searched (exactly one expected), version into package.ip.version and the driver bindto, clock uncertainty inherited (27%), xclbinutil/vivado checked (a2b91a0). flow_target=vivado stays, by design.
U13 Fixed platform (.xpfm/.xsa) and part arguments; a board outside the JSON needs no code change (a2b91a0).
U14 Fixed One driver per AXI mode under templates/vitis_unified/drivers/, per-board copies and c_drivers removed, register offsets read from the .hwh, PL.reset() in __init__ (reset_pl), separate output dtype, dma_name argument, ap_fixed docstring removed (a2b91a0).
item 9 Done io_parallel and ap_fixed interfaces documented as non-goals of this backend.

Cleanups and longer term

U Status Note
U8 Fixed build_prj.tcl/build_opt.tcl no longer written, one test bench under <project>_test.cpp, tar written once (18011f4).
U10 Fixed Vitis FIFO pass split into fifo_depth_profiling (pre pass, write, cosim, post pass) and fifo_depth_optimization flows; VitisUnified inherits them (d102cca).
U15 Fixed One substitution style (_fill_template, {PLACEHOLDER} tokens, markers only for blocks) (35c6731). The mem_rd/mem_wr labels are kept: they now label the loops and help when reading synthesis reports.
U16 Fixed Dead code removed, _get_kernel_declaration uses the AXI mode (35c6731).
U17 Fixed Limit computed from the real suffix lengths, both numbers in the message (35c6731).
U18 Fixed Last-beat condition written per loop index; contract stated in the docs, the driver docstring and the wrapper source (35c6731, 25eaacd).
U19 Fixed Comment that depth is simulation-only; kept at one sample, which is what the test bench sends per start.
U20 Fixed Option 2: `hls::axis_data<T, AXIS_ENABLE_LAST

Changes outside the VitisUnified backend

File Change Why For
backends/vitis/passes/fifo_depth_optimization.py, backends/vitis/vitis_backend.py FIFO pass split into pre / profile / post passes and two flows So VitisUnified inherits the #642 design instead of adding a fourth copy of the pass U10
backends/vivado/vivado_backend.py, backends/vitis/vitis_backend.py name parameter on the constructors, defaults unchanged Lets the subclass call super().__init__() instead of bypassing it U6
report/vivado_report.py, report/__init__.py csynth and cosim parsers extracted into functions Same report formats, only the paths differ; the unified report reuses them U5
writer/vivado_writer.py write_tb_data split out of write_test_bench The unified test bench needs the same tb_data files without a second <project>_test.cpp U8
test/pytest/conftest.py, test/pytest/synthesis_helpers.py VitisUnified entries The CI synthesis helper is table-driven per backend U5

No behaviour change for the Vivado, Vitis, VivadoAccelerator or Coyote backends.

Test results

  • test/pytest/test_vitis_unified.py, without Vitis: 32 passed, 9 skipped (gated).
  • With Vitis 2023.2: all 41 pass. csim, cosim and FIFO depth optimization for both AXI modes; kv260 bitstreams for axi_stream, axi_master and a 2-input/2-output axi_master model.

KV260 (PYNQ 3.0.1), generated drivers, batch of 10, compared with the software prediction saved by the test:

Bitstream AXI mode Ports max abs diff hw vs sw Second run identical
simple U-Net axi_stream 1 in, 1 out 0.0 yes
simple U-Net axi_master 1 in, 1 out 0.0 yes
2-in / 2-out CNN axi_master 2 in, 2 out 0.0 yes

Script: test/board/vitis_unified_hw_test.py.

@JanFSchulte JanFSchulte added please test Trigger testing by creating local PR branch and removed please test Trigger testing by creating local PR branch labels Sep 14, 2026

@vloncar vloncar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed response, this is exactly what I was hoping to see.

I checked the update independently at the current head. I went through all the new commits against your table, ran the new test suite (same as your numbers), and reran the 32 configurations from my original review. I didn't run the hardware tests myself, but I trust your numbers. All 22 findings are addressed. The input array with the wrong dtype now predicts correctly instead of silently returning zeros, and double on axi_stream and BramFactor weights are rejected at conversion with a clear message instead of failing later in the C++ build. Every invalid configuration I tried now stops at conversion and tells the user what to do, and all 24 valid ones give results identical to the Vitis backend. The platform and part arguments together with the board-independent driver solve the board support problem properly, a board that is not in the list needs no code changes at all.

The places where you went a different way than I (in truth, Claude) suggested (like keeping TKEEP since the DMA needs it, the loop labels, the simulation-only depth) are all fine with me.

One small non-blocking follow-up: docs/backend/accelerator.rst still doesn't mention this backend, so someone reading the VivadoAccelerator page won't learn about its successor. A one-line cross-reference would do, and it can be a separate PR along with other documentation fixes we have planned.

Looks good. Approving.

@vloncar

vloncar commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

The test failures are unrelated to this PR. Merging. Huge thanks to the team for making this.

@vloncar
vloncar merged commit d31d729 into fastmachinelearning:main Sep 20, 2026
5 of 7 checks passed
JanFSchulte added a commit that referenced this pull request Sep 29, 2026
…kend

The guard added in #1376 skipped rows of channel_info.csv with an empty
instance path. The Vitis backend writes all model FIFOs with an empty
instance path, so no depths were read and FifoDepthOptimizationPost
failed with a KeyError (#1570). Select rows by FIFO name instead, which
also skips the Vitis Unified wrapper's own streams, strip fields instead
of dropping the last character of the file name, and raise a clear error
when a profiled depth is missing.
vloncar pushed a commit that referenced this pull request Oct 14, 2026
* Fix FIFO depth optimization parsing of channel_info.csv for Vitis backend

The guard added in #1376 skipped rows of channel_info.csv with an empty
instance path. The Vitis backend writes all model FIFOs with an empty
instance path, so no depths were read and FifoDepthOptimizationPost
failed with a KeyError (#1570). Select rows by FIFO name instead, which
also skips the Vitis Unified wrapper's own streams, strip fields instead
of dropping the last character of the file name, and raise a clear error
when a profiled depth is missing.

* slight cleanup of Claude comments

* [pre-commit.ci] auto fixes from pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
@vloncar vloncar mentioned this pull request Oct 17, 2026
14 of 32 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New hls4ml feature please test Trigger testing by creating local PR branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants