Skip to content

Commit 5485c95

Browse files
authored
Merge pull request #509 from nicologhielmetti/fifo_depth_merge
FIFO depth optimization
2 parents 794e371 + d0ec0f8 commit 5485c95

File tree

10 files changed

+329
-23
lines changed

10 files changed

+329
-23
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import json
2+
3+
from pyDigitalWaveTools.vcd.parser import VcdParser
4+
5+
from hls4ml.model.optimizer.optimizer import ConfigurableOptimizerPass, ModelOptimizerPass
6+
7+
8+
def populate_values(values, name, data, depth):
9+
values.append({'name': name, 'data': [], 'max': 0, 'depth': 0})
10+
get_values = lambda x: int(x[1][1:], 2)
11+
values[-1]['data'] = [get_values(x) for x in data]
12+
values[-1]['max'] = max(values[-1]['data'])
13+
values[-1]['depth'] = int(depth[0][1][1:], 2)
14+
return values
15+
16+
17+
def set_big_fifos(vars_to_profile, profiling_fifo_depth):
18+
for k, v in vars_to_profile.items():
19+
v.pragma = (v.pragma[0], profiling_fifo_depth)
20+
21+
22+
def get_vcd_data(model):
23+
model.write()
24+
model.build(reset=False, csim=True, synth=True, cosim=True, validation=False, export=False, vsynth=False,
25+
fifo_opt=True)
26+
27+
with open(
28+
model.config.get_output_dir() + '/' + model.config.get_project_name() + '_prj' + '/solution1/sim/verilog/fifo_opt.vcd') as vcd_file:
29+
vcd = VcdParser()
30+
vcd.parse(vcd_file)
31+
data = vcd.scope.toJson()
32+
return data
33+
34+
35+
def generate_max_depth_file(model, maxs):
36+
with open(model.config.get_output_dir() + '/max_depth.json', 'w') as f:
37+
json.dump(maxs, f, indent=4)
38+
39+
40+
def set_fifo_depth(model, maxs):
41+
for k, v in model.output_vars.items():
42+
filtered_max = [x['max'] for x in maxs if v.name in x['name']]
43+
if len(filtered_max) == 0:
44+
continue
45+
if len(filtered_max) > 1:
46+
print('WARNING! Check names of FIFOs')
47+
v.pragma = (v.pragma[0], filtered_max[0] + 1)
48+
49+
50+
class FifoDepthOptimization(ConfigurableOptimizerPass, ModelOptimizerPass):
51+
def __init__(self):
52+
self.values = []
53+
54+
def transform(self, model):
55+
# use `large_fifo_depth = 0` to keep the default fifo depth
56+
profiling_fifo_depth = getattr(self, 'profiling_fifo_depth', 100_000)
57+
58+
# check axi-stream or io-stream, if not one the 2 exit
59+
if not (model.config.get_config_value('IOType') == 'io_stream'):
60+
raise Exception('To use this optimization you have to set `IOType` field to `io_stream` in the HLS config')
61+
62+
# initialize all the fifos to `profiling_fifo_depth` so that they will be automatically implemented in BRAMs
63+
# and so they will be profiled
64+
if profiling_fifo_depth:
65+
vars_to_profile = {k: v for k, v in model.output_vars.items() if v != model.get_output_variables()[0] and
66+
v != model.get_input_variables()[0]}
67+
68+
set_big_fifos(vars_to_profile, profiling_fifo_depth)
69+
70+
data = get_vcd_data(model)
71+
72+
if len(data['children']) == 0:
73+
print("FIFO depth optimization found no FIFOs implemented using BRAMs in the design, no optimization is possible. Consider increasing profiling_fifo_depth.")
74+
return False
75+
76+
n_elem = len(data['children'][0]['children'][0]['children'])
77+
for i in range(n_elem):
78+
name = data['children'][0]['children'][0]['children'][i]['name']
79+
data_p = data['children'][0]['children'][0]['children'][i]['children'][0]['data']
80+
depth = data['children'][0]['children'][0]['children'][i]['children'][1]['data']
81+
populate_values(self.values, name, data_p, depth)
82+
83+
maxs = [{'name': i['name'], 'max': i['max'], 'depth': i['depth']} for i in self.values]
84+
85+
generate_max_depth_file(model, maxs)
86+
87+
set_fifo_depth(model, maxs)
88+
89+
print('[hls4ml] - FIFO optimization completed')
90+
return False

