diff --git a/b_asic/GUI/about_window.py b/b_asic/GUI/about_window.py
index 0cb5fa7e3ee73eac015995592db86f4f8c3972c4..699e1b7cbbd8e1b1929ad7fe44df7b88238526ae 100644
--- a/b_asic/GUI/about_window.py
+++ b/b_asic/GUI/about_window.py
@@ -7,7 +7,7 @@ QUESTIONS = {
     "Moving operations": "To drag an operation, select the operation on the workspace and drag it around.",
     "Selecting operations": "To select one operation just press it once, it will then turn grey.",
     "Selecting multiple operations using dragging": "To select multiple operations using your mouse, \ndrag the mouse while pressing left mouse button, any operation under the selection box will then be selected.",
-    "Selecting multiple operations using without dragging": "To select mutliple operations using without dragging, \npress 'Ctrl+LMouseButton' on any operation. Alternatively press 'Ctrl+A' to select all operations.",
+    "Selecting multiple operations using without dragging": "To select mutliple operations using without dragging, \npress 'Ctrl+LMouseButton' on any operation.",
     "Remove operations": "To remove an operation, select the operation to be deleted, \nfinally press RMouseButton to bring up the context menu, then press 'Delete'.",
     "Remove multiple operations": "To remove multiple operations, \nselect all operations to be deleted and press 'Delete' on your keyboard.",
     "Connecting operations": "To connect operations, select the ports on the operation to connect from, \nthen select the next port by pressing 'Ctrl+LMouseButton' on the destination port. Tip: You can chain connection by selecting the ports in the order they should be connected.",
@@ -41,7 +41,6 @@ class KeybindsWindow(QDialog):
         self.dialog_layout.addWidget(frame)
 
         keybinds_label = QLabel(
-            "'Ctrl+A' - Select all operations on the workspace.\n"
             "'Ctrl+R' - Reload the operation list to add any new operations created.\n"
             "'Ctrl+Q' - Quit the application.\n"
             "'Ctrl+LMouseButton' - On a operation will select the operation, without deselecting the other operations.\n"
diff --git a/b_asic/GUI/arrow.py b/b_asic/GUI/arrow.py
index 3c931150f3af95f6f2f2d17552c91072e41c4008..aea8fec3b488d7fbfe49766f3a23b3fa2346a920 100644
--- a/b_asic/GUI/arrow.py
+++ b/b_asic/GUI/arrow.py
@@ -9,10 +9,12 @@ from b_asic import Signal
 
 class Arrow(QGraphicsLineItem):
 
-    def __init__(self, source, destination, window, parent=None):
+    def __init__(self, source, destination, window, create_signal=True, parent=None):
         super(Arrow, self).__init__(parent)
         self.source = source
-        self.signal = Signal(source.port, destination.port)
+        # if an signal does not exist create one
+        if create_signal:
+            self.signal = Signal(source.port, destination.port)
         self.destination = destination
         self._window = window
         self.moveLine()
diff --git a/b_asic/GUI/drag_button.py b/b_asic/GUI/drag_button.py
index cd54115cfe0dd4b3c9365b21aaeb45124fa45944..fe78f46323400555fce5687cc2459009e61c0a4a 100644
--- a/b_asic/GUI/drag_button.py
+++ b/b_asic/GUI/drag_button.py
@@ -70,7 +70,8 @@ class DragButton(QPushButton):
 
                     button.move(button.mapToParent(event.pos() - self._mouse_press_pos))
 
-        self._window.update()
+        self._window.scene.update()
+        self._window.graphic_view.update()
         super(DragButton, self).mouseMoveEvent(event)
 
     def mouseReleaseEvent(self, event):
@@ -116,15 +117,20 @@ class DragButton(QPushButton):
 
     def remove(self):
         self._window.logger.info(f"Removing operation with name {self.operation.name}.")
-        self._window.scene.removeItem(self._window.operationDict[self])
+        self._window.scene.removeItem(self._window.dragOperationSceneDict[self])
 
         _signals = []
         for signal, ports in self._window.signalPortDict.items():
-            if any([port in self._window.portDict[self] for port in ports]):
+            if any(map(lambda port: set(port).intersection(set(self._window.portDict[self])), ports)):
                 self._window.logger.info(f"Removed signal with name: {signal.signal.name} to/from operation: {self.operation.name}.")
                 signal.remove()
                 _signals.append(signal)
 
+        if self in self._window.opToSFG:
+            self._window.logger.info(f"Operation detected in existing sfg, removing sfg with name: {self._window.opToSFG[self].name}.")
+            self._window.opToSFG = {op: self._window.opToSFG[op] for op in self._window.opToSFG if self._window.opToSFG[op] is self._window.opToSFG[self]}
+            del self._window.sfg_dict[self._window.opToSFG[self].name]
+
         for signal in _signals:
             del self._window.signalPortDict[signal]
 
@@ -135,5 +141,5 @@ class DragButton(QPushButton):
         if self in self._window.pressed_operations:
             self._window.pressed_operations.remove(self)
 
-        if self in self._window.operationDict.keys():
-            del self._window.operationDict[self]
\ No newline at end of file
+        if self in self._window.dragOperationSceneDict.keys():
+            del self._window.dragOperationSceneDict[self]
diff --git a/b_asic/GUI/gui_interface.py b/b_asic/GUI/gui_interface.py
index 50c6c1dd419744b9366057a357af8e81f4a4c64a..a46a9d7984386daf9398bfe45267dba59a0eea31 100644
--- a/b_asic/GUI/gui_interface.py
+++ b/b_asic/GUI/gui_interface.py
@@ -64,9 +64,16 @@ class Ui_main_window(object):
         self.special_operations_page.setGeometry(QtCore.QRect(0, 0, 171, 217))
         self.special_operations_page.setObjectName("special_operations_page")
         self.special_operations_list = QtWidgets.QListWidget(self.special_operations_page)
-        self.special_operations_list.setGeometry(QtCore.QRect(10, 0, 141, 81))
+        self.special_operations_list.setGeometry(QtCore.QRect(10, 0, 141, 211))
         self.special_operations_list.setObjectName("special_operations_list")
         self.operation_list.addItem(self.special_operations_page, "")
+        self.custom_operations_page = QtWidgets.QWidget()
+        self.custom_operations_page.setGeometry(QtCore.QRect(0, 0, 171, 217))
+        self.custom_operations_page.setObjectName("custom_operations_page")
+        self.custom_operations_list = QtWidgets.QListWidget(self.custom_operations_page)
+        self.custom_operations_list.setGeometry(QtCore.QRect(10, 0, 141, 211))
+        self.custom_operations_list.setObjectName("custom_operations_list")
+        self.operation_list.addItem(self.custom_operations_page, "")
         main_window.setCentralWidget(self.centralwidget)
         self.menu_bar = QtWidgets.QMenuBar(main_window)
         self.menu_bar.setGeometry(QtCore.QRect(0, 0, 897, 21))
@@ -231,8 +238,12 @@ class Ui_main_window(object):
         self.status_bar = QtWidgets.QStatusBar(main_window)
         self.status_bar.setObjectName("status_bar")
         main_window.setStatusBar(self.status_bar)
+        self.load_menu = QtWidgets.QAction(main_window)
+        self.load_menu.setObjectName("load_menu")
         self.save_menu = QtWidgets.QAction(main_window)
         self.save_menu.setObjectName("save_menu")
+        self.load_operations = QtWidgets.QAction(main_window)
+        self.load_operations.setObjectName("load_operations")
         self.exit_menu = QtWidgets.QAction(main_window)
         self.exit_menu.setObjectName("exit_menu")
         self.actionUndo = QtWidgets.QAction(main_window)
@@ -252,7 +263,9 @@ class Ui_main_window(object):
         self.actionToolbar = QtWidgets.QAction(main_window)
         self.actionToolbar.setCheckable(True)
         self.actionToolbar.setObjectName("actionToolbar")
+        self.file_menu.addAction(self.load_menu)
         self.file_menu.addAction(self.save_menu)
+        self.file_menu.addAction(self.load_operations)
         self.file_menu.addSeparator()
         self.file_menu.addAction(self.exit_menu)
         self.edit_menu.addAction(self.actionUndo)
@@ -287,6 +300,10 @@ class Ui_main_window(object):
         self.special_operations_list.setSortingEnabled(False)
         self.special_operations_list.setSortingEnabled(__sortingEnabled)
         self.operation_list.setItemText(self.operation_list.indexOf(self.special_operations_page), _translate("main_window", "Special operations"))
+        __sortingEnabled = self.special_operations_list.isSortingEnabled()
+        self.custom_operations_list.setSortingEnabled(False)
+        self.custom_operations_list.setSortingEnabled(__sortingEnabled)
+        self.operation_list.setItemText(self.operation_list.indexOf(self.custom_operations_page), _translate("main_window", "Custom operation"))
         self.file_menu.setTitle(_translate("main_window", "File"))
         self.edit_menu.setTitle(_translate("main_window", "Edit"))
         self.view_menu.setTitle(_translate("main_window", "View"))
@@ -297,7 +314,9 @@ class Ui_main_window(object):
         self.aboutBASIC.setText(_translate("main_window", "About B-ASIC"))
         self.faqBASIC.setText(_translate("main_window", "FAQ"))
         self.keybindsBASIC.setText(_translate("main_window", "Keybinds"))
-        self.save_menu.setText(_translate("main_window", "Save"))
+        self.load_menu.setText(_translate("main_window", "Load SFG"))
+        self.save_menu.setText(_translate("main_window", "Save SFG"))
+        self.load_operations.setText(_translate("main_window", "Load Operations"))
         self.exit_menu.setText(_translate("main_window", "Exit"))
         self.exit_menu.setShortcut(_translate("main_window", "Ctrl+Q"))
         self.actionUndo.setText(_translate("main_window", "Undo"))
diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py
index a353ca3cb2446f143131edb7ac6f439c66ad684d..0607100a2f45b618f179b8a2bffcc98d52eb9173 100644
--- a/b_asic/GUI/main_window.py
+++ b/b_asic/GUI/main_window.py
@@ -5,6 +5,7 @@ This python file is the main window of the GUI for B-ASIC.
 
 from pprint import pprint
 from os import getcwd, path
+import importlib
 import logging
 logging.basicConfig(level=logging.INFO)
 import sys
@@ -16,11 +17,14 @@ from arrow import Arrow
 from port_button import PortButton
 from show_pc_window import ShowPCWindow
 
-from b_asic import Operation, SFG, InputPort, OutputPort, FastSimulation
+from b_asic.simulation import Simulation
+from b_asic import Operation, SFG, InputPort, OutputPort, Input, Output, FastSimulation
 import b_asic.core_operations as c_oper
 import b_asic.special_operations as s_oper
 from utils import decorate_class, handle_error
 from simulate_sfg_window import SimulateSFGWindow, Plot
+from select_sfg_window import SelectSFGWindow
+from b_asic.save_load_structure import *
 
 from numpy import linspace
 
@@ -28,10 +32,14 @@ from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QActio
 QStatusBar, QMenuBar, QLineEdit, QPushButton, QSlider, QScrollArea, QVBoxLayout,\
 QHBoxLayout, QDockWidget, QToolBar, QMenu, QLayout, QSizePolicy, QListWidget,\
 QListWidgetItem, QGraphicsView, QGraphicsScene, QShortcut, QGraphicsTextItem,\
-QGraphicsProxyWidget, QInputDialog, QTextEdit
-from PySide2.QtCore import Qt, QSize
+QGraphicsProxyWidget, QInputDialog, QTextEdit, QFileDialog
+from PySide2.QtCore import Qt, QSize, QFileInfo
 from PySide2.QtGui import QIcon, QFont, QPainter, QPen, QBrush, QKeySequence
 
+from tkinter import Tk
+from tkinter.filedialog import askopenfilename, askopenfile
+
+
 MIN_WIDTH_SCENE = 600
 MIN_HEIGHT_SCENE = 520
 
@@ -46,14 +54,16 @@ class MainWindow(QMainWindow):
         self._operations_from_name = dict()
         self.zoom = 1
         self.sfg_name_i = 0
-        self.operationDict = dict()
+        self.dragOperationSceneDict = dict()
+        self.operationDragDict = dict()
         self.operationItemSceneList = []
         self.signalList = []
         self.pressed_operations = []
         self.portDict = dict()
         self.signalPortDict = dict()
+        self.opToSFG = dict()
         self.pressed_ports = []
-        self.sfg_list = []
+        self.sfg_dict = dict()
         self._window = self
         self.logger = logging.getLogger(__name__)
         self.init_ui()
@@ -62,8 +72,6 @@ class MainWindow(QMainWindow):
 
         self.shortcut_core = QShortcut(QKeySequence("Ctrl+R"), self.ui.operation_box)
         self.shortcut_core.activated.connect(self._refresh_operations_list_from_namespace)
-        self.shortcut_select_all = QShortcut(QKeySequence("Ctrl+A"), self.graphic_view)
-        self.shortcut_select_all.activated.connect(self._select_all_operations)
         self.scene.selectionChanged.connect(self._select_operations)
 
         self.move_button_index = 0
@@ -80,6 +88,17 @@ class MainWindow(QMainWindow):
         self.ui.faqBASIC.triggered.connect(self.display_faq_page)
         self.ui.aboutBASIC.triggered.connect(self.display_about_page)
         self.ui.keybindsBASIC.triggered.connect(self.display_keybinds_page)
+        self.ui.core_operations_list.itemClicked.connect(self.on_list_widget_item_clicked)
+        self.ui.special_operations_list.itemClicked.connect(self.on_list_widget_item_clicked)
+        self.ui.custom_operations_list.itemClicked.connect(self.on_list_widget_item_clicked)
+        self.ui.save_menu.triggered.connect(self.save_work)
+        self.ui.load_menu.triggered.connect(self.load_work)
+        self.ui.load_operations.triggered.connect(self.add_namespace)
+        self.ui.exit_menu.triggered.connect(self.exit_app)
+        self.shortcut_open = QShortcut(QKeySequence("Ctrl+O"), self)
+        self.shortcut_open.activated.connect(self.load_work)
+        self.shortcut_save = QShortcut(QKeySequence("Ctrl+S"), self)
+        self.shortcut_save.activated.connect(self.save_work)
         self.shortcut_help = QShortcut(QKeySequence("Ctrl+?"), self)
         self.shortcut_help.activated.connect(self.display_faq_page)
 
@@ -87,9 +106,6 @@ class MainWindow(QMainWindow):
         self.logger.info("For questions please refer to 'Ctrl+?', or visit the 'Help' section on the toolbar.")
 
     def init_ui(self):
-        self.ui.core_operations_list.itemClicked.connect(self.on_list_widget_item_clicked)
-        self.ui.special_operations_list.itemClicked.connect(self.on_list_widget_item_clicked)
-        self.ui.exit_menu.triggered.connect(self.exit_app)
         self.create_toolbar_view()
         self.create_graphics_view()
 
@@ -103,6 +119,7 @@ class MainWindow(QMainWindow):
     def create_toolbar_view(self):
         self.toolbar = self.addToolBar("Toolbar")
         self.toolbar.addAction("Create SFG", self.create_SFG_from_toolbar)
+        self.toolbar.addAction("Clear Workspace", self.clear_workspace)
 
     def resizeEvent(self, event):
         self.ui.operation_box.setGeometry(10, 10, self.ui.operation_box.width(), self.height())
@@ -122,32 +139,135 @@ class MainWindow(QMainWindow):
         else:
             self.is_show_names = False
 
-        for operation in self.operationDict.keys():
+        for operation in self.dragOperationSceneDict.keys():
             operation.label.setOpacity(self.is_show_names)
             operation.is_show_name = self.is_show_names
 
+    def _save_work(self):
+        sfg = self.sfg_widget.sfg
+        file_dialog = QFileDialog()
+        file_dialog.setDefaultSuffix(".py")
+        module, accepted = file_dialog.getSaveFileName()
+        if not accepted:
+            return
+
+        self.logger.info(f"Saving sfg to path: {module}.")
+        operation_positions = dict()
+        for operation_drag, operation_scene in self.dragOperationSceneDict.items():
+            operation_positions[operation_drag.operation.graph_id] = (operation_scene.x(), operation_scene.y())
+
+        try:
+            with open(module, "w+") as file_obj:
+                file_obj.write(sfg_to_python(sfg, suffix=f"positions = {str(operation_positions)}"))
+        except Exception as e:
+            self.logger.error(f"Failed to save sfg to path: {module}, with error: {e}.")
+            return
+
+        self.logger.info(f"Saved sfg to path: {module}.")
+
+    def save_work(self):
+        self.sfg_widget = SelectSFGWindow(self)
+        self.sfg_widget.show()
+
+        # Wait for input to dialog.
+        self.sfg_widget.ok.connect(self._save_work)
+
+    def load_work(self):
+        module, accepted = QFileDialog().getOpenFileName()
+        if not accepted:
+            return
+
+        self.logger.info(f"Loading sfg from path: {module}.")
+        try:
+            sfg, positions = python_to_sfg(module)
+        except ImportError as e:
+            self.logger.error(f"Failed to load module: {module} with the following error: {e}.")
+            return
+
+        while sfg.name in self.sfg_dict:
+            self.logger.warning(f"Duplicate sfg with name: {sfg.name} detected. Please choose a new name.")
+            name, accepted = QInputDialog.getText(self, "Change SFG Name", "Name: ", QLineEdit.Normal)
+            if not accepted:
+                return
+
+            sfg.name = name
+
+        for op in sfg.split():
+            self.create_operation(op, positions[op.graph_id] if op.graph_id in positions else None)
+
+        def connect_ports(ports):
+            for port in ports:
+                for signal in port.signals:
+                    source = [source for source in self.portDict[self.operationDragDict[signal.source.operation]] if source.port is signal.source]
+                    destination = [destination for destination in self.portDict[self.operationDragDict[signal.destination.operation]] if destination.port is signal.destination]
+
+                    if source and destination:
+                        self.connect_button(source[0], destination[0])
+
+            for port in self.pressed_ports:
+                port.select_port()
+
+        for op in sfg.split():
+            connect_ports(op.inputs)
+
+        for op in sfg.split():
+            self.operationDragDict[op].setToolTip(sfg.name)
+
+        self.sfg_dict[sfg.name] = sfg
+        self.logger.info(f"Loaded sfg from path: {module}.")
+        self.update()
+
     def exit_app(self):
         self.logger.info("Exiting the application.")
         QApplication.quit()
 
+    def clear_workspace(self):
+        self.logger.info("Clearing workspace from operations and sfgs.")
+        self.pressed_operations.clear()
+        self.pressed_ports.clear()
+        self.operationItemSceneList.clear()
+        self.operationDragDict.clear()
+        self.dragOperationSceneDict.clear()
+        self.signalList.clear()
+        self.portDict.clear()
+        self.signalPortDict.clear()
+        self.sfg_dict.clear()
+        self.scene.clear()
+        self.logger.info("Workspace cleared.")
+
     def create_SFG_from_toolbar(self):
         inputs = []
         outputs = []
         for op in self.pressed_operations:
-            if isinstance(op.operation, s_oper.Input):
+            if isinstance(op.operation, Input):
                 inputs.append(op.operation)
-            elif isinstance(op.operation, s_oper.Output):
+            elif isinstance(op.operation, Output):
                 outputs.append(op.operation)
 
-        name = QInputDialog.getText(self, "Create SFG", "Name: ", QLineEdit.Normal)
-        self.logger.info(f"Creating SFG with name: {name[0]} from selected operations.")
+        name, accepted = QInputDialog.getText(self, "Create SFG", "Name: ", QLineEdit.Normal)
+        if not accepted:
+            return
+
+        if name == "":
+            self.logger.warning(f"Failed to initialize SFG with empty name.")
+            return
 
-        sfg = SFG(inputs=inputs, outputs=outputs, name=name[0])
-        self.logger.info(f"Created SFG with name: {name[0]} from selected operations.")
+        self.logger.info(f"Creating SFG with name: {name} from selected operations.")
+
+        sfg = SFG(inputs=inputs, outputs=outputs, name=name)
+        self.logger.info(f"Created SFG with name: {name} from selected operations.")
+
+        sfg_operations = sfg.operations.copy()
+        for op in self.pressed_operations:
+            operation = [op_ for op_ in sfg_operations if op_.type_name() == op.operation.type_name()][0]
+            op.operation = operation
+            sfg_operations.remove(operation)
 
         for op in self.pressed_operations:
             op.setToolTip(sfg.name)
-        self.sfg_list.append(sfg)
+            self.opToSFG[op] = sfg
+
+        self.sfg_dict[sfg.name] = sfg
 
     def show_precedence_chart(self):
         self.dialog = ShowPCWindow(self)
@@ -196,18 +316,32 @@ class MainWindow(QMainWindow):
 
         self.logger.info(f"Added operations from namespace: {namespace.__name__}.")
 
-    def _create_operation(self, item):
-        self.logger.info(f"Creating operation of type: {item.text()}.")
+    def add_namespace(self):
+        module, accepted = QFileDialog().getOpenFileName()
+        if not accepted:
+            return
+
+        spec = importlib.util.spec_from_file_location(f"{QFileInfo(module).fileName()}", module)
+        namespace = importlib.util.module_from_spec(spec)
+        spec.loader.exec_module(namespace)
+
+        self.add_operations_from_namespace(namespace, self.ui.custom_operations_list)
+
+    def create_operation(self, op, position=None):
+        self.logger.info(f"Creating operation of type: {op.type_name()}.")
         try:
-            attr_oper = self._operations_from_name[item.text()]()
-            attr_button = DragButton(attr_oper.graph_id, attr_oper, attr_oper.type_name().lower(), True, self)
-            attr_button.move(250, 100)
+            attr_button = DragButton(op.graph_id, op, op.type_name().lower(), True, window = self)
+            if position is None:
+                attr_button.move(250, 100)
+            else:
+                attr_button.move(*position)
+
             attr_button.setFixedSize(55, 55)
             attr_button.setStyleSheet("background-color: white; border-style: solid;\
             border-color: black; border-width: 2px")
             self.add_ports(attr_button)
 
-            icon_path = path.join("operation_icons", f"{attr_oper.type_name().lower()}.png")
+            icon_path = path.join("operation_icons", f"{op.type_name().lower()}.png")
             if not path.exists(icon_path):
                 icon_path = path.join("operation_icons", f"custom_operation.png")
             attr_button.setIcon(QIcon(icon_path))
@@ -217,16 +351,26 @@ class MainWindow(QMainWindow):
             color: black }""")
             attr_button.setParent(None)
             attr_button_scene = self.scene.addWidget(attr_button)
-            attr_button_scene.moveBy(self.move_button_index * 100, 0)
+            if position is None:
+                attr_button_scene.moveBy(self.move_button_index * 100, 0)
             attr_button_scene.setFlag(attr_button_scene.ItemIsSelectable, True)
             self.move_button_index += 1
-            operation_label = QGraphicsTextItem(attr_oper.type_name(), attr_button_scene)
+            operation_label = QGraphicsTextItem(op.type_name(), attr_button_scene)
             if not self.is_show_names:
                 operation_label.setOpacity(0)
             operation_label.setTransformOriginPoint(operation_label.boundingRect().center())
             operation_label.moveBy(10, -20)
             attr_button.add_label(operation_label)
-            self.operationDict[attr_button] = attr_button_scene
+            self.operationDragDict[op] = attr_button
+            self.dragOperationSceneDict[attr_button] = attr_button_scene
+        except Exception as e:
+            self.logger.error(f"Unexpected error occured while creating operation: {e}.")
+
+    def _create_operation_item(self, item):
+        self.logger.info(f"Creating operation of type: {item.text()}.")
+        try:
+            attr_oper = self._operations_from_name[item.text()]()
+            self.create_operation(attr_oper)
         except Exception as e:
             self.logger.error(f"Unexpected error occured while creating operation: {e}.")
 
@@ -240,7 +384,7 @@ class MainWindow(QMainWindow):
         self.logger.info("Finished refreshing operation list.")
 
     def on_list_widget_item_clicked(self, item):
-        self._create_operation(item)
+        self._create_operation_item(item)
 
     def keyPressEvent(self, event):
         if event.key() == Qt.Key_Delete:
@@ -250,37 +394,39 @@ class MainWindow(QMainWindow):
             self.pressed_operations.clear()
         super().keyPressEvent(event)
 
-    def connectButton(self, button):
+    def _connect_button(self, event):
         if len(self.pressed_ports) < 2:
-            self.logger.warn("Can't connect less than two ports. Please select more.")
+            self.logger.warning("Can't connect less than two ports. Please select more.")
             return
 
         for i in range(len(self.pressed_ports) - 1):
-            if isinstance(self.pressed_ports[i].port, OutputPort) and \
-            isinstance(self.pressed_ports[i+1].port, InputPort):
-                self.logger.info(f"Connecting: {self.pressed_ports[i].operation.operation.type_name()} -> {self.pressed_ports[i + 1].operation.operation.type_name()}")
-                line = Arrow(self.pressed_ports[i], self.pressed_ports[i + 1], self)
-                self.signalPortDict[line] = [self.pressed_ports[i], self.pressed_ports[i + 1]]
+            source = self.pressed_ports[i] if isinstance(self.pressed_ports[i].port, OutputPort) else self.pressed_ports[i + 1]
+            destination = self.pressed_ports[i + 1] if source is not self.pressed_ports[i + 1] else self.pressed_ports[i]
+            if source.port.operation is destination.port.operation:
+                continue
 
-                self.scene.addItem(line)
-                self.signalList.append(line)
+            self.connect_button(source, destination)
 
         for port in self.pressed_ports:
             port.select_port()
 
+    def connect_button(self, source, destination):
+        signal_exists = any([signal.destination is destination.port for signal in source.port.signals])
+        self.logger.info(f"Connecting: {source.operation.operation.type_name()} -> {destination.operation.operation.type_name()}.")
+        line = Arrow(source, destination, self, create_signal=not signal_exists)
+        if line not in self.signalPortDict:
+            self.signalPortDict[line] = []
+
+        self.signalPortDict[line].append((source, destination))
+        self.scene.addItem(line)
+        self.signalList.append(line)
+
         self.update()
 
     def paintEvent(self, event):
         for signal in self.signalPortDict.keys():
             signal.moveLine()
 
-    def _select_all_operations(self):
-        self.logger.info("Selecting all operations in the workspace.")
-        self.pressed_operations.clear()
-        for button in self.operationDict.keys():
-            button._toggle_button(pressed=False)
-            self.pressed_operations.append(button)
-
     def _select_operations(self):
         selected = [button.widget() for button in self.scene.selectedItems()]
         for button in selected:
@@ -296,12 +442,11 @@ class MainWindow(QMainWindow):
         for sfg, properties in self.dialog.properties.items():
             self.logger.info(f"Simulating sfg with name: {sfg.name}.")
             simulation = FastSimulation(sfg, input_providers=properties["input_values"])
-            simulation.run_for(properties["iteration_count"], save_results=properties["all_results"])
+            l_result = simulation.run_for(properties["iteration_count"], save_results=properties["all_results"])
 
-            if properties["all_results"]:
-                print(f"{'=' * 10} {sfg.name} {'=' * 10}")
-                pprint(simulation.results)
-                print(f"{'=' * 10} /{sfg.name} {'=' * 10}")
+            print(f"{'=' * 10} {sfg.name} {'=' * 10}")
+            pprint(simulation.results if properties["all_results"] else l_result)
+            print(f"{'=' * 10} /{sfg.name} {'=' * 10}")
 
             if properties["show_plot"]:
                 self.logger.info(f"Opening plot for sfg with name: {sfg.name}.")
@@ -312,7 +457,7 @@ class MainWindow(QMainWindow):
     def simulate_sfg(self):
         self.dialog = SimulateSFGWindow(self)
 
-        for sfg in self.sfg_list:
+        for _, sfg in self.sfg_dict.items():
             self.dialog.add_sfg_to_dialog(sfg)
 
         self.dialog.show()
diff --git a/b_asic/GUI/port_button.py b/b_asic/GUI/port_button.py
index 11c36ef5206ae4935a5bcf675a10eee22239d8dc..06e1ee455a8ab0b7ad7552e18bf0bec6733a83aa 100644
--- a/b_asic/GUI/port_button.py
+++ b/b_asic/GUI/port_button.py
@@ -18,7 +18,7 @@ class PortButton(QPushButton):
         self._m_press = False
 
         self.setStyleSheet("background-color: white")
-        self.connectionRequested.connect(self._window.connectButton)
+        self.connectionRequested.connect(self._window._connect_button)
 
     def contextMenuEvent(self, event):
         menu = QMenu()
diff --git a/b_asic/GUI/properties_window.py b/b_asic/GUI/properties_window.py
index 22fdf7d2e9329571e98e38e4aad242b6fd50733e..04d899fd317dfbaa6a5b65934edd62a9fc479b68 100644
--- a/b_asic/GUI/properties_window.py
+++ b/b_asic/GUI/properties_window.py
@@ -1,5 +1,5 @@
 from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\
-QLabel, QCheckBox
+QLabel, QCheckBox, QGridLayout
 from PySide2.QtCore import Qt
 from PySide2.QtGui import QIntValidator
 
@@ -17,6 +17,7 @@ class PropertiesWindow(QDialog):
         self.edit_name = QLineEdit(self.operation.operation_path_name)
         self.name_layout.addWidget(self.name_label)
         self.name_layout.addWidget(self.edit_name)
+        self.latency_fields = dict()
 
         self.vertical_layout = QVBoxLayout()
         self.vertical_layout.addLayout(self.name_layout)
@@ -43,6 +44,76 @@ class PropertiesWindow(QDialog):
         self.show_name_layout.addWidget(self.check_show_name)
         self.vertical_layout.addLayout(self.show_name_layout)
 
+        if self.operation.operation.input_count > 0:
+            self.latency_layout = QHBoxLayout()
+            self.latency_label = QLabel("Set Latency For Input Ports (-1 for None):")
+            self.latency_layout.addWidget(self.latency_label)
+            self.vertical_layout.addLayout(self.latency_layout)
+
+            input_grid = QGridLayout()
+            x, y = 0, 0
+            for i in range(self.operation.operation.input_count):
+                input_layout = QHBoxLayout()
+                input_layout.addStretch()
+                if i % 2 == 0 and i > 0:
+                    x += 1
+                    y = 0
+
+                input_label = QLabel("in" + str(i))
+                input_layout.addWidget(input_label)
+                input_value = QLineEdit()
+                try:
+                    input_value.setPlaceholderText(str(self.operation.operation.latency))
+                except ValueError:
+                    input_value.setPlaceholderText("-1")
+                int_valid = QIntValidator()
+                int_valid.setBottom(-1)
+                input_value.setValidator(int_valid)
+                input_value.setFixedWidth(50)
+                self.latency_fields["in" + str(i)] = input_value
+                input_layout.addWidget(input_value)
+                input_layout.addStretch()
+                input_layout.setSpacing(10)
+                input_grid.addLayout(input_layout, x, y)
+                y += 1
+
+            self.vertical_layout.addLayout(input_grid)
+
+        if self.operation.operation.output_count > 0:
+            self.latency_layout = QHBoxLayout()
+            self.latency_label = QLabel("Set Latency For Output Ports (-1 for None):")
+            self.latency_layout.addWidget(self.latency_label)
+            self.vertical_layout.addLayout(self.latency_layout)
+
+            input_grid = QGridLayout()
+            x, y = 0, 0
+            for i in range(self.operation.operation.output_count):
+                input_layout = QHBoxLayout()
+                input_layout.addStretch()
+                if i % 2 == 0 and i > 0:
+                    x += 1
+                    y = 0
+
+                input_label = QLabel("out" + str(i))
+                input_layout.addWidget(input_label)
+                input_value = QLineEdit()
+                try:
+                    input_value.setPlaceholderText(str(self.operation.operation.latency))
+                except ValueError:
+                    input_value.setPlaceholderText("-1")
+                int_valid = QIntValidator()
+                int_valid.setBottom(-1)
+                input_value.setValidator(int_valid)
+                input_value.setFixedWidth(50)
+                self.latency_fields["out" + str(i)] = input_value
+                input_layout.addWidget(input_value)
+                input_layout.addStretch()
+                input_layout.setSpacing(10)
+                input_grid.addLayout(input_layout, x, y)
+                y += 1
+
+            self.vertical_layout.addLayout(input_grid)
+
         self.ok = QPushButton("OK")
         self.ok.clicked.connect(self.save_properties)
         self.vertical_layout.addWidget(self.ok)
@@ -53,11 +124,14 @@ class PropertiesWindow(QDialog):
         self.operation.name = self.edit_name.text()
         self.operation.label.setPlainText(self.operation.name)
         if self.operation.operation_path_name == "c":
-            self.operation.operation.value = self.edit_constant.text()
+            self.operation.operation.value = int(self.edit_constant.text())
         if self.check_show_name.isChecked():
             self.operation.label.setOpacity(1)
             self.operation.is_show_name = True
         else:
             self.operation.label.setOpacity(0)
             self.operation.is_show_name = False
+
+        self.operation.operation.set_latency_offsets({port: int(self.latency_fields[port].text()) if self.latency_fields[port].text() and int(self.latency_fields[port].text()) > 0 else None for port in self.latency_fields})
+
         self.reject()
\ No newline at end of file
diff --git a/b_asic/GUI/select_sfg_window.py b/b_asic/GUI/select_sfg_window.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b70c2c27f066f8ab28808673d347eec717f1e5c
--- /dev/null
+++ b/b_asic/GUI/select_sfg_window.py
@@ -0,0 +1,39 @@
+from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\
+QLabel, QCheckBox, QSpinBox, QGroupBox, QFrame, QFormLayout, QGridLayout, QSizePolicy, QFileDialog, QShortcut, QComboBox
+from PySide2.QtCore import Qt, Signal
+from PySide2.QtGui import QIntValidator, QKeySequence
+
+from matplotlib.backends import qt_compat
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.figure import Figure
+
+
+class SelectSFGWindow(QDialog):
+    ok = Signal()
+
+    def __init__(self, window):
+        super(SelectSFGWindow, self).__init__()
+        self._window = window
+        self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
+        self.setWindowTitle("Select SFG")
+
+        self.dialog_layout = QVBoxLayout()
+        self.ok_btn = QPushButton("Ok")
+        self.ok_btn.clicked.connect(self.save_properties)
+        self.dialog_layout.addWidget(self.ok_btn)
+        self.combo_box = QComboBox()
+
+        self.sfg = None
+        self.setLayout(self.dialog_layout)
+        self.add_sfgs_to_layout()
+
+    def add_sfgs_to_layout(self):
+        for sfg in self._window.sfg_dict:
+            self.combo_box.addItem(sfg)
+
+        self.dialog_layout.addWidget(self.combo_box)
+
+    def save_properties(self):
+        self.sfg = self._window.sfg_dict[self.combo_box.currentText()]
+        self.accept()
+        self.ok.emit()
diff --git a/b_asic/GUI/show_pc_window.py b/b_asic/GUI/show_pc_window.py
index 3946ba9f821caed80893c52cb27213cbe3143edf..5952ca991c70a4a466183900adab6f0fb39610a1 100644
--- a/b_asic/GUI/show_pc_window.py
+++ b/b_asic/GUI/show_pc_window.py
@@ -10,7 +10,7 @@ class ShowPCWindow(QDialog):
     def __init__(self, window):
         super(ShowPCWindow, self).__init__()
         self._window = window
-        self.check_box_list = []
+        self.check_box_dict = dict()
         self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
         self.setWindowTitle("Show PC")
 
@@ -21,29 +21,28 @@ class ShowPCWindow(QDialog):
         self.setLayout(self.dialog_layout)
 
     def add_sfg_to_dialog(self):
-        sfg_layout = QVBoxLayout()
-        options_layout = QFormLayout()
+        self.sfg_layout = QVBoxLayout()
+        self.options_layout = QFormLayout()
 
-        for sfg in self._window.sfg_list:
+        for sfg in self._window.sfg_dict:
             check_box = QCheckBox()
-            options_layout.addRow(sfg.name, check_box)
-            self.check_box_list.append(check_box)
+            self.options_layout.addRow(sfg, check_box)
+            self.check_box_dict[check_box] = sfg
 
-        sfg_layout.addLayout(options_layout)
+        self.sfg_layout.addLayout(self.options_layout)
 
         frame = QFrame()
         frame.setFrameShape(QFrame.HLine)
         frame.setFrameShadow(QFrame.Sunken)
         self.dialog_layout.addWidget(frame)
 
-        self.dialog_layout.addLayout(sfg_layout)
+        self.dialog_layout.addLayout(self.sfg_layout)
 
     def show_precedence_graph(self):
-        for i, check_box in enumerate(self.check_box_list):
+        for check_box, sfg in self.check_box_dict.items():
             if check_box.isChecked():
-                self._window.logger.info("Creating a precedence chart from " + self._window.sfg_list[i].name)
-                self._window.sfg_list[i].show_precedence_graph()
-                break
-        
+                self._window.logger.info(f"Creating a precedence chart from sfg with name: {sfg}.")
+                self._window.sfg_dict[sfg].show_precedence_graph()
+
         self.accept()
         self.pc.emit()
\ No newline at end of file
diff --git a/b_asic/GUI/simulate_sfg_window.py b/b_asic/GUI/simulate_sfg_window.py
index 48e584dc87e045322f5d92946b431d32d7e180b5..9e60e3dbeb1f8036e55e0992eb65feb985aa6c31 100644
--- a/b_asic/GUI/simulate_sfg_window.py
+++ b/b_asic/GUI/simulate_sfg_window.py
@@ -16,6 +16,7 @@ class SimulateSFGWindow(QDialog):
         self._window = window
         self.properties = dict()
         self.sfg_to_layout = dict()
+        self.input_fields = dict()
         self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
         self.setWindowTitle("Simulate SFG")
 
@@ -36,32 +37,50 @@ class SimulateSFGWindow(QDialog):
         spin_box.setRange(0, 2147483647)
         options_layout.addRow("Iteration Count: ", spin_box)
 
-        check_box = QCheckBox()
-        options_layout.addRow("Plot Results: ", check_box)
-
-        check_box = QCheckBox()
-        options_layout.addRow("Get All Results: ", check_box)
-
-        input_layout = QVBoxLayout()
-        input_label = QLabel("Input Values: ")
-        input_layout.addWidget(input_label)
-        input_grid = QGridLayout()
-        x, y = 0, 0
-        for i in range(sfg.input_count):
-            if i % 2 == 0 and i > 0:
-                x += 1
-                y = 0
-
-            input_value = QLineEdit()
-            input_value.setPlaceholderText(str(i))
-            input_value.setValidator(QIntValidator())
-            input_value.setFixedWidth(50)
-            input_grid.addWidget(input_value, x, y)
-            y += 1
-
-        input_layout.addLayout(input_grid)
+        check_box_plot = QCheckBox()
+        options_layout.addRow("Plot Results: ", check_box_plot)
+
+        check_box_all = QCheckBox()
+        options_layout.addRow("Get All Results: ", check_box_all)
+
         sfg_layout.addLayout(options_layout)
-        sfg_layout.addLayout(input_layout)
+
+        self.input_fields[sfg] = {
+            "iteration_count": spin_box,
+            "show_plot": check_box_plot,
+            "all_results": check_box_all,
+            "input_values": []
+        }
+
+        if sfg.input_count > 0:
+            input_label = QHBoxLayout()
+            input_label = QLabel("Input Values:")
+            options_layout.addRow(input_label)
+
+            input_grid = QGridLayout()
+            x, y = 0, 0
+            for i in range(sfg.input_count):
+                input_layout = QHBoxLayout()
+                input_layout.addStretch()
+                if i % 2 == 0 and i > 0:
+                    x += 1
+                    y = 0
+
+                input_label = QLabel("in" + str(i))
+                input_layout.addWidget(input_label)
+                input_value = QLineEdit()
+                input_value.setPlaceholderText("0")
+                input_value.setValidator(QIntValidator())
+                input_value.setFixedWidth(50)
+                input_layout.addWidget(input_value)
+                input_layout.addStretch()
+                input_layout.setSpacing(10)
+                input_grid.addLayout(input_layout, x, y)
+
+                self.input_fields[sfg]["input_values"].append(input_value)
+                y += 1
+
+            sfg_layout.addLayout(input_grid)
 
         frame = QFrame()
         frame.setFrameShape(QFrame.HLine)
@@ -72,21 +91,17 @@ class SimulateSFGWindow(QDialog):
         self.dialog_layout.addLayout(sfg_layout)
 
     def save_properties(self):
-        for sfg, sfg_row in self.sfg_to_layout.items():
-            _option_values = sfg_row.children()[0]
-            _input_values = sfg_row.children()[1].children()[0]
-            _, spin_box, _, check_box_plot, _, check_box_all = map(lambda x: _option_values.itemAt(x).widget(), range(_option_values.count()))
-            input_values = map(lambda x: _input_values.itemAt(x).widget(), range(_input_values.count()))
-            input_values = [int(widget.text()) if widget.text() else 0 for widget in input_values]
+        for sfg, properties in self.input_fields.items():
+
             self.properties[sfg] = {
-                "iteration_count": spin_box.value(),
-                "show_plot": check_box_plot.isChecked(),
-                "all_results": check_box_all.isChecked(),
-                "input_values": input_values
+                "iteration_count": self.input_fields[sfg]["iteration_count"].value(),
+                "show_plot": self.input_fields[sfg]["show_plot"].isChecked(),
+                "all_results": self.input_fields[sfg]["all_results"].isChecked(),
+                "input_values": [int(widget.text()) if widget.text() else 0 for widget in self.input_fields[sfg]["input_values"]]
             }
 
             # If we plot we should also print the entire data, since you can't really interact with the graph.
-            if check_box_plot.isChecked():
+            if self.properties[sfg]["show_plot"]:
                 self.properties[sfg]["all_results"] = True
 
         self.accept()
@@ -126,10 +141,8 @@ class Plot(FigureCanvas):
             self._window.logger.info(f"Saved plot: {self.sfg.name} to path: {path}.")
 
     def _plot_values_sfg(self):
-        x_axis = list(range(len(self.simulation.results.keys())))
+        x_axis = list(range(len(self.simulation.results["0"])))
         for _output in range(self.sfg.output_count):
-            y_axis = list()
-            for _iter in range(len(self.simulation.results.keys())):
-                y_axis.append(self.simulation.results[_iter][str(_output)])
+            y_axis = self.simulation.results[str(_output)]
 
             self.axes.plot(x_axis, y_axis)
diff --git a/b_asic/__init__.py b/b_asic/__init__.py
index f0293649d362f419818723cc751823594d655982..9405cbcb3d106a344d160d40108bc41fff1537d9 100644
--- a/b_asic/__init__.py
+++ b/b_asic/__init__.py
@@ -11,4 +11,5 @@ from b_asic.signal_flow_graph import *
 from b_asic.signal import *
 from b_asic.simulation import *
 from b_asic.special_operations import *
+from b_asic.save_load_structure import *
 from b_asic.schema import *
diff --git a/b_asic/operation.py b/b_asic/operation.py
index 08d6bf52c9b85b9d233e4f8f766b99105be94ac1..0778b5747fd8ad342bc076187293b65a0524741e 100644
--- a/b_asic/operation.py
+++ b/b_asic/operation.py
@@ -552,6 +552,9 @@ class AbstractOperation(Operation, AbstractGraphComponent):
 
     @property
     def latency(self) -> int:
+        if None in [inp.latency_offset for inp in self.inputs] or None in [outp.latency_offset for outp in self.outputs]:
+            raise ValueError("All native offsets have to set to a non-negative value to calculate the latency.")
+
         return max(((outp.latency_offset - inp.latency_offset) for outp, inp in it.product(self.outputs, self.inputs)))
 
     def set_latency(self, latency: int) -> None:
@@ -563,7 +566,15 @@ class AbstractOperation(Operation, AbstractGraphComponent):
 
     @property
     def latency_offsets(self) -> Sequence[Sequence[int]]:
-        return ([inp.latency_offset for inp in self.inputs], [outp.latency_offset for outp in self.outputs])
+        latency_offsets = dict()
+
+        for i, inp in enumerate(self.inputs):
+            latency_offsets["in" + str(i)] = inp.latency_offset
+
+        for i, outp in enumerate(self.outputs):
+            latency_offsets["out" + str(i)] = outp.latency_offset
+
+        return latency_offsets
 
     def set_latency_offsets(self, latency_offsets: Dict[str, int]) -> None:
         for port_str, latency_offset in latency_offsets.items():
@@ -574,7 +585,7 @@ class AbstractOperation(Operation, AbstractGraphComponent):
                 self.input(int(index_str)).latency_offset = latency_offset
             elif port_str.startswith("out"):
                 index_str = port_str[3:]
-                assert index_str.isdigit(), "INcorrectly formatted index in string, expected 'out' + index"
+                assert index_str.isdigit(), "Incorrectly formatted index in string, expected 'out' + index"
                 self.output(int(index_str)).latency_offset = latency_offset
             else:
                 raise ValueError("Incorrectly formatted string, expected 'in' + index or 'out' + index")
diff --git a/b_asic/save_load_structure.py b/b_asic/save_load_structure.py
new file mode 100644
index 0000000000000000000000000000000000000000..5311d3fe72b1bb711b21c0aa3070b538db81d2d6
--- /dev/null
+++ b/b_asic/save_load_structure.py
@@ -0,0 +1,78 @@
+"""@package docstring
+B-ASIC Save/Load SFG Module.
+Given a structure try to serialize it and save it to a file.
+Given a serialized file try to deserialize it and load it to the library.
+"""
+
+from b_asic import SFG, GraphComponent
+
+from datetime import datetime
+from inspect import signature
+from os import path
+
+def sfg_to_python(sfg: SFG, counter: int = 0, suffix: str = None) -> str:
+    result = (
+        "\n\"\"\"\nB-ASIC automatically generated SFG file.\n" +
+        "Name: " + f"{sfg.name}" + "\n" +
+        "Last saved: " + f"{datetime.now()}" + ".\n" +
+        "\"\"\""
+    )
+
+    result += "\nfrom b_asic import SFG, Signal, Input, Output"
+    for op in {type(op) for op in sfg.operations}:
+        result += f", {op.__name__}"
+
+    def kwarg_unpacker(comp: GraphComponent, params=None) -> str:
+        if params is None:
+            params_filtered = {attr: getattr(op, attr) for attr in signature(op.__init__).parameters if attr != "latency" and hasattr(op, attr)}
+            params = {attr: getattr(op, attr) if not isinstance(getattr(op, attr), str) else f'"{getattr(op, attr)}"' for attr in params_filtered}
+
+        return ", ".join([f"{param[0]}={param[1]}" for param in params.items()])
+
+    result += "\n# Inputs:\n"
+    for op in sfg._input_operations:
+        result += f"{op.graph_id} = Input({kwarg_unpacker(op)})\n"
+
+    result += "\n# Outputs:\n"
+    for op in sfg._output_operations:
+        result += f"{op.graph_id} = Output({kwarg_unpacker(op)})\n"
+
+    result += "\n# Operations:\n"
+    for op in sfg.split():
+        if isinstance(op, SFG):
+            counter += 1
+            result = sfg_to_python(op, counter) + result
+            continue
+
+        result += f"{op.graph_id} = {op.__class__.__name__}({kwarg_unpacker(op)})\n"
+
+    result += "\n# Signals:\n"
+    connections = list() # Keep track of already existing connections to avoid adding duplicates
+    for op in sfg.split():
+        for out in op.outputs:
+            for signal in out.signals:
+                dest_op = signal.destination.operation
+                connection = f"\nSignal(source={op.graph_id}.output({op.outputs.index(signal.source)}), destination={dest_op.graph_id}.input({dest_op.inputs.index(signal.destination)}))"
+                if connection in connections:
+                    continue
+
+                result += connection
+                connections.append(connection)
+
+    inputs = "[" + ", ".join(op.graph_id for op in sfg.input_operations) + "]"
+    outputs = "[" + ", ".join(op.graph_id for op in sfg.output_operations) + "]"
+    sfg_name = sfg.name if sfg.name else "sfg" + str(counter) if counter > 0 else 'sfg'
+    result += f"\n{sfg_name} = SFG(inputs={inputs}, outputs={outputs}, name='{sfg_name}')\n"
+    result += "\n# SFG Properties:\n" + "prop = {'name':" + f"{sfg_name}" + "}"
+
+    if suffix is not None:
+        result += "\n" + suffix + "\n"
+
+    return result
+
+def python_to_sfg(path: str) -> SFG:
+    with open(path) as f:
+        code = compile(f.read(), path, 'exec')
+        exec(code, globals(), locals())
+
+    return locals()["prop"]["name"], locals()["positions"] if "positions" in locals() else None
\ No newline at end of file
diff --git a/test/test_operation.py b/test/test_operation.py
index 52ae8c53457994642c505f722655b47551862ba5..f4af81b57b8d30fe71025ab82f75f57f195ce350 100644
--- a/test/test_operation.py
+++ b/test/test_operation.py
@@ -147,19 +147,19 @@ class TestLatency:
         bfly = Butterfly(latency=5)
 
         assert bfly.latency == 5
-        assert list(bfly.latency_offsets) == [[0, 0], [5, 5]]
+        assert bfly.latency_offsets == {'in0': 0, 'in1': 0, 'out0': 5, 'out1': 5}
 
     def test_latency_offsets_constructor(self):
         bfly = Butterfly(latency_offsets={'in0': 2, 'in1': 3, 'out0': 5, 'out1': 10})
 
         assert bfly.latency == 8
-        assert list(bfly.latency_offsets) == [[2, 3], [5, 10]]
+        assert bfly.latency_offsets == {'in0': 2, 'in1': 3, 'out0': 5, 'out1': 10}
 
     def test_latency_and_latency_offsets_constructor(self):
         bfly = Butterfly(latency=5, latency_offsets={'in1': 2, 'out0': 9})
 
         assert bfly.latency == 9
-        assert list(bfly.latency_offsets) == [[0, 2], [9, 5]]
+        assert bfly.latency_offsets == {"in0": 0, "in1": 2, "out0": 9, "out1": 5}
 
     def test_set_latency(self):
         bfly = Butterfly()
@@ -167,14 +167,14 @@ class TestLatency:
         bfly.set_latency(9)
 
         assert bfly.latency == 9
-        assert list(bfly.latency_offsets) == [[0, 0], [9, 9]]
+        assert bfly.latency_offsets == {"in0": 0, "in1": 0, "out0": 9, "out1": 9}
 
     def test_set_latency_offsets(self):
         bfly = Butterfly()
 
         bfly.set_latency_offsets({'in0': 3, 'out1': 5})
 
-        assert list(bfly.latency_offsets) == [[3, None], [None, 5]]
+        assert bfly.latency_offsets == {'in0': 3, "in1": None, "out0": None, 'out1': 5}
 
 
 class TestCopyOperation:
@@ -183,4 +183,4 @@ class TestCopyOperation:
 
         bfly_copy = bfly.copy_component()
 
-        assert list(bfly_copy.latency_offsets) == [[4, 2], [10, 9]]
+        assert bfly_copy.latency_offsets == {'in0': 4, 'in1': 2, 'out0': 10, 'out1': 9}
diff --git a/test/test_sfg.py b/test/test_sfg.py
index 460996c13c15c6cef094a75f8bba1df79087d590..f534143bc83f7a0afdb000fd4563429490334b71 100644
--- a/test/test_sfg.py
+++ b/test/test_sfg.py
@@ -1,4 +1,7 @@
+from os import path, remove
 import pytest
+import random
+import string
 import io
 import sys
 
@@ -6,6 +9,8 @@ import sys
 from b_asic import SFG, Signal, Input, Output, Constant, ConstantMultiplication, Addition, Multiplication, Delay, \
     Butterfly, Subtraction, SquareRoot
 
+from b_asic.save_load_structure import sfg_to_python, python_to_sfg
+
 
 class TestInit:
     def test_direct_input_to_output_sfg_construction(self):
@@ -784,6 +789,83 @@ class TestRemove:
             sfg_simple_filter.remove_operation("add1")
 
 
+class TestSaveLoadSFG:
+    def get_path(self, existing=False):
+        path_ = "".join(random.choices(string.ascii_uppercase, k=4)) + ".py"
+        while path.exists(path_) if not existing else not path.exists(path_):
+            path_ = "".join(random.choices(string.ascii_uppercase, k=4)) + ".py"
+
+        return path_
+
+    def test_save_simple_sfg(self, sfg_simple_filter):
+        result = sfg_to_python(sfg_simple_filter)
+        path_ = self.get_path()
+
+        assert not path.exists(path_)
+        with open(path_, "w") as file_obj:
+            file_obj.write(result)
+
+        assert path.exists(path_)
+
+        with open(path_, "r") as file_obj:
+            assert file_obj.read() == result
+
+        remove(path_)
+
+    def test_save_complex_sfg(self, precedence_sfg_delays_and_constants):
+        result = sfg_to_python(precedence_sfg_delays_and_constants)
+        path_ = self.get_path()
+
+        assert not path.exists(path_)
+        with open(path_, "w") as file_obj:
+            file_obj.write(result)
+
+        assert path.exists(path_)
+
+        with open(path_, "r") as file_obj:
+            assert file_obj.read() == result
+
+        remove(path_)
+
+    def test_load_simple_sfg(self, sfg_simple_filter):
+        result = sfg_to_python(sfg_simple_filter)
+        path_ = self.get_path()
+
+        assert not path.exists(path_)
+        with open(path_, "w") as file_obj:
+            file_obj.write(result)
+
+        assert path.exists(path_)
+
+        simple_filter_, _ = python_to_sfg(path_)
+
+        assert str(sfg_simple_filter) == str(simple_filter_)
+        assert sfg_simple_filter.evaluate([2]) == simple_filter_.evaluate([2])
+
+        remove(path_)
+
+    def test_load_complex_sfg(self, precedence_sfg_delays_and_constants):
+        result = sfg_to_python(precedence_sfg_delays_and_constants)
+        path_ = self.get_path()
+
+        assert not path.exists(path_)
+        with open(path_, "w") as file_obj:
+            file_obj.write(result)
+
+        assert path.exists(path_)
+
+        precedence_sfg_registers_and_constants_, _ = python_to_sfg(path_)
+
+        assert str(precedence_sfg_delays_and_constants) == str(precedence_sfg_registers_and_constants_)
+
+        remove(path_)
+
+    def test_load_invalid_path(self):
+        path_ = self.get_path(existing=False)
+        with pytest.raises(Exception):
+            python_to_sfg(path_)
+
+
 class TestGetComponentsOfType:
     def test_get_no_operations_of_type(self, sfg_two_inputs_two_outputs):
         assert [op.name for op in sfg_two_inputs_two_outputs.get_components_with_type_name(Multiplication.type_name())] \