From 55591304d27daff8a0130f710576c1734a1349b7 Mon Sep 17 00:00:00 2001 From: Jovan Mitrevski Date: Thu, 17 Jul 2025 18:17:39 -0500 Subject: [PATCH 1/4] attempt to add an defines for emulation in CMSSW --- hls4ml/backends/vitis/vitis_backend.py | 4 ++++ hls4ml/templates/vivado/firmware/defines.h | 4 ++++ hls4ml/writer/vivado_writer.py | 10 ++++++++++ 3 files changed, 18 insertions(+) diff --git a/hls4ml/backends/vitis/vitis_backend.py b/hls4ml/backends/vitis/vitis_backend.py index f72818a279..c3a22ed848 100644 --- a/hls4ml/backends/vitis/vitis_backend.py +++ b/hls4ml/backends/vitis/vitis_backend.py @@ -61,6 +61,7 @@ def create_initial_config( namespace=None, write_weights_txt=True, write_tar=False, + write_emulation_constants=False, tb_output_stream='both', **_, ): @@ -76,6 +77,8 @@ def create_initial_config( write_weights_txt (bool, optional): If True, writes weights to .txt files which speeds up compilation. Defaults to True. write_tar (bool, optional): If True, compresses the output directory into a .tar.gz file. Defaults to False. + write_emulation_constants (bool, optional): If True, write constants to define.h useful for emulation. + Defaults to False. tb_output_stream (str, optional): Controls where to write the output. Options are 'stdout', 'file' and 'both'. Defaults to 'both'. @@ -94,6 +97,7 @@ def create_initial_config( 'WriteWeightsTxt': write_weights_txt, 'WriteTar': write_tar, 'TBOutputStream': tb_output_stream, + 'WriteEmulationConstants': write_emulation_constants, } return config diff --git a/hls4ml/templates/vivado/firmware/defines.h b/hls4ml/templates/vivado/firmware/defines.h index 35eb959599..61bab99889 100644 --- a/hls4ml/templates/vivado/firmware/defines.h +++ b/hls4ml/templates/vivado/firmware/defines.h @@ -4,8 +4,10 @@ #include "ap_fixed.h" #include "ap_int.h" #include "nnet_utils/nnet_types.h" +#include #include #include +#include // hls-fpga-machine-learning insert namespace-start @@ -13,6 +15,8 @@ // hls-fpga-machine-learning insert layer-precision +// hls-fpga-machine-learning insert emulator-defines + // hls-fpga-machine-learning insert namespace-end #endif diff --git a/hls4ml/writer/vivado_writer.py b/hls4ml/writer/vivado_writer.py index 6658f583d8..3958497cd8 100644 --- a/hls4ml/writer/vivado_writer.py +++ b/hls4ml/writer/vivado_writer.py @@ -377,6 +377,16 @@ def write_defines(self, model): if namespace is not None: newline += '}\n' + elif '// hls-fpga-machine-learning insert emulator-defines' in line: + newline = line + + if model.config.get_writer_config().get('WriteEmulationConstants', False): + input_types = [f'std::array<{v.type.name}, {v.size_cpp()}>' for v in model.get_input_variables()] + output_types = [f'std::array<{v.type.name}, {v.size_cpp()}>' for v in model.get_output_variables()] + input_types_str = ', '.join(input_types) + output_types_str = ', '.join(output_types) + newline += '\n' + f'using inputs_t = std::tuple<{input_types_str}>;' + newline += '\n' + f'using outputs_t = std::tuple<{output_types_str}>;\n' else: newline = line fout.write(newline) From 9b861b05b69f6d8faf86bd5f36c99a6e21de2038 Mon Sep 17 00:00:00 2001 From: Jovan Mitrevski Date: Fri, 18 Jul 2025 12:57:11 -0500 Subject: [PATCH 2/4] add emulator wrapper --- hls4ml/templates/vivado/firmware/myproject.h | 2 ++ hls4ml/writer/vivado_writer.py | 24 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/hls4ml/templates/vivado/firmware/myproject.h b/hls4ml/templates/vivado/firmware/myproject.h index 5b34ae4c02..899b85b484 100644 --- a/hls4ml/templates/vivado/firmware/myproject.h +++ b/hls4ml/templates/vivado/firmware/myproject.h @@ -14,6 +14,8 @@ void myproject( // hls-fpga-machine-learning insert header ); +// hls-fpga-machine-learning insert emulator-defines + // hls-fpga-machine-learning insert namespace-end #endif diff --git a/hls4ml/writer/vivado_writer.py b/hls4ml/writer/vivado_writer.py index 3958497cd8..ffb47dfc87 100644 --- a/hls4ml/writer/vivado_writer.py +++ b/hls4ml/writer/vivado_writer.py @@ -322,6 +322,30 @@ def write_project_header(self, model): if namespace is not None: newline += '}\n' + elif '// hls-fpga-machine-learning insert emulator-defines' in line: + newline = line + + if model.config.get_writer_config().get('WriteEmulationConstants', False): + brams_def_str = ', '.join([b.definition_cpp(as_reference=False) for b in model_brams]) + brams_call_str = ', '.join([b.name for b in model_brams]) + + input_call_str = ', '.join([f'std::get<{n}>(inputs).data()' for n in range(len(model_inputs))]) + output_call_str = ', '.join([f'std::get<{n}>(outputs).data()' for n in range(len(model_outputs))]) + + newline += ( + f'\ninline void {model.config.get_project_name()}_emulator(' + 'inputs_t& inputs, outputs_t& outputs' # the inputs_t should ideally be const + ) + if len(model_brams) > 0: + newline += ',\n' + brams_def_str + newline += ') {\n' + newline += indent + model.config.get_project_name() + '(\n' + newline += indent + indent + input_call_str + ',\n' + newline += indent + indent + output_call_str + if len(model_brams) > 0: + newline += ',\n' + indent + indent + brams_call_str + newline += '\n' + indent + ');\n}\n' + else: newline = line fout.write(newline) From bb51d54523d84b1e154880a51c0ba0675e340234 Mon Sep 17 00:00:00 2001 From: Jovan Mitrevski Date: Tue, 22 Jul 2025 10:34:36 -0500 Subject: [PATCH 3/4] add some support for io_stream on the hls4ml side --- hls4ml/writer/vivado_writer.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/hls4ml/writer/vivado_writer.py b/hls4ml/writer/vivado_writer.py index ffb47dfc87..f4277a5b75 100644 --- a/hls4ml/writer/vivado_writer.py +++ b/hls4ml/writer/vivado_writer.py @@ -329,8 +329,12 @@ def write_project_header(self, model): brams_def_str = ', '.join([b.definition_cpp(as_reference=False) for b in model_brams]) brams_call_str = ', '.join([b.name for b in model_brams]) - input_call_str = ', '.join([f'std::get<{n}>(inputs).data()' for n in range(len(model_inputs))]) - output_call_str = ', '.join([f'std::get<{n}>(outputs).data()' for n in range(len(model_outputs))]) + if model.config.get_config_value('IOType') == 'io_stream': + input_call_str = ', '.join([f'std::get<{n}>(inputs)' for n in range(len(model_inputs))]) + output_call_str = ', '.join([f'std::get<{n}>(outputs)' for n in range(len(model_outputs))]) + else: + input_call_str = ', '.join([f'std::get<{n}>(inputs).data()' for n in range(len(model_inputs))]) + output_call_str = ', '.join([f'std::get<{n}>(outputs).data()' for n in range(len(model_outputs))]) newline += ( f'\ninline void {model.config.get_project_name()}_emulator(' @@ -405,8 +409,12 @@ def write_defines(self, model): newline = line if model.config.get_writer_config().get('WriteEmulationConstants', False): - input_types = [f'std::array<{v.type.name}, {v.size_cpp()}>' for v in model.get_input_variables()] - output_types = [f'std::array<{v.type.name}, {v.size_cpp()}>' for v in model.get_output_variables()] + if model.config.get_config_value('IOType') == 'io_stream': + input_types = [f'hls::stream<{v.type.name}>' for v in model.get_input_variables()] + output_types = [f'hls::stream<{v.type.name}>' for v in model.get_output_variables()] + else: + input_types = [f'std::array<{v.type.name}, {v.size_cpp()}>' for v in model.get_input_variables()] + output_types = [f'std::array<{v.type.name}, {v.size_cpp()}>' for v in model.get_output_variables()] input_types_str = ', '.join(input_types) output_types_str = ', '.join(output_types) newline += '\n' + f'using inputs_t = std::tuple<{input_types_str}>;' From e648df070d0bb318f0971839dab7605d6acc91ba Mon Sep 17 00:00:00 2001 From: Jovan Mitrevski Date: Tue, 22 Jul 2025 10:56:32 -0500 Subject: [PATCH 4/4] add pytest for emulation --- test/pytest/test_writer_config.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/pytest/test_writer_config.py b/test/pytest/test_writer_config.py index 52a082ce49..3ffdfb0a00 100644 --- a/test/pytest/test_writer_config.py +++ b/test/pytest/test_writer_config.py @@ -33,6 +33,18 @@ def test_namespace(keras_model, namespace, io_type, backend): hls_model.compile() # It's enough that the model compiles +@pytest.mark.parametrize('io_type', ['io_stream', 'io_parallel']) +@pytest.mark.parametrize('backend', ['Vitis']) # Only Vitis is supported +def test_emulator(keras_model, io_type, backend): + + config = hls4ml.utils.config_from_keras_model(keras_model, granularity='name', backend=backend) + odir = str(test_root_path / f'hls4mlprj_emulation_{backend}_{io_type}') + hls_model = hls4ml.converters.convert_from_keras_model( + keras_model, hls_config=config, io_type=io_type, output_dir=odir, write_emulation_constants=True, backend=backend + ) + hls_model.compile() # It's enough that the model compiles + + @pytest.mark.parametrize('backend', ['Vivado', 'Vitis']) # No Quartus for now @pytest.mark.parametrize('write_tar', [True, False]) def test_write_tar(keras_model, write_tar, backend):