Skip to content

Commit 19c541a

Browse files
authored
Add tracing support for the quartus backend (#583)
* partial first attempt to add tracing to quartus backend * continue adding tracing for quartus * Add trace pytest, fix bug uncovered in pytest * add docstring * remove redundant declaration, fix pytest
1 parent 5485c95 commit 19c541a

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed

hls4ml/templates/quartus/firmware/nnet_utils/nnet_helpers.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include <fstream>
2727
#include <algorithm>
2828
#include <map>
29+
#include <sstream>
30+
#include <iostream>
2931

3032
namespace nnet {
3133

@@ -58,6 +60,45 @@ constexpr int pow2(int x){
5860
return x == 0 ? 1 : 2 * pow2(x - 1);
5961
}
6062

63+
template<class data_T, class save_T>
64+
void save_output_array(data_T *data, save_T *ptr, size_t layer_size) {
65+
for(int i = 0; i < layer_size; i++) {
66+
ptr[i] = static_cast<save_T>(data[i].to_double());
67+
}
68+
}
69+
70+
// We don't want to include save_T in this function because it will be inserted into myproject.cpp
71+
// so a workaround with element size is used
72+
template<class data_T>
73+
void save_layer_output(data_T *data, const char *layer_name, size_t layer_size) {
74+
if (!trace_enabled) return;
75+
76+
if (trace_outputs) {
77+
if (trace_outputs->count(layer_name) > 0) {
78+
if (trace_type_size == 4) {
79+
save_output_array(data, (float *) (*trace_outputs)[layer_name], layer_size);
80+
} else if (trace_type_size == 8) {
81+
save_output_array(data, (double *) (*trace_outputs)[layer_name], layer_size);
82+
} else {
83+
std::cout << "Unknown trace type!" << std::endl;
84+
}
85+
} else {
86+
std::cout << "Layer name: " << layer_name << " not found in debug storage!" << std::endl;
87+
}
88+
} else {
89+
std::ostringstream filename;
90+
filename << "./tb_data/" << layer_name << "_output.log"; //TODO if run as a shared lib, path should be ../tb_data
91+
std::fstream out;
92+
out.open(filename.str(), std::ios::app);
93+
assert(out.is_open());
94+
for(int i = 0; i < layer_size; i++) {
95+
out << data[i] << " "; // We don't care about precision in text files
96+
}
97+
out << std::endl;
98+
out.close();
99+
}
100+
}
101+
61102
}
62103

63104
#endif

hls4ml/writer/quartus_writer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ def write_project_cpp(self, model):
128128
func = layer.get_attr('function_cpp', None)
129129
if func:
130130
newline += ' ' + func + '\n'
131+
if model.config.trace_output and layer.get_attr('Trace', False):
132+
newline += '#ifndef HLS_SYNTHESIS\n'
133+
for var in vars:
134+
newline += ' nnet::save_layer_output<{}>({}, "{}", {});\n'.format(var.type.name, var.name, layer.name, var.size_cpp())
135+
newline += '#endif\n'
131136
newline += '\n'
132137

133138
# Just copy line
@@ -400,8 +405,7 @@ def write_bridge(self, model):
400405
newline = ''
401406
for layer in model.get_layers():
402407
func = layer.get_attr('function_cpp')
403-
if func and model.config.trace_output and model.config.get_layer_config_value(layer, 'Trace',
404-
False):
408+
if func and model.config.trace_output and layer.get_attr('Trace', False):
405409
vars = layer.get_variables()
406410
for var in vars:
407411
newline += indent + 'nnet::trace_outputs->insert(std::pair<std::string, void *>("{}", (void *) malloc({} * element_size)));\n'.format(

test/pytest/test_trace.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import pytest
2+
import hls4ml
3+
import hls4ml.model.profiling
4+
import tensorflow as tf
5+
import numpy as np
6+
from pathlib import Path
7+
from tensorflow.keras.layers import Dense, Activation
8+
9+
test_root_path = Path(__file__).parent
10+
11+
@pytest.mark.parametrize('backend', ['Vivado', 'Quartus'])
12+
def test_trace(backend):
13+
'''Test the tracing feature with a simple Keras model.'''
14+
model = tf.keras.models.Sequential()
15+
model.add(Dense(2,
16+
input_shape=(1,),
17+
name='Dense',
18+
use_bias=True,
19+
kernel_initializer= tf.keras.initializers.RandomUniform(minval=1, maxval=10),
20+
bias_initializer='zeros',
21+
kernel_regularizer=None,
22+
bias_regularizer=None,
23+
activity_regularizer=None,
24+
kernel_constraint=None,
25+
bias_constraint=None))
26+
model.add(Activation(activation='elu', name='Activation'))
27+
model.compile(optimizer='adam', loss='mse')
28+
29+
X_input = np.random.rand(100,1)
30+
31+
keras_prediction = model.predict(X_input)
32+
33+
config = hls4ml.utils.config_from_keras_model(model, granularity='name')
34+
for layer in config['LayerName'].keys():
35+
config['LayerName'][layer]['Trace'] = True
36+
37+
output_dir = str(test_root_path / f'hls4mlprj_trace_{backend}')
38+
39+
hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir, backend=backend)
40+
41+
hls_model.compile()
42+
hls4ml_pred, hls4ml_trace = hls_model.trace(X_input)
43+
keras_trace = hls4ml.model.profiling.get_ymodel_keras(model, X_input)
44+
45+
np.testing.assert_allclose(hls4ml_trace['Dense'], keras_trace['Dense'], rtol=1e-2, atol=0.01)
46+
np.testing.assert_allclose(hls4ml_pred, keras_prediction, rtol=1e-2, atol=0.01)

0 commit comments

Comments
 (0)