Repository: fastmachinelearning/hls4ml
Issue: LSTM model with Strategy "resource" results in siginificant AUC loss

## Prerequisites
Please make sure to check off these prerequisites before submitting a bug report.
- [x] Test that the bug appears on the current version of the master branch. Make sure to include the commit hash of the commit you checked out.
-  [x] Check that the issue hasn't already been reported, by checking the currently open issues.
- [x] If there are steps to reproduce the problem, make sure to write them down below.
- [ ] If relevant, please include the hls4ml project files, which were created directly before and/or after the bug.

## Quick summary
Created an LSTM neural network with Keras which achieves 98.40% AUC score on the test set. However, the HLS-model only achieves 54.75% when the "Strategy" is set to "Resource". If "Strategy" is set to "Latency", the expected AUC scores are received, i.e. 98.40% (Keras) and 98.16% (HLS4ML).

## Details
The problem occurs with the latest release (v0.7.0) as with the main branch 51be56eeefe7c1f46945528143ef0ac38d174a22 . 

With strategy=resource:
```
model = load_model('LSTM_32_units_32_seq_length.h5')
model.summary()
prj_name = "hls4ml_LSTM_test"
hls_config = hls4ml.utils.config_from_keras_model(model, granularity="name")

hls_config['Model']['Strategy'] = 'Resource'
hls_config['Model']['ReuseFactor'] = 16384
for layer in hls_config['LayerName'].keys():
    hls_config['LayerName'][layer]['Strategy'] = 'Resource'
    hls_config['LayerName'][layer]['ReuseFactor'] = 16384

print(hls_config)
config = hls4ml.converters.create_config(backend="VivadoAccelerator")
config["HLSConfig"] = hls_config
config["KerasModel"] = model
config["OutputDir"] = prj_name
config["IOType"] = "io_stream"
hls_model = hls4ml.converters.keras_to_hls(config)
print(config)
hls_model.compile()

predictions = model.predict(X_test)
prediction_errors = np.mean(np.abs(Z_test - predictions), axis=1)
print(f"AUC Keras: {roc_auc_score(Y_test, prediction_errors)}")

predictions = hls_model.predict(np.ascontiguousarray(X_test))
prediction_errors = np.mean(np.abs(Z_test - predictions), axis=1)
print(f"AUC HLS4ML: {roc_auc_score(Y_test, prediction_errors)}")
```
Results:
```
AUC Keras: 0.9840026507030819
AUC HLS4ML: 0.5475289911373609
```

With strategy=latency:
```
model = load_model('LSTM_32_units_32_seq_length.h5')
model.summary()
prj_name = "hls4ml_LSTM_test"
hls_config = hls4ml.utils.config_from_keras_model(model, granularity="name")

hls_config['Model']['Strategy'] = 'Latency'
# hls_config['Model']['ReuseFactor'] = 16384
for layer in hls_config['LayerName'].keys():
    hls_config['LayerName'][layer]['Strategy'] = 'Latency'
    # hls_config['LayerName'][layer]['ReuseFactor'] = 16384

print(hls_config)
config = hls4ml.converters.create_config(backend="VivadoAccelerator")
config["HLSConfig"] = hls_config
config["KerasModel"] = model
config["OutputDir"] = prj_name
config["IOType"] = "io_stream"
hls_model = hls4ml.converters.keras_to_hls(config)
print(config)
hls_model.compile()

predictions = model.predict(X_test)
prediction_errors = np.mean(np.abs(Z_test - predictions), axis=1)
print(f"AUC Keras: {roc_auc_score(Y_test, prediction_errors)}")

predictions = hls_model.predict(np.ascontiguousarray(X_test))
prediction_errors = np.mean(np.abs(Z_test - predictions), axis=1)
print(f"AUC HLS4ML: {roc_auc_score(Y_test, prediction_errors)}")
```
Results:
```
AUC Keras: 0.9840026507030819
AUC HLS4ML: 0.9815923809354193
```

