Skip to content

Vitis Accelerator IP Flow #1134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 107 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
312832f
Initial commit
steltze Apr 19, 2024
d2b5a15
Set change the backend
steltze Apr 19, 2024
02659dd
Change the accelerator config script
steltze Apr 19, 2024
56296b6
Set the vitis accelerator template
steltze Apr 19, 2024
7dd0173
Set vitis accelerator writer
steltze Apr 19, 2024
6f181b8
Fix writes init
steltze Apr 19, 2024
bd2e52e
Include separable convolution resource implementation
steltze May 10, 2024
b795240
Separate depthwise resource strategy to 3 cases
steltze May 29, 2024
eeb04d4
Complete vitis accelerator wrapper for io_stream case
steltze May 29, 2024
7e47c85
Fix call to wrong backend writer
steltze May 31, 2024
5a2a38f
Fix vitis accelerator writer
steltze May 31, 2024
99f9429
Fix include in axi wrapper header file writer
steltze Jun 11, 2024
b9609dc
Change python-cpp bridge writer
steltze Jun 11, 2024
4f69c16
Fix tlast handling in axis wrapper writer
steltze Jun 11, 2024
014a7b2
Extend convert_data to handle stream type, use that for the bridge
steltze Jun 11, 2024
723073e
Add zcu102 to the supported boards json
steltze Jun 14, 2024
290896b
Fix some c synthesis warnings
steltze Jun 20, 2024
c9dfcf2
Group more tests per YAML to reduce the number of envs created
vloncar Apr 11, 2024
d3b8e20
Support negative_slope in quantized_relu
vloncar Mar 25, 2024
b32984f
[pre-commit.ci] auto fixes from pre-commit hooks
pre-commit-ci[bot] Mar 28, 2024
98273a0
Fix activation check in profiling
vloncar Apr 16, 2024
1640c4b
Stage initial set of changes for the Catapult backend (#956)
dgburnette Apr 15, 2024
2a71a83
[pre-commit.ci] pre-commit autoupdate
pre-commit-ci[bot] Apr 15, 2024
6ac964c
fix unwanted tested file change in #956
calad0i Apr 16, 2024
ec95e01
Fix SR backend synth missing variables
bo3z Apr 10, 2024
5de1bf5
Test for SR backend config
vloncar Apr 16, 2024
a6fec36
Upsampling support for PyTorch models
vloncar Mar 6, 2024
1b72b19
Split Catapult types into separate file
vloncar Apr 15, 2024
28521d0
Split Quartus types into separate file
vloncar Apr 15, 2024
a44707d
Split Vivado types into separate file
vloncar Apr 15, 2024
cefab60
Increase precision of Softsign test
vloncar Apr 18, 2024
440901b
Use quantized input in binary CNN test
vloncar Apr 18, 2024
c351a02
Add UnspecifiedPrecisionType
vloncar Aug 20, 2023
4d9d35a
Rudimentary optimizer to infer 'auto' precision
vloncar Aug 20, 2023
32ae9b6
Auto precision test
vloncar Aug 20, 2023
932b01e
Sepconv fixes
vloncar Aug 20, 2023
6a65fed
update precision propagation for signed, select im2col for quartus pa…
jmitrevs Jan 26, 2024
41b7e98
Make inferring no_bias a configurable option of the optimizer
vloncar Feb 6, 2024
24253e1
updates to infering precision from qonnx branch
jmitrevs Apr 16, 2024
6ee8189
remove count, become more selective on when True is returned
jmitrevs Apr 17, 2024
b5add0c
fix pooling precision
calad0i Apr 17, 2024
665c904
remove typing
calad0i Apr 17, 2024
b366d24
Fix avg pooling op check
vloncar Apr 18, 2024
f0ca865
Optimizer to remove expensive Transpose that serves as Flatten
vloncar Feb 21, 2024
1e416b5
Generalize removal of Transpose after flatten so it works on 1D as well
vloncar Feb 27, 2024
2a5d8de
Remove transpose of input if n_chan=1
vloncar Feb 27, 2024
3969523
SepConv1d/2d for io_parallel w/ Latency strategy
vloncar May 13, 2024
52252ca
Cosmetic parameter config fixes
vloncar May 13, 2024
be56b93
Tests for SepConv io_parallel
vloncar May 13, 2024
b0085a1
[pre-commit.ci] pre-commit autoupdate
pre-commit-ci[bot] Apr 29, 2024
44bc8f3
Update pytest docker image to 0.5.4
jmitrevs Apr 26, 2024
a7826e0
bump to 0.5.5
jmitrevs May 1, 2024
41ab6af
fix pre-commit warning
jmitrevs Apr 25, 2024
c0f8d9f
change writing of obsolete ".h5" to ".keras" files
jmitrevs Apr 26, 2024
bcfd685
Fix extension test for Keras v3
vloncar May 1, 2024
8c09595
Support ParallelizationFactor in SepConv1D/2D
vloncar May 15, 2024
11819ac
updated pytest docker image
jmitrevs May 30, 2024
39d9232
Don't test io_parallel for Catapult test and reduce the size of test …
vloncar Jun 3, 2024
68a83d6
Add explicit DepthwiseConv tests and simpligy SepConv tests
vloncar Jun 9, 2024
8a9d556
[pre-commit.ci] pre-commit autoupdate
pre-commit-ci[bot] Jun 10, 2024
ad86387
Initial commit
steltze Apr 19, 2024
4ea329b
Stage initial set of changes for the Catapult backend (#956)
dgburnette Apr 15, 2024
992b9b7
Rudimentary optimizer to infer 'auto' precision
vloncar Aug 20, 2023
8174465
Sepconv fixes
vloncar Aug 20, 2023
84ff2c6
Optimizer to remove expensive Transpose that serves as Flatten
vloncar Feb 21, 2024
518796d
Remove transpose of input if n_chan=1
vloncar Feb 27, 2024
238e35c
Optimizer to remove expensive Transpose that serves as Flatten
vloncar Feb 21, 2024
c10dd82
Remove transpose of input if n_chan=1
vloncar Feb 27, 2024
d6fe369
fix up automatic precision inferrence
jmitrevs Jun 13, 2024
7290a29
starting towards being able to split seperable
jmitrevs Jun 11, 2024
13fcf0a
complete implementation of seperable -> dw + pw, untested
jmitrevs Jun 12, 2024
92e7222
make conv_same_pad also trigger on depthwise, varius bug fixes
jmitrevs Jun 12, 2024
f12a7ea
add parsing of depth multiplier for 1D depthwise conv
jmitrevs Jun 13, 2024
4d24e4e
Merge remote-tracking branch 'upstream/main' into vitis_accelerator_i…
Aug 26, 2024
e2d270e
Finish resolving conficts with main
Aug 26, 2024
fa6bd66
Supress removing tar for now
steltze Nov 18, 2024
b42210d
Fix csynth and cosim
steltze Nov 18, 2024
1303bba
Fix tcl script to find cosim report
steltze Nov 18, 2024
8d3a1f2
Correct PYNQ Z2 vivado tcl script, bitstream generated
steltze Nov 18, 2024
a8e0497
Clean pynq tcl script
steltze Nov 19, 2024
48686d3
Fix compatibility of nnet helper functions with vitis axis
steltze Nov 19, 2024
bae450b
Setup vivado tcl script for zcu102
steltze Nov 19, 2024
dde9124
Rename backend to VitisAcceleratorIPFLow to prevent conflicts with ke…
steltze Nov 19, 2024
663181f
Fix compatiblity between axi stream and io parallel
steltze Nov 20, 2024
e32f4d0
Update pynq driver for zcu102
steltze Nov 20, 2024
c52ec75
Run pre-commit
steltze Nov 20, 2024
9d9e645
Remove unused file
steltze Nov 20, 2024
80697c0
Remove unused xclbin generator
steltze Nov 20, 2024
f467829
Clean backends init
steltze Nov 27, 2024
4c74550
Fix backend import sequence
steltze Nov 27, 2024
542b950
Start cleaning up code
steltze Feb 19, 2025
c78aec2
Start integrating FIFO depth optimizer
steltze Feb 19, 2025
62b5c27
Fix FIFO depth optimizer
steltze Feb 20, 2025
d5f2192
Run precommit
steltze Feb 20, 2025
34b0929
Merge branch 'main' into vitis_accelerator_ip_flow
steltze Feb 20, 2025
14b413e
Update build_prj.tcl
steltze Feb 21, 2025
800423f
Merge branch 'main' into vitis_accelerator_ip_flow
steltze Mar 6, 2025
9f1c8b3
Address pr comments and merge main
steltze Mar 6, 2025
4763692
Include tests without fifo optimization and checks for bitstream gene…
steltze Mar 6, 2025
e66ad40
Run precommit and remove unused override testbench
steltze Mar 6, 2025
f51be88
Fix qonnx test
steltze Mar 7, 2025
85c233c
Fix keras fifo optimization test
steltze Mar 7, 2025
b91b641
Fix test documentation
steltze Mar 7, 2025
5bc54d3
Fix vivado project path in the build tcl for zcu102
steltze Mar 7, 2025
0a0d7d1
Skip all tests
steltze Mar 7, 2025
da4f8b5
Merge branch 'main' into vitis_accelerator_ip_flow
steltze Mar 7, 2025
e55c52e
Link backend fifo optimization options
steltze Mar 13, 2025
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
8 changes: 7 additions & 1 deletion hls4ml/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@
from hls4ml.backends.vivado_accelerator.vivado_accelerator_config import VivadoAcceleratorConfig # noqa: F401

from hls4ml.backends.catapult.catapult_backend import CatapultBackend # isort: skip

from hls4ml.backends.vitis.vitis_backend import VitisBackend # isort: skip
from hls4ml.backends.vitis_accelerator_ip_flow.vitis_accelerator_ip_flow_backend import ( # isort: skip
VitisAcceleratorIPFlowBackend,
)
from hls4ml.backends.vitis_accelerator_ip_flow.vitis_accelerator_ip_flow_config import ( # isort: skip # noqa: F401
VitisAcceleratorIPFlowConfig,
)

register_backend('Vivado', VivadoBackend)
register_backend('VivadoAccelerator', VivadoAcceleratorBackend)
register_backend('Vitis', VitisBackend)
register_backend('VitisAcceleratorIPFlow', VitisAcceleratorIPFlowBackend)
register_backend('Quartus', QuartusBackend)
register_backend('Catapult', CatapultBackend)
register_backend('SymbolicExpression', SymbolicExpressionBackend)
Expand Down
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import json
import os

from hls4ml.model.optimizer.optimizer import ConfigurableOptimizerPass, ModelOptimizerPass


def initialize_large_fifos(model, profiling_fifo_depth):
"""Set all FIFO depths equal to a large value so that they can be profiled.

Args:
model (ModelGraph): The model to which FIFO depth optimization is applied.
profiling_fifo_depth (int): A large non-negative integer, must be larger than the max expected depth of the FIFOs.

Returns:
Dict[str, int]: A dictionary containing FIFO names as keys and their initial depths as values is returned for
comparison with the optimized depths.
"""

# filter all the output variables and keep only the internal FIFOs, excluding output objects that are not FIFOs and the
# input and output FIFOs as they can't be profiled and are implementation dependant i.e AXI Stream, AXI Master or
# connected to another IP
vars_to_profile = {
output_variable_name: output_variable
for output_variable_name, output_variable in model.output_vars.items()
if ("VivadoStreamVariable" in str(type(output_variable)))
and output_variable != model.get_output_variables()[0]
and output_variable != model.get_input_variables()[0]
}

# initialize all the fifos to `profiling_fifo_depth` so that they will be automatically implemented in BRAMs and so
# they will be profiled. Alternatively, "config_dataflow -override_user_fifo_depth profiling_fifo_depth" can be
# used inside build_prj.tcl to override all FIFO depths with the specified value
initial_fifo_depths = {}
for output_variable in vars_to_profile.values():
if output_variable.pragma:
initial_fifo_depths[output_variable.name] = int(output_variable.pragma[1])
output_variable.pragma = (output_variable.pragma[0], profiling_fifo_depth)

inp = model.get_input_variables()[0]
initial_fifo_depths['in_local'] = int(inp.pragma[1])
inp.pragma = (inp.pragma[0], profiling_fifo_depth)

outp = model.get_output_variables()[0]
initial_fifo_depths['out_local'] = int(outp.pragma[1])
outp.pragma = (outp.pragma[0], profiling_fifo_depth)
return initial_fifo_depths


def execute_cosim_to_profile_fifos(model):
"""Execute a cosimulation with a testh bench that calls the top function - Vitis IP at **least twice**,
to properly profile the max FIFO depths. The function will momentarily replace the initial test bench
with a suitable one for the optimization, and after the optimizer pass, the original test bench reinitialized.

Args:
model (ModelGraph): The model to which FIFO depth optimization is applied.
"""
model.write()

model.build(
reset=False,
csim=False,
synth=True,
cosim=True,
validation=False,
export=False,
vsynth=False,
fifo_opt=True,
)

return


def get_vitis_optimized_fifo_depths(model):
"""Parse the files generated by the cosimulation to retrieve the optimized depths for the FIFOs.
Attention, only the FIFOs between the layers are profiled!

Args:
model (ModelGraph): The model to which FIFO depth optimization is applied.

Returns:
Dict[str, int]: A dictionary that contains the FIFO names as keys and the optimized depths as values.
"""
# channel.zip is generated after the cosimulation and contains the chan_status*.csv files
# in the chan_status*.csv files the max depth achieved during cosimulation can be found at the last (4th) line
path_to_zip_file = (
model.config.get_output_dir()
+ "/"
+ model.config.get_project_name()
+ "_prj"
+ "/solution1/.autopilot/db/channel_depth_info/"
)

os.system(f"unzip -q -o {path_to_zip_file}channel.zip -d {path_to_zip_file}")

# the channel_info.csv file contains the mapping of each fifo name (i.e layer4_out_U) to the respective
# chan_status*.csv file
names_file_path = (
model.config.get_output_dir()
+ "/"
+ model.config.get_project_name()
+ "_prj"
+ "/solution1/.autopilot/db/channel_info.csv"
)

csv_fifo_depth_files = {}
with open(names_file_path) as names_file:
for line in names_file:
layer_name = line.split(",")[1]
csv_file_name = line.split(",")[3][:-1]
csv_fifo_depth_files[layer_name] = csv_file_name

optmized_fifo_depths = {}
for layer_name, file_name in csv_fifo_depth_files.items():
with open(path_to_zip_file + file_name) as chan_status_file:
lines = chan_status_file.readlines()
optmized_fifo_depths[layer_name[:-2]] = int(
lines[-1]
) # remove "_U" from the layer name string and keep the last line of the file that contains the max depth

return optmized_fifo_depths


def generate_depths_file(model, initial_fifo_depths, optimized_fifo_depths):
"""Generate a json file with the names of the FIFOs, the initial depths set by hls4ml and their optimized depths,
for post-processing. The json file is not used by the rest of the pipeline, it is only produced for the user.

Args:
model (ModelGraph): The model to which FIFO depth optimization is applied.
initial_fifo_depths (Dict[str, int]): A dictionary that contains the FIFO names as keys and the initial
depths as values.
optmized_fifo_depths (Dict[str, int]): A dictionary that contains the FIFO names as keys and the optimized
depths as values.
"""
depths = {}
for fifo_name in initial_fifo_depths.keys():
depths[fifo_name] = {}
depths[fifo_name]['initial'] = initial_fifo_depths[fifo_name]
depths[fifo_name]['optimized'] = optimized_fifo_depths[fifo_name]

with open(model.config.get_output_dir() + "/fifo_depths.json", "w") as f:
json.dump(depths, f, indent=4)


def set_optimized_fifo_depths(model, optimized_fifo_depths):
"""Set the new optimized FIFO depths.

Args:
model (ModelGraph): The model to which FIFO depth optimization is applied.
optmized_fifo_depths (Dict[str, int]): A dictionary that contains the FIFO names as keys and the optimized
depths as values.
"""

# iterate through the layer output FIFOs
for output_variable in model.output_vars.values():
if (
("VivadoStreamVariable" in str(type(output_variable)))
or (output_variable.name == 'in_local')
or (output_variable.name == 'out_local')
):
if output_variable.pragma:

if output_variable.name not in optimized_fifo_depths.keys():
continue

filtered_depth = optimized_fifo_depths[output_variable.name]
output_variable.pragma = (output_variable.pragma[0], filtered_depth)

inp = model.get_input_variables()[0]
inp.pragma = (inp.pragma[0], optimized_fifo_depths['in_local'])

outp = model.get_output_variables()[0]
outp.pragma = (inp.pragma[0], optimized_fifo_depths['out_local'])
return


class FifoDepthOptimization(ConfigurableOptimizerPass, ModelOptimizerPass):
def __init__(self):
pass

def transform(self, model):
"""Perform FIFO depth optimization between the FIFOs of all layers to reduce resource utilization as the
initial FIFOs set by hls4ml might be larger than required. At the end of the optimization the FIFOs will
have the largest depths achieved during cosimulation without causing any deadlocks between the layers
(producer-consumer), thus no additional delays between the layers. In some cases, this optimization
might lead to bigger FIFOs than initially set by the hls4ml tool in order to prevent deadlocks.

Args:
model (ModelGraph): The model to which FIFO depth optimization is applied.

Raises:
ValueError: If the FIFO depth for profiling provided by the user is not a non-negative integer.
RuntimeError: If the IO type is not set to "io_stream".

Returns:
bool: The execution state of the Optimzer Pass
"""

# use `large_fifo_depth = 0` to keep the default fifo depth
# consider changing 100_000 either with a very very large value > of any total bram storage space
# or via vitis 2023.2 c-simulation
profiling_fifo_depth = getattr(self, "profiling_fifo_depth", 100_000)

if not isinstance(profiling_fifo_depth, int) or profiling_fifo_depth <= 0:
raise ValueError("The FIFO depth for profiling (profiling_fifo_depth variable) must be a positive integer.")

# check axi-stream or io-stream
if not (model.config.get_config_value("IOType") == "io_stream"):
raise RuntimeError("To use this optimization you have to set `IOType` field to `io_stream` in the HLS config.")

initial_fifo_depths = initialize_large_fifos(model, profiling_fifo_depth)

execute_cosim_to_profile_fifos(model)

optimized_fifo_depths = get_vitis_optimized_fifo_depths(model)

generate_depths_file(model, initial_fifo_depths, optimized_fifo_depths)

set_optimized_fifo_depths(model, optimized_fifo_depths)

print("[hls4ml] - FIFO optimization completed")
return False
14 changes: 14 additions & 0 deletions hls4ml/backends/vitis_accelerator_ip_flow/supported_boards.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"pynq-z2": {
"part": "xc7z020clg400-1",
"tcl_scripts": {"axi_lite": "axi_lite_design.tcl", "axi_stream": "axi_stream_design.tcl"},
"python_drivers": {"axi_stream": "axi_stream_driver.py"},
"c_drivers": {}
},
"zcu102": {
"part": "xczu9eg-ffvb1156-2-e",
"tcl_scripts": { "axi_stream": "axi_stream_design.tcl"},
"python_drivers": {"axi_stream": "axi_stream_driver.py"},
"c_drivers": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import os

from hls4ml.backends import VitisBackend, VivadoBackend
from hls4ml.model.flow import register_flow
from hls4ml.report import parse_vivado_report


class VitisAcceleratorIPFlowBackend(VitisBackend):
def __init__(self):
super(VivadoBackend, self).__init__(name='VitisAcceleratorIPFlow')
Copy link
Contributor

@nghielme nghielme Feb 22, 2025

Choose a reason for hiding this comment

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

Why calling super(VivadoBackend, self) and not super(VitisBackend, self)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it does not work cause VitisBackend already sets the name. I could find a workaround

Copy link
Contributor

@jmitrevs jmitrevs Mar 3, 2025

Choose a reason for hiding this comment

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

I think this is a result of the strange inheritance structure that we have. We will try to rationalize it in the future, so hopefully this can be updated then. But for now, given the mess our inheritance structure is, I think whatever works is fine.

self._register_layer_attributes()
self._register_flows()

def build(
self,
model,
reset=False,
csim=True,
synth=True,
cosim=False,
validation=False,
export=False,
vsynth=False,
fifo_opt=False,
bitfile=False,
):
# run the VitisBackend build
super().build(
model,
reset=reset,
csim=csim,
synth=synth,
cosim=cosim,
validation=validation,
export=export,
vsynth=vsynth,
fifo_opt=fifo_opt,
)

# now make a bitfile
if bitfile:
curr_dir = os.getcwd()
os.chdir(model.config.get_output_dir())
try:
os.system('vivado -mode batch -source design.tcl') # check if this is accepted as a command
except Exception:
print("Something went wrong, check the Vivado logs")
os.chdir(curr_dir)

return parse_vivado_report(model.config.get_output_dir())

def create_initial_config(
self,
board='pynq-z2',
part=None,
clock_period=5,
clock_uncertainty='12.5%',
io_type='io_parallel',
interface='axi_stream',
driver='python',
input_type='float',
output_type='float',
):
'''
Create initial accelerator config with default parameters

Args:
board: one of the keys defined in supported_boards.json
clock_period: clock period passed to hls project
io_type: io_parallel or io_stream
interface: `axi_stream`: generate hardware designs and drivers which exploit axi stream channels.
`axi_master`: generate hardware designs and drivers which exploit axi master channels.
`axi_lite` : generate hardware designs and drivers which exploit axi lite channels. (Don't use it
to exchange large amount of data)
driver: `python`: generates the python driver to use the accelerator in the PYNQ stack.
`c`: generates the c driver to use the accelerator bare-metal.
input_type: the wrapper input precision. Can be `float` or an `ap_type`. Note: VivadoAcceleratorBackend
will round the number of bits used to the next power-of-2 value.
output_type: the wrapper output precision. Can be `float` or an `ap_type`. Note:
VivadoAcceleratorBackend will round the number of bits used to the next power-of-2 value.
platform: development target platform

Returns:
populated config
'''
board = board if board is not None else 'pynq-z2'
config = super().create_initial_config(part, clock_period, clock_uncertainty, io_type)
config['AcceleratorConfig'] = {}
config['AcceleratorConfig']['Board'] = board
config['AcceleratorConfig']['Interface'] = interface # axi_stream, axi_master, axi_lite
config['AcceleratorConfig']['Driver'] = driver
config['AcceleratorConfig']['Precision'] = {}
config['AcceleratorConfig']['Precision']['Input'] = {}
config['AcceleratorConfig']['Precision']['Output'] = {}
config['AcceleratorConfig']['Precision']['Input'] = input_type # float, double or ap_fixed<a,b>
config['AcceleratorConfig']['Precision']['Output'] = output_type # float, double or ap_fixed<a,b>

return config

def get_default_flow(self):
return self._default_flow

def get_writer_flow(self):
return self._writer_flow

def _register_flows(self):
vitis_ip = 'vitis:ip'
writer_passes = ['make_stamp', 'vitisacceleratoripflow:write_hls']
self._writer_flow = register_flow('write', writer_passes, requires=['vitis:ip'], backend=self.name)
self._default_flow = vitis_ip

# Register the fifo depth optimization flow which is different from the one for vivado
fifo_depth_opt_passes = [
'vitisacceleratoripflow:fifo_depth_optimization'
] + writer_passes # After optimization, a new project will be written

register_flow('fifo_depth_optimization', fifo_depth_opt_passes, requires=['vitis:ip'], backend=self.name)
Loading
Loading