Skip to content

Commit 608c136

Browse files
committed
Initial implementation of Libero backend for PolarFire line of FPGAs
1 parent 2366f9b commit 608c136

33 files changed

+5059
-0
lines changed

hls4ml/backends/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from hls4ml.backends.backend import Backend, get_available_backends, get_backend, register_backend # noqa: F401
22
from hls4ml.backends.fpga.fpga_backend import FPGABackend # noqa: F401
3+
from hls4ml.backends.libero.libero_backend import LiberoBackend
34
from hls4ml.backends.oneapi.oneapi_backend import OneAPIBackend
45
from hls4ml.backends.quartus.quartus_backend import QuartusBackend
56
from hls4ml.backends.symbolic.symbolic_backend import SymbolicExpressionBackend
@@ -18,3 +19,4 @@
1819
register_backend('Catapult', CatapultBackend)
1920
register_backend('SymbolicExpression', SymbolicExpressionBackend)
2021
register_backend('oneAPI', OneAPIBackend)
22+
register_backend('Libero', LiberoBackend)

hls4ml/backends/libero/__init__.py

Whitespace-only changes.
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import os
2+
import subprocess
3+
import sys
4+
5+
from hls4ml.backends import FPGABackend
6+
from hls4ml.model.attributes import ChoiceAttribute
7+
from hls4ml.model.flow import register_flow
8+
from hls4ml.model.layers import Dense, Layer
9+
from hls4ml.model.optimizer import layer_optimizer
10+
from hls4ml.report import parse_libero_report
11+
12+
13+
class LiberoBackend(FPGABackend):
14+
def __init__(self):
15+
super().__init__(name='Libero')
16+
self._register_layer_attributes()
17+
self._register_flows()
18+
19+
def _register_layer_attributes(self):
20+
strategy_layers = [
21+
Dense,
22+
]
23+
24+
for layer in strategy_layers:
25+
attrs = self.attribute_map.get(layer, [])
26+
attrs.append(
27+
ChoiceAttribute(
28+
'strategy',
29+
choices=['Latency', 'Resource'],
30+
default='Latency',
31+
)
32+
)
33+
self.attribute_map[layer] = attrs
34+
35+
def _register_flows(self):
36+
initializers = self._get_layer_initializers()
37+
init_flow = register_flow('init_layers', initializers, requires=['optimize'], backend=self.name)
38+
39+
libero_types = [
40+
'libero:transform_types',
41+
'libero:set_pipeline_style',
42+
]
43+
libero_types_flow = register_flow('specific_types', libero_types, requires=[init_flow], backend=self.name)
44+
45+
template_flow = register_flow('apply_templates', self._get_layer_templates, requires=[init_flow], backend=self.name)
46+
47+
writer_passes = ['make_stamp', 'libero:write_hls']
48+
self._writer_flow = register_flow('write', writer_passes, requires=['libero:ip'], backend=self.name)
49+
50+
ip_flow_requirements = [
51+
'optimize',
52+
init_flow,
53+
libero_types_flow,
54+
template_flow,
55+
]
56+
57+
self._default_flow = register_flow('ip', None, requires=ip_flow_requirements, backend=self.name)
58+
59+
def get_default_flow(self):
60+
return self._default_flow
61+
62+
def get_writer_flow(self):
63+
return self._writer_flow
64+
65+
def create_initial_config(
66+
self,
67+
fpga_family='PolarFire',
68+
part='MPF300',
69+
board='hw_only',
70+
clock_period=5,
71+
clock_uncertainty='27%',
72+
io_type='io_parallel',
73+
namespace=None,
74+
write_weights_txt=True,
75+
write_tar=False,
76+
**_,
77+
):
78+
"""Create initial configuration of the Libero backend.
79+
80+
Args:
81+
part (str, optional): The FPGA part to be used. Defaults to 'MPF300'.
82+
clock_period (int, optional): The clock period. Defaults to 5.
83+
clock_uncertainty (str, optional): The clock uncertainty. Defaults to 27%.
84+
io_type (str, optional): Type of implementation used. One of
85+
'io_parallel' or 'io_stream'. Defaults to 'io_parallel'.
86+
namespace (str, optional): If defined, place all generated code within a namespace. Defaults to None.
87+
write_weights_txt (bool, optional): If True, writes weights to .txt files which speeds up compilation.
88+
Defaults to True.
89+
write_tar (bool, optional): If True, compresses the output directory into a .tar.gz file. Defaults to False.
90+
91+
Returns:
92+
dict: initial configuration.
93+
"""
94+
config = {}
95+
96+
config['FPGAFamily'] = fpga_family if fpga_family is not None else 'PolarFire'
97+
config['Part'] = part if part is not None else 'MPF300'
98+
config['Board'] = board if board is not None else 'hw_only'
99+
config['ClockPeriod'] = clock_period if clock_period is not None else 5
100+
config['IOType'] = io_type if io_type is not None else 'io_parallel'
101+
config['HLSConfig'] = {}
102+
config['WriterConfig'] = {
103+
'Namespace': namespace,
104+
'WriteWeightsTxt': write_weights_txt,
105+
'WriteTar': write_tar,
106+
}
107+
108+
return config
109+
110+
def build(
111+
self,
112+
model,
113+
reset=False,
114+
skip_preqs=False,
115+
sw_compile=True,
116+
hw=True,
117+
cosim=False,
118+
rtl_synth=False,
119+
fpga=False,
120+
**kwargs,
121+
):
122+
"""Build the model using Libero suite and SmartHLS compiler. Additional arguments passed to the function in form of
123+
`<arg>=True` will be passed as an argument to the `shls` command. See SmartHLS user guide for list of possible
124+
command line options.
125+
126+
Args:
127+
model (ModelGraph): Model to build
128+
reset (bool, optional): Clean up any existing files. Defaults to False.
129+
skip_preqs(bool, optional): Skip any prerequisite step that is outdated. Defaults to False.
130+
sw_compile (bool, optional): Compile the generated HLS in software. Defaults to True.
131+
hw (bool, optional): Compile the software to hardware, producing a set of Verilog HDL files. Defaults to True.
132+
cosim (bool, optional): Run co-simulation. Defaults to False.
133+
rtl_synth (bool, optional): Run RTL synthesis for resource results. This will take less time than `fpga`.
134+
Defaults to False.
135+
fpga (bool, optional): Synthesize the generated hardware to target FPGA. This runs RTL synthesis and
136+
place-and-route for resource and timing results. Defaults to False.
137+
138+
Raises:
139+
Exception: Raised if the `shls` command has not been found
140+
CalledProcessError: Raised if SmartHLS returns non-zero code for any of the commands executed
141+
142+
Returns:
143+
dict: Detailed report produced by SmartHLS.
144+
"""
145+
if 'linux' in sys.platform:
146+
found = os.system('command -v shls > /dev/null')
147+
if found != 0:
148+
raise Exception('Libero/SmartHLS installation not found. Make sure "shls" is on PATH.')
149+
150+
def run_shls_cmd(cmd_name):
151+
subprocess.run(
152+
['shls', '-s', cmd_name],
153+
shell=False,
154+
check=True,
155+
stdout=sys.stdout,
156+
stderr=sys.stderr,
157+
cwd=model.config.get_output_dir(),
158+
)
159+
160+
if reset:
161+
run_shls_cmd('clean')
162+
if sw_compile:
163+
run_shls_cmd('sw_compile')
164+
if hw:
165+
run_shls_cmd('hw')
166+
if cosim:
167+
run_shls_cmd('cosim')
168+
if rtl_synth:
169+
run_shls_cmd('rtl_synth')
170+
if fpga:
171+
run_shls_cmd('fpga')
172+
173+
for arg_name, arg_val in kwargs.items():
174+
if arg_val:
175+
run_shls_cmd(arg_name)
176+
177+
return parse_libero_report(model.config.get_output_dir())
178+
179+
@layer_optimizer(Layer)
180+
def init_base_layer(self, layer):
181+
reuse_factor = layer.model.config.get_reuse_factor(layer)
182+
layer.set_attr('reuse_factor', reuse_factor)
183+
184+
@layer_optimizer(Dense)
185+
def init_dense(self, layer):
186+
if layer.model.config.is_resource_strategy(layer):
187+
n_in, n_out = self.get_layer_mult_size(layer)
188+
self.set_target_reuse_factor(layer)
189+
self.set_closest_reuse_factor(layer, n_in, n_out)
190+
layer.set_attr('strategy', 'resource')
191+
else:
192+
layer.set_attr('strategy', 'latency')
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from hls4ml.backends.fpga.fpga_types import (
2+
ArrayVariableConverter,
3+
ExponentPrecisionType,
4+
FixedPrecisionConverter,
5+
FixedPrecisionType,
6+
InplaceStreamVariableConverter,
7+
IntegerPrecisionType,
8+
PrecisionDefinition,
9+
StreamVariableConverter,
10+
VariableDefinition,
11+
XnorPrecisionType,
12+
)
13+
14+
# region ArrayVariable
15+
16+
17+
class LiberoArrayVariableDefinition(VariableDefinition):
18+
def definition_cpp(self, name_suffix='', as_reference=False):
19+
return '{type} {name}{suffix}[{shape}]'.format(
20+
type=self.type.name, name=self.name, suffix=name_suffix, shape=self.size_cpp()
21+
)
22+
23+
24+
class LiberoInplaceArrayVariableDefinition(VariableDefinition):
25+
def definition_cpp(self):
26+
return f'auto& {self.name} = {self.input_var.name}'
27+
28+
29+
class LiberoArrayVariableConverter(ArrayVariableConverter):
30+
def __init__(self, type_converter):
31+
super().__init__(type_converter=type_converter, prefix='Libero', definition_cls=LiberoArrayVariableDefinition)
32+
33+
34+
class LiberoInplaceArrayVariableConverter(ArrayVariableConverter):
35+
def __init__(self, type_converter):
36+
super().__init__(type_converter=type_converter, prefix='Libero', definition_cls=LiberoInplaceArrayVariableDefinition)
37+
38+
39+
# endregion
40+
41+
# region StreamVariable
42+
43+
44+
class LiberoStreamVariableDefinition(VariableDefinition):
45+
def definition_cpp(self, name_suffix='', as_reference=False):
46+
if as_reference: # Function parameter
47+
return f'hls::FIFO<{self.type.name}> &{self.name}{name_suffix}'
48+
else: # Declaration
49+
return 'hls::FIFO<{type}> {name}{suffix}({depth})'.format(
50+
type=self.type.name, name=self.name, depth=self.pragma[1], suffix=name_suffix
51+
)
52+
53+
54+
class LiberoInplaceStreamVariableDefinition(VariableDefinition):
55+
def definition_cpp(self):
56+
return f'auto& {self.name} = {self.input_var.name}'
57+
58+
59+
class LiberoStreamVariableConverter(StreamVariableConverter):
60+
def __init__(self, type_converter):
61+
super().__init__(type_converter=type_converter, prefix='Libero', definition_cls=LiberoStreamVariableDefinition)
62+
63+
64+
# endregion
65+
66+
# region InplaceStreamVariable
67+
68+
69+
class LiberoInplaceStreamVariableConverter(InplaceStreamVariableConverter):
70+
def __init__(self, type_converter):
71+
super().__init__(
72+
type_converter=type_converter, prefix='Libero', definition_cls=LiberoInplaceStreamVariableDefinition
73+
)
74+
75+
76+
# endregion
77+
78+
# region Precision types
79+
80+
81+
class LAPIntegerPrecisionDefinition(PrecisionDefinition):
82+
def definition_cpp(self):
83+
typestring = 'hls::ap_{signed}int<{width}>'.format(signed='u' if not self.signed else '', width=self.width)
84+
return typestring
85+
86+
87+
class LAPFixedPrecisionDefinition(PrecisionDefinition):
88+
def _rounding_mode_cpp(self, mode):
89+
if mode is not None:
90+
return 'AP_' + str(mode)
91+
92+
def _saturation_mode_cpp(self, mode):
93+
if mode is not None:
94+
return 'AP_' + str(mode)
95+
96+
def definition_cpp(self):
97+
args = [
98+
self.width,
99+
self.integer,
100+
self._rounding_mode_cpp(self.rounding_mode),
101+
self._saturation_mode_cpp(self.saturation_mode),
102+
]
103+
if args[2] == 'AP_TRN' and args[3] == 'AP_WRAP':
104+
# This is the default, so we won't write the full definition for brevity
105+
args[2] = args[3] = None
106+
107+
args = ','.join([str(arg) for arg in args if arg is not None])
108+
typestring = 'hls::ap_{signed}fixpt<{args}>'.format(signed='u' if not self.signed else '', args=args)
109+
return typestring
110+
111+
112+
class LAPTypeConverter(FixedPrecisionConverter):
113+
def __init__(self):
114+
super().__init__(
115+
type_map={
116+
FixedPrecisionType: LAPFixedPrecisionDefinition,
117+
IntegerPrecisionType: LAPIntegerPrecisionDefinition,
118+
ExponentPrecisionType: LAPIntegerPrecisionDefinition,
119+
XnorPrecisionType: LAPIntegerPrecisionDefinition,
120+
},
121+
prefix='LAP',
122+
)
123+
124+
125+
# endregion

0 commit comments

Comments
 (0)