Overview of the LSTM network:
```
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_3 (InputLayer)        [(None, 32, 95)]          0         
                                                                 
 lstm_2 (LSTM)               (None, 32)                16384     
                                                                 
 activation_4 (Activation)   (None, 32)                0         
                                                                 
 dense_2 (Dense)             (None, 95)                3135      
                                                                 
 activation_5 (Activation)   (None, 95)                0         
                                                                 
=================================================================
Total params: 19,519
Trainable params: 19,519
Non-trainable params: 0
_________________________________________________________________
```
### Steps to Reproduce
See previous section.

### Expected behavior
As I understood the strategy shouldn't influence the AUC score/performance of the model. 

### Actual behavior
Different strategies result in different AUC scores.


Changed files:
hls4ml/backends/vivado/passes/recurrent_templates.py, hls4ml/backends/vivado/vivado_backend.py, hls4ml/model/layers.py, hls4ml/backends/quartus/quartus_backend.py, test/pytest/test_rnn.py

diff --git a/hls4ml/backends/quartus/quartus_backend.py b/hls4ml/backends/quartus/quartus_backend.py
index cfccdbde..55327ee9 100644
--- a/hls4ml/backends/quartus/quartus_backend.py
+++ b/hls4ml/backends/quartus/quartus_backend.py
@@ -4,7 +4,7 @@ from contextlib import contextmanager
 import numpy as np
 
 from hls4ml.backends import FPGABackend
-from hls4ml.model.attributes import ConfigurableAttribute
+from hls4ml.model.attributes import ConfigurableAttribute, TypeAttribute
 from hls4ml.model.flow import register_flow
 from hls4ml.model.layers import GRU, LSTM, Activation, Conv1D, Conv2D, Dense, Embedding, Layer, SimpleRNN, Softmax
 from hls4ml.model.optimizer import get_backend_passes, layer_optimizer
@@ -39,6 +39,8 @@ class QuartusBackend(FPGABackend):
         for layer in rnn_layers:
             attrs = self.attribute_map.get(layer, [])
             attrs.append(ConfigurableAttribute('recurrent_reuse_factor', default=1))
+            attrs.append(ConfigurableAttribute('table_size', default=1024))
+            attrs.append(TypeAttribute('table', default=FixedPrecisionType(18, 8)))
             self.attribute_map[layer] = attrs
 
     def _register_flows(self):
@@ -312,16 +314,6 @@ class QuartusBackend(FPGABackend):
         reuse_factor = layer.model.config.get_reuse_factor(layer)
         layer.set_attr('recurrent_reuse_factor', reuse_factor)
 
-        index_t = IntegerPrecisionType(width=1, signed=False)
-        layer.set_attr('index_t', index_t)
-
-        if 'table_t' not in layer.attributes:
-            layer.set_attr(
-                'table_t', NamedType(name=layer.name + '_table_t', precision=FixedPrecisionType(width=18, integer=8))
-            )
-        if 'table_size' not in layer.attributes:
-            layer.set_attr('table_size', 1024)
-
         # We don't use RF yet
         if True:  # layer.model.config.is_resource_strategy(layer): ... Quartus only supports Dense resource multiplication
             n_in, n_out, n_in_recr, n_out_recr = self.get_layer_mult_size(layer)
@@ -367,14 +359,4 @@ class QuartusBackend(FPGABackend):
         reuse_factor = layer.model.config.get_reuse_factor(layer)
         layer.set_attr('recurrent_reuse_factor', reuse_factor)
 
-        index_t = IntegerPrecisionType(width=1, signed=False)
-        layer.set_attr('index_t', index_t)
-
-        if 'table_t' not in layer.attributes:
-            layer.set_attr(
-                'table_t', NamedType(name=layer.name + '_table_t', precision=FixedPrecisionType(width=18, integer=8))
-            )
-        if 'table_size' not in layer.attributes:
-            layer.set_attr('table_size', 1024)
-
         # TODO - Consider setting and using RF
