diff --git a/hls4ml/backends/vitis/vitis_backend.py b/hls4ml/backends/vitis/vitis_backend.py index 561d6c128..7b193a685 100644 --- a/hls4ml/backends/vitis/vitis_backend.py +++ b/hls4ml/backends/vitis/vitis_backend.py @@ -64,6 +64,7 @@ def create_initial_config( namespace=None, write_weights_txt=True, write_tar=False, + write_emulation_constants=False, tb_output_stream='both', **_, ): @@ -79,6 +80,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'. @@ -97,6 +100,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 d1758f486..8013d891a 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 headers // hls-fpga-machine-learning insert namespace-start @@ -14,6 +16,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/templates/vivado/firmware/myproject.h b/hls4ml/templates/vivado/firmware/myproject.h index 5b34ae4c0..899b85b48 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 b087a42fd..a08c71897 100644 --- a/hls4ml/writer/vivado_writer.py +++ b/hls4ml/writer/vivado_writer.py @@ -322,6 +322,34 @@ 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]) + + 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(' + '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) @@ -385,6 +413,20 @@ 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): + 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}>;' + newline += '\n' + f'using outputs_t = std::tuple<{output_types_str}>;\n' else: newline = line fout.write(newline) diff --git a/test/pytest/test_writer_config.py b/test/pytest/test_writer_config.py index 52a082ce4..3ffdfb0a0 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):