1
+ import pytest
1
2
import hls4ml
2
- import tensorflow as tf
3
3
import numpy as np
4
- import pytest
4
+ import tensorflow as tf
5
5
from pathlib import Path
6
6
7
7
test_root_path = Path (__file__ ).parent
8
8
9
9
# Keras implementation of a custom layer
10
-
11
10
class KReverse (tf .keras .layers .Layer ):
12
11
''' Keras implementation of a hypothetical custom layer '''
13
12
def __init__ (self ):
@@ -16,8 +15,7 @@ def __init__(self):
16
15
def call (self , inputs ):
17
16
return tf .reverse (inputs , axis = [- 1 ])
18
17
19
- # hls4ml implementations
20
-
18
+ # hls4ml layer implementation
21
19
class HReverse (hls4ml .model .layers .Layer ):
22
20
''' hls4ml implementation of a hypothetical custom layer '''
23
21
@@ -27,8 +25,35 @@ def initialize(self):
27
25
dims = inp .dim_names
28
26
self .add_output_variable (shape , dims )
29
27
28
+ # hls4ml optimizer to remove duplicate optimizer
29
+ class RemoveDuplicateReverse (hls4ml .model .optimizer .OptimizerPass ):
30
+ '''OptimizerPass to remove consecutive HReverse layers.'''
31
+
32
+ def match (self , node ):
33
+ return isinstance (node , HReverse ) and \
34
+ isinstance (node .get_input_node (), HReverse )
35
+
36
+ def transform (self , model , node ):
37
+ first = node .get_input_node ()
38
+ second = node
30
39
31
- # Templates
40
+ model .remove_node (first , rewire = True )
41
+ model .remove_node (second , rewire = True )
42
+ return True
43
+
44
+ # Parser for converter
45
+ def parse_reverse_layer (keras_layer , input_names , input_shapes , data_reader , config ):
46
+ layer = {}
47
+ layer ['class_name' ] = 'HReverse'
48
+ layer ['name' ] = keras_layer ['config' ]['name' ]
49
+ layer ['n_in' ] = input_shapes [0 ][1 ]
50
+
51
+ if input_names is not None :
52
+ layer ['inputs' ] = input_names
53
+
54
+ return layer , [shape for shape in input_shapes [0 ]]
55
+
56
+ # HLS Templates - No specific pragmas used; generic enough for both Intel and Vivado
32
57
33
58
rev_config_template = """struct config{index} : nnet::reverse_config {{
34
59
static const unsigned n_in = {n_in};
@@ -55,8 +80,6 @@ def format(self, node):
55
80
params = self ._default_function_params (node )
56
81
return self .template .format (** params )
57
82
58
-
59
- # HLS implementation
60
83
rev_hls = \
61
84
"""#ifndef NNET_REVERSE_H_
62
85
#define NNET_REVERSE_H_
@@ -74,8 +97,6 @@ def format(self, node):
74
97
data_T input[CONFIG_T::n_in],
75
98
data_T reversed[CONFIG_T::n_in]
76
99
) {
77
- #pragma HLS PIPELINE
78
-
79
100
for (int i = 0; i < CONFIG_T::n_in; i++) {
80
101
reversed[CONFIG_T::n_in - 1 - i] = input[i];
81
102
}
@@ -86,43 +107,19 @@ def format(self, node):
86
107
#endif
87
108
"""
88
109
89
- class RemoveDuplicateReverse (hls4ml .model .optimizer .OptimizerPass ):
90
- '''OptimizerPass to remove consecutive HReverse layers.'''
91
-
92
- def match (self , node ):
93
- return isinstance (node , HReverse ) and \
94
- isinstance (node .get_input_node (), HReverse )
95
-
96
- def transform (self , model , node ):
97
- first = node .get_input_node ()
98
- second = node
99
-
100
- model .remove_node (first , rewire = True )
101
- model .remove_node (second , rewire = True )
102
- return True
103
-
104
- # Parser for converter
105
- def parse_reverse_layer (keras_layer , input_names , input_shapes , data_reader , config ):
106
- layer = {}
107
- layer ['class_name' ] = 'HReverse'
108
- layer ['name' ] = keras_layer ['config' ]['name' ]
109
- layer ['n_in' ] = input_shapes [0 ][1 ]
110
-
111
- if input_names is not None :
112
- layer ['inputs' ] = input_names
113
-
114
- return layer , [shape for shape in input_shapes [0 ]]
115
-
116
- def test_extensions (tmp_path ):
110
+ @pytest .fixture (scope = 'session' , autouse = True )
111
+ def regsister_custom_layer ():
117
112
# Register the converter for custom Keras layer
118
113
hls4ml .converters .register_keras_layer_handler ('KReverse' , parse_reverse_layer )
119
114
120
115
# Register the hls4ml's IR layer
121
116
hls4ml .model .layers .register_layer ('HReverse' , HReverse )
122
117
118
+ @pytest .mark .parametrize ('backend_id' , ['Vivado' , 'Quartus' ])
119
+ def test_extensions (tmp_path , backend_id ):
123
120
# Register the optimization passes (if any)
124
- backend = hls4ml .backends .get_backend ('Vivado' )
125
- backend .register_pass ('remove_duplicate_reverse' , RemoveDuplicateReverse , flow = 'vivado :optimize' )
121
+ backend = hls4ml .backends .get_backend (backend_id )
122
+ backend .register_pass ('remove_duplicate_reverse' , RemoveDuplicateReverse , flow = f' { backend_id . lower () } :optimize' )
126
123
127
124
# Register template passes for the given backend
128
125
backend .register_template (HReverseConfigTemplate )
@@ -148,15 +145,15 @@ def test_extensions(tmp_path):
148
145
149
146
hmodel = hls4ml .converters .convert_from_keras_model (
150
147
kmodel ,
151
- output_dir = str (test_root_path / 'hls4mlprj_extensions ' ),
152
- backend = 'Vivado' ,
148
+ output_dir = str (test_root_path / f'hls4mlprj_extensions_ { backend_id } ' ),
149
+ backend = backend_id ,
153
150
io_type = 'io_parallel' ,
154
- hls_config = { 'Model' : { 'Precision' : 'ap_int<4 >' , 'ReuseFactor' : 1 } })
151
+ hls_config = { 'Model' : { 'Precision' : 'ap_int<6 >' , 'ReuseFactor' : 1 } })
155
152
156
153
hmodel .compile ()
157
154
hres = hmodel .predict (x .astype ('float32' ))
158
155
159
156
# Check if the optimizer pass was applied
160
- assert 'vivado :remove_duplicate_reverse' in hmodel ._applied_flows [0 ]['vivado :optimize' ]
157
+ assert f' { backend_id . lower () } :remove_duplicate_reverse' in hmodel ._applied_flows [0 ][f' { backend_id . lower () } :optimize' ]
161
158
162
159
np .testing .assert_array_equal (kres , hres )
0 commit comments