diff --git a/hls4ml/backends/vivado/passes/recurrent_templates.py b/hls4ml/backends/vivado/passes/recurrent_templates.py
index 8b031dfb..c2d665e4 100644
--- a/hls4ml/backends/vivado/passes/recurrent_templates.py
+++ b/hls4ml/backends/vivado/passes/recurrent_templates.py
@@ -16,7 +16,7 @@ recr_mult_config_template = """struct config{index} : nnet::dense_config {{
     typedef {accum_t.name} accum_t;
     typedef {bias_t.name} bias_t;
     typedef {weight_t.name} weight_t;
-    typedef ap_{index_t} index_t;
+    typedef {index_t.name} index_t;
     template<class x_T, class y_T>
     using product = nnet::product::{product_type}<x_T, y_T>;
 }};\\n"""
@@ -28,7 +28,7 @@ activ_config_template = """struct {type}_config{index} : nnet::activ_config {{
     static const unsigned table_size = {table_size};
     static const unsigned io_type = nnet::{iotype};
     static const unsigned reuse_factor = {reuse};
-    typedef ap_{table_t} table_t;
+    typedef {table_t.name} table_t;
 }};\\n"""
 
 recr_activ_config_template = """struct {type}_config{index}_recr : nnet::activ_config {{
@@ -36,7 +36,7 @@ recr_activ_config_template = """struct {type}_config{index}_recr : nnet::activ_c
     static const unsigned table_size = {table_size};
     static const unsigned io_type = nnet::{iotype};
     static const unsigned reuse_factor = {reuse};
