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 all 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
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
thesps marked this conversation as resolved.
Show resolved Hide resolved

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'])
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']
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