-
Notifications
You must be signed in to change notification settings - Fork 472
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
base: main
Are you sure you want to change the base?
Changes from 107 commits
312832f
d2b5a15
02659dd
56296b6
7dd0173
6f181b8
bd2e52e
b795240
eeb04d4
7e47c85
5a2a38f
99f9429
b9609dc
4f69c16
014a7b2
723073e
290896b
c9dfcf2
d3b8e20
b32984f
98273a0
1640c4b
2a71a83
6ac964c
ec95e01
5de1bf5
a6fec36
1b72b19
28521d0
a44707d
cefab60
440901b
c351a02
4d9d35a
32ae9b6
932b01e
6a65fed
41b7e98
24253e1
6ee8189
b5add0c
665c904
b366d24
f0ca865
1e416b5
2a5d8de
3969523
52252ca
be56b93
b0085a1
44bc8f3
a7826e0
41ab6af
c0f8d9f
bcfd685
8c09595
11819ac
39d9232
68a83d6
8a9d556
ad86387
4ea329b
992b9b7
8174465
84ff2c6
518796d
238e35c
c10dd82
d6fe369
7290a29
13fcf0a
92e7222
f12a7ea
4d24e4e
e2d270e
fa6bd66
b42210d
1303bba
8d3a1f2
a8e0497
48686d3
bae450b
dde9124
663181f
e32f4d0
c52ec75
9d9e645
80697c0
f467829
4c74550
542b950
c78aec2
62b5c27
d5f2192
34b0929
14b413e
800423f
9f1c8b3
4763692
e66ad40
f51be88
85c233c
b91b641
5bc54d3
0a0d7d1
da4f8b5
e55c52e
a7d6ea7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it does not work cause There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) |
Uh oh!
There was an error while loading. Please reload this page.