-    typedef ap_{table_t} table_t;
+    typedef {table_t.name} table_t;
 }};\\n"""
 
 # LSTM + GRU templates
diff --git a/hls4ml/backends/vivado/vivado_backend.py b/hls4ml/backends/vivado/vivado_backend.py
index 743754d1..6afab940 100644
--- a/hls4ml/backends/vivado/vivado_backend.py
+++ b/hls4ml/backends/vivado/vivado_backend.py
@@ -5,7 +5,7 @@ import numpy as np
 
 from hls4ml.backends import FPGABackend
 from hls4ml.backends.fpga.fpga_types import APTypeConverter, HLSTypeConverter, VivadoArrayVariableConverter
-from hls4ml.model.attributes import ChoiceAttribute, ConfigurableAttribute
+from hls4ml.model.attributes import ChoiceAttribute, ConfigurableAttribute, TypeAttribute
 from hls4ml.model.flow import register_flow
 from hls4ml.model.layers import (
     GRU,
@@ -51,6 +51,8 @@ class VivadoBackend(FPGABackend):
             attrs = self.attribute_map.get(layer, [])
             attrs.append(ConfigurableAttribute('recurrent_reuse_factor', default=1))
             attrs.append(ConfigurableAttribute('static', value_type=bool, default=True))
+            attrs.append(ConfigurableAttribute('table_size', default=1024))
+            attrs.append(TypeAttribute('table', default=FixedPrecisionType(18, 8)))
             self.attribute_map[layer] = attrs
 
         # Add ParallelizationFactor to Conv1D/2D
@@ -393,46 +395,30 @@ class VivadoBackend(FPGABackend):
         reuse_factor = layer.model.config.get_reuse_factor(layer)
         layer.set_attr('recurrent_reuse_factor', reuse_factor)
 
-        index_t = IntegerPrecisionType(width=1, signed=False)
-
-        if 'table_t' not in layer.attributes:
-            layer.set_attr('table_t', FixedPrecisionType(width=18, integer=8))
-        if 'table_size' not in layer.attributes:
-            layer.set_attr('table_size', 1024)
         if layer.model.config.is_resource_strategy(layer):
             n_in, n_out, n_in_recr, n_out_recr = self.get_layer_mult_size(layer)
             self.set_closest_reuse_factor(layer, n_in, n_out)
             self.set_closest_reuse_factor(layer, n_in_recr, n_out_recr, attribute='recurrent_reuse_factor')
-            layer.weights['weight'].data = np.transpose(layer.weights['weight'].data)
-            layer.weights['recurrent_weight'].data = np.transpose(layer.weights['recurrent_weight'].data)
             layer.set_attr('strategy', 'resource')
         else:
             layer.set_attr('strategy', 'latency')
 
-        layer.set_attr('index_t', index_t)
+        layer.set_attr('index_t', NamedType(f'layer{layer.index}_index', IntegerPrecisionType(width=1, signed=False)))
 
     @layer_optimizer(GRU)
     def init_gru(self, layer):
         reuse_factor = layer.model.config.get_reuse_factor(layer)
         layer.set_attr('recurrent_reuse_factor', reuse_factor)
 
-        index_t = IntegerPrecisionType(width=1, signed=False)
-
-        if 'table_t' not in layer.attributes:
-            layer.set_attr('table_t', FixedPrecisionType(width=18, integer=8))
-        if 'table_size' not in layer.attributes:
-            layer.set_attr('table_size', 1024)
         if layer.model.config.is_resource_strategy(layer):
             n_in, n_out, n_in_recr, n_out_recr = self.get_layer_mult_size(layer)
             self.set_closest_reuse_factor(layer, n_in, n_out)
             self.set_closest_reuse_factor(layer, n_in_recr, n_out_recr, attribute='recurrent_reuse_factor')
-            layer.weights['weight'].data = np.transpose(layer.weights['weight'].data)
-            layer.weights['recurrent_weight'].data = np.transpose(layer.weights['recurrent_weight'].data)
             layer.set_attr('strategy', 'resource')
         else:
             layer.set_attr('strategy', 'latency')
 
-        layer.set_attr('index_t', index_t)
+        layer.set_attr('index_t', NamedType(f'layer{layer.index}_index', IntegerPrecisionType(width=1, signed=False)))
 
     @layer_optimizer(GarNet)
     def init_garnet(self, layer):
diff --git a/hls4ml/model/layers.py b/hls4ml/model/layers.py
index e60fb43a..1e0d8d9b 100644
--- a/hls4ml/model/layers.py
+++ b/hls4ml/model/layers.py
@@ -112,7 +112,7 @@ class Layer:
                 config_value, str
             ):  # TODO maybe move this to __setitem__ of AttributeDict?
                 precision = self.model.config.backend.convert_precision_string(config_value)
-                config_value = NamedType(self.name + config_key, precision)
+                config_value = NamedType(self.name + '_' + config_key, precision)
             self.attributes[config_key] = config_value
 
         self.initialize()
diff --git a/test/pytest/test_rnn.py b/test/pytest/test_rnn.py
index 81c90359..3e6e9780 100644
--- a/test/pytest/test_rnn.py
+++ b/test/pytest/test_rnn.py
@@ -84,7 +84,8 @@ def test_rnn_parsing(rnn_layer, return_sequences):
 )
 @pytest.mark.parametrize('return_sequences', [True, False])
 @pytest.mark.parametrize('static', [True, False])
-def test_rnn_accuracy(rnn_layer, return_sequences, backend, io_type, static):
+@pytest.mark.parametrize('strategy', ['latency', 'resource'])
+def test_rnn_accuracy(rnn_layer, return_sequences, backend, io_type, strategy, static):
     # Subtract 0.5 to include negative values
     input_shape = (12, 8)
     X = np.random.rand(50, *input_shape) - 0.5
@@ -109,8 +110,9 @@ def test_rnn_accuracy(rnn_layer, return_sequences, backend, io_type, static):
         keras_model, granularity='name', default_precision=default_precision, backend=backend
     )
     hls_config['LayerName'][layer_name]['static'] = static
-    prj_name = 'hls4mlprj_rnn_accuracy_{}_static_{}_ret_seq_{}_{}_{}'.format(
-        rnn_layer.__class__.__name__.lower(), int(static), int(return_sequences), backend, io_type
+    hls_config['LayerName'][layer_name]['Strategy'] = strategy
+    prj_name = 'hls4mlprj_rnn_accuracy_{}_static_{}_ret_seq_{}_{}_{}_{}'.format(
+        rnn_layer.__class__.__name__.lower(), int(static), int(return_sequences), backend, io_type, strategy
     )
     output_dir = str(test_root_path / prj_name)