Skip to content
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

FIFO depth optimization #509

Merged
merged 16 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
73 changes: 73 additions & 0 deletions hls4ml/backends/vivado/passes/fifo_depth_optimization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import json

from pyDigitalWaveTools.vcd.parser import VcdParser
thesps marked this conversation as resolved.
Show resolved Hide resolved

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


class FifoDepthOptimization(ConfigurableOptimizerPass, ModelOptimizerPass):
def __init__(self):
self.values = []

def match(self, node):
return True

def _populate_values(self, name, data, depth):
self.values.append({'name': name, 'data': [], 'max': 0, 'depth': 0})
get_values = lambda x: int(x[1][1:], 2)
self.values[-1]['data'] = [get_values(x) for x in data]
self.values[-1]['max'] = max(self.values[-1]['data'])
self.values[-1]['depth'] = int(depth[0][1][1:], 2)

def transform(self, model):
model.fifo_opt = True
# use `large_fifo_depth = 0` to keep the default fifo depth
profiling_fifo_depth = getattr(self, 'profiling_fifo_depth', 100000)

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

# initialize all the fifos to `profiling_fifo_depth` so that they will be automatically implemented in BRAMs and so they will be
# profiled

if profiling_fifo_depth:

for k, v in model.output_vars.items():
if v == model.get_input_variables()[0] or v == model.get_output_variables()[0]:
continue
v.pragma = (v.pragma[0], profiling_fifo_depth)

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

with open(
model.config.get_output_dir() + '/' + model.config.get_project_name() + '_prj' + '/solution1/sim/verilog/fifo_opt.vcd') as vcd_file:
vcd = VcdParser()
vcd.parse(vcd_file)
data = vcd.scope.toJson()

n_elem = len(data['children'][0]['children'][0]['children'])
thesps marked this conversation as resolved.
Show resolved Hide resolved
for i in range(n_elem):
name = data['children'][0]['children'][0]['children'][i]['name']
data_p = data['children'][0]['children'][0]['children'][i]['children'][0]['data']
depth = data['children'][0]['children'][0]['children'][i]['children'][1]['data']
self._populate_values(name, data_p, depth)

maxs = [{'name': i['name'], 'max': i['max'], 'depth': i['depth']} for i in self.values]

with open(model.config.get_output_dir() + '/max_depth.json', 'w') as f:
json.dump(maxs, f, indent=4)

for k, v in model.output_vars.items():
filtered_max = [x['max'] for x in maxs if v.cppname in x['name']]
if len(filtered_max) == 0:
continue
if len(filtered_max) > 1:
print('WARNING! Check names of FIFOs')
thesps marked this conversation as resolved.
Show resolved Hide resolved
v.pragma = (v.pragma[0], filtered_max[0] + 1)

model.write()
print('[hls4ml] - FIFO optimization completed')
return False
8 changes: 7 additions & 1 deletion hls4ml/backends/vivado/vivado_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,17 @@ def _register_flows(self):
writer_flow_requirements = ['optimize', vivado_types_flow, template_flow]
self._writer_flow = register_flow('write', writer_passes, requires=writer_flow_requirements, backend=self.name)

fifo_depth_opt_passes = [
'vivado:fifo_depth_optimization'
]

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

all_passes = get_backend_passes(self.name)

extras = [
# Ideally this should be empty
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
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
]

if len(extras) > 0:
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import json

from pyDigitalWaveTools.vcd.parser import VcdParser

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


class FifoDepthOptimization(ConfigurableOptimizerPass, ModelOptimizerPass):
def __init__(self):
self.values = []

def match(self, node):
return True

def _populate_values(self, name, data, depth):
self.values.append({'name': name, 'data': [], 'max': 0, 'depth': 0})
get_values = lambda x: int(x[1][1:], 2)
self.values[-1]['data'] = [get_values(x) for x in data]
self.values[-1]['max'] = max(self.values[-1]['data'])
self.values[-1]['depth'] = int(depth[0][1][1:], 2)

def transform(self, model):
model.fifo_opt = True
# use `large_fifo_depth = 0` to keep the default fifo depth
profiling_fifo_depth = getattr(self, 'profiling_fifo_depth', 100000)

# check axi-stream or io-stream, if not one the 2 exit
if not(model.config.get_config_value('IOType') == 'io_stream' or
model.config.get_config_value('AcceleratorConfig')['Interface'] == 'axi_stream' or
model.config.get_config_value('AcceleratorConfig')['Interface'] == 'axi_master'):
raise Exception('To use this optimization you have to set `IOType` field to `io_stream` in the HLS config '
'or `axi_stream` or `axi_master` in `AcceleratorConfig` interface field')

