Skip to content

Commit

Permalink
Merge pull request #509 from nicologhielmetti/fifo_depth_merge
Browse files Browse the repository at this point in the history
FIFO depth optimization
  • Loading branch information
thesps committed Jul 21, 2022
2 parents 794e371 + d0ec0f8 commit 5485c95
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 23 deletions.
90 changes: 90 additions & 0 deletions hls4ml/backends/vivado/passes/fifo_depth_optimization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import json

from pyDigitalWaveTools.vcd.parser import VcdParser

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


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


def set_big_fifos(vars_to_profile, profiling_fifo_depth):
for k, v in vars_to_profile.items():
v.pragma = (v.pragma[0], profiling_fifo_depth)


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

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()
return data


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


def set_fifo_depth(model, maxs):
for k, v in model.output_vars.items():
filtered_max = [x['max'] for x in maxs if v.name 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)


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

def transform(self, model):
# use `large_fifo_depth = 0` to keep the default fifo depth
profiling_fifo_depth = getattr(self, 'profiling_fifo_depth', 100_000)

# 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:
vars_to_profile = {k: v for k, v in model.output_vars.items() if v != model.get_output_variables()[0] and
v != model.get_input_variables()[0]}

set_big_fifos(vars_to_profile, profiling_fifo_depth)

data = get_vcd_data(model)

if len(data['children']) == 0:
print("FIFO depth optimization found no FIFOs implemented using BRAMs in the design, no optimization is possible. Consider increasing profiling_fifo_depth.")
return False

n_elem = len(data['children'][0]['children'][0]['children'])
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']
populate_values(self.values, name, data_p, depth)

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

generate_max_depth_file(model, maxs)

set_fifo_depth(model, maxs)

print('[hls4ml] - FIFO optimization completed')
return False
14 changes: 10 additions & 4 deletions hls4ml/backends/vivado/vivado_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,17 @@ def _register_flows(self):
]
self._writer_flow = register_flow('write', writer_passes, requires=['vivado:ip'], backend=self.name)

fifo_depth_opt_passes = [
'vivado:fifo_depth_optimization'
] + writer_passes # After optimization, a new project will be written

register_flow('fifo_depth_optimization', fifo_depth_opt_passes, requires=[self._writer_flow], 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 Expand Up @@ -105,16 +111,16 @@ def create_initial_config(self, part='xcku115-flvb2104-2-i', clock_period=5, io_

return config

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

curr_dir = os.getcwd()
os.chdir(model.config.get_output_dir())
os.system('vivado_hls -f build_prj.tcl "reset={reset} csim={csim} synth={synth} cosim={cosim} validation={validation} export={export} vsynth={vsynth}"'
.format(reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth))
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}"'
.format(reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth, fifo_opt=fifo_opt))
os.chdir(curr_dir)

return parse_vivado_report(model.config.get_output_dir())
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from hls4ml.backends.vivado.passes.fifo_depth_optimization import set_big_fifos, get_vcd_data, populate_values, \
generate_max_depth_file, set_fifo_depth
from hls4ml.model.optimizer.optimizer import ConfigurableOptimizerPass, ModelOptimizerPass


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

def transform(self, model):
# use `large_fifo_depth = 0` to keep the default fifo depth
profiling_fifo_depth = getattr(self, 'profiling_fifo_depth', 100_000)

# 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:
set_big_fifos(model.output_vars, profiling_fifo_depth)

data = get_vcd_data(model)

for i in range(1, len(data['children'][0]['children'][0]['children'])):
# wrapper fifos
populate_values(self.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']
populate_values(self.values, name, data_p, depth)

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

generate_max_depth_file(model, maxs)

set_fifo_depth(model, maxs)

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)

print('[hls4ml] - FIFO optimization completed')
return False
16 changes: 14 additions & 2 deletions hls4ml/backends/vivado_accelerator/vivado_accelerator_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ def __init__(self):
super(VivadoBackend, self).__init__(name='VivadoAccelerator')
self._register_flows()

def build(self, model, reset=False, csim=True, synth=True, cosim=False, validation=False, export=False, vsynth=False, bitfile=False):
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 VivadoBackend build
report = super().build(model, reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth)
report = super().build(model, reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth, fifo_opt=fifo_opt)
# Get Config to view Board and Platform
from hls4ml.backends import VivadoAcceleratorConfig
vivado_accelerator_config=VivadoAcceleratorConfig(model.config, model.get_input_variables(),model.get_output_variables())
Expand Down Expand Up @@ -98,8 +98,20 @@ def create_initial_config(self, board='pynq-z2', part=None, clock_period=5, io_t

return config

def get_default_flow(self):
return self._default_flow

def get_writer_flow(self):
return self._writer_flow

def _register_flows(self):
vivado_ip = 'vivado:ip'
writer_passes = ['make_stamp', 'vivadoaccelerator:write_hls']
self._writer_flow = register_flow('write', writer_passes, requires=[vivado_ip], backend=self.name)
self._default_flow = vivado_ip

fifo_depth_opt_passes = [
'vivadoaccelerator:fifo_depth_optimization'
] + writer_passes

register_flow('fifo_depth_optimization', fifo_depth_opt_passes, requires=[self._writer_flow], backend=self.name)
17 changes: 11 additions & 6 deletions hls4ml/report/vivado_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,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 @@ -109,7 +114,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

0 comments on commit 5485c95

Please sign in to comment.