diff --git a/hls4ml/templates/quartus/firmware/nnet_utils/nnet_helpers.h b/hls4ml/templates/quartus/firmware/nnet_utils/nnet_helpers.h index 1027e8fb00..afb3018f6e 100755 --- a/hls4ml/templates/quartus/firmware/nnet_utils/nnet_helpers.h +++ b/hls4ml/templates/quartus/firmware/nnet_utils/nnet_helpers.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include namespace nnet { @@ -58,6 +60,45 @@ constexpr int pow2(int x){ return x == 0 ? 1 : 2 * pow2(x - 1); } +template +void save_output_array(data_T *data, save_T *ptr, size_t layer_size) { + for(int i = 0; i < layer_size; i++) { + ptr[i] = static_cast(data[i].to_double()); + } +} + +// We don't want to include save_T in this function because it will be inserted into myproject.cpp +// so a workaround with element size is used +template +void save_layer_output(data_T *data, const char *layer_name, size_t layer_size) { + if (!trace_enabled) return; + + if (trace_outputs) { + if (trace_outputs->count(layer_name) > 0) { + if (trace_type_size == 4) { + save_output_array(data, (float *) (*trace_outputs)[layer_name], layer_size); + } else if (trace_type_size == 8) { + save_output_array(data, (double *) (*trace_outputs)[layer_name], layer_size); + } else { + std::cout << "Unknown trace type!" << std::endl; + } + } else { + std::cout << "Layer name: " << layer_name << " not found in debug storage!" << std::endl; + } + } else { + std::ostringstream filename; + filename << "./tb_data/" << layer_name << "_output.log"; //TODO if run as a shared lib, path should be ../tb_data + std::fstream out; + out.open(filename.str(), std::ios::app); + assert(out.is_open()); + for(int i = 0; i < layer_size; i++) { + out << data[i] << " "; // We don't care about precision in text files + } + out << std::endl; + out.close(); + } +} + } #endif diff --git a/hls4ml/writer/quartus_writer.py b/hls4ml/writer/quartus_writer.py index 8fa4209152..3e8ef57589 100644 --- a/hls4ml/writer/quartus_writer.py +++ b/hls4ml/writer/quartus_writer.py @@ -128,6 +128,11 @@ def write_project_cpp(self, model): func = layer.get_attr('function_cpp', None) if func: newline += ' ' + func + '\n' + if model.config.trace_output and layer.get_attr('Trace', False): + newline += '#ifndef HLS_SYNTHESIS\n' + for var in vars: + newline += ' nnet::save_layer_output<{}>({}, "{}", {});\n'.format(var.type.name, var.name, layer.name, var.size_cpp()) + newline += '#endif\n' newline += '\n' # Just copy line @@ -400,8 +405,7 @@ def write_bridge(self, model): newline = '' for layer in model.get_layers(): func = layer.get_attr('function_cpp') - if func and model.config.trace_output and model.config.get_layer_config_value(layer, 'Trace', - False): + if func and model.config.trace_output and layer.get_attr('Trace', False): vars = layer.get_variables() for var in vars: newline += indent + 'nnet::trace_outputs->insert(std::pair("{}", (void *) malloc({} * element_size)));\n'.format( diff --git a/test/pytest/test_trace.py b/test/pytest/test_trace.py new file mode 100644 index 0000000000..ce01c4213e --- /dev/null +++ b/test/pytest/test_trace.py @@ -0,0 +1,46 @@ +import pytest +import hls4ml +import hls4ml.model.profiling +import tensorflow as tf +import numpy as np +from pathlib import Path +from tensorflow.keras.layers import Dense, Activation + +test_root_path = Path(__file__).parent + +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus']) +def test_trace(backend): + '''Test the tracing feature with a simple Keras model.''' + model = tf.keras.models.Sequential() + model.add(Dense(2, + input_shape=(1,), + name='Dense', + use_bias=True, + kernel_initializer= tf.keras.initializers.RandomUniform(minval=1, maxval=10), + bias_initializer='zeros', + kernel_regularizer=None, + bias_regularizer=None, + activity_regularizer=None, + kernel_constraint=None, + bias_constraint=None)) + model.add(Activation(activation='elu', name='Activation')) + model.compile(optimizer='adam', loss='mse') + + X_input = np.random.rand(100,1) + + keras_prediction = model.predict(X_input) + + config = hls4ml.utils.config_from_keras_model(model, granularity='name') + for layer in config['LayerName'].keys(): + config['LayerName'][layer]['Trace'] = True + + output_dir = str(test_root_path / f'hls4mlprj_trace_{backend}') + + hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir, backend=backend) + + hls_model.compile() + hls4ml_pred, hls4ml_trace = hls_model.trace(X_input) + keras_trace = hls4ml.model.profiling.get_ymodel_keras(model, X_input) + + np.testing.assert_allclose(hls4ml_trace['Dense'], keras_trace['Dense'], rtol=1e-2, atol=0.01) + np.testing.assert_allclose(hls4ml_pred, keras_prediction, rtol=1e-2, atol=0.01)