# initialize all the fifos to 10000 so that they will be automatically implemented in BRAMs and so they will be
# profiled

if profiling_fifo_depth:

for k, v in model.output_vars.items():
v.pragma = (v.pragma[0], profiling_fifo_depth)

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

with open(
model.config.get_output_dir() + '/' + model.config.get_project_name() + '_prj' + '/solution1/sim/verilog/fifo_opt.vcd') as vcd_file:
vcd = VcdParser()
vcd.parse(vcd_file)
data = vcd.scope.toJson()

for i in range(1, len(data['children'][0]['children'][0]['children'])):
# wrapper fifos
self._populate_values(data['children'][0]['children'][0]['children'][i]['name'],
data['children'][0]['children'][0]['children'][i]['children'][0]['data'],
data['children'][0]['children'][0]['children'][i]['children'][1]['data'])

n_elem = len(data['children'][0]['children'][0]['children'][0]['children'])
for i in range(n_elem):
name = data['children'][0]['children'][0]['children'][0]['children'][i]['name']
data_p = data['children'][0]['children'][0]['children'][0]['children'][i]['children'][0]['data']
depth = data['children'][0]['children'][0]['children'][0]['children'][i]['children'][1]['data']
self._populate_values(name, data_p, depth)

maxs = [{'name': i['name'], 'max': i['max'], 'depth': i['depth']} for i in self.values]

with open(model.config.get_output_dir() + '/max_depth.json', 'w') as f:
json.dump(maxs, f, indent=4)

for k, v in model.output_vars.items():
filtered_max = [x['max'] for x in maxs if v.cppname in x['name']]
if len(filtered_max) == 0:
continue
if len(filtered_max) > 1:
print('WARNING! Check names of FIFOs')
v.pragma = (v.pragma[0], filtered_max[0] + 1)

inp = model.get_input_variables()[0]
out = model.get_output_variables()[0]
for x in maxs:
if 'in_local' in x['name']:
inp.pragma = (inp.pragma[0], x['max'] + 1)
elif 'out_local' in x['name']:
out.pragma = (out.pragma[0], x['max'] + 1)

model.write()
print('[hls4ml] - FIFO optimization completed')
return False
24 changes: 16 additions & 8 deletions hls4ml/backends/vivado_accelerator/vivado_accelerator_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,20 @@ def create_initial_config(self, board='pynq-z2', part=None, clock_period=5, io_t
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):
#TODO expand this to include new accelerator flow
parent_flows = get_backend_flows(backend='vivado')
for flow_name in parent_flows:
flow = get_flow(flow_name)
acc_flow = register_flow(flow_name.replace('vivado:', ''), flow.optimizers, requires=flow.requires, backend=self.name)
if ':write' in flow_name:
self._writer_flow = acc_flow
self._default_flow = 'vivadoaccelerator:ip'
vivado_writer = ['vivado:write']
vivado_accel_writer = ['vivadoaccelerator:write_hls']
self._writer_flow = register_flow('write', vivado_accel_writer, requires=vivado_writer, backend=self.name)
self._default_flow = 'vivado:ip'

fifo_depth_opt_passes = [
'vivadoaccelerator:fifo_depth_optimization'
]

register_flow('fifo_depth_optimization', fifo_depth_opt_passes, requires=['vivado:ip'], backend=self.name)
3 changes: 3 additions & 0 deletions hls4ml/converters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ def _check_hls_config(config, hls_config):
if 'LayerType' in hls_config:
config['HLSConfig']['LayerType'] = hls_config['LayerType']

if 'Flows' in hls_config:
config['HLSConfig']['Flows'] = hls_config['Flows']

if 'Optimizers' in hls_config:
config['HLSConfig']['Optimizers'] = hls_config['Optimizers']

Expand Down
17 changes: 11 additions & 6 deletions hls4ml/report/vivado_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,22 @@ def read_vivado_report(hls_dir, full_report=False):
print('Reports for solution "{}":\n'.format(sln))
_find_reports(sln_dir + '/' + sln, top_func_name, full_report)

def _parse_build_script(script_path):
def _parse_build_script(path):
prj_dir = None
top_func_name = None

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

with open(project_path, 'r') as f:
for line in f.readlines():
if 'set myproject' in line:
prj_dir = line.split('"')[-2] + '_prj'

return prj_dir, top_func_name

def _find_solutions(sln_dir):
Expand Down Expand Up @@ -105,7 +110,7 @@ def parse_vivado_report(hls_dir):
top_func_name = None

if os.path.isfile(hls_dir + '/build_prj.tcl'):
prj_dir, top_func_name = _parse_build_script(hls_dir + '/build_prj.tcl')
prj_dir, top_func_name = _parse_build_script(hls_dir)

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