diff --git a/docker/quicktest.sh b/docker/quicktest.sh
index 02e014cd3cc7bb88eebd02f03ff599913079152b..b06feccdc578a59c8ef00531871e1211c2a407e5 100755
--- a/docker/quicktest.sh
+++ b/docker/quicktest.sh
@@ -16,6 +16,11 @@ elif [ $1 = "rtlsim" ]; then
 elif [ $1 = "end2end" ]; then
   echo "Running end2end test suite with no parallelism"
   python setup.py test --addopts "-k end2end"
+elif [ $1 = "full" ]; then
+  echo "Running full test suite, each step with appropriate parallelism"
+  $0 main;
+  $0 rtlsim;
+  $0 end2end;
 else
   echo "Unrecognized argument to quicktest.sh"
 fi
diff --git a/src/finn/core/modelwrapper.py b/src/finn/core/modelwrapper.py
index 646add188c5d475cf37ccd33cf24d29d61754ae1..98b234592ebe0c704fafd1eed980325d8566e7e2 100644
--- a/src/finn/core/modelwrapper.py
+++ b/src/finn/core/modelwrapper.py
@@ -36,6 +36,11 @@ from onnx import TensorProto
 import finn.util.basic as util
 import finn.util.onnx as onnxutil
 from finn.core.datatype import DataType
+from finn.transformation.general import (
+    RemoveUnusedTensors,
+    RemoveStaticGraphInputs,
+    SortGraph,
+)
 
 
 class ModelWrapper:
@@ -87,7 +92,7 @@ class ModelWrapper:
         """Runs given anaylsis_fxn on this model and return resulting dict."""
         return analysis_fxn(self)
 
