Skip to content

Commit 3c188a8

Browse files
authored
Control where TB writes output (stdout, file, or both) (#1249)
1 parent 77b8331 commit 3c188a8

File tree

5 files changed

+60
-9
lines changed

5 files changed

+60
-9
lines changed

hls4ml/backends/vitis/vitis_backend.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def create_initial_config(
5050
namespace=None,
5151
write_weights_txt=True,
5252
write_tar=False,
53+
tb_output_stream='both',
5354
**_,
5455
):
5556
"""Create initial configuration of the Vitis backend.
@@ -64,6 +65,8 @@ def create_initial_config(
6465
write_weights_txt (bool, optional): If True, writes weights to .txt files which speeds up compilation.
6566
Defaults to True.
6667
write_tar (bool, optional): If True, compresses the output directory into a .tar.gz file. Defaults to False.
68+
tb_output_stream (str, optional): Controls where to write the output. Options are 'stdout', 'file' and 'both'.
69+
Defaults to 'both'.
6770
6871
Returns:
6972
dict: initial configuration.
@@ -79,6 +82,7 @@ def create_initial_config(
7982
'Namespace': namespace,
8083
'WriteWeightsTxt': write_weights_txt,
8184
'WriteTar': write_tar,
85+
'TBOutputStream': tb_output_stream,
8286
}
8387

8488
return config

hls4ml/backends/vivado/vivado_backend.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ def create_initial_config(
202202
namespace=None,
203203
write_weights_txt=True,
204204
write_tar=False,
205+
tb_output_stream='both',
205206
**_,
206207
):
207208
"""Create initial configuration of the Vivado backend.
@@ -216,6 +217,8 @@ def create_initial_config(
216217
write_weights_txt (bool, optional): If True, writes weights to .txt files which speeds up compilation.
217218
Defaults to True.
218219
write_tar (bool, optional): If True, compresses the output directory into a .tar.gz file. Defaults to False.
220+
tb_output_stream (str, optional): Controls where to write the output. Options are 'stdout', 'file' and 'both'.
221+
Defaults to 'both'.
219222
220223
Returns:
221224
dict: initial configuration.
@@ -231,6 +234,7 @@ def create_initial_config(
231234
'Namespace': namespace,
232235
'WriteWeightsTxt': write_weights_txt,
233236
'WriteTar': write_tar,
237+
'TBOutputStream': tb_output_stream,
234238
}
235239

236240
return config

hls4ml/model/graph.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def __init__(self, config):
6060
'Namespace': None,
6161
'WriteWeightsTxt': True,
6262
'WriteTar': False,
63+
'TBOutputStream': 'both',
6364
}
6465

6566
self._parse_hls_config()

hls4ml/writer/vivado_writer.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -570,20 +570,25 @@ def write_test_bench(self, model):
570570

571571
elif '// hls-fpga-machine-learning insert tb-output' in line:
572572
newline = line
573-
for out in model_outputs:
574-
newline += indent + 'nnet::print_result<{}, {}>({}, fout);\n'.format(
575-
out.type.name, out.size_cpp(), out.name
576-
) # TODO enable this
573+
tb_stream = model.config.get_writer_config().get('TBOutputStream', 'both')
574+
if tb_stream != 'stdout':
575+
for out in model_outputs:
576+
newline += indent + 'nnet::print_result<{}, {}>({}, fout);\n'.format(
577+
out.type.name, out.size_cpp(), out.name
578+
) # TODO enable this
577579

578580
elif (
579581
'// hls-fpga-machine-learning insert output' in line
580582
or '// hls-fpga-machine-learning insert quantized' in line
581583
):
582584
newline = line
583-
for out in model_outputs:
584-
newline += indent + 'nnet::print_result<{}, {}>({}, std::cout, true);\n'.format(
585-
out.type.name, out.size_cpp(), out.name
586-
)
585+
tb_stream = model.config.get_writer_config().get('TBOutputStream', 'both')
586+
keep_output = str(tb_stream != 'stdout').lower() # We keep output if we need to write it to file too.
587+
if tb_stream != 'file':
588+
for out in model_outputs:
589+
newline += indent + 'nnet::print_result<{}, {}>({}, std::cout, {});\n'.format(
590+
out.type.name, out.size_cpp(), out.name, keep_output
591+
)
587592

588593
elif '// hls-fpga-machine-learning insert namespace' in line:
589594
newline = ''

test/pytest/test_writer_config.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
@pytest.fixture(scope='module')
1515
def keras_model():
1616
model = Sequential()
17-
model.add(Dense(10, activation='softmax', input_shape=(15,)))
17+
model.add(Dense(10, kernel_initializer='zeros', use_bias=False, input_shape=(15,)))
1818
model.compile()
1919
return model
2020

@@ -69,3 +69,40 @@ def test_write_weights_txt(keras_model, write_weights_txt, backend):
6969

7070
txt_written = os.path.exists(odir + '/firmware/weights/w2.txt')
7171
assert txt_written == write_weights_txt
72+
73+
74+
@pytest.mark.skip(reason='Skipping for now as it needs the installation of the compiler.')
75+
@pytest.mark.parametrize('backend', ['Vivado', 'Vitis'])
76+
@pytest.mark.parametrize('tb_output_stream', ['stdout', 'file', 'both'])
77+
def test_tb_output_stream(capfd, keras_model, tb_output_stream, backend):
78+
79+
config = hls4ml.utils.config_from_keras_model(keras_model, granularity='name')
80+
odir = str(test_root_path / f'hls4mlprj_tb_output_stream_{tb_output_stream}_{backend}')
81+
if os.path.exists(odir):
82+
shutil.rmtree(odir)
83+
84+
hls_model = hls4ml.converters.convert_from_keras_model(
85+
keras_model,
86+
io_type='io_stream',
87+
hls_config=config,
88+
output_dir=odir,
89+
backend=backend,
90+
tb_output_stream=tb_output_stream,
91+
)
92+
hls_model.build(csim=True, synth=False)
93+
94+
# Check the output based on tb_output_stream
95+
tb_file_path = os.path.join(odir, 'tb_data/csim_results.log')
96+
97+
with open(tb_file_path) as tb_file:
98+
tb_content = tb_file.read()
99+
if tb_output_stream in ['file', 'both']:
100+
assert len(tb_content) > 0, 'Testbench output file expected to contain model outputs, but is empty'
101+
else:
102+
assert len(tb_content) == 0, 'Testbench output file expected to be empty, but contains data'
103+
104+
captured = capfd.readouterr()
105+
if tb_output_stream in ['stdout', 'both']:
106+
assert '0 0 0 0 0 0 0 0 0 0' in captured.out, 'Expected model output not found in stdout'
107+
else:
108+
assert '0 0 0 0 0 0 0 0 0 0' not in captured.out, 'Model output should not be printed to stdout'

0 commit comments

Comments
 (0)