hls4ml/backends/vivado/vivado_backend.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,17 @@ def _register_flows(self):
7272
]
7373
self._writer_flow = register_flow('write', writer_passes, requires=['vivado:ip'], backend=self.name)
7474

75+
fifo_depth_opt_passes = [
76+
'vivado:fifo_depth_optimization'
77+
] + writer_passes # After optimization, a new project will be written
78+
79+
register_flow('fifo_depth_optimization', fifo_depth_opt_passes, requires=[self._writer_flow], backend=self.name)
80+
7581
all_passes = get_backend_passes(self.name)
7682

7783
extras = [
7884
# Ideally this should be empty
79-
opt_pass for opt_pass in all_passes if opt_pass not in initializers + streaming_passes + quantization_passes + optimization_passes + vivado_types + templates + writer_passes
85+
opt_pass for opt_pass in all_passes if opt_pass not in initializers + streaming_passes + quantization_passes + optimization_passes + vivado_types + templates + writer_passes + fifo_depth_opt_passes
8086
]
8187

8288
if len(extras) > 0:
@@ -105,16 +111,16 @@ def create_initial_config(self, part='xcku115-flvb2104-2-i', clock_period=5, io_
105111

106112
return config
107113

108-
def build(self, model, reset=False, csim=True, synth=True, cosim=False, validation=False, export=False, vsynth=False):
114+
def build(self, model, reset=False, csim=True, synth=True, cosim=False, validation=False, export=False, vsynth=False, fifo_opt=False):
109115
if 'linux' in sys.platform:
110116
found = os.system('command -v vivado_hls > /dev/null')
111117
if found != 0:
112118
raise Exception('Vivado HLS installation not found. Make sure "vivado_hls" is on PATH.')
113119

114120
curr_dir = os.getcwd()
115121
os.chdir(model.config.get_output_dir())
116-
os.system('vivado_hls -f build_prj.tcl "reset={reset} csim={csim} synth={synth} cosim={cosim} validation={validation} export={export} vsynth={vsynth}"'
117-
.format(reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth))
122+
os.system('vivado_hls -f build_prj.tcl "reset={reset} csim={csim} synth={synth} cosim={cosim} validation={validation} export={export} vsynth={vsynth} fifo_opt={fifo_opt}"'
123+
.format(reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth, fifo_opt=fifo_opt))
118124
os.chdir(curr_dir)
119125

120126
return parse_vivado_report(model.config.get_output_dir())

hls4ml/backends/vivado_accelerator/passes/__init__.py

Whitespace-only changes.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from hls4ml.backends.vivado.passes.fifo_depth_optimization import set_big_fifos, get_vcd_data, populate_values, \
2+
generate_max_depth_file, set_fifo_depth
3+
from hls4ml.model.optimizer.optimizer import ConfigurableOptimizerPass, ModelOptimizerPass
4+
5+
6+
class FifoDepthOptimization(ConfigurableOptimizerPass, ModelOptimizerPass):
7+
def __init__(self):
8+
self.values = []
9+
10+
def transform(self, model):
11+
# use `large_fifo_depth = 0` to keep the default fifo depth
12+
profiling_fifo_depth = getattr(self, 'profiling_fifo_depth', 100_000)
13+
14+
# check axi-stream or io-stream, if not one the 2 exit
15+
if not(model.config.get_config_value('IOType') == 'io_stream' or
16+
model.config.get_config_value('AcceleratorConfig')['Interface'] == 'axi_stream' or
17+
model.config.get_config_value('AcceleratorConfig')['Interface'] == 'axi_master'):
18+
raise Exception('To use this optimization you have to set `IOType` field to `io_stream` in the HLS config '
19+
'or `axi_stream` or `axi_master` in `AcceleratorConfig` interface field')
20+
21+
# initialize all the fifos to 10000 so that they will be automatically implemented in BRAMs and so they will be
22+
# profiled
23+
24+
if profiling_fifo_depth:
25+
set_big_fifos(model.output_vars, profiling_fifo_depth)
26+
27+
data = get_vcd_data(model)
28+
29+
for i in range(1, len(data['children'][0]['children'][0]['children'])):
30+
# wrapper fifos
31+
populate_values(self.values,
32+
data['children'][0]['children'][0]['children'][i]['name'],
33+
data['children'][0]['children'][0]['children'][i]['children'][0]['data'],
34+
data['children'][0]['children'][0]['children'][i]['children'][1]['data'])
35+
36+
n_elem = len(data['children'][0]['children'][0]['children'][0]['children'])
37+
for i in range(n_elem):
38+
name = data['children'][0]['children'][0]['children'][0]['children'][i]['name']
39+
data_p = data['children'][0]['children'][0]['children'][0]['children'][i]['children'][0]['data']
40+
depth = data['children'][0]['children'][0]['children'][0]['children'][i]['children'][1]['data']
41+
populate_values(self.values, name, data_p, depth)
42+
43+
maxs = [{'name': i['name'], 'max': i['max'], 'depth': i['depth']} for i in self.values]
44+
45+
generate_max_depth_file(model, maxs)
46+
47+
set_fifo_depth(model, maxs)
48+
49+
inp = model.get_input_variables()[0]
50+
out = model.get_output_variables()[0]
51+
for x in maxs:
52+
if 'in_local' in x['name']:
53+
inp.pragma = (inp.pragma[0], x['max'] + 1)
54+
elif 'out_local' in x['name']:
55+
out.pragma = (out.pragma[0], x['max'] + 1)
56+
57+
print('[hls4ml] - FIFO optimization completed')
58+
return False

hls4ml/backends/vivado_accelerator/vivado_accelerator_backend.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ def __init__(self):
99
super(VivadoBackend, self).__init__(name='VivadoAccelerator')
1010
self._register_flows()
1111

12-
def build(self, model, reset=False, csim=True, synth=True, cosim=False, validation=False, export=False, vsynth=False, bitfile=False):
12+
def build(self, model, reset=False, csim=True, synth=True, cosim=False, validation=False, export=False, vsynth=False, fifo_opt=False, bitfile=False):
1313
# run the VivadoBackend build
14-
report = super().build(model, reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth)
14+
report = super().build(model, reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth, fifo_opt=fifo_opt)
1515
# Get Config to view Board and Platform
1616
from hls4ml.backends import VivadoAcceleratorConfig
1717
vivado_accelerator_config=VivadoAcceleratorConfig(model.config, model.get_input_variables(),model.get_output_variables())
@@ -98,8 +98,20 @@ def create_initial_config(self, board='pynq-z2', part=None, clock_period=5, io_t
9898

9999
return config
100100

101+
def get_default_flow(self):
102+
return self._default_flow
103+
104+
def get_writer_flow(self):
105+
return self._writer_flow
106+
101107
def _register_flows(self):
102108
vivado_ip = 'vivado:ip'
103109
writer_passes = ['make_stamp', 'vivadoaccelerator:write_hls']
104110
self._writer_flow = register_flow('write', writer_passes, requires=[vivado_ip], backend=self.name)
105111
self._default_flow = vivado_ip
112+
113+
fifo_depth_opt_passes = [
114+
'vivadoaccelerator:fifo_depth_optimization'
115+
] + writer_passes
116+
117+
register_flow('fifo_depth_optimization', fifo_depth_opt_passes, requires=[self._writer_flow], backend=self.name)

hls4ml/report/vivado_report.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,22 @@ def read_vivado_report(hls_dir, full_report=False):
3131
print('Reports for solution "{}":\n'.format(sln))
3232
_find_reports(sln_dir + '/' + sln, top_func_name, full_report)
3333

34-
def _parse_build_script(script_path):
34+
def _parse_build_script(path):
3535
prj_dir = None
3636
top_func_name = None
3737

38-
with open(script_path, 'r') as f:
38+
build_path = path + '/build_prj.tcl'
39+
project_path = path + '/project.tcl'
40+
with open(build_path, 'r') as f:
3941
for line in f.readlines():
40-
if 'open_project' in line:
41-
prj_dir = line.split()[-1]
42-
elif 'set_top' in line:
42+
if 'set_top' in line:
4343
top_func_name = line.split()[-1]
4444

45+
with open(project_path, 'r') as f:
46+
for line in f.readlines():
47+
if 'set myproject' in line:
48+
prj_dir = line.split('"')[-2] + '_prj'
49+
4550
return prj_dir, top_func_name
4651

4752
def _find_solutions(sln_dir):
@@ -109,7 +114,7 @@ def parse_vivado_report(hls_dir):
109114
top_func_name = None
110115

111116
if os.path.isfile(hls_dir + '/build_prj.tcl'):
112-
prj_dir, top_func_name = _parse_build_script(hls_dir + '/build_prj.tcl')
117+
prj_dir, top_func_name = _parse_build_script(hls_dir)
113118

114119
if prj_dir is None or top_func_name is None:
115120
print('Unable to read project data. Exiting.')

0 commit comments

Comments
 (0)