-    def transform(self, transformation, make_deepcopy=True):
+    def transform(self, transformation, make_deepcopy=True, cleanup=True):
         """Applies given Transformation repeatedly until no more changes can be made
         and returns a transformed ModelWrapper instance.
 
@@ -101,6 +106,22 @@ class ModelWrapper:
             (transformed_model, model_was_changed) = transformation.apply(
                 transformed_model
             )
+        if cleanup:
+            transformed_model.cleanup()
+        return transformed_model
+
+    def cleanup(self):
+        "Run cleanup transformations on the model."
+        transformed_model = self
+        cleanup_transforms = [
+            RemoveUnusedTensors(),
+            RemoveStaticGraphInputs(),
+            SortGraph(),
+        ]
+        for trn in cleanup_transforms:
+            transformed_model = transformed_model.transform(
+                trn, cleanup=False, make_deepcopy=False
+            )
         return transformed_model
 
     def check_compatibility(self):
diff --git a/src/finn/custom_op/fpgadataflow/streamingfclayer_batch.py b/src/finn/custom_op/fpgadataflow/streamingfclayer_batch.py
index bc266e4934c41d6f5f1261e0a30e90cb72ba83a8..181e04f7142053708cc5b2338a8078f6c9fc8303 100644
--- a/src/finn/custom_op/fpgadataflow/streamingfclayer_batch.py
+++ b/src/finn/custom_op/fpgadataflow/streamingfclayer_batch.py
@@ -77,7 +77,7 @@ class StreamingFCLayer_Batch(HLSCustomOp):
             "weightDataType": ("s", True, ""),
             "outputDataType": ("s", True, ""),
             # FINN DataType for accumulator -- auto-computed and updated
-            "accDataType": ("s", False, "DataType.INT32"),
+            "accDataType": ("s", False, "INT32"),
             # use xnor-popcount for binary weights/inputs, thus treating them
             # as bipolar
             "binaryXnorMode": ("i", False, 0),
@@ -483,6 +483,10 @@ class StreamingFCLayer_Batch(HLSCustomOp):
                     adt = DataType.get_smallest_possible(0 - acc_max)
             else:
                 adt = DataType.get_smallest_possible(acc_max)
+            # ensure a datatype divisible by 8-bits in case this is the last node
+            bw = roundup_to_integer_multiple(adt.bitwidth(), 8)
+            new_adt_name = adt.name.replace(str(adt.bitwidth()), str(bw))
+            adt = DataType[new_adt_name]
             self.set_nodeattr("accDataType", adt.name)
             # for no-activation nodes, output dt = acc dt
             self.set_nodeattr("outputDataType", adt.name)
diff --git a/src/finn/transformation/fpgadataflow/create_dataflow_partition.py b/src/finn/transformation/fpgadataflow/create_dataflow_partition.py
index 5ec4ab14d65d63523856a6bb107bf75c1ca5a261..fb8b4358abd772d13c355f797649dc3b51975b4d 100644
--- a/src/finn/transformation/fpgadataflow/create_dataflow_partition.py
+++ b/src/finn/transformation/fpgadataflow/create_dataflow_partition.py
@@ -112,6 +112,7 @@ class CreateDataflowPartition(Transformation):
                     "dataflow_partition" + str(target_partition_id) + "_"
                 )
                 df_model_filename = df_model_dir + "/df_model.onnx"
+                df_model.cleanup()
                 df_model.save(df_model_filename)
                 # remove all dataflow nodes from the non-dataflow model
                 # keep track of where the dataflow part starts
diff --git a/src/finn/transformation/general.py b/src/finn/transformation/general.py
index 4303eb17f39a9949f5729e895e449bbb6a633033..690364a7d827086217ffdf9d246c2f4c5f395772 100644
--- a/src/finn/transformation/general.py
+++ b/src/finn/transformation/general.py
@@ -189,6 +189,9 @@ class SortGraph(Transformation):
     # Probably this is faster than copying initializers and more robust in general
 
     def apply(self, model):
+        if len(model.graph.node) == 1:
+            # single-node graph, nothing to sort
+            return (model, False)
         # Gather graph structure
         graph_dependencies = {}
         node_list = [
@@ -214,7 +217,7 @@ class SortGraph(Transformation):
         for new_idx, sorted_idx in enumerate(sorted_node_indexes):
             model.graph.node.insert(new_idx, node_list[sorted_idx])
 
-        return model, False
+        return (model, False)
 
 
 class ConvertSubToAdd(Transformation):
diff --git a/src/finn/transformation/merge_onnx_models.py b/src/finn/transformation/merge_onnx_models.py
index 5dc6127ed189311c72a119932394aca4745e3608..ceacab197150fe6d32e3a9eda268aed186b1a8bc 100644
--- a/src/finn/transformation/merge_onnx_models.py
+++ b/src/finn/transformation/merge_onnx_models.py
@@ -31,12 +31,12 @@ from onnx import helper
 
 from finn.transformation import Transformation
 from finn.core.modelwrapper import ModelWrapper
-import finn.util.basic as util
 from finn.transformation.infer_shapes import InferShapes
 from finn.transformation.infer_datatypes import InferDataTypes
 from finn.transformation.infer_data_layouts import InferDataLayouts
 from finn.transformation.general import (
     GiveReadableTensorNames,
+    GiveRandomTensorNames,
     GiveUniqueNodeNames,
     GiveUniqueParameterTensors,
 )
@@ -59,6 +59,9 @@ class MergeONNXModels(Transformation):
         graph_modified = False
         pre_model = self.pre_model
         post_model = copy.deepcopy(model)
+        # to avoid mix-ups, start by giving all tensors random names
+        pre_model = pre_model.transform(GiveRandomTensorNames())
+        post_model = post_model.transform(GiveRandomTensorNames())
 
         # check for dynamic outputs of pre model
         dyn_outp = []
@@ -94,27 +97,6 @@ class MergeONNXModels(Transformation):
         for n in post_model.graph.node:
             n.name = ""
 
-        # randomize all tensor names
-        names1 = pre_model.get_all_tensor_names()
-        names2 = post_model.get_all_tensor_names()
-        used_names = names1 + names2
-
-        # pre_model
-        for tensor_name in names1:
-            new_name = util.random_string()
-            while new_name in used_names:
-                new_name = util.random_string()
-            pre_model.rename_tensor(tensor_name, new_name)
-            used_names.append(new_name)
-
-        # post_model
-        for tensor in names2:
-            new_name = util.random_string()
-            while new_name in used_names:
-                new_name = util.random_string()
-            post_model.rename_tensor(tensor_name, new_name)
-            used_names.append(new_name)
-
         # check if models can be merged
         output_model_a = dyn_outp[0].name
         input_model_b = dyn_inp[0].name
@@ -124,6 +106,9 @@ class MergeONNXModels(Transformation):
             output_a_shape == input_b_shape
         ), "Models can't be merged! Shapes don't match."
 
+        pre_model.save("pre.onnx")
+        post_model.save("post.onnx")
+
         # connect output of one model to input of the other
         for n in pre_model.graph.node:
             if output_model_a == n.output[0]:
@@ -132,83 +117,43 @@ class MergeONNXModels(Transformation):
         # extract information for new model
 
         # nodes
-        node_list_a = pre_model.graph.node
-        node_list_b = post_model.graph.node
-
-        node_list = node_list_a
-        for node in node_list_b:
-            node_list.append(node)
+        node_pre = [node for node in pre_model.graph.node]
+        node_post = [node for node in post_model.graph.node]
+        node_new = node_pre + node_post
 
         # in and output
         inp = pre_model.graph.input[0]
         outp = post_model.graph.output[0]
 
+        vi_pre = [x for x in pre_model.graph.value_info]
+        out_pre = [x for x in pre_model.graph.output]
+        qa_pre = [x for x in pre_model.graph.quantization_annotation]
+        init_pre = [x for x in pre_model.graph.initializer]
+
+        vi_post = [x for x in post_model.graph.value_info]
+        qa_post = [x for x in post_model.graph.quantization_annotation]
+        init_post = [x for x in post_model.graph.initializer]
+
+        vi_new = vi_pre + vi_post + out_pre
+        qa_new = qa_pre + qa_post
+        init_new = init_pre + init_post
+
         # create new graph and model
         new_graph = helper.make_graph(
-            nodes=node_list,
+            nodes=node_new,
             name="fuse-graph",
             inputs=[inp],
             outputs=[outp],
-            value_info=[],
+            value_info=vi_new,
         )
 
         new_model = helper.make_model(new_graph, producer_name="fuse_model")
         new_model = ModelWrapper(new_model)
 
-        # add value info from both models to new model
-        # pre model
-        vi_pre = [x for x in pre_model.graph.input]
-        vi_pre += [x for x in pre_model.graph.output]
-        vi_pre += [x for x in pre_model.graph.value_info]
-        for vi in vi_pre:
-            # preserve intializers, quantization/sparsity annotation, etc.
-            # initializer
-            init_val = pre_model.get_initializer(vi.name)
-            if init_val is not None:
-                new_model.set_initializer(vi.name, init_val)
-            # FINN datatype
-            dtype = pre_model.get_tensor_datatype(vi.name)
-            new_model.set_tensor_datatype(vi.name, dtype)
-            # data layout
-            data_layout = pre_model.get_tensor_layout(vi.name)
-            if data_layout is not None:
-                new_model.set_tensor_layout(vi.name, data_layout)
-            # sparsity
-            sparsity = pre_model.get_tensor_sparsity(vi.name)
-            if sparsity is not None:
-                new_model.set_tensor_sparsity(vi.name, sparsity)
-            # graph input should not be part of graph.value_info, so don't insert
-            # if current vi == inp, but the quantization annotation is preserved
-            if vi == inp:
-                continue
-            new_model.graph.value_info.append(vi)
-
-        # post model
-        vi_model = [x for x in post_model.graph.input]
-        vi_model += [x for x in post_model.graph.output]
-        vi_model += [x for x in post_model.graph.value_info]
-        for vi in vi_model:
-            # preserve intializers, quantization/sparsity annotation, etc.
-            # initializer
-            init_val = post_model.get_initializer(vi.name)
-            if init_val is not None:
-                new_model.set_initializer(vi.name, init_val)
-            # FINN datatype
-            dtype = post_model.get_tensor_datatype(vi.name)
-            new_model.set_tensor_datatype(vi.name, dtype)
-            # data layout
-            data_layout = post_model.get_tensor_layout(vi.name)
-            if data_layout is not None:
-                new_model.set_tensor_layout(vi.name, data_layout)
-            # sparsity
-            sparsity = post_model.get_tensor_sparsity(vi.name)
-            if sparsity is not None:
-                new_model.set_tensor_sparsity(vi.name, sparsity)
-            # graph output should not be part of graph.value_info, so don't insert
-            # if current vi == outp, but the quantization annotation is preserved
-            if vi == outp:
-                continue
-            new_model.graph.value_info.append(vi)
+        for i in init_new:
+            new_model.graph.initializer.append(i)
+        for qa in qa_new:
+            new_model.graph.quantization_annotation.append(qa)
 
         # tidy-up new model
         model = new_model
diff --git a/src/finn/util/basic.py b/src/finn/util/basic.py
index 62d5947e3b7e06375cc9d48a2cf32b4f685e7861..cc759bebb1b856a84e25978d442e460332092d23 100644
--- a/src/finn/util/basic.py
+++ b/src/finn/util/basic.py
@@ -156,13 +156,19 @@ def make_build_dir(prefix=""):
 
 
 def get_by_name(container, name, name_field="name"):
-    """Return item from container by .name field if it exists, None otherwise"""
+    """Return item from container by .name field if it exists, None otherwise.
+    Will throw an Exception if multiple items are found, since this violates the
+    ONNX standard."""
     names = [getattr(x, name_field) for x in container]
-    try:
-        ind = names.index(name)
-        return container[ind]
-    except ValueError:
+
+    inds = [i for i, e in enumerate(names) if e == name]
+    if len(inds) > 1:
+        raise Exception("Found multiple get_by_name matches, undefined behavior")
+    elif len(inds) == 0:
         return None
+    else:
+        ind = inds[0]
+        return container[ind]
 
 
 def remove_by_name(container, name, name_field="name"):
diff --git a/tests/fpgadataflow/test_convert_to_hls_layers_fc.py b/tests/fpgadataflow/test_convert_to_hls_layers_fc.py
index 30d5ae64cfec84e089426d389a8cb607cd71c12f..bd600c6c57d00d5fc03152f75b9f2f8c6beeeb2c 100644
--- a/tests/fpgadataflow/test_convert_to_hls_layers_fc.py
+++ b/tests/fpgadataflow/test_convert_to_hls_layers_fc.py
@@ -89,7 +89,6 @@ def test_convert_to_hls_layers_tfc_w1a1():
     assert fc3.op_type == "StreamingFCLayer_Batch"
     assert model.get_tensor_shape(fc3.input[0]) == [1, 64]
     assert model.get_tensor_shape(fc3.input[1]) == [64, 10]
-    os.remove(export_onnx_path)
 
     fc0w = getCustomOp(fc0)
     fc0w.set_nodeattr("SIMD", 784)