Skip to content

Commit ee73ffb

Browse files
authored
Helpers for CMSSW emulation (#1336)
* attempt to add an defines for emulation in CMSSW * add emulator wrapper * add some support for io_stream on the hls4ml side * add pytest for emulation
1 parent 71123c8 commit ee73ffb

File tree

5 files changed

+64
-0
lines changed

5 files changed

+64
-0
lines changed

hls4ml/backends/vitis/vitis_backend.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def create_initial_config(
6464
namespace=None,
6565
write_weights_txt=True,
6666
write_tar=False,
67+
write_emulation_constants=False,
6768
tb_output_stream='both',
6869
**_,
6970
):
@@ -79,6 +80,8 @@ def create_initial_config(
7980
write_weights_txt (bool, optional): If True, writes weights to .txt files which speeds up compilation.
8081
Defaults to True.
8182
write_tar (bool, optional): If True, compresses the output directory into a .tar.gz file. Defaults to False.
83+
write_emulation_constants (bool, optional): If True, write constants to define.h useful for emulation.
84+
Defaults to False.
8285
tb_output_stream (str, optional): Controls where to write the output. Options are 'stdout', 'file' and 'both'.
8386
Defaults to 'both'.
8487
@@ -97,6 +100,7 @@ def create_initial_config(
97100
'WriteWeightsTxt': write_weights_txt,
98101
'WriteTar': write_tar,
99102
'TBOutputStream': tb_output_stream,
103+
'WriteEmulationConstants': write_emulation_constants,
100104
}
101105

102106
return config

hls4ml/templates/vivado/firmware/defines.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
#include "ap_fixed.h"
55
#include "ap_int.h"
66
#include "nnet_utils/nnet_types.h"
7+
#include <array>
78
#include <cstddef>
89
#include <cstdio>
10+
#include <tuple>
911
// hls-fpga-machine-learning insert headers
1012

1113
// hls-fpga-machine-learning insert namespace-start
@@ -14,6 +16,8 @@
1416

1517
// hls-fpga-machine-learning insert layer-precision
1618

19+
// hls-fpga-machine-learning insert emulator-defines
20+
1721
// hls-fpga-machine-learning insert namespace-end
1822

1923
#endif

hls4ml/templates/vivado/firmware/myproject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ void myproject(
1414
// hls-fpga-machine-learning insert header
1515
);
1616

17+
// hls-fpga-machine-learning insert emulator-defines
18+
1719
// hls-fpga-machine-learning insert namespace-end
1820

1921
#endif

hls4ml/writer/vivado_writer.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,34 @@ def write_project_header(self, model):
322322
if namespace is not None:
323323
newline += '}\n'
324324

325+
elif '// hls-fpga-machine-learning insert emulator-defines' in line:
326+
newline = line
327+
328+
if model.config.get_writer_config().get('WriteEmulationConstants', False):
329+
brams_def_str = ', '.join([b.definition_cpp(as_reference=False) for b in model_brams])
330+
brams_call_str = ', '.join([b.name for b in model_brams])
331+
332+
if model.config.get_config_value('IOType') == 'io_stream':
333+
input_call_str = ', '.join([f'std::get<{n}>(inputs)' for n in range(len(model_inputs))])
334+
output_call_str = ', '.join([f'std::get<{n}>(outputs)' for n in range(len(model_outputs))])
335+
else:
336+
input_call_str = ', '.join([f'std::get<{n}>(inputs).data()' for n in range(len(model_inputs))])
337+
output_call_str = ', '.join([f'std::get<{n}>(outputs).data()' for n in range(len(model_outputs))])
338+
339+
newline += (
340+
f'\ninline void {model.config.get_project_name()}_emulator('
341+
'inputs_t& inputs, outputs_t& outputs' # the inputs_t should ideally be const
342+
)
343+
if len(model_brams) > 0:
344+
newline += ',\n' + brams_def_str
345+
newline += ') {\n'
346+
newline += indent + model.config.get_project_name() + '(\n'
347+
newline += indent + indent + input_call_str + ',\n'
348+
newline += indent + indent + output_call_str
349+
if len(model_brams) > 0:
350+
newline += ',\n' + indent + indent + brams_call_str
351+
newline += '\n' + indent + ');\n}\n'
352+
325353
else:
326354
newline = line
327355
fout.write(newline)
@@ -385,6 +413,20 @@ def write_defines(self, model):
385413
if namespace is not None:
386414
newline += '}\n'
387415

416+
elif '// hls-fpga-machine-learning insert emulator-defines' in line:
417+
newline = line
418+
419+
if model.config.get_writer_config().get('WriteEmulationConstants', False):
420+
if model.config.get_config_value('IOType') == 'io_stream':
421+
input_types = [f'hls::stream<{v.type.name}>' for v in model.get_input_variables()]
422+
output_types = [f'hls::stream<{v.type.name}>' for v in model.get_output_variables()]
423+
else:
424+
input_types = [f'std::array<{v.type.name}, {v.size_cpp()}>' for v in model.get_input_variables()]
425+
output_types = [f'std::array<{v.type.name}, {v.size_cpp()}>' for v in model.get_output_variables()]
426+
input_types_str = ', '.join(input_types)
427+
output_types_str = ', '.join(output_types)
428+
newline += '\n' + f'using inputs_t = std::tuple<{input_types_str}>;'
429+
newline += '\n' + f'using outputs_t = std::tuple<{output_types_str}>;\n'
388430
else:
389431
newline = line
390432
fout.write(newline)

test/pytest/test_writer_config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ def test_namespace(keras_model, namespace, io_type, backend):
3333
hls_model.compile() # It's enough that the model compiles
3434

3535

36+
@pytest.mark.parametrize('io_type', ['io_stream', 'io_parallel'])
37+
@pytest.mark.parametrize('backend', ['Vitis']) # Only Vitis is supported
38+
def test_emulator(keras_model, io_type, backend):
39+
40+
config = hls4ml.utils.config_from_keras_model(keras_model, granularity='name', backend=backend)
41+
odir = str(test_root_path / f'hls4mlprj_emulation_{backend}_{io_type}')
42+
hls_model = hls4ml.converters.convert_from_keras_model(
43+
keras_model, hls_config=config, io_type=io_type, output_dir=odir, write_emulation_constants=True, backend=backend
44+
)
45+
hls_model.compile() # It's enough that the model compiles
46+
47+
3648
@pytest.mark.parametrize('backend', ['Vivado', 'Vitis']) # No Quartus for now
3749
@pytest.mark.parametrize('write_tar', [True, False])
3850
def test_write_tar(keras_model, write_tar, backend):

0 commit comments

Comments
 (0)