From 1b3465dbf44782110717921834ea4149fee74f83 Mon Sep 17 00:00:00 2001
From: Angus Lothian <anglo547@student.liu.se>
Date: Tue, 26 May 2020 17:04:37 +0200
Subject: [PATCH] Change so that Basic Operation also inherits from abstract
 graph component and removed name property implementation

---
 .clang-format                                 |    8 +-
 .gitignore                                    |   69 +-
 CMakeLists.txt                                |   56 +-
 Doxyfile                                      | 2553 +++++++++++++++++
 MANIFEST.in                                   |    1 +
 README.md                                     |   29 +-
 b_asic/GUI/__init__.py                        |   15 +
 b_asic/GUI/about_window.py                    |  130 +
 b_asic/GUI/arrow.py                           |   49 +
 b_asic/GUI/drag_button.py                     |  156 +
 b_asic/GUI/gui_interface.py                   |  326 +++
 b_asic/GUI/gui_interface.ui                   |  763 +++++
 b_asic/GUI/main_window.py                     |  625 ++++
 b_asic/GUI/operation_icons/abs.png            |  Bin 0 -> 1251 bytes
 b_asic/GUI/operation_icons/abs_grey.png       |  Bin 0 -> 1369 bytes
 b_asic/GUI/operation_icons/add.png            |  Bin 0 -> 1208 bytes
 b_asic/GUI/operation_icons/add_grey.png       |  Bin 0 -> 1300 bytes
 b_asic/GUI/operation_icons/bfly.png           |  Bin 0 -> 5358 bytes
 b_asic/GUI/operation_icons/bfly_grey.png      |  Bin 0 -> 5138 bytes
 b_asic/GUI/operation_icons/c.png              |  Bin 0 -> 5203 bytes
 b_asic/GUI/operation_icons/c_grey.png         |  Bin 0 -> 5020 bytes
 b_asic/GUI/operation_icons/cmul.png           |  Bin 0 -> 3601 bytes
 b_asic/GUI/operation_icons/cmul_grey.png      |  Bin 0 -> 3848 bytes
 b_asic/GUI/operation_icons/conj.png           |  Bin 0 -> 3993 bytes
 b_asic/GUI/operation_icons/conj_grey.png      |  Bin 0 -> 3887 bytes
 .../GUI/operation_icons/custom_operation.png  |  Bin 0 -> 6298 bytes
 .../operation_icons/custom_operation_grey.png |  Bin 0 -> 6457 bytes
 b_asic/GUI/operation_icons/div.png            |  Bin 0 -> 1318 bytes
 b_asic/GUI/operation_icons/div_grey.png       |  Bin 0 -> 1352 bytes
 b_asic/GUI/operation_icons/in.png             |  Bin 0 -> 1951 bytes
 b_asic/GUI/operation_icons/in_grey.png        |  Bin 0 -> 2093 bytes
 b_asic/GUI/operation_icons/max.png            |  Bin 0 -> 5822 bytes
 b_asic/GUI/operation_icons/max_grey.png       |  Bin 0 -> 5639 bytes
 b_asic/GUI/operation_icons/min.png            |  Bin 0 -> 3675 bytes
 b_asic/GUI/operation_icons/min_grey.png       |  Bin 0 -> 3595 bytes
 b_asic/GUI/operation_icons/mul.png            |  Bin 0 -> 2222 bytes
 b_asic/GUI/operation_icons/mul_grey.png       |  Bin 0 -> 2383 bytes
 b_asic/GUI/operation_icons/out.png            |  Bin 0 -> 1962 bytes
 b_asic/GUI/operation_icons/out_grey.png       |  Bin 0 -> 2089 bytes
 b_asic/GUI/operation_icons/sqrt.png           |  Bin 0 -> 2994 bytes
 b_asic/GUI/operation_icons/sqrt_grey.png      |  Bin 0 -> 2942 bytes
 b_asic/GUI/operation_icons/sub.png            |  Bin 0 -> 1155 bytes
 b_asic/GUI/operation_icons/sub_grey.png       |  Bin 0 -> 1208 bytes
 b_asic/GUI/operation_icons/t.png              |  Bin 0 -> 4369 bytes
 b_asic/GUI/operation_icons/t_grey.png         |  Bin 0 -> 4304 bytes
 b_asic/GUI/port_button.py                     |   59 +
 b_asic/GUI/properties_window.py               |  146 +
 b_asic/GUI/select_sfg_window.py               |   39 +
 b_asic/GUI/show_pc_window.py                  |   49 +
 b_asic/GUI/simulate_sfg_window.py             |  175 ++
 b_asic/GUI/utils.py                           |   20 +
 b_asic/__init__.py                            |   17 +-
 b_asic/core_operations.py                     |  402 ++-
 b_asic/graph_component.py                     |  139 +-
 b_asic/graph_id.py                            |   26 -
 b_asic/operation.py                           |  702 +++--
 b_asic/port.py                                |  233 +-
 b_asic/precedence_chart.py                    |   21 -
 b_asic/save_load_structure.py                 |   90 +
 b_asic/schema.py                              |  117 +-
 b_asic/signal.py                              |  100 +-
 b_asic/signal_flow_graph.py                   |  858 +++++-
 b_asic/simulation.py                          |  134 +-
 b_asic/special_operations.py                  |  117 +
 legacy/README.md                              |   11 +
 legacy/simulation_oop/core_operations.h       |  236 ++
 legacy/simulation_oop/custom_operation.cpp    |   30 +
 legacy/simulation_oop/custom_operation.h      |   35 +
 legacy/simulation_oop/operation.cpp           |  156 +
 legacy/simulation_oop/operation.h             |  132 +
 legacy/simulation_oop/signal_flow_graph.cpp   |  144 +
 legacy/simulation_oop/signal_flow_graph.h     |   82 +
 legacy/simulation_oop/simulation.cpp          |  138 +
 legacy/simulation_oop/simulation.h            |   66 +
 legacy/simulation_oop/special_operations.cpp  |   78 +
 legacy/simulation_oop/special_operations.h    |   55 +
 logo_tiny.png                                 |  Bin 0 -> 10071 bytes
 setup.py                                      |   60 +-
 small_logo.png                                |  Bin 0 -> 40475 bytes
 src/algorithm.h                               |  325 +++
 src/debug.h                                   |   80 +
 src/main.cpp                                  |   28 +-
 src/number.h                                  |   13 +
 src/simulation.cpp                            |   61 +
 src/simulation.h                              |   12 +
 src/simulation/compile.cpp                    |  313 ++
 src/simulation/compile.h                      |   61 +
 src/simulation/format_code.h                  |  129 +
 src/simulation/instruction.h                  |   57 +
 src/simulation/run.cpp                        |  176 ++
 src/simulation/run.h                          |   23 +
 src/simulation/simulation.cpp                 |  129 +
 src/simulation/simulation.h                   |   54 +
 src/span.h                                    |  314 ++
 test/conftest.py                              |    3 +-
 test/fixtures/operation_tree.py               |  121 +-
 test/fixtures/port.py                         |   16 +-
 test/fixtures/signal.py                       |    4 +-
 test/fixtures/signal_flow_graph.py            |  245 ++
 test/operation/test_abstract_operation.py     |   77 -
 test/test_core_operations.py                  |  395 ++-
 test/test_fast_simulation.py                  |  232 ++
 test/test_graph_id_generator.py               |   19 +-
 test/test_inputport.py                        |  102 +-
 test/test_operation.py                        |  185 +-
 test/test_outputport.py                       |   72 +-
 test/test_schema.py                           |   67 +
 test/test_sfg.py                              | 1018 +++++++
 test/test_signal.py                           |   40 +-
 test/test_simulation.py                       |  202 ++
 110 files changed, 12730 insertions(+), 1290 deletions(-)
 create mode 100644 Doxyfile
 create mode 100644 b_asic/GUI/__init__.py
 create mode 100644 b_asic/GUI/about_window.py
 create mode 100644 b_asic/GUI/arrow.py
 create mode 100644 b_asic/GUI/drag_button.py
 create mode 100644 b_asic/GUI/gui_interface.py
 create mode 100644 b_asic/GUI/gui_interface.ui
 create mode 100644 b_asic/GUI/main_window.py
 create mode 100644 b_asic/GUI/operation_icons/abs.png
 create mode 100644 b_asic/GUI/operation_icons/abs_grey.png
 create mode 100644 b_asic/GUI/operation_icons/add.png
 create mode 100644 b_asic/GUI/operation_icons/add_grey.png
 create mode 100644 b_asic/GUI/operation_icons/bfly.png
 create mode 100644 b_asic/GUI/operation_icons/bfly_grey.png
 create mode 100644 b_asic/GUI/operation_icons/c.png
 create mode 100644 b_asic/GUI/operation_icons/c_grey.png
 create mode 100644 b_asic/GUI/operation_icons/cmul.png
 create mode 100644 b_asic/GUI/operation_icons/cmul_grey.png
 create mode 100644 b_asic/GUI/operation_icons/conj.png
 create mode 100644 b_asic/GUI/operation_icons/conj_grey.png
 create mode 100644 b_asic/GUI/operation_icons/custom_operation.png
 create mode 100644 b_asic/GUI/operation_icons/custom_operation_grey.png
 create mode 100644 b_asic/GUI/operation_icons/div.png
 create mode 100644 b_asic/GUI/operation_icons/div_grey.png
 create mode 100644 b_asic/GUI/operation_icons/in.png
 create mode 100644 b_asic/GUI/operation_icons/in_grey.png
 create mode 100644 b_asic/GUI/operation_icons/max.png
 create mode 100644 b_asic/GUI/operation_icons/max_grey.png
 create mode 100644 b_asic/GUI/operation_icons/min.png
 create mode 100644 b_asic/GUI/operation_icons/min_grey.png
 create mode 100644 b_asic/GUI/operation_icons/mul.png
 create mode 100644 b_asic/GUI/operation_icons/mul_grey.png
 create mode 100644 b_asic/GUI/operation_icons/out.png
 create mode 100644 b_asic/GUI/operation_icons/out_grey.png
 create mode 100644 b_asic/GUI/operation_icons/sqrt.png
 create mode 100644 b_asic/GUI/operation_icons/sqrt_grey.png
 create mode 100644 b_asic/GUI/operation_icons/sub.png
 create mode 100644 b_asic/GUI/operation_icons/sub_grey.png
 create mode 100644 b_asic/GUI/operation_icons/t.png
 create mode 100644 b_asic/GUI/operation_icons/t_grey.png
 create mode 100644 b_asic/GUI/port_button.py
 create mode 100644 b_asic/GUI/properties_window.py
 create mode 100644 b_asic/GUI/select_sfg_window.py
 create mode 100644 b_asic/GUI/show_pc_window.py
 create mode 100644 b_asic/GUI/simulate_sfg_window.py
 create mode 100644 b_asic/GUI/utils.py
 delete mode 100644 b_asic/graph_id.py
 delete mode 100644 b_asic/precedence_chart.py
 create mode 100644 b_asic/save_load_structure.py
 create mode 100644 b_asic/special_operations.py
 create mode 100644 legacy/README.md
 create mode 100644 legacy/simulation_oop/core_operations.h
 create mode 100644 legacy/simulation_oop/custom_operation.cpp
 create mode 100644 legacy/simulation_oop/custom_operation.h
 create mode 100644 legacy/simulation_oop/operation.cpp
 create mode 100644 legacy/simulation_oop/operation.h
 create mode 100644 legacy/simulation_oop/signal_flow_graph.cpp
 create mode 100644 legacy/simulation_oop/signal_flow_graph.h
 create mode 100644 legacy/simulation_oop/simulation.cpp
 create mode 100644 legacy/simulation_oop/simulation.h
 create mode 100644 legacy/simulation_oop/special_operations.cpp
 create mode 100644 legacy/simulation_oop/special_operations.h
 create mode 100644 logo_tiny.png
 create mode 100644 small_logo.png
 create mode 100644 src/algorithm.h
 create mode 100644 src/debug.h
 create mode 100644 src/number.h
 create mode 100644 src/simulation.cpp
 create mode 100644 src/simulation.h
 create mode 100644 src/simulation/compile.cpp
 create mode 100644 src/simulation/compile.h
 create mode 100644 src/simulation/format_code.h
 create mode 100644 src/simulation/instruction.h
 create mode 100644 src/simulation/run.cpp
 create mode 100644 src/simulation/run.h
 create mode 100644 src/simulation/simulation.cpp
 create mode 100644 src/simulation/simulation.h
 create mode 100644 src/span.h
 create mode 100644 test/fixtures/signal_flow_graph.py
 delete mode 100644 test/operation/test_abstract_operation.py
 create mode 100644 test/test_fast_simulation.py
 create mode 100644 test/test_schema.py
 create mode 100644 test/test_sfg.py
 create mode 100644 test/test_simulation.py

diff --git a/.clang-format b/.clang-format
index 7548f76b..22e04bab 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,6 +1,6 @@
 AccessModifierOffset: -4
 
-AlignAfterOpenBracket: DontAlign
+AlignAfterOpenBracket: Align
 AlignConsecutiveAssignments: false
 AlignConsecutiveDeclarations: false
 AlignConsecutiveMacros: true
@@ -9,10 +9,10 @@ AlignOperands: false
 AlignTrailingComments: true
 
 AllowAllArgumentsOnNextLine: true
-AllowAllConstructorInitializersOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: false
 AllowAllParametersOfDeclarationOnNextLine: false
 AllowShortBlocksOnASingleLine: Empty
-AllowShortCaseLabelsOnASingleLine: true
+AllowShortCaseLabelsOnASingleLine: false
 AllowShortFunctionsOnASingleLine: Empty
 AllowShortIfStatementsOnASingleLine: Never
 AllowShortLambdasOnASingleLine: Inline
@@ -56,7 +56,7 @@ CommentPragmas: ''
 
 CompactNamespaces: false
 
-ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
 ConstructorInitializerIndentWidth: 4
 
 ContinuationIndentWidth: 4
diff --git a/.gitignore b/.gitignore
index 0bdf5476..c1e09d2e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,33 +1,36 @@
-.vs/
-.vscode/
-build*/
-bin*/
-logs/
-dist/
-CMakeLists.txt.user*
-*.autosave
-*.creator
-*.creator.user*
-\#*\#
-/.emacs.desktop
-/.emacs.desktop.lock
-*.elc
-auto-save-list
-tramp
-.\#*
-*~
-.fuse_hudden*
-.directory
-.Trash-*
-.nfs*
-Thumbs.db
-Thumbs.db:encryptable
-ehthumbs.db
-ehthumbs_vista.db
-$RECYCLE.BIN/
-*.stackdump
-[Dd]esktop.ini
-*.egg-info
-__pycache__/
-env/
-venv/
\ No newline at end of file
+.vs/
+.vscode/
+build*/
+bin*/
+doc/
+dist/
+CMakeLists.txt.user*
+*.autosave
+*.creator
+*.creator.user*
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+*~
+.fuse_hudden*
+.directory
+.Trash-*
+.nfs*
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+$RECYCLE.BIN/
+*.stackdump
+[Dd]esktop.ini
+*.egg-info
+__pycache__/
+env/
+venv/
+*.pyd
+*.so
+_b_asic_debug_log.txt
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3471fb4f..3f8304e3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,17 +2,19 @@ cmake_minimum_required(VERSION 3.8)
 
 project(
 	"B-ASIC"
-	VERSION 0.0.1
-	DESCRIPTION "Better ASIC Toolbox for python3"
+	VERSION 1.0.0
+	DESCRIPTION "Better ASIC Toolbox for Python 3"
 	LANGUAGES C CXX
 )
 
-find_package(fmt 5.2.1 REQUIRED)
+# Find dependencies.
+find_package(fmt REQUIRED)
 find_package(pybind11 CONFIG REQUIRED)
 
-set(LIBRARY_NAME "b_asic")
-set(TARGET_NAME "_${LIBRARY_NAME}")
+set(LIBRARY_NAME "b_asic") # Name of the python library directory.
+set(TARGET_NAME "_${LIBRARY_NAME}") # Name of this extension module.
 
+# Set output directory for compiled binaries.
 if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
 	include(GNUInstallDirs)
 	set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_INSTALL_LIBDIR}")
@@ -29,22 +31,42 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
 
+# Add files to be compiled into Python module.
 pybind11_add_module(
 	"${TARGET_NAME}"
+
+	# Main files.
 	"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp"
+	"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation.cpp"
+	
+	# For DOD simulation.
+	"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/compile.cpp"
+	"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/run.cpp"
+	"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/simulation.cpp"
+
+	# For OOP simulation (see legacy folder).
+	#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/custom_operation.cpp"
+	#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/operation.cpp"
+	#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/signal_flow_graph.cpp"
+	#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/simulation.cpp"
+	#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/special_operations.cpp"
 )
 
+# Include headers.
 target_include_directories(
 	"${TARGET_NAME}"
     PRIVATE
         "${CMAKE_CURRENT_SOURCE_DIR}/src"
 )
 
+# Use C++17.
 target_compile_features(
 	"${TARGET_NAME}"
 	PRIVATE
 		cxx_std_17
 )
+
+# Set compiler-specific options using generator expressions.
 target_compile_options(
 	"${TARGET_NAME}"
 	PRIVATE
@@ -60,20 +82,20 @@ target_compile_options(
 		>
 )
 
+# Add libraries. Note: pybind11 is already added in pybind11_add_module.
 target_link_libraries(
 	"${TARGET_NAME}"
 	PRIVATE
-		fmt::fmt
+		$<TARGET_NAME_IF_EXISTS:fmt::fmt-header-only>
+		$<$<NOT:$<TARGET_EXISTS:fmt::fmt-header-only>>:fmt::fmt>
 )
 
-add_custom_target(
-	remove_old_python_dir ALL
-	COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${LIBRARY_NAME}"
-	COMMENT "Removing old python directory ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${LIBRARY_NAME}"
-)
-add_custom_target(
-	copy_python_dir ALL
-	COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_LIST_DIR}/${LIBRARY_NAME}" "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${LIBRARY_NAME}"
-	COMMENT "Copying python files to ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${LIBRARY_NAME}"
-	DEPENDS "${TARGET_NAME}" remove_old_python_dir
-)
\ No newline at end of file
+# Copy binaries to project folder for debugging during development.
+if(NOT ASIC_BUILDING_PYTHON_DISTRIBUTION)
+	add_custom_target(
+		copy_binaries ALL
+		COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${TARGET_NAME}>" "${CMAKE_CURRENT_LIST_DIR}"
+		COMMENT "Copying binaries to ${CMAKE_CURRENT_LIST_DIR}"
+		DEPENDS "${TARGET_NAME}"
+	)
+endif()
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 00000000..6ee7c4a5
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,2553 @@
+# Doxyfile 1.8.18
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "B-ASIC"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Better ASIC Toolbox for Python 3"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           = "logo_tiny.png"
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = "doc"
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all generated output in the proper direction.
+# Possible values are: None, LTR, RTL and Context.
+# The default value is: None.
+
+OUTPUT_TEXT_DIRECTION  = None
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER         = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines (in the resulting output). You can put ^^ in the value part of an
+# alias to insert a newline as if a physical newline was in the original file.
+# When you need a literal { or } or , in the value part of an alias you have to
+# escape them by means of a backslash (\), this can lead to conflicts with the
+# commands \{ and \} for these it is advised to use the version @{ and @} or use
+# a double escape (\\{ and \\})
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = YES
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
+# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 5
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL   = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# (including Cygwin) ands Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation. If
+# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = b_asic
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
+# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
+# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.idl \
+                         *.ddl \
+                         *.odl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.cs \
+                         *.d \
+                         *.php \
+                         *.php4 \
+                         *.php5 \
+                         *.phtml \
+                         *.inc \
+                         *.m \
+                         *.markdown \
+                         *.md \
+                         *.mm \
+                         *.dox \
+                         *.doc \
+                         *.txt \
+                         *.py \
+                         *.pyw \
+                         *.f90 \
+                         *.f95 \
+                         *.f03 \
+                         *.f08 \
+                         *.f18 \
+                         *.f \
+                         *.for \
+                         *.vhd \
+                         *.vhdl \
+                         *.ucf \
+                         *.qsf \
+                         *.ice
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             = .
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: https://developer.apple.com/xcode/), introduced with OSX
+# 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png The default and svg Looks nicer but requires the
+# pdf2svg tool.
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT    = png
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE      =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment.
+# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = https://cdn.jsdelivr.net/npm/mathjax@2
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         =
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD    = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
diff --git a/MANIFEST.in b/MANIFEST.in
index ce996f6c..96b265fd 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,5 @@
 include README.md
 include LICENSE
 include CMakeLists.txt
+include b_asic/GUI/operation_icons/*
 recursive-include src *.cpp *.h
diff --git a/README.md b/README.md
index fd98f919..db11db76 100644
--- a/README.md
+++ b/README.md
@@ -9,15 +9,27 @@ How to build and debug the library during development.
 ### Prerequisites
 The following packages are required in order to build the library:
 * cmake 3.8+
-  * gcc 7+/clang 7+/msvc 16+
-  * fmtlib 5.2.1+
-  * pybind11 2.3.0+
+* gcc 7+/clang 7+/msvc 16+
+* fmtlib
+* pybind11 2.3.0+
 * python 3.6+
+* Python:
   * setuptools
-  * wheel
   * pybind11
   * numpy
-  * pyside2/pyqt5
+  * pyside2
+
+To build a binary distribution, the following additional packages are required:
+* Python:
+  * wheel
+
+To run the test suite, the following additional packages are required:
+* Python:
+  * pytest
+  * pytest-cov (for testing with coverage)
+  
+To generate the documentation, the following additional packages are required:
+* doxygen
 
 ### Using CMake directly
 How to build using CMake.
@@ -99,6 +111,13 @@ pytest
 pytest --cov=b_asic --cov-report html test
 ```
 
+### Generating documentation
+In `B-ASIC`:
+```
+doxygen
+```
+The output gets written to `B-ASIC/doc`.
+
 ## Usage
 How to build and use the library as a user.
 
diff --git a/b_asic/GUI/__init__.py b/b_asic/GUI/__init__.py
new file mode 100644
index 00000000..f330b589
--- /dev/null
+++ b/b_asic/GUI/__init__.py
@@ -0,0 +1,15 @@
+"""B-ASIC GUI Module.
+
+Graphical user interface for B-ASIC.
+"""
+from b_asic.GUI.main_window import *
+from b_asic.GUI.about_window import *
+from b_asic.GUI.arrow import *
+from b_asic.GUI.drag_button import *
+from b_asic.GUI.gui_interface import *
+from b_asic.GUI.port_button import *
+from b_asic.GUI.properties_window import *
+from b_asic.GUI.select_sfg_window import *
+from b_asic.GUI.show_pc_window import *
+from b_asic.GUI.simulate_sfg_window import *
+from b_asic.GUI.utils import *
diff --git a/b_asic/GUI/about_window.py b/b_asic/GUI/about_window.py
new file mode 100644
index 00000000..699e1b7c
--- /dev/null
+++ b/b_asic/GUI/about_window.py
@@ -0,0 +1,130 @@
+from PySide2.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QDialog, QLabel, QFrame, QScrollArea
+from PySide2.QtCore import Qt
+
+
+QUESTIONS = {
+    "Adding operations": "Select an operation under 'Special operations' or 'Core operations' to add it to the workspace.",
+    "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.",
+    "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.",
+    "Creating a signal-flow-graph": "To create a signal-flow-graph (SFG), \ncouple together the operations you wish to create a sfg from, then select all operations you wish to include in the sfg, \nfinally press 'Create SFG' in the upper left corner and enter the name of the sfg.",
+    "Simulating a signal-flow-graph": "To simulate a signal-flow-graph (SFG), press the run button in the toolbar, \nthen press 'Simulate SFG' and enter the properties of the simulation.",
+    "Properties of simulation": "The properties of the simulation are, 'Iteration Count': The number of iterations to run the simulation for, \n'Plot Results': Open a plot over the output in matplotlib, \n'Get All Results': Print the detailed output from simulating the sfg in the terminal, \n'Input Values': The input values to the SFG by index of the port."
+}
+
+
+class KeybindsWindow(QDialog):
+    def __init__(self, window):
+        super(KeybindsWindow, self).__init__()
+        self._window = window
+        self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
+        self.setWindowTitle("B-ASIC Keybinds")
+
+        self.dialog_layout = QVBoxLayout()
+        self.setLayout(self.dialog_layout)
+
+        self.add_information_to_layout()
+
+    def add_information_to_layout(self):
+        information_layout = QVBoxLayout()
+
+        title_label = QLabel("B-ASIC / Better ASIC Toolbox")
+        subtitle_label = QLabel("Keybinds in the GUI.")
+
+        frame = QFrame()
+        frame.setFrameShape(QFrame.HLine)
+        frame.setFrameShadow(QFrame.Sunken)
+        self.dialog_layout.addWidget(frame)
+
+        keybinds_label = QLabel(
+            "'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"
+            "'Ctrl+S' (Plot) - Save the plot if a plot is visible.\n"
+            "'Ctrl+?' - Open the FAQ section."
+        )
+
+        information_layout.addWidget(title_label)
+        information_layout.addWidget(subtitle_label)
+
+        self.dialog_layout.addLayout(information_layout)
+        self.dialog_layout.addWidget(frame)
+
+        self.dialog_layout.addWidget(keybinds_label)
+
+
+class AboutWindow(QDialog):
+    def __init__(self, window):
+        super(AboutWindow, self).__init__()
+        self._window = window
+        self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
+        self.setWindowTitle("About B-ASIC")
+
+        self.dialog_layout = QVBoxLayout()
+        self.setLayout(self.dialog_layout)
+
+        self.add_information_to_layout()
+
+    def add_information_to_layout(self):
+        information_layout = QVBoxLayout()
+
+        title_label = QLabel("B-ASIC / Better ASIC Toolbox")
+        subtitle_label = QLabel("Construct, simulate and analyze components of an ASIC.")
+
+        frame = QFrame()
+        frame.setFrameShape(QFrame.HLine)
+        frame.setFrameShadow(QFrame.Sunken)
+        self.dialog_layout.addWidget(frame)
+
+        about_label = QLabel(
+            "B-ASIC is a open source tool using the B-ASIC library to construct, simulate and analyze ASICs.\n"
+            "B-ASIC is developed under the MIT-license and any extension to the program should follow that same license.\n"
+            "To read more about how the GUI works please refer to the FAQ under 'Help'."
+        )
+
+        information_layout.addWidget(title_label)
+        information_layout.addWidget(subtitle_label)
+
+        self.dialog_layout.addLayout(information_layout)
+        self.dialog_layout.addWidget(frame)
+
+        self.dialog_layout.addWidget(about_label)
+
+
+class FaqWindow(QDialog):
+    def __init__(self, window):
+        super(FaqWindow, self).__init__()
+        self._window = window
+        self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
+        self.setWindowTitle("Frequently Asked Questions")
+
+        self.dialog_layout = QVBoxLayout()
+        self.scroll_area = QScrollArea()
+        self.setLayout(self.dialog_layout)
+        for question, answer in QUESTIONS.items():
+            self.add_question_to_layout(question, answer)
+
+        self.scroll_area.setWidget(self)
+        self.scroll_area.setWidgetResizable(True)
+
+    def add_question_to_layout(self, question, answer):
+        question_layout = QVBoxLayout()
+        answer_layout = QHBoxLayout()
+
+        question_label = QLabel(question)
+        question_layout.addWidget(question_label)
+
+        answer_label = QLabel(answer)
+        answer_layout.addWidget(answer_label)
+
+        frame = QFrame()
+        frame.setFrameShape(QFrame.HLine)
+        frame.setFrameShadow(QFrame.Sunken)
+        self.dialog_layout.addWidget(frame)
+
+        question_layout.addLayout(answer_layout)
+        self.dialog_layout.addLayout(question_layout)
diff --git a/b_asic/GUI/arrow.py b/b_asic/GUI/arrow.py
new file mode 100644
index 00000000..7df52d05
--- /dev/null
+++ b/b_asic/GUI/arrow.py
@@ -0,0 +1,49 @@
+from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QAction,\
+QStatusBar, QMenuBar, QLineEdit, QPushButton, QSlider, QScrollArea, QVBoxLayout,\
+QHBoxLayout, QDockWidget, QToolBar, QMenu, QLayout, QSizePolicy, QListWidget, QListWidgetItem,\
+QGraphicsLineItem, QGraphicsWidget
+from PySide2.QtCore import Qt, QSize, QLineF, QPoint, QRectF
+from PySide2.QtGui import QIcon, QFont, QPainter, QPen
+
+from b_asic.signal import Signal
+
+class Arrow(QGraphicsLineItem):
+
+    def __init__(self, source, destination, window, signal=None, parent=None):
+        super(Arrow, self).__init__(parent)
+        self.source = source
+        self.signal = Signal(source.port, destination.port) if signal is None else signal
+        self.destination = destination
+        self._window = window
+        self.moveLine()
+        self.source.moved.connect(self.moveLine)
+        self.destination.moved.connect(self.moveLine)
+
+    def contextMenuEvent(self, event):
+        menu = QMenu()
+        menu.addAction("Delete", self.remove)
+        menu.exec_(self.cursor().pos())
+
+    def remove(self):
+        self.signal.remove_destination()
+        self.signal.remove_source()
+        self._window.scene.removeItem(self)
+        if self in self._window.signalList:
+            self._window.signalList.remove(self)
+
+        if self in self._window.signalPortDict:
+            for port1, port2 in self._window.signalPortDict[self]:
+                for operation, operation_ports in self._window.portDict.items():
+                    if (port1 in operation_ports or port2 in operation_ports) and operation in self._window.opToSFG:
+                        self._window.logger.info(f"Operation detected in existing sfg, removing sfg with name: {self._window.opToSFG[operation].name}.")
+                        del self._window.sfg_dict[self._window.opToSFG[operation].name]
+                        self._window.opToSFG = {op: self._window.opToSFG[op] for op in self._window.opToSFG if self._window.opToSFG[op] is not self._window.opToSFG[operation]}
+
+        del self._window.signalPortDict[self]
+
+    def moveLine(self):
+        self.setPen(QPen(Qt.black, 3))
+        self.setLine(QLineF(self.source.operation.x()+self.source.x()+14,\
+             self.source.operation.y()+self.source.y()+7.5,\
+             self.destination.operation.x()+self.destination.x(),\
+             self.destination.operation.y()+self.destination.y()+7.5))
diff --git a/b_asic/GUI/drag_button.py b/b_asic/GUI/drag_button.py
new file mode 100644
index 00000000..a1d8118e
--- /dev/null
+++ b/b_asic/GUI/drag_button.py
@@ -0,0 +1,156 @@
+"""B-ASIC Drag Button Module.
+
+Contains a GUI class for drag buttons.
+"""
+
+import os.path
+
+from b_asic.GUI.properties_window import PropertiesWindow
+from b_asic.GUI.utils import decorate_class, handle_error
+
+from PySide2.QtWidgets import QPushButton, QMenu, QAction
+from PySide2.QtCore import Qt, QSize, Signal
+from PySide2.QtGui import QIcon
+
+
+@decorate_class(handle_error)
+class DragButton(QPushButton):
+    """Drag button class.
+
+    This class creates a drag button which can be clicked, dragged and dropped.
+    """
+
+    connectionRequested = Signal(QPushButton)
+    moved = Signal()
+
+    def __init__(self, name, operation, operation_path_name, is_show_name, window, parent=None):
+        self.name = name
+        self.ports = []
+        self.is_show_name = is_show_name
+        self._window = window
+        self.operation = operation
+        self.operation_path_name = operation_path_name
+        self.clicked = 0
+        self.pressed = False
+        self._m_press = False
+        self._m_drag = False
+        self._mouse_press_pos = None
+        self._mouse_move_pos = None
+        super(DragButton, self).__init__(parent)
+
+    def contextMenuEvent(self, event):
+        menu = QMenu()
+        properties = QAction("Properties")
+        menu.addAction(properties)
+        properties.triggered.connect(self.show_properties_window)
+
+        delete = QAction("Delete")
+        menu.addAction(delete)
+        delete.triggered.connect(self.remove)
+        menu.exec_(self.cursor().pos())
+
+    def show_properties_window(self):
+        self.properties_window = PropertiesWindow(self, self._window)
+        self.properties_window.show()
+
+    def add_label(self, label):
+        self.label = label
+
+    def mousePressEvent(self, event):
+        if event.button() == Qt.LeftButton:
+            self._m_press = True
+            self._mouse_press_pos = event.pos()
+            self._mouse_move_pos = event.pos()
+
+        super(DragButton, self).mousePressEvent(event)
+
+    def mouseMoveEvent(self, event):
+        if event.buttons() == Qt.LeftButton and self._m_press:
+            self._m_drag = True
+            self.move(self.mapToParent(event.pos() - self._mouse_press_pos))
+            if self in self._window.pressed_operations:
+                for button in self._window.pressed_operations:
+                    if button is self:
+                        continue
+
+                    button.move(button.mapToParent(
+                        event.pos() - self._mouse_press_pos))
+
+        self._window.scene.update()
+        self._window.graphic_view.update()
+        super(DragButton, self).mouseMoveEvent(event)
+
+    def mouseReleaseEvent(self, event):
+        self._m_press = False
+        if self._m_drag:
+            if self._mouse_press_pos is not None:
+                moved = event.pos() - self._mouse_press_pos
+                if moved.manhattanLength() > 3:
+                    event.ignore()
+
+            self._m_drag = False
+
+        else:
+            self.select_button(event.modifiers())
+
+        super(DragButton, self).mouseReleaseEvent(event)
+
+    def _toggle_button(self, pressed=False):
+        self.pressed = not pressed
+        self.setStyleSheet(f"background-color: {'white' if not self.pressed else 'grey'}; border-style: solid;\
+        border-color: black; border-width: 2px")
+        path_to_image = os.path.join(os.path.dirname(
+            __file__), 'operation_icons', f"{self.operation_path_name}{'_grey.png' if self.pressed else '.png'}")
+        self.setIcon(QIcon(path_to_image))
+        self.setIconSize(QSize(55, 55))
+
+    def select_button(self, modifiers=None):
+        if modifiers != Qt.ControlModifier:
+            for button in self._window.pressed_operations:
+                button._toggle_button(button.pressed)
+
+            self._toggle_button(self.pressed)
+            self._window.pressed_operations = [self]
+
+        else:
+            self._toggle_button(self.pressed)
+            if self in self._window.pressed_operations:
+                self._window.pressed_operations.remove(self)
+            else:
+                self._window.pressed_operations.append(self)
+
+        for signal in self._window.signalList:
+            signal.update()
+
+    def remove(self):
+        self._window.logger.info(
+            f"Removing operation with name {self.operation.name}.")
+        self._window.scene.removeItem(
+            self._window.dragOperationSceneDict[self])
+
+        _signals = []
+        for signal, ports in self._window.signalPortDict.items():
+            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}.")
+                _signals.append(signal)
+
+        for signal in _signals:
+            signal.remove()
+
+        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}.")
+            del self._window.sfg_dict[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 not self._window.opToSFG[self]}
+
+        for port in self._window.portDict[self]:
+            if port in self._window.pressed_ports:
+                self._window.pressed_ports.remove(port)
+
+        if self in self._window.pressed_operations:
+            self._window.pressed_operations.remove(self)
+
+        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
new file mode 100644
index 00000000..bfbe76f4
--- /dev/null
+++ b/b_asic/GUI/gui_interface.py
@@ -0,0 +1,326 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'gui_interface.ui'
+#
+# Created by: PyQt5 UI code generator 5.14.2
+#
+# WARNING! All changes made in this file will be lost!
+
+
+from PySide2 import QtCore, QtGui, QtWidgets
+
+
+class Ui_main_window(object):
+    def setupUi(self, main_window):
+        main_window.setObjectName("main_window")
+        main_window.setEnabled(True)
+        main_window.resize(897, 633)
+        self.centralwidget = QtWidgets.QWidget(main_window)
+        self.centralwidget.setObjectName("centralwidget")
+        self.operation_box = QtWidgets.QGroupBox(self.centralwidget)
+        self.operation_box.setGeometry(QtCore.QRect(10, 10, 201, 531))
+        self.operation_box.setLayoutDirection(QtCore.Qt.LeftToRight)
+        self.operation_box.setAutoFillBackground(False)
+        self.operation_box.setStyleSheet("QGroupBox { \n"
+"     border: 2px solid gray; \n"
+"     border-radius: 3px;\n"
+"     margin-top: 0.5em; \n"
+" } \n"
+"\n"
+"QGroupBox::title {\n"
+"    subcontrol-origin: margin;\n"
+"    left: 10px;\n"
+"    padding: 0 3px 0 3px;\n"
+"}")
+        self.operation_box.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
+        self.operation_box.setFlat(False)
+        self.operation_box.setCheckable(False)
+        self.operation_box.setObjectName("operation_box")
+        self.operation_list = QtWidgets.QToolBox(self.operation_box)
+        self.operation_list.setGeometry(QtCore.QRect(10, 20, 171, 271))
+        self.operation_list.setAutoFillBackground(False)
+        self.operation_list.setObjectName("operation_list")
+        self.core_operations_page = QtWidgets.QWidget()
+        self.core_operations_page.setGeometry(QtCore.QRect(0, 0, 171, 217))
+        self.core_operations_page.setObjectName("core_operations_page")
+        self.core_operations_list = QtWidgets.QListWidget(self.core_operations_page)
+        self.core_operations_list.setGeometry(QtCore.QRect(10, 0, 141, 211))
+        self.core_operations_list.setMinimumSize(QtCore.QSize(141, 0))
+        self.core_operations_list.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed)
+        self.core_operations_list.setDragEnabled(False)
+        self.core_operations_list.setDragDropMode(QtWidgets.QAbstractItemView.NoDragDrop)
+        self.core_operations_list.setMovement(QtWidgets.QListView.Static)
+        self.core_operations_list.setFlow(QtWidgets.QListView.TopToBottom)
+        self.core_operations_list.setProperty("isWrapping", False)
+        self.core_operations_list.setResizeMode(QtWidgets.QListView.Adjust)
+        self.core_operations_list.setLayoutMode(QtWidgets.QListView.SinglePass)
+        self.core_operations_list.setViewMode(QtWidgets.QListView.ListMode)
+        self.core_operations_list.setUniformItemSizes(False)
+        self.core_operations_list.setWordWrap(False)
+        self.core_operations_list.setSelectionRectVisible(False)
+        self.core_operations_list.setObjectName("core_operations_list")
+        self.operation_list.addItem(self.core_operations_page, "")
+        self.special_operations_page = QtWidgets.QWidget()
+        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, 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))
+        palette = QtGui.QPalette()
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 255, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush)
+        brush = QtGui.QBrush(QtGui.QColor(127, 127, 127))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush)
+        brush = QtGui.QBrush(QtGui.QColor(170, 170, 170))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 220))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 128))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.PlaceholderText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 255, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush)
+        brush = QtGui.QBrush(QtGui.QColor(127, 127, 127))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush)
+        brush = QtGui.QBrush(QtGui.QColor(170, 170, 170))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 220))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 128))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.PlaceholderText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(127, 127, 127))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 255, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush)
+        brush = QtGui.QBrush(QtGui.QColor(127, 127, 127))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush)
+        brush = QtGui.QBrush(QtGui.QColor(170, 170, 170))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush)
+        brush = QtGui.QBrush(QtGui.QColor(127, 127, 127))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(127, 127, 127))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush)
+        brush = QtGui.QBrush(QtGui.QColor(255, 255, 220))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush)
+        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 128))
+        brush.setStyle(QtCore.Qt.SolidPattern)
+        palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.PlaceholderText, brush)
+        self.menu_bar.setPalette(palette)
+        self.menu_bar.setObjectName("menu_bar")
+        self.file_menu = QtWidgets.QMenu(self.menu_bar)
+        self.file_menu.setObjectName("file_menu")
+        self.edit_menu = QtWidgets.QMenu(self.menu_bar)
+        self.edit_menu.setObjectName("edit_menu")
+        self.view_menu = QtWidgets.QMenu(self.menu_bar)
+        self.view_menu.setObjectName("view_menu")
+        self.run_menu = QtWidgets.QMenu(self.menu_bar)
+        self.run_menu.setObjectName("run_menu")
+        self.help_menu = QtWidgets.QMenu(self.menu_bar)
+        self.help_menu.setObjectName("help_menu")
+        main_window.setMenuBar(self.menu_bar)
+        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.actionSimulateSFG = QtWidgets.QAction(main_window)
+        self.actionSimulateSFG.setObjectName("actionSimulateSFG")
+        self.actionShowPC = QtWidgets.QAction(main_window)
+        self.actionShowPC.setObjectName("actionShowPC")
+        self.aboutBASIC = QtWidgets.QAction(main_window)
+        self.aboutBASIC.setObjectName("aboutBASIC")
+        self.faqBASIC = QtWidgets.QAction(main_window)
+        self.faqBASIC.setObjectName("faqBASIC")
+        self.keybindsBASIC = QtWidgets.QAction(main_window)
+        self.keybindsBASIC.setObjectName("keybindsBASIC")
+        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.view_menu.addAction(self.actionToolbar)
+        self.run_menu.addAction(self.actionShowPC)
+        self.run_menu.addAction(self.actionSimulateSFG)
+        self.help_menu.addAction(self.aboutBASIC)
+        self.help_menu.addAction(self.faqBASIC)
+        self.help_menu.addAction(self.keybindsBASIC)
+        self.menu_bar.addAction(self.file_menu.menuAction())
+        self.menu_bar.addAction(self.edit_menu.menuAction())
+        self.menu_bar.addAction(self.view_menu.menuAction())
+        self.menu_bar.addAction(self.run_menu.menuAction())
+        self.menu_bar.addAction(self.help_menu.menuAction())
+
+        self.retranslateUi(main_window)
+        self.operation_list.setCurrentIndex(1)
+        self.core_operations_list.setCurrentRow(-1)
+        QtCore.QMetaObject.connectSlotsByName(main_window)
+
+    def retranslateUi(self, main_window):
+        _translate = QtCore.QCoreApplication.translate
+        main_window.setWindowTitle(_translate("main_window", "B-ASIC"))
+        self.operation_box.setTitle(_translate("main_window", "Operations"))
+        self.core_operations_list.setSortingEnabled(False)
+        __sortingEnabled = self.core_operations_list.isSortingEnabled()
+        self.core_operations_list.setSortingEnabled(False)
+        self.core_operations_list.setSortingEnabled(__sortingEnabled)
+        self.operation_list.setItemText(self.operation_list.indexOf(self.core_operations_page), _translate("main_window", "Core operations"))
+        __sortingEnabled = self.special_operations_list.isSortingEnabled()
+        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"))
+        self.run_menu.setTitle(_translate("main_window", "Run"))
+        self.actionShowPC.setText(_translate("main_window", "Show PC"))
+        self.help_menu.setTitle(_translate("main_window", "Help"))
+        self.actionSimulateSFG.setText(_translate("main_window", "Simulate SFG"))
+        self.aboutBASIC.setText(_translate("main_window", "About B-ASIC"))
+        self.faqBASIC.setText(_translate("main_window", "FAQ"))
+        self.keybindsBASIC.setText(_translate("main_window", "Keybinds"))
+        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.actionToolbar.setText(_translate("main_window", "Toolbar"))
+
+
+if __name__ == "__main__":
+    import sys
+    app = QtWidgets.QApplication(sys.argv)
+    main_window = QtWidgets.QMainWindow()
+    ui = Ui_main_window()
+    ui.setupUi(main_window)
+    main_window.show()
+    sys.exit(app.exec_())
diff --git a/b_asic/GUI/gui_interface.ui b/b_asic/GUI/gui_interface.ui
new file mode 100644
index 00000000..38274781
--- /dev/null
+++ b/b_asic/GUI/gui_interface.ui
@@ -0,0 +1,763 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>main_window</class>
+ <widget class="QMainWindow" name="main_window">
+  <property name="enabled">
+   <bool>true</bool>
+  </property>
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>897</width>
+    <height>633</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <widget class="QGroupBox" name="operation_box">
+    <property name="geometry">
+     <rect>
+      <x>10</x>
+      <y>10</y>
+      <width>201</width>
+      <height>531</height>
+     </rect>
+    </property>
+    <property name="layoutDirection">
+     <enum>Qt::LeftToRight</enum>
+    </property>
+    <property name="autoFillBackground">
+     <bool>false</bool>
+    </property>
+    <property name="styleSheet">
+     <string notr="true">QGroupBox { 
+     border: 2px solid gray; 
+     border-radius: 3px;
+	 margin-top: 0.5em; 
+ } 
+
+QGroupBox::title {
+    subcontrol-origin: margin;
+    left: 10px;
+    padding: 0 3px 0 3px;
+}</string>
+    </property>
+    <property name="title">
+     <string>Operations</string>
+    </property>
+    <property name="alignment">
+     <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+    </property>
+    <property name="flat">
+     <bool>false</bool>
+    </property>
+    <property name="checkable">
+     <bool>false</bool>
+    </property>
+    <widget class="QToolBox" name="operation_list">
+     <property name="geometry">
+      <rect>
+       <x>10</x>
+       <y>20</y>
+       <width>171</width>
+       <height>271</height>
+      </rect>
+     </property>
+     <property name="autoFillBackground">
+      <bool>false</bool>
+     </property>
+     <property name="currentIndex">
+      <number>1</number>
+     </property>
+     <widget class="QWidget" name="core_operations_page">
+      <property name="geometry">
+       <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>171</width>
+        <height>217</height>
+       </rect>
+      </property>
+      <attribute name="label">
+       <string>Core operations</string>
+      </attribute>
+      <widget class="QListWidget" name="core_operations_list">
+       <property name="geometry">
+        <rect>
+         <x>10</x>
+         <y>0</y>
+         <width>141</width>
+         <height>211</height>
+        </rect>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>141</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="editTriggers">
+        <set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
+       </property>
+       <property name="dragEnabled">
+        <bool>false</bool>
+       </property>
+       <property name="dragDropMode">
+        <enum>QAbstractItemView::NoDragDrop</enum>
+       </property>
+       <property name="movement">
+        <enum>QListView::Static</enum>
+       </property>
+       <property name="flow">
+        <enum>QListView::TopToBottom</enum>
+       </property>
+       <property name="isWrapping" stdset="0">
+        <bool>false</bool>
+       </property>
+       <property name="resizeMode">
+        <enum>QListView::Adjust</enum>
+       </property>
+       <property name="layoutMode">
+        <enum>QListView::SinglePass</enum>
+       </property>
+       <property name="viewMode">
+        <enum>QListView::ListMode</enum>
+       </property>
+       <property name="uniformItemSizes">
+        <bool>false</bool>
+       </property>
+       <property name="wordWrap">
+        <bool>false</bool>
+       </property>
+       <property name="selectionRectVisible">
+        <bool>false</bool>
+       </property>
+       <property name="currentRow">
+        <number>-1</number>
+       </property>
+       <property name="sortingEnabled">
+        <bool>false</bool>
+       </property>
+       <item>
+        <property name="text">
+         <string>Addition</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Subtraction</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Multiplication</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Division</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Constant</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Constant multiplication</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Square root</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Complex conjugate</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Absolute</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Max</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Min</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Butterfly</string>
+        </property>
+       </item>
+      </widget>
+     </widget>
+     <widget class="QWidget" name="special_operations_page">
+      <property name="geometry">
+       <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>171</width>
+        <height>217</height>
+       </rect>
+      </property>
+      <attribute name="label">
+       <string>Special operations</string>
+      </attribute>
+      <widget class="QListWidget" name="special_operations_list">
+       <property name="geometry">
+        <rect>
+         <x>10</x>
+         <y>0</y>
+         <width>141</width>
+         <height>81</height>
+        </rect>
+       </property>
+       <item>
+        <property name="text">
+         <string>Input</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Output</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Delay</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Custom</string>
+        </property>
+       </item>
+      </widget>
+     </widget>
+    </widget>
+   </widget>
+  </widget>
+  <widget class="QMenuBar" name="menu_bar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>897</width>
+     <height>21</height>
+    </rect>
+   </property>
+   <property name="palette">
+    <palette>
+     <active>
+      <colorrole role="WindowText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Button">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>255</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Light">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Midlight">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Dark">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>127</red>
+         <green>127</green>
+         <blue>127</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Mid">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>170</red>
+         <green>170</green>
+         <blue>170</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Text">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="BrightText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="ButtonText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Base">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Window">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Shadow">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="AlternateBase">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="ToolTipBase">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>220</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="ToolTipText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="PlaceholderText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="128">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+     </active>
+     <inactive>
+      <colorrole role="WindowText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Button">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>255</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Light">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Midlight">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Dark">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>127</red>
+         <green>127</green>
+         <blue>127</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Mid">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>170</red>
+         <green>170</green>
+         <blue>170</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Text">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="BrightText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="ButtonText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Base">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Window">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Shadow">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="AlternateBase">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="ToolTipBase">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>220</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="ToolTipText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="PlaceholderText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="128">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+     </inactive>
+     <disabled>
+      <colorrole role="WindowText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>127</red>
+         <green>127</green>
+         <blue>127</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Button">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>255</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Light">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Midlight">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Dark">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>127</red>
+         <green>127</green>
+         <blue>127</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Mid">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>170</red>
+         <green>170</green>
+         <blue>170</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Text">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>127</red>
+         <green>127</green>
+         <blue>127</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="BrightText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="ButtonText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>127</red>
+         <green>127</green>
+         <blue>127</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Base">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Window">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="Shadow">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="AlternateBase">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>255</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="ToolTipBase">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>255</red>
+         <green>255</green>
+         <blue>220</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="ToolTipText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="255">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+      <colorrole role="PlaceholderText">
+       <brush brushstyle="SolidPattern">
+        <color alpha="128">
+         <red>0</red>
+         <green>0</green>
+         <blue>0</blue>
+        </color>
+       </brush>
+      </colorrole>
+     </disabled>
+    </palette>
+   </property>
+   <widget class="QMenu" name="file_menu">
+    <property name="title">
+     <string>File</string>
+    </property>
+    <addaction name="save_menu"/>
+    <addaction name="separator"/>
+    <addaction name="exit_menu"/>
+   </widget>
+   <widget class="QMenu" name="edit_menu">
+    <property name="title">
+     <string>Edit</string>
+    </property>
+    <addaction name="actionUndo"/>
+    <addaction name="actionRedo"/>
+   </widget>
+   <widget class="QMenu" name="view_menu">
+    <property name="title">
+     <string>View</string>
+    </property>
+    <addaction name="actionToolbar"/>
+   </widget>
+   <addaction name="file_menu"/>
+   <addaction name="edit_menu"/>
+   <addaction name="view_menu"/>
+  </widget>
+  <widget class="QStatusBar" name="status_bar"/>
+  <action name="save_menu">
+   <property name="text">
+    <string>Save</string>
+   </property>
+  </action>
+  <action name="exit_menu">
+   <property name="text">
+    <string>Exit</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+Q</string>
+   </property>
+  </action>
+  <action name="actionUndo">
+   <property name="text">
+    <string>Undo</string>
+   </property>
+  </action>
+  <action name="actionRedo">
+   <property name="text">
+    <string>Redo</string>
+   </property>
+  </action>
+  <action name="actionToolbar">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Toolbar</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py
new file mode 100644
index 00000000..630e4af0
--- /dev/null
+++ b/b_asic/GUI/main_window.py
@@ -0,0 +1,625 @@
+"""B-ASIC Main Window Module.
+
+This file opens the main window of the GUI for B-ASIC when run.
+"""
+
+from tkinter.filedialog import askopenfilename, askopenfile
+from tkinter import Tk
+from PySide2.QtGui import QIcon, QFont, QPainter, QPen, QBrush, QKeySequence
+from PySide2.QtCore import Qt, QSize, QFileInfo
+from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QAction,\
+    QStatusBar, QMenuBar, QLineEdit, QPushButton, QSlider, QScrollArea, QVBoxLayout,\
+    QHBoxLayout, QDockWidget, QToolBar, QMenu, QLayout, QSizePolicy, QListWidget,\
+    QListWidgetItem, QGraphicsView, QGraphicsScene, QShortcut, QGraphicsTextItem,\
+    QGraphicsProxyWidget, QInputDialog, QTextEdit, QFileDialog
+from numpy import linspace
+from b_asic.save_load_structure import *
+from select_sfg_window import SelectSFGWindow
+from simulate_sfg_window import SimulateSFGWindow, Plot
+from utils import decorate_class, handle_error
+import b_asic.special_operations as s_oper
+import b_asic.core_operations as c_oper
+from b_asic import Operation, SFG, InputPort, OutputPort, Input, Output, FastSimulation
+from b_asic.simulation import Simulation
+from show_pc_window import ShowPCWindow
+from port_button import PortButton
+from arrow import Arrow
+from gui_interface import Ui_main_window
+from drag_button import DragButton
+from about_window import AboutWindow, FaqWindow, KeybindsWindow
+import sys
+from pprint import pprint
+from os import getcwd, path
+import importlib
+import logging
+import sys
+
+from b_asic.GUI.about_window import AboutWindow, FaqWindow, KeybindsWindow
+from b_asic.GUI.drag_button import DragButton
+from b_asic.GUI.gui_interface import Ui_main_window
+from b_asic.GUI.arrow import Arrow
+from b_asic.GUI.port_button import PortButton
+from b_asic.GUI.show_pc_window import ShowPCWindow
+from b_asic.GUI.utils import decorate_class, handle_error
+from b_asic.GUI.simulate_sfg_window import SimulateSFGWindow, Plot
+from b_asic.GUI.select_sfg_window import SelectSFGWindow
+
+from b_asic import FastSimulation
+from b_asic.simulation import Simulation
+from b_asic.operation import Operation
+from b_asic.port import InputPort, OutputPort
+from b_asic.signal_flow_graph import SFG
+from b_asic.special_operations import Input, Output
+import b_asic.core_operations as c_oper
+import b_asic.special_operations as s_oper
+from b_asic.save_load_structure import *
+
+from numpy import linspace
+
+from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QAction,\
+    QStatusBar, QMenuBar, QLineEdit, QPushButton, QSlider, QScrollArea, QVBoxLayout,\
+    QHBoxLayout, QDockWidget, QToolBar, QMenu, QLayout, QSizePolicy, QListWidget,\
+    QListWidgetItem, QGraphicsView, QGraphicsScene, QShortcut, QGraphicsTextItem,\
+    QGraphicsProxyWidget, QInputDialog, QTextEdit, QFileDialog
+from PySide2.QtCore import Qt, QSize, QFileInfo
+from PySide2.QtGui import QIcon, QFont, QPainter, QPen, QBrush, QKeySequence
+
+
+MIN_WIDTH_SCENE = 600
+MIN_HEIGHT_SCENE = 520
+logging.basicConfig(level=logging.INFO)
+
+
+@decorate_class(handle_error)
+class MainWindow(QMainWindow):
+    def __init__(self):
+        super(MainWindow, self).__init__()
+        self.ui = Ui_main_window()
+        self.ui.setupUi(self)
+        self.setWindowIcon(QIcon('small_logo.png'))
+        self.scene = None
+        self._operations_from_name = dict()
+        self.zoom = 1
+        self.sfg_name_i = 0
+        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_dict = dict()
+        self._window = self
+        self.logger = logging.getLogger(__name__)
+        self.init_ui()
+        self.add_operations_from_namespace(
+            c_oper, self.ui.core_operations_list)
+        self.add_operations_from_namespace(
+            s_oper, self.ui.special_operations_list)
+
+        self.shortcut_core = QShortcut(
+            QKeySequence("Ctrl+R"), self.ui.operation_box)
+        self.shortcut_core.activated.connect(
+            self._refresh_operations_list_from_namespace)
+        self.scene.selectionChanged.connect(self._select_operations)
+
+        self.move_button_index = 0
+        self.is_show_names = True
+
+        self.check_show_names = QAction("Show operation names")
+        self.check_show_names.triggered.connect(self.view_operation_names)
+        self.check_show_names.setCheckable(True)
+        self.check_show_names.setChecked(1)
+        self.ui.view_menu.addAction(self.check_show_names)
+
+        self.ui.actionShowPC.triggered.connect(self.show_precedence_chart)
+        self.ui.actionSimulateSFG.triggered.connect(self.simulate_sfg)
+        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)
+        self.shortcut_signal = QShortcut(QKeySequence(Qt.Key_Space), self)
+        self.shortcut_signal.activated.connect(self._connect_button)
+
+        self.logger.info("Finished setting up GUI")
+        self.logger.info(
+            "For questions please refer to 'Ctrl+?', or visit the 'Help' section on the toolbar.")
+
+    def init_ui(self):
+        self.create_toolbar_view()
+        self.create_graphics_view()
+
+    def create_graphics_view(self):
+        self.scene = QGraphicsScene(self)
+        self.graphic_view = QGraphicsView(self.scene, self)
+        self.graphic_view.setRenderHint(QPainter.Antialiasing)
+        self.graphic_view.setGeometry(
+            self.ui.operation_box.width(), 20, self.width(), self.height())
+        self.graphic_view.setDragMode(QGraphicsView.RubberBandDrag)
+
+    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())
+        self.graphic_view.setGeometry(self.ui.operation_box.width(
+        ) + 20, 60, self.width() - self.ui.operation_box.width() - 20, self.height()-30)
+        super(MainWindow, self).resizeEvent(event)
+
+    def wheelEvent(self, event):
+        if event.modifiers() == Qt.ControlModifier:
+            old_zoom = self.zoom
+            self.zoom += event.angleDelta().y()/2500
+            self.graphic_view.scale(self.zoom, self.zoom)
+            self.zoom = old_zoom
+
+    def view_operation_names(self):
+        if self.check_show_names.isChecked():
+            self.is_show_names = True
+        else:
+            self.is_show_names = False
+
+        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.opToSFG[self.operationDragDict[op]] = sfg
+
+        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, Input):
+                inputs.append(op.operation)
+            elif isinstance(op.operation, Output):
+                outputs.append(op.operation)
+
+        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
+
+        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.")
+
+        def check_equality(signal, signal_2):
+            if not (signal.source.operation.type_name() == signal_2.source.operation.type_name()
+                    and signal.destination.operation.type_name() == signal_2.destination.operation.type_name()):
+                return False
+
+            if hasattr(signal.source.operation, "value") and hasattr(signal_2.source.operation, "value") \
+                    and hasattr(signal.destination.operation, "value") and hasattr(signal_2.destination.operation, "value"):
+                if not (signal.source.operation.value == signal_2.source.operation.value
+                        and signal.destination.operation.value == signal_2.destination.operation.value):
+                    return False
+
+            if hasattr(signal.source.operation, "name") and hasattr(signal_2.source.operation, "name") \
+                    and hasattr(signal.destination.operation, "name") and hasattr(signal_2.destination.operation, "name"):
+                if not (signal.source.operation.name == signal_2.source.operation.name
+                        and signal.destination.operation.name == signal_2.destination.operation.name):
+                    return False
+
+            try:
+                _signal_source_index = [signal.source.operation.outputs.index(
+                    port) for port in signal.source.operation.outputs if signal in port.signals]
+                _signal_2_source_index = [signal_2.source.operation.outputs.index(
+                    port) for port in signal_2.source.operation.outputs if signal_2 in port.signals]
+            except ValueError:
+                return False  # Signal output connections not matching
+
+            try:
+                _signal_destination_index = [signal.destination.operation.inputs.index(
+                    port) for port in signal.destination.operation.inputs if signal in port.signals]
+                _signal_2_destination_index = [signal_2.destination.operation.inputs.index(
+                    port) for port in signal_2.destination.operation.inputs if signal_2 in port.signals]
+            except ValueError:
+                return False  # Signal input connections not matching
+
+            if not (_signal_source_index == _signal_2_source_index and _signal_destination_index == _signal_2_destination_index):
+                return False
+
+            return True
+
+        for pressed_op in self.pressed_operations:
+            for operation in sfg.operations:
+                for input_ in operation.inputs:
+                    for signal in input_.signals:
+                        for line in self.signalPortDict:
+                            if check_equality(line.signal, signal):
+                                line.source.operation.operation = signal.source.operation
+                                line.destination.operation.operation = signal.destination.operation
+
+                for output_ in operation.outputs:
+                    for signal in output_.signals:
+                        for line in self.signalPortDict:
+                            if check_equality(line.signal, signal):
+                                line.source.operation.operation = signal.source.operation
+                                line.destination.operation.operation = signal.destination.operation
+
+        for op in self.pressed_operations:
+            op.setToolTip(sfg.name)
+            self.opToSFG[op] = sfg
+
+        self.sfg_dict[sfg.name] = sfg
+
+    def show_precedence_chart(self):
+        self.dialog = ShowPCWindow(self)
+        self.dialog.add_sfg_to_dialog()
+        self.dialog.show()
+
+    def _determine_port_distance(self, length, ports):
+        """Determine the distance between each port on the side of an operation.
+        The method returns the distance that each port should have from 0.
+        """
+        return [length / 2] if ports == 1 else linspace(0, length, ports)
+
+    def add_ports(self, operation):
+        _output_ports_dist = self._determine_port_distance(
+            55 - 17, operation.operation.output_count)
+        _input_ports_dist = self._determine_port_distance(
+            55 - 17, operation.operation.input_count)
+        self.portDict[operation] = list()
+
+        for i, dist in enumerate(_input_ports_dist):
+            port = PortButton(
+                ">", operation, operation.operation.input(i), self)
+            self.portDict[operation].append(port)
+            operation.ports.append(port)
+            port.move(0, dist)
+            port.show()
+
+        for i, dist in enumerate(_output_ports_dist):
+            port = PortButton(
+                ">", operation, operation.operation.output(i), self)
+            self.portDict[operation].append(port)
+            operation.ports.append(port)
+            port.move(55 - 12, dist)
+            port.show()
+
+    def get_operations_from_namespace(self, namespace):
+        self.logger.info(
+            f"Fetching operations from namespace: {namespace.__name__}.")
+        return [comp for comp in dir(namespace) if hasattr(getattr(namespace, comp), "type_name")]
+
+    def add_operations_from_namespace(self, namespace, _list):
+        for attr_name in self.get_operations_from_namespace(namespace):
+            attr = getattr(namespace, attr_name)
+            try:
+                attr.type_name()
+                item = QListWidgetItem(attr_name)
+                _list.addItem(item)
+                self._operations_from_name[attr_name] = attr
+            except NotImplementedError:
+                pass
+
+        self.logger.info(
+            f"Added operations from namespace: {namespace.__name__}.")
+
+    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):
+        try:
+            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(path.dirname(
+                __file__), "operation_icons", f"{op.type_name().lower()}.png")
+            if not path.exists(icon_path):
+                icon_path = path.join(path.dirname(
+                    __file__), "operation_icons", f"custom_operation.png")
+            attr_button.setIcon(QIcon(icon_path))
+            attr_button.setIconSize(QSize(55, 55))
+            attr_button.setToolTip("No sfg")
+            attr_button.setStyleSheet(""" QToolTip { background-color: white;
+            color: black }""")
+            attr_button.setParent(None)
+            attr_button_scene = self.scene.addWidget(attr_button)
+            if position is None:
+                attr_button_scene.moveBy(
+                    int(self.scene.width() / 2), int(self.scene.height() / 2))
+            attr_button_scene.setFlag(attr_button_scene.ItemIsSelectable, True)
+            operation_label = QGraphicsTextItem(op.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.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}.")
+
+    def _refresh_operations_list_from_namespace(self):
+        self.logger.info("Refreshing operation list.")
+        self.ui.core_operations_list.clear()
+        self.ui.special_operations_list.clear()
+
+        self.add_operations_from_namespace(
+            c_oper, self.ui.core_operations_list)
+        self.add_operations_from_namespace(
+            s_oper, self.ui.special_operations_list)
+        self.logger.info("Finished refreshing operation list.")
+
+    def on_list_widget_item_clicked(self, item):
+        self._create_operation_item(item)
+
+    def keyPressEvent(self, event):
+        if event.key() == Qt.Key_Delete:
+            for pressed_op in self.pressed_operations:
+                pressed_op.remove()
+                self.move_button_index -= 1
+            self.pressed_operations.clear()
+        super().keyPressEvent(event)
+
+    def _connect_button(self, *event):
+        if len(self.pressed_ports) < 2:
+            self.logger.warning(
+                "Can't connect less than two ports. Please select more.")
+            return
+
+        for i in range(len(self.pressed_ports) - 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:
+                self.logger.warning("Can't connect to the same port")
+                continue
+
+            if type(source.port) == type(destination.port):
+                self.logger.warning(
+                    f"Can't connect port of type: {type(source.port).__name__} to port of type: {type(destination.port).__name__}.")
+                continue
+
+            self.connect_button(source, destination)
+
+        for port in self.pressed_ports:
+            port.select_port()
+
+    def connect_button(self, source, destination):
+        signal_exists = (
+            signal for signal in source.port.signals if signal.destination is destination.port)
+        self.logger.info(
+            f"Connecting: {source.operation.operation.type_name()} -> {destination.operation.operation.type_name()}.")
+        try:
+            line = Arrow(source, destination, self, signal=next(signal_exists))
+        except StopIteration:
+            line = Arrow(source, destination, self)
+
+        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_operations(self):
+        selected = [button.widget() for button in self.scene.selectedItems()]
+        for button in selected:
+            button._toggle_button(pressed=False)
+
+        for button in self.pressed_operations:
+            if button not in selected:
+                button._toggle_button(pressed=True)
+
+        self.pressed_operations = selected
+
+    def _simulate_sfg(self):
+        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"])
+            l_result = simulation.run_for(
+                properties["iteration_count"], save_results=properties["all_results"])
+
+            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}.")
+                self.logger.info(
+                    "To save the plot press 'Ctrl+S' when the plot is focused.")
+                self.plot = Plot(simulation, sfg, self)
+                self.plot.show()
+
+    def simulate_sfg(self):
+        self.dialog = SimulateSFGWindow(self)
+
+        for _, sfg in self.sfg_dict.items():
+            self.dialog.add_sfg_to_dialog(sfg)
+
+        self.dialog.show()
+
+        # Wait for input to dialog. Kinda buggy because of the separate window in the same thread.
+        self.dialog.simulate.connect(self._simulate_sfg)
+
+    def display_faq_page(self):
+        self.faq_page = FaqWindow(self)
+        self.faq_page.scroll_area.show()
+
+    def display_about_page(self):
+        self.about_page = AboutWindow(self)
+        self.about_page.show()
+
+    def display_keybinds_page(self):
+        self.keybinds_page = KeybindsWindow(self)
+        self.keybinds_page.show()
+
+
+def start_gui():
+    app = QApplication(sys.argv)
+    window = MainWindow()
+    window.show()
+    sys.exit(app.exec_())
+
+
+if __name__ == "__main__":
+    start_gui()
diff --git a/b_asic/GUI/operation_icons/abs.png b/b_asic/GUI/operation_icons/abs.png
new file mode 100644
index 0000000000000000000000000000000000000000..6573d4d96928d32a3a59641e877108ab60d38c19
GIT binary patch
literal 1251
zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>fw3{u**Ty%$lXc7)79C`(9+CI
z*GSKhfk9(p>BRHeE`cJ)?Tt_5FZK`<o4jGxMF~~2Q$A8&vyN#rpFLx2f8|fbUh_#g
zQ`as!a%??oJ!5q0T9Jv9rnZ`HOsqOL+uU@L<@Vz9&+FdXF0Ve%T5Yj5<x3WS%BD(P
zkq?&7l|G-lCBEwAf_Y_8i@MhJu{H;XY&l<@leVXH_ExR=zpqP8f3%Bfo7KZW*Q=in
z2_FhxU3vA1?87ou(O4ln;j+vk$A{Lz{gHpNiY`6MpS-o;1pl|N#Pm<%$Llsux@2_d
zQ&E-COVOnz@}E_GKfZa>b0qJhUWpUShleSX3jgpJ9_TpZyyN2~eQm8L)m=X?KT!#v
z7@5l3n>uO9iN`{vuB(o#s=4%^_Sl^E?SFp!*CPwAue$f7c~<)2iubLzay4=!k6oXb
zDAKp^6{p_jr+@A!^3-?i3%ypwcVHvq^YB#NZQSCsFZ^8LwlyPgmC$x|p$}zTX9Y8M
zH@2~^PG4H<C#SGSFTRww`u7F(=d#`Q+@a}v7Os3T9~dW`1s;*b3=G`DAk4@xYmNj1
z12c!Gi(^Q|oVT|Py_6Fr8XnHPdd)6$gW!~p8ut{h3+(CY-MZDBbK`lzJqL2OZW5}z
zy2dSWK6}ua1Ltdw{x_;Ol$?J0>E*KBwvr99`}b8d{5Z+N^zg^l*^e#cs`!2gKC7%N
z+$(&dYOma~Osg9XA13=;HpwxY{XE-Uhl8n6K>#1|;njb~Q#R+V@1NO!-J<Vb_91)A
zFaLgjkbd&(SCxg8)ved%b#?zP9zV!!Zf;)ve*fmpoB8d}3vxF(C~&Y09pcc(a{dqd
zckbRT{qFty?@_xWblPs;zU@D+LumqYbxn<ooSfXZsOqY!Upo(fP$L*>`<~fu=<e=*
z`07>G`mNnZA5C8VP?yPa!__R)HDRmY%Qcr59cjWJ>Vr<!KyUi#<!{TXKRrJNPDZC(
zKij<jzM=3=w(Os`+TZV+_#RH%T)EHg-zxTl;g(PS6%*k@>-oFm<KpU$%Y0C-Y<3Xf
eU}<#t!TWoT-Kn+rHu?iI3WKMspUXO@geCwV2+aoo

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/abs_grey.png b/b_asic/GUI/operation_icons/abs_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..4e16da3110d3497c7dab55b9cd9edf22aef4c097
GIT binary patch
literal 1369
zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>fw4K$**Ty%$lXc7)79C`(9+CI
z*GSKhfk9(p>BRHeE`cJ)?LRk9l<!Si7_rA>ic{T{BW{wMs=mon;%Cn^-+$#y#a{DE
zDXVitRn6*je{kQ>%3j(rWsT07PTtGK_s(4LF?wG1?&qENd!LuQ-<iO#c6G|bH_ahd
zi`ms`PW?Hw=uy>CPwuN*E{O*5=3aR}f7zm1+l0-JV*5i^f7)5UR!_(@WW!aC{uQ(4
z3C<Il7k55G>^+zH>dpX{A2B<kr*m`NkFMHrAYbe__n+jAay&hadjlgnrx)(%x!N*M
zXxjA0o~qZcJgQwY{fhhL;>|+0kKQ)mFlgj&)jTG7*xUZFQT5BjhPcQ;#r^v8?GLYd
zvdSns<zl3wUlY&WMe97jt~fH|MWCeTrm+A2zg_L%<r7(T{r7@bM@{Ul4s2!KnS1$<
zo!QwtoEGyQYQ>(4w{2SJe{s9_tmguEW)~F3UOH4=u#;8SKgxsuscQQUTe0Tv#u*U}
z%c57rYJFL=<NQly7V{^o^Ai3uSUufS{Zl-6OZr{2*?(69V}-N8BeIx*fm;}a85w5H
zkzin8uJLqn45^s&_V!+{*hq<nhf^*8u{?1I2+D9-<|*niQ7v5I^6iLcO_!>LcQOa_
ztkiXh_LOYn;c)bss9+S@)$^fkv*6j3dCTv7*tqXmYv`K43F+z2&wl;-^*!^Cb2(<e
zi(h_ze!hOr{Q2kKS4;l5nUkNt{!iI`?*r$36a=RI`<qx_U%zJk`uoTA3fAA-UvJOH
z&%gh7nev3^=jZpYUAxvgnyXH1^UXJI3hN>SSeP6Y@DUG>6@Qnn{QmB)g`M5Jk{i#S
zJ-c!L{(KYe<etKU0)u^Zf2}gh%FD}bs=wtdoY~6!^TWf#7M7NgB{@BY$DTfYy2SFw
zvoBvtYJPr7z4WGATwiQn;n5}q0S>0dp+b0n-+SQy+1uOmk00-sS3hf4`>SLR&^y!2
z7jZo4la-b2-Mo3T=jQP6aHgHf1jBRBv%Sgw^K2x6$|tAE+t<~czWqTNsBwMV-d*oS
zBO0{DSQ;G!IED%l5moZw;o<gVyZQcBIk~yHmt<;;=bt}*>eQ(vH-VAgnl8qdlAoXd
z=IvYE8i6})*Vo72e_2T|kXV>NiOzq%oo`(}|Nm5RadGo~wl{9v*kNIBKmQxM$e%;o
zwr}^3-KfC9a_G{lSFi5(%h^W#xE}rE>e;l-`~NW}`-XUGuuW42W=;lAS3j3^P6<r_
DpbP}7

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/add.png b/b_asic/GUI/operation_icons/add.png
new file mode 100644
index 0000000000000000000000000000000000000000..504e641e4642d9c03deeea9911927bbe714f053e
GIT binary patch
literal 1208
zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k2}mkgS)OEIU~J5Eb`B^Ga(7bjbagf}v@|o*
zHPSO=V9=OYI`O==OQ6Vcd*c)Ni#^1|CU2N^Q9{-1l#i6xtYaF@XU`bhU-?t9*L+gW
z)U}I_99z#?&lsJ$R%GI&sja3P6RXb6HaDGQxxM)O^Sbx8%d5|`R$Hu1`I5z-vZ+#6
z<b&mNrO)SXiLZLOU|w0&qONs)tj)n8Th3SKr0pr4y;W=e@9R?2AMIk=X7w=8_3EcX
z!iR!aS6+Q0`>;$^G*-w?xGb~C@u78af8?L6qDznRCvPn{!T&8RG5wSH@w$zZE*TyA
zR8(d3QgmsF{AX3)k8j@e9Lf8rSK`F-;bF?8!aqEQ2RhC;@A!C0Ut8-*b=S|!PgKGu
zMyB%ircPRN;;~Sv>#F0bYA*e!JvOI(`=1~G^~i$jtL{B%o|S&M;(hC_T#X#bW7j7p
ziu5gf#i_UX>7P4_JoO#>La$Zv9oWeDJUmr*8@KrE3qM!5ZOsT=CA3{#=tCLTS;36m
zjcu%})0fuz$tmp7i!bG^{(V9Hxoo#RcWC;ag)3jo2gV6!fk$L90|U1(2s1Lwnj^u$
zz;xHs#WAE}&f8moUebvo4T0&iSYP-(;!|OX+4AE7(~+$^8h(6V7;ri0mf^3`MRPuz
z6);c#VEDO6Uyp&|N5<ukhb5BN+WvTROyaZ6|C%}fjkwf>#W)iu3g9L>&)(vmvv~US
z>E>tU=XZZ-u7AQ`w)^h(qj~xH*YACo;p}eN;K4D3h$AI`430(oJAL@!hWqdBtLAV1
z@a4;wKY#WV{P^^#X!qS^zt<$qd;a_P@28)Wdn)T{{r76GIh(e*X5RVKdC!4*YW{8C
zP{ZYk-^Y7i+azwkU3*0?{prV#g+Fhexq9{L*RQ3!&TIAgi~1G(x_L&ttho5|hYuSH
zD)!&MfA{Xv-!)w4(tgOdxH?6s3>{+G<95SiQPp#%ZvV%B+2n8ai`CM}XFk8K&f5G)
zE8Sb;{qtZC{Qh|~{WV*<@f;r;28Oknv-UCnyXTYX`rNGvnEDw!UHx3vIVCg!00LAO
A^8f$<

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/add_grey.png b/b_asic/GUI/operation_icons/add_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..a7620d2b56c8a5d06b2c04ff994eb334777008f4
GIT binary patch
literal 1300
zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>fw4K$**Ty%$lXc7)79C`(9+CI
z*GSKhfk9(p>BRHeE`cJ)?LRk9l<!Si7_rA>ic{T{BW{wMs=mon;%Cn^-+$#y#a{DE
zDXVitRn6*je{kQ>%3j(rWsT07PTtGK_s(4LF?wG1?&qENd!LuQ-<iO#c6G|bH_ahd
zi`ms`PW?Hw=uy>CPwuN*E{O*5=3aR}f7zm1+l0-JV*5i^f7)5UR!_(@WW!aC{uQ(4
z3C<Il7k55G>^+zH>dpX{A2B<kr*m`NkFMHrAYbe__n+jAay&hadjlgnrx)(%x!N*M
zXxjA0o~qZcJgQwY{fhhL;>|+0kKQ)mFlgj&)jTG7*xUZFQT5BjhPcQ;#r^v8?GLYd
zvdSns<zl3wUlY&WMe97jt~fH|MWCeTrm+A2zg_L%<r7(T{r7@bM@{Ul4s2!KnS1$<
zo!QwtoEGyQYQ>(4w{2SJe{s9_tmguEW)~F3UOH4=u#;8SKgxsuscQQUTe0Tv#u*U}
z%c57rYJFL=<NQly7V{^o^Ai3uSUufS{Zl-6OZr{2*?(69V}-N8BeIx*fm;}a85w5H
zkzin8HurRK45^s&_O_u{N+d(u#osPGEC&yGTsYynqc5(hbLLEMfjdo&>kgz!oaN)<
zadqT5BW1F}^Z2}n(r$6lt;K$62lxIFsVQhKva+!Fk<9Qv#j@9M+UhvQ*ICI|nG@!6
zvK)H0UVhHJdFztc*R-x)y_&zio+D+}vcj60KTDev=KhiDP#0rqbP(XcNBp?`zxhM;
z-Fx@?+S}Xx=i1uX$k^BYnNca|su2I?-@iH?v2Oplwe|J$D|!DP6XtGmP~c!0Dn!n?
z%9}+yWz5aZi{pO2wXwCW{q^SY<HtMp?VDG4_PdeqWeZEonP1P{WZt=Z_w3JJ{~mq)
zdiA?>ox8%k2mux*M+MwO+F#T663K7gzdv95bo-}IpN#I^yJuHgmYtpb=Iz_FdtUsW
zu`KhObvkcKa&mG^Ow5_er`wAwD=o#kk1jtaCo9|g`Sa(Q;(<0BhfrX00F&D)_CNP?
z^7H%W&YkN&|7kYolWrhuZ57l1Mq*OhhvM%~i!A5OpC7)J_1Za;V-f4BSk`F!>u@kN
lZUDx>@8YY<3BJ1jS++}hR;-+2Cj!h@44$rjF6*2UngA3==uQ9t

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/bfly.png b/b_asic/GUI/operation_icons/bfly.png
new file mode 100644
index 0000000000000000000000000000000000000000..9948a964d353e7325c696ae7f12e1ded09cdc13f
GIT binary patch
literal 5358
zcmc&&<y%x=xSb&cMd{9=8$<;}8iqy$1Zj}&8l{nDK)OLvhLG-1LOP_u9}*5RlyoX3
z$lcse_dmEF&YAP<+0U+bt@W;Vzt_}IdT@{S9s~k;prQ=d2ET*15B?qSuGFu}g+QRe
z_VV(YZ?u)<U@8joB0^#!BK(g8gdh;EM32OAUbq(dfb`=**8*iq%H*2NNosbHA$3~S
z%zm!W(UHf}|6O^vizYdxR-$_Qt8oLNM%I<&iAky9!nNkUV_BlYNn*`!#(xDINPhAg
z$Mt(!Y4zKI$g0kVpZrkl7t6`mJXP7&$6QZCR8&<QZs<#$hH*b9n>LTEMxMOCGqmX|
zEm%#@Fj@-J$K7PzFUx(V*BCIK>=Y)XQe>X?2w9By%Xot;2gKBhYjUI8Akpb7bK7I8
zfq>eisYl(%2;WCr6h&{DPS~Fx?e50(x*Q3-mBTs2SS7h%-52VN9FhNgG$qK(v*s6d
z@qLX=FVVpIL9BIB;ovH%heFu^I|n>|Sh?Qj^v2cfPw&T>vc<K~OxqssgYbE0E+?A)
znM8B)xcu$A0`==xXy*HYkzaLx_!4&3LVxL5^EVMtWlda^C^p(@m60}ck{){A8zr%8
z4MyUY+ZMgkV1%^^n0Y+#`#Zt;iy>N?K-ab{zjQMX#N@7<vcVe&gn;b!fkHB~sUZ+D
zG8MS2j(7Inf{&)|Ivw8Of%O+YfiNUAgoPPZ*YfcZ4{RlgmaUXbH8_^kMXVGvbzfWB
znIrW{Heatj8^dtyljJ9Ono6<oUKuw8w}QJHv!-3ApWwj(k&+@#9M#hU1A%edjS-x)
zOQ~_m1wmVBrPcLykH15yyzR31_WkPncks0y-NR?b;(~?Y*LWXMiHV8f-@TidPa(HP
zMM@fy%=tp(pSl{c9FC?8K8XyJ84ke>BZiXy|1?D5E1Q@wsH&=JYH30HGthg)Qzdg`
z#f6%>y5W+3r!Z-0ri+UUE^h8%(`r13zM*0F<Rr=1*x2Smt0)#Wwvhc4+xf-Cw@IF@
zH8!Hb(wCne+K$nuq^35Ww`hElH!v_*-N7Sb>;<QT0jPRU#%VECsDe*K6w%(^p5mOW
zp<1WQ*KQjBLh1r1KR>?`DX;9~Blh+0zW7L{FaaSUA+@+C-_-Z-N9RYDgbSp{DOS)A
zk8KHmR2oGfkx0UL>xa~Cm%r^tvP6kAG&CY(WBb4o;G0Pa#cX#66IqoGSL3A-G_6fd
zl;U3da5uMy5|Wa&0oT4M>FIAu6`nEeLoW_GZffi%Sr)A!5WQ;CzD!}qiBB(>;EIZ<
zj~``{T6*8Q6DezJle#RneQQ-GyAukHAY|yC$d@M}AqkGA5>z$J%OI?ResN#DBjUO^
zQs1k_AmvXjkGwByWyN-nfWUe$yo6{bXm_cj<LDfTCS-UP#w?F~effLq{A65u-6f&~
zXC}zX#^#v*>nOG5;K)c=M~5^Dh0@f~Ve?XhV?N^+wzbjw`1rIT4~YAz2^l5urKP3Y
zu1|k*2?(UMvZFN7ScuID=Ds|=KI)!eY`o>2ooIHow2xI)8vALH-EIVfFQl(~9qlHC
z`V%rUqw?h=rjQc$A!;%X4h{*)$vu{KRUrHiEqd|&kNT)k^hccA1MINAzP^dAw-U&W
z{cp*f%IfNHeSQ7bF&ft5a5Wjw-TOXoDH?5jetBszo-2uHma~FGNp3N-xoHPiQYvU^
zG2Hizy#Lvl$btvzV*~m;eY0`@(W6Ji^z?jQ;$>$bo*)JVC=}MS975lA#yg?;->j^y
z!=c!BIu{lgHa0fu{-{EQrYen^AG)BQvT<;%1m65*8W|yXZ>(dv{(I0dI6gj*%B$!0
zYm5Pmv^tg}9(%Kp=IGsE$Pa=n<hCqxetr%U5Fiu~5TI`j3{Fp<afa;9rGPCzLlqYz
z?Vr$|US$-EjAn}^PtVMF*-3qf%FLwo1aL}AO6p$p`Ngn-!E|{nIyM@nB_wpZEVT5k
zpWxu(iB@uZ%8cbn3;w0jkFrO9cCP@t&A#E~(K0g|KR!8`YjXZMi~jQFLwr2Z<HwJa
zt^XMr8rJniQ$21s72LM<7lNUxEmG6cq9hAhtZi*spFX7qJaK#bHl>xpWoUS~*6+-P
z{;3-zJUqOlx;nj;9rEJ91cdG$FE6jtY)!Bc!z4E1=HI*82%U=VZaExWT+I2=>fzyG
zLMy}Me?NY(Jbp~J6nG=4k^MA(Gqjja)FrICTF}D6;&Y7PWtxq%bLFf&&phZ6`|%@e
zI*$fDF%h)8>(txdf6HwlA)%)MSD#TmM<Z=MCvN|)d;~0C%D~;ggkcd8tJ~A%h~_Dq
zqM{=GMh6Ns8cj?|iOdoA0w=1t-!xzN^=s}=hqTRdeTS?s7NsR6GWz<|8hKK&U*G*5
zT8R6&z3pISWd&np#RjT^Xl9Kv3%sWI^XE@ZT^*^1v0+GLNkzqdb93|0erHwa%7&7%
zvU_;}m)u~U8mn)l$#?M%e=n~BL@F?)W3klj%v7^VNYE28N+h(VD(dR$Dr;yUv$E)t
zl9B)x*}c?^>81Sje+zNL=#9Vn(~xm$q+D0U$1_TH&o??^Ha3urjx(887gkE~j1=$G
zC$XoB)TqFz7KMah0T2(@QI+K9LuqJe4uAiKBM^1p@K$LU%bQSmg=QTA7DT0nN^$gE
zQ53vvl?IxcL6ZeatL<lt@(K#!g^la4^z`sTyI|WHD7@Z<1(RK7U4C|U+^wxG^p1r0
zA>`=jh+4#n8i017Jy-jEd(;bPVrK|0K|)s6u=7=3;6IvBJR(Iy!^{=elrB14c=+3A
z3_ujz_U47P1}aNSyRI%yjyAGfR~Fm-P;r&7qKK52zP>YecIH2=Dbq7Bcn@T-sU=#N
zmzSX4X0&_8EYSJCyLq<bx#4+vOiiv!0-oh%9G9j#wJ*GEV`?KK@#kJB>1{s=xbiOk
zzLhyrZPMx~>}Q5RJaV+NvlDUM+1Z%`k+&8fHQsuurKJ_|wG~~BXC6}<6LWvgHYMp(
zSXfxU&-BDZr?IiIp}(1pjZN}}@5Qk-B{4C~??yG3QObWd|DcKuT3S|SVOaU<^h41a
zx3F-kQ2#6-hS9qhIy&T*F+!nTolQ=2-4&IU)(1x4l2cQ4qtzCgmIJ!TA+vLH%zS+J
z0aGN9qIcEO1r%=nUE?wxztq+a`PuBs3W(0Bk)2Q@?twrcLKF)~Jk(uYe94gxl){Av
zb-nJaFsNVN-9>Nvxu8EonV6W8CB0Og<WB0^KQCmmvan#8nwqwOfv2aZL;NbXcXqhx
zoj;ZZ**&2J(sN9kn_!cjoegJ7-WkmSYW6@+PcN4p-@dSn9sf2dT3j5jOQ;b~MGmB9
zqg{!fx-Lrm-ky&3qLI7R)yaW1Mq)ZTI{)93N^Qdl8dV_A7z;Eetlg<6pyUeznUK6l
znNt|CraT!R3?aoWG-`Iqko0T%-&TkJxCB;JJ);r#@G<slgNerdseCY2rMM@hKi`n9
zKNJ-mIK=dPiQ(Up7eEyh6!9rNd*9h9<049s_yh(brJcr_1NZlrXs%^{F!)h66y71s
zwuScny%`MhD1y#xDhLSs>*da1?KCzHmwoh?n%Y{7$k#QZ`IDog#1=LoE!x;SO#X+^
z8QLVg-kBM_?WA7#^DL1E)gAwy&CoXBFqi!YSmQKT7w*UK4ZC|a``y$<g;@uqi;D|-
zN12hB@z-#|7&{jiel#V2QXwU7Sc&RPl}WAT0Aa=lm6PdI37Gb~hZepgBO`}BMAFK>
z)!2q1#M844_GHECA6V?4-^14mKR@^OKgUEQ%@pbKp}fgB)Ishl;|nxqziE>4Ifid1
z!TZ|U+iSfJOhGJ$w9Sr#&}}|1L30s31H;j<U`OZJm_`#L7Yv>V=BaUAlKQ>%U1?FO
zP~d!A`bOC#sbpe+DKG{wczEFszii|&ur$nKVg~ynkO|T-u9#QleN`r{AEzDGU2q}b
z|873hHtNO2MMU$Y@>CFz+^O&SD&-A^l`H$*-1aj6&R63#Zs=X7TD+Ftr4KXAi_1*~
zWs7@h3?bkOPiP;!s?cZlq8nAFO{ljWS2c($83fD0Y;AGhHd2BlA8>Q+RXRkHs-mo%
z&>BvuLYdI<?~;IsNh+m`UW>%Y$Vh>n-08g)RP4=0j80MH5MUqIDKaDpmrY#Us7xDf
z&7qzF^CGBB;)4P6sh!Hg^L+~&u|7lr5~Z3}R7p&Ed3oVXMsq3~G=G|RD)TNG2iN5z
zr+fH`soH0zl0A~J7@@U|jbj0M9o6@EczFYouyg?v)GAY<u4*73(^=5OHiiB3v;76k
z@-obeF61*Fo1kF4*Ikr0BO_zfq6{7zzOuD7`vEDuDCMg!t}=1LKs?ilfUHgwR>&9y
zG1$QD{QS@Y(n0yiKR#}Sk=W7Eagm*zSxAT^H8oXPMJ2J7VtRA2y%o*hMt&9)L-QEv
zb$2ctiL|)ByiFBy>gqA*y%HpCS`!+Zl$5l)HLT(`u<!n!_GckxW@eiG_yPk1W9Y@S
zD}Ip2V`07Covn>pN}VA|OiX;9+hC}tH&f+)b$#u=j}}{7TYG(Veq_=3$&Q>DBI*0P
zv%&tmcY5OJ@%nJqmH*y+qey)Iq+LQ<+8wY=eIp~wpH0rUy-ix)CbovBC#_la33gmf
zv5Edrilq(LDo_-@Fp-s&%^EPMc@0GpF)g0gM?~NOvn-Czn<~YKGpop;7Zxad)Y9V&
zo16^lmrUBcYoA-%H5vkqDxNz4rf7a4-4O6{aG_gG#Zv)OrdvMI>}pmz<o;`aKdyPv
zw|ws4XA2HsutxNF+}WQ$&R(t#`N&VPDnIgLV;=zjQ5^NbBBn?!UF0vHhld9jKfm+$
zr~Lfkkk=C*W%u@<^N);<uDf80*shy2awTZY-u+2UVgIr@SI@Z^cyn#jA;`lMVp=Wg
zo6Y^QxPcZ2sNGv|4hxcH!L<MMRTnYc;RLn5e;i7Oqa46D|31oT2gsdYXYQv-yN7Pe
zog!z&91S@3eJvh4#8gzAN?U~Wjg5UPebGk#l{GaJo9;+LhN$J`*Ij2BmkyNtvN(lB
zMVMI8#e{PIR^U7_(|TskM~zKRfff3-D@AykpoBmdFaC#2gJJRU@!b+QhC%#Q73KU0
z8r?P>>j3#2aNVM{`uWQjnnt?xG}v)+qdY*_)#*=HewRa@Nr0e<YO@YXGXVS^;8tR2
zZRhH&6&bJ}{xNB9z!3_0Rj%Oc`wZkB)HzLBM#k*6<4m=g=r%?sE-p^-M%K;UJs8OJ
z<aO!$f&x~sD-JI@L3TfxT!T8RyNEY$DE!jffh*}*TI!gdiIv221o;36snB*iP|tvX
z0L6AaXT=!m{HSDo3Nqs5t>?W0CM{uwg=~Ei>*U@oJQ<mpJm-v-P13%nyYGQ*6`2-U
z4=M>pVPQ#_tQ)p^@`FOctc6a8C-2><LGsIaBa0DID=>$%)VXaLRBLpMMo_T&CuFvs
zo0Btw`x{W#*%GaS*%L`su~=%M5SshQ^ivu}MuDeeK~QXc6BCmx>6>r@DyzS{ZN*2c
z1NESmI`wDx3<@rnsq&RHm|*5ZbF8sk$!N=gIAQU|?B;*he=R}2RoshvLVZ7^+Vy8s
zr3uk{nkRM%D_*7z;fe)a{jqeqwbxKCH;QuM@sOmPoLEo_6o|h4IXpZ}!1*FKEIK+`
z>wxOnOEC_PJD}9t+wPg4rv)OqciF5;PDfW>Hmx{~v-V+PLUVVqozClEv9Eb4fcH`u
z4UX_-vAQfsDAAwtn~NKbX1#rU%)b{uXV@++`o2BQ1%pB8M4S>@W1F-tMIwb%)zl1B
zRqsVdM!r5hJ>4@D{Nd2(1Txz$AoSig2AI5&sj0ee8B$VG6)2Qk%J@v~cwr&;!-omF
zuU>h2n;xF7h(xO3tohst4F=RqbY5)xFq$iw1^l>>tE=nA@^U8-8)}G+xp@~bruxdt
z_|dVk*1)uh@d^qaxa}1d7WQ9IQ&RRbN_fM%Iemwgmds8Cef&NFzx@jM()qUKZ)?~r
z@ama*zq5UzcGK}naC)M%qgkQ~OrnyKCZ~eFe#I)u9JRohzL%2r)><&-6Z(*q)zj4#
zyknXlaDAEoB2NkxPe(=dDKpd&^4iumf<fGKd2_O$>}gS<a1Ia50a&o&OtmytR#q+G
zkiNY6Rc7Pm;+P`ctWppZ1aVqu(XAa&@cpwL-tKp{vfLHw=INQ%%Alnwqr<_>3<WmJ
zdp%We&80}I*=3=|Y3@-<O3Lg07@9IO2Z?wrlpF?w85|z&0dTU~D*(Zotue<Q8XBr`
zUse3QJNNQOfVbwqoL1#%P>Lm{r$>M?j)kB90T?T(u&TVAmxYy;goft5kY#`8)RcBo
z3nD+LBqJw>;YWo*C@AV!fNx#$KY0+xDCzKDxKTgC&8-vI91?2kNc-=_%rIDI;j4U8
zB$fph5HcZ;Z5{QTXB4-P0if12HjY7|SjefU3K|;7pXF3d2fA8XV%ysO>i-5R%O2!|
zo*MI><?ZeHQmiAEdy((m5eSF-!;e87Dl9Avir$HxKez9#x4`;#I##zM_ZM2dwu$lc
zgWh7LrKNHBIg7hCP(}a0UD^EKn<ANdyv-Oo(YRa@__IpDVh(9h0(?vL{~}~aQ8o=x
g3Xzc8o2Y+8o1t>Ux+GyI;LZ%9qM!k<l6xKUKPVmktN;K2

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/bfly_grey.png b/b_asic/GUI/operation_icons/bfly_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc282efe67637dda8b5e76b0f4c35015b19ddd93
GIT binary patch
literal 5138
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
G?0*1WO|Yi`

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/c.png b/b_asic/GUI/operation_icons/c.png
new file mode 100644
index 0000000000000000000000000000000000000000..0068adae8130f7b384f7fd70277fb8f87d9dbf94
GIT binary patch
literal 5203
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

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/c_grey.png b/b_asic/GUI/operation_icons/c_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..d7d3e585e21e70aa8b89b85008e5306184ccff8c
GIT binary patch
literal 5020
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
Y>Xp_0qRxzf{T+y=s;)|jk}cu?0AeLb6#xJL

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/cmul.png b/b_asic/GUI/operation_icons/cmul.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e7ff82b3aa577886da6686f62df936e8aa3572e
GIT binary patch
literal 3601
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

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/cmul_grey.png b/b_asic/GUI/operation_icons/cmul_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..8fe92d2606b8bc1c2bce98ed45cccb6236e36476
GIT binary patch
literal 3848
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
Nu>V{ztu%Ix`9G0n5%2&2

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/conj.png b/b_asic/GUI/operation_icons/conj.png
new file mode 100644
index 0000000000000000000000000000000000000000..c74c9de7f45c16b972ddc072e5e0203dcb902f1b
GIT binary patch
literal 3993
zcma)9XHZjJyA4v+(2*t`MCsCEP>}!u0un{Q0!Rx1L_;r9A{bhNp)=|W;ROvKN);iY
zDhLs<P!d6+5HT2ms1S-EAmDd=znMGt-kCf1`?1fNGtWM|thJuCpPl7$#zAzS;yw@v
zB<hHOy8`dQKTSjkc-CKPC<B4`a3Qv~F43+IHfoM`wh&_r2*kj|&=>?dQV?4(q6a@G
zGhk&p7+!&pl`VQ)LYF@TdF8C=RMLMWZRm}u)!26Y(_@90+x28xf1_XupI2bLOhMu8
z4D%-c#9`7g^FoW(=#jZ(o@I5?h+q=59{uC0IQmI~fy|P{oW|<#2e}&Vy|Oq@@~wtk
z!8AAbmXRb(P**I8qFerJLh<fQ#~$i&_Bp%pm0syyx7vhpjxsy$&;f6$Q_^uEQP%7e
zZh0QtSECp+;YE~)LGkqq{=qA91IbN=43plKs6-R)fvOnQ)kDbT@87d&VatXwHUdj*
zbYbK+*tm!A#<p#lVWg+aNxHT9mZNpCz#~vJC$O+`@Uv8`UCqEDZFt^mM03#k?{MEW
z+P#UIkDRoU;J$cX#)r@&7=`|c0)Ls@`(O7OHh<ZkQU|9Ho}GV}DAv=&H+M16fVy9f
zG`jiFo^s_}ja2JlsinAmLy}iIahZa(!BscTsHk-r`o@YT{Te+yr%bfke?GYD{-gPF
zfRnuu2#;tGXutHI#s?}Xl?Rx6<_NcTk1t(dB{`3;sSYepSR3MW?*5&CQ|PDnV@$ja
zAjM(BV!|TM^2gP#eC@TXqM8Ix1XAieDuhy8FeS(LA?ye3pak!_SdYLsqbyjOv!;y6
zV(GMM_nGeGTdM4(Q*+qv>D5Hcwtw2)a@+{?)lY1mj)}>W?%LXKiW2m|?XDXj4TAtR
z_`coGI}SxJIoxQ8kkmf*Mz^o8EaKWVJ$+f?fEc{;kStL!G&J=0c8PcsK}KGrU=P2#
zs~_+iI30n*N$4Dv(8!2PaX~z*@9aFNsi|pr1LNW%(c9abewJeiPB(16I<8v^jqI(;
z8yXyhoj-qjDXR<rUqdY$=j7xV{@$^%@%2^xm@uc@xU*aI0)|CIMj96u6%~+3-)<we
zwzl#M3sY${xYF^M!Oz{@dh&$4BZh|kvqL04(18O7RPtyF1HX9Pd2F^nIw<I?hLq3@
z|E@B==&<JIb)_?jS?M_WsrX1}f=1!;Oa#ML3ry>AkA$|HN}X3a<CzCU$KW#&Q4U(t
zS$&SL3yw<gmpCh(DJ*c-7CT&T-+LMf?!OH!)z#Gn`8gG|N#To33)6(fQEOkv4a=?F
z(P*?42)|lSp^RH^xx2e_HhxaHHiq-@@#U73C^lln0#K-3o<5ckDB-p_eBazNvF!y6
ze6P7#da^bA=ejXmR9w8`-aXLH&W?|zFu(E5hYyd8jg5UOxt1MG3~YX%-G>*~ghH9M
zbAD<<{uIy1`B8OSI0{0dGZ?9DZP0Ml44izHqz2tp3B+%#Wa&VrKtVz%L}E6a7zqN?
z9{-TZy6_n!Pq@!jmogaOTXL0VDJm&-eW&R6H)A)W+%H_vFg2C7wY61&zNcm2@fFXW
zsT`Y%;1?1SYS`LMPEHm9gMakA+{NSO-7y&b50jHmZ)EFmxm;^3b~}AKWxVD*G>Stw
zcs9o36iwrQi9=Axrm_e2e<<gl;A>XwS2kYNK0KV4{;{>OQGCSmXQ_6%_>&vI;x>Mr
z{hr5;o$#KUmG;iX&5u1mlC*y$4<~)OY9RKhdqmhGc^mrb)hlhKtm<X;J2>PQdq$b7
zhey_l#kRg$BmJ6Q7}!ZX5*lJ5C4_<{-s*F#1?Wwq2LQZh1H2Q;K1VP-0X?W?OW=#{
zX!xrM`Bg$oTcq{^JTx^KBX>&!YBKS7V+dq++!PRoh^S}=D{R1^b!ujzg5Y@etV2RV
z`v9*pUPlNe;ac$5P|%w)%gHZ!d0AP-D^G7yq=b85;J}(c0VT^sndsR!k(=Ptcl3)H
zqRkYUKf!@<L}{eHPgne_;E1(`6;C3+pIWBbdxX~Kh!TvJXZD{@b2?&568{#iNx%As
z<Ca>xnPF&YgKXBFKfJQNb)?SH*fCXz78PJbY;RRTyop%#X-&E<(0f^77~$&XW~uT=
zwxy*?-ZZ`?oz_!_%*<pc6pD{!+W6Is@vCcJ6|~c)t^eUEb;GufOx7s1O7Tg&W7w3i
z8kC-q(H?lh%1SkOxHOU*AZOsIg;nI4*xB2!UyqI5S>=V-*VjM3q;Dv0@8Ixx?2(%W
zzC!QXwQGHA6TZBK0QuY(C~GgTNvrNzTYZiHUlIRPE;G28n3x`R#L@z<^{#KS$Cg|6
z_wSfD<yOAfyM_ZiwXm2^J$zFszjw%_moHy3Tf<eIwTtErbMkO)j^5tNkgzEce4jeC
zI6wbpO^vRAfB+%1P?0|}EnpzmpVbjdZp@XDlf!p(92XE4CWbP`jV1hjvCD!C24i%3
z+K3v`bZK;Kj6<Ef_x852g!SRGW$8mfeHr`ZXpp(#zlpQ6vpP?Mgxhm*BHZua;V6{G
zaG9kZRY651_rU}0kEPFv@`RgKyBquvlIX(10*a)ruKuvLcJ-Bk4L3!8P{z9}UQ$O#
zXJeKWPJ{68+2eBld`95wJ6WNH*MKNkR8-y4Qdu=+yNUUryj*piH=S2msnvvxQs$4O
zGnw*H(b4p&Dg6$w%a^n3>h$UV1kPDo^L@mwBI!fbWr_O)1qI0@GjlLaFz@7wh|i6+
z^Yo#5uhyC60aZCx<)Z+#q8~FvBh*BLH<^@>kZ?p_UxuZn07gr#FS3ZClhmxR2US)6
zBW0Fd8!i>{g91<2-gym<jL5MrH=zV|_4L@h5xFgfu8;{|c=rzE$&)8Xj~~~iw#RT?
z$KJj3Su>}-?35ook6nKs+_tkmX$*xPp|(e`ZOkAL)a;vL6p5n(h6DMZ42y~~0j!<|
zsUVX_r>3$!nu7ooejUx%gWlLOtGl2#{P&5&R0T;%zD`!|{QMOF1S27n-076x%2`=i
zK%W-oEM>xnn3*Zm_cXJdZk&Jm$J&~@g@ppp<ta1B&rhwUwpLPA)rjg?_8hhw7!e_H
z?OomT>hfe=DtB%U|Ky3uRN}XKU_};SPIyH{#o+L8KfS`i?)qsbr~Kk#oesycKG-fb
z<gK>*#f!wS#SfIz+jt_!11!*ED{h%?nY2y_3!9{ho{0M{!O|L8-Gr5;>?U+A_3kH=
z4ni<Ji>yw&V=qr&kLNQOuFJn)S65e8w6wU#6m<;*^FOMo>1AD?Ht1-N`r_2J+<)*d
zNNCBtVM5+np(xIvB!|QC!(f~-tAJ4Kkx0?h)KnVe<u9yZ{@GKT0)QFID=JD&X6NL*
zVE5sT1Ny;DNQO}PKF(x#x1E#IzTNHBvrR(@tFzC;TKg~%VrXI9hO?TovNFQi8GikG
zEAzp7OeSD#T?wl+h@%%)BfmL#r1vU?+}!MjiSEx<xPrmJF_=20c1RwZpS!SN=jtkT
zrtG8+RY6839XRKvV%{QAq)KF_7~0Pd6cQ2wP2O%}&bEZSb@B1Zy{&ig7BHm(B)_0Q
zvd*hjjQ8|s8S}wQv`t8e&bvAfO{(LEph1|Dm@ts8gbyD)=`2>~^?<%iQ8KAAG!x9h
z39Izr_=Vxeu779ZgjKOFF-0vS5xA49Z%pI4Rd?aXR##VF@E#zx{2s!boSbO9a8hw`
zR$-wO9mx>y6L1CAki>g>G3G6>rMfzBa&qRT8C^?ZMsIp-Ed2zp%k9TZ-^NBfz^mrb
zqb``D9}Yip*bO#*aB#3^Y3VU@mTX5H9UZ;NeOnD+cZz#Fzwz1DzE)P;!QHKIMomM2
z9+?Yqa5x+=mt-y^zdU7E0myhuOG^}~IcUfxI@**>CIiq;$0!UIn+E$)JeR*UcHase
zF13&bYM@JP5%-ze-15ae7Ad<YF_44FTinA<L!UnR0ZD}7GOAD{G`M45Wp}sgqeqW6
zR$d#ufB*%<xpU`m2lOrglSa;K=l{F>nMo5rB{6AM?96Nq76)7mkjbSONkSjHXKU>n
zeRNc<!|T!|LRZ&GI?_beTi{9HYuPSf6J%1J<+?mIjHTP**k1ui7rWvJ7$Y%ERz~I%
zx4K-Gr4_)4QM0qNW41?E&j($&a3?+jeYr%mvaZf~TipfF1K>_0MDdVbv(3X*Kn?M2
zq7mQ6mYBRu#VFL&)JRB3s8JpJtJ`6@$|o`@*CtSCG#nGXJo5tZLt6xbz|`IpLJCIB
z(DqhVRo(CI_QjZLYYQQfND*;yxk;_*`^7L%FE9V*_Yv$~!KNXL=-HmI)_}#Sb|7QD
z%p?&PFUkQfk^F1nSkuts$4AY~&7Y&10BCs2iE025kKbdigon#bg7@yV2F`_<nHd0?
zV+rQ@L^&gkLx%)?Q+6oKAQ%iJrgD;yo-WXYT=DkVkG^t6L_tAeW4<Df20>~|uP;pE
zIUF<)4E5QI#$dpJWj>x<Aa?@50Db|WG(CG8w%9(W0@PKv!hqGeZBwn_D&icvgX#;t
z#x9MHb^!5{%QR^+34RmLucNO|YaThJ*HKI&=_S{m(>R<bxbopzW7hPxg}M2Gq@Rl!
zFVRx0Ls2UTHFI<Gwr>rcaHCU~pJ6Tj@m?O_W-&Ug!YhlNJPkd){eW$uNI-#M9U5wo
znwItglGXqD9qiQa?dUq6r;7ai{3z0XS$$YQfToSDEf8&>h2pk1KIb8voNU6w8<|09
zv{ng;WSq2)X+mloXcYjY*4=ZJGBed4jUq`(N&=hx%PQp=Q=2|c5N5DgIY3=rdOX$%
z6TS6=`>?TbAm`Y}BUDE-$(i2*EpZE;*q^NHWYXTfd#{9qSf4&koNp)tZ0^Q((qF;^
zVU2{5%ZEEzhRLiuAHQ%oM<v9UwpOhPE!qFRS^TG^Lh8nsMZn9**NyHkFJEvdW8X=d
zT_QTzKgVDC?-!N-XgI^psT<Ez1-mZ)jeS=&5~%odQw`fIO;1@?mn}#wPn$L*G|XGs
Y9~r66(dq96u6H0uyEE_x8^6?l0o&<x82|tP

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/conj_grey.png b/b_asic/GUI/operation_icons/conj_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..33f1e60e686cb711644a875341c34a12b8dfe4c9
GIT binary patch
literal 3887
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

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/custom_operation.png b/b_asic/GUI/operation_icons/custom_operation.png
new file mode 100644
index 0000000000000000000000000000000000000000..a598abaaf05934222c457fc8b141431ca04f91be
GIT binary patch
literal 6298
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

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/custom_operation_grey.png b/b_asic/GUI/operation_icons/custom_operation_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..a1d72e3b7f67fb78acccf8f5fb417be62cbc2b2a
GIT binary patch
literal 6457
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

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/div.png b/b_asic/GUI/operation_icons/div.png
new file mode 100644
index 0000000000000000000000000000000000000000..d7bf8908ed0acae344dc04adf6a96ae8448bb9d4
GIT binary patch
literal 1318
zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>fw3{u**Ty%$lXc7)79C`(9+CI
z*GSKhfk9(p>BRHeE`cJ)?Tt_5FZK`<o4jGxMF~~2Q$A8&vyN#rpFLx2f8|fbUh_#g
zQ`as!a%??oJ!5q0T9Jv9rnZ`HOsqOL+uU@L<@Vz9&+FdXF0Ve%T5Yj5<x3WS%BD(P
zkq?&7l|G-lCBEwAf_Y_8i@MhJu{H;XY&l<@leVXH_ExR=zpqP8f3%Bfo7KZW*Q=in
z2_FhxU3vA1?87ou(O4ln;j+vk$A{Lz{gHpNiY`6MpS-o;1pl|N#Pm<%$Llsux@2_d
zQ&E-COVOnz@}E_GKfZa>b0qJhUWpUShleSX3jgpJ9_TpZyyN2~eQm8L)m=X?KT!#v
z7@5l3n>uO9iN`{vuB(o#s=4%^_Sl^E?SFp!*CPwAue$f7c~<)2iubLzay4=!k6oXb
zDAKp^6{p_jr+@A!^3-?i3%ypwcVHvq^YB#NZQSCsFZ^8LwlyPgmC$x|p$}zTX9Y8M
zH@2~^PG4H<C#SGSFTRww`u7F(=d#`Q+@a}v7Os3T9~dW`1s;*b3=G`DAk4@xYmNj1
z1GBfMi(^Q|oVRxk^OPb*94;=pJ2UX)Y=tz=b9*(*9`;6@<IpV%YZ7>H=56P#pxd)v
z3$AM9J9l!`lK)?s*8T8M%`%&RzFxNf_)UhIkB@ILAGq(MAi%=(@cDD;-+${Qj(?Qe
zeBf&DV+$F+_WtbqASp-c5f4vum;9(-8@4)e^UXP1%Qi;v^!N7*`@0C9XwBVzJ8^H^
z{H<l%Z~NNt-Y-1Tq#(e-)QFRKka>?gT5tN{mnBx&Z<998sF2!cJp26f{e8zDzxm$#
zx6Z!jamCr053-IG0)2A$_*P{xmPQ8w4q6d2{@A|c=jT8C`t|E8w-42S`t!%;&b@nn
z+wMPU+_PuTpO5o0cP!byeY@xPVnQA>_<4EH_wV23%AaTD<>jf}&Hv;uZ{ECR_k`9R
z6Xb4kP~c#pkns8U{OXpgS*AH=vo~MU6YGBX@nhkmP7S7=8{U>#uettu^R;(nyE~UZ
zwC;-_5c@V~s}oN@EwY&Fm%cVuM@-oNaCzG2pMS1@teEq6-^Tmz+Y4pZTMEaVwdgzi
zu;9mzGD2R*8QCOBeE9si7Z`vK|Ni|8O3StX-qdqGdH3#J(%}R5&w+#Rk3`!+e!d^#
b{~2cQOK`hd#PkH1*BCrq{an^LB{Ts58(99Y

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/div_grey.png b/b_asic/GUI/operation_icons/div_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..de3a82c369dde1b6a28ea93e5362e6eb8879fe68
GIT binary patch
literal 1352
zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>fw4K$**Ty%$lXc7)79C`(9+CI
z*GSKhfk9(p>BRHeE`cJ)?LRk9l<!Si7_rA>ic{T{BW{wMs=mon;%Cn^-+$#y#a{DE
zDXVitRn6*je{kQ>%3j(rWsT07PTtGK_s(4LF?wG1?&qENd!LuQ-<iO#c6G|bH_ahd
zi`ms`PW?Hw=uy>CPwuN*E{O*5=3aR}f7zm1+l0-JV*5i^f7)5UR!_(@WW!aC{uQ(4
z3C<Il7k55G>^+zH>dpX{A2B<kr*m`NkFMHrAYbe__n+jAay&hadjlgnrx)(%x!N*M
zXxjA0o~qZcJgQwY{fhhL;>|+0kKQ)mFlgj&)jTG7*xUZFQT5BjhPcQ;#r^v8?GLYd
zvdSns<zl3wUlY&WMe97jt~fH|MWCeTrm+A2zg_L%<r7(T{r7@bM@{Ul4s2!KnS1$<
zo!QwtoEGyQYQ>(4w{2SJe{s9_tmguEW)~F3UOH4=u#;8SKgxsuscQQUTe0Tv#u*U}
z%c57rYJFL=<NQly7V{^o^Ai3uSUufS{Zl-6OZr{2*?(69V}-N8BeIx*fm;}a85w5H
zkzin8&h~V145^s&_O5MKbfiSX!`Jqi20IGG4NkOjJ60{f#J8s><X-#jD=Y7uW4g0z
zrcz|1qW6qrXCH9L2zo4&la5n=)c533<tgUpA58UYj=J8Oc{Z*7@1LKa3mE48Do(eW
z`!8Z`nDy;>EOE0hU%ov1ZZ1FL`k4+2Z|rR={{Q=1P*AX;H!@-MyLa!}&d<01|NKp)
z!t>|P50_Zoy%F#s$_5Cyk94s*DhRMJQHOYV^MCV)|GW0?mEBkWf8Vvcn>TMxw42{=
zBj~CiT~=IdtTX-e)^~9+F&rs}*F+GowdU;Z#K|X9X85RO-@SYLwsfUboYDl|-ya?x
z-sb+mJYu5)2TPL!t%yS}>X~b6Yi++x{^0%W^Un<tI&QVaUmWaef0=-U%F4^#`3VMH
z;+OT6{(gSP>V)b}e*F0HMSh>$iK|zy-ta$kjXKUh^uj*Q_xF#Fk3anQvE$m^^7r?6
zyN@P4>C|Alxj{dEAJ6HhMQe9&zM1nRv!YX!n?Pu-uQY%2_V)I}+w<>V+x7d|+1V49
zKU4)8bo%t^-2Mak8<HFaI9M7TXiVIwuPUgoxA#$-oO^~T`s>%%*VEIjFTeb9JZtN%
zn}=*ZteQJ--aT;W9r}0i;zj%R_V)UmYJrN@fB(b}NqO_+KHMP*%!>@3u6{1-oD!M<
DcE=DR

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/in.png b/b_asic/GUI/operation_icons/in.png
new file mode 100644
index 0000000000000000000000000000000000000000..ebfd1a23e5723496e69f02bebc959d68e3c2f986
GIT binary patch
literal 1951
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
II0mNu8zMAYN&o-=

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/in_grey.png b/b_asic/GUI/operation_icons/in_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..07d3362f93039ca65966bc04d0ea481840072559
GIT binary patch
literal 2093
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

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/max.png b/b_asic/GUI/operation_icons/max.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f759155f632e090a5dea49644f8dbc5dff7b4f4
GIT binary patch
literal 5822
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
Q3=|?Ktt?e0@h;^50K?!I<^TWy

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/max_grey.png b/b_asic/GUI/operation_icons/max_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..8179fb2e812cc617a012a1fb3d338e1c99e07aa7
GIT binary patch
literal 5639
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

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/min.png b/b_asic/GUI/operation_icons/min.png
new file mode 100644
index 0000000000000000000000000000000000000000..5365002a74b91d91e40c95091ab8c052cfe3f3c5
GIT binary patch
literal 3675
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
B4W$48

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/min_grey.png b/b_asic/GUI/operation_icons/min_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..6978cd91288a9d5705903efb017e2dc3b42b1af1
GIT binary patch
literal 3595
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
Xe<H|e;pqhMJHcgZ1-3+A^H2RhKfJ_S

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/mul.png b/b_asic/GUI/operation_icons/mul.png
new file mode 100644
index 0000000000000000000000000000000000000000..2042dd16781e64ea97ed5323b79f4f310f760009
GIT binary patch
literal 2222
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

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/mul_grey.png b/b_asic/GUI/operation_icons/mul_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..00e2304b634e02810d6a17aa2850c9afe4922eb9
GIT binary patch
literal 2383
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
Rm;qNZgyiOh=eUGj|2Kca8$tj8

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/out.png b/b_asic/GUI/operation_icons/out.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7da51bbe3640b03b9f7d89f9dd90543c3022273
GIT binary patch
literal 1962
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
a1w1X(e7Qe%p^yXYdyt<`5Uvp$bM8-`B2lgY

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/out_grey.png b/b_asic/GUI/operation_icons/out_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..2cde317beecf019db5c4eac2a35baa8ef8e99f5e
GIT binary patch
literal 2089
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
O8xR5KhHbD9rv3*~kZ}zF

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/sqrt.png b/b_asic/GUI/operation_icons/sqrt.png
new file mode 100644
index 0000000000000000000000000000000000000000..8160862b675680bb5fc2f31a0a5f870b73352bbb
GIT binary patch
literal 2994
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
JYV!M){{jm#Vr&2a

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/sqrt_grey.png b/b_asic/GUI/operation_icons/sqrt_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..4353217de9c38d665f1364a7f7c501c444232abc
GIT binary patch
literal 2942
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
a$N#=zXF@_6r55Sn_(&!egp!laQU3x;by|M_

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/sub.png b/b_asic/GUI/operation_icons/sub.png
new file mode 100644
index 0000000000000000000000000000000000000000..73db57daf9984fd1c1eb0775a1e0535b786396a8
GIT binary patch
literal 1155
zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>fw3{u**Ty%$lXc7)79C`(9+CI
z*GSKhfk9(p>BRHeE`cJ)?Tt_5FZK`<o4jGxMF~~2Q$A8&vyN#rpFLx2f8|fbUh_#g
zQ`as!a%??oJ!5q0T9Jv9rnZ`HOsqOL+uU@L<@Vz9&+FdXF0Ve%T5Yj5<x3WS%BD(P
zkq?&7l|G-lCBEwAf_Y_8i@MhJu{H;XY&l<@leVXH_ExR=zpqP8f3%Bfo7KZW*Q=in
z2_FhxU3vA1?87ou(O4ln;j+vk$A{Lz{gHpNiY`6MpS-o;1pl|N#Pm<%$Llsux@2_d
zQ&E-COVOnz@}E_GKfZa>b0qJhUWpUShleSX3jgpJ9_TpZyyN2~eQm8L)m=X?KT!#v
z7@5l3n>uO9iN`{vuB(o#s=4%^_Sl^E?SFp!*CPwAue$f7c~<)2iubLzay4=!k6oXb
zDAKp^6{p_jr+@A!^3-?i3%ypwcVHvq^YB#NZQSCsFZ^8LwlyPgmC$x|p$}zTX9Y8M
zH@2~^PG4H<C#SGSFTRww`u7F(=d#`Q+@a}v7Os3T9~dW`1s;*b3=G`DAk4@xYmNj1
z1Jepm7srr_Id5+n_C0bCa0ry1sBrp`*OHhS%mpeb0)jbPgbglTXSr4tR;6Fe?#f#8
z;=p4Q-}S%$+VLGQ-@R76;l7W801H#%|H^{hciSrGZNI^4W`6$j2A$K_H`fbrFf}R+
zN8<NZ_Ic-@Km1v<&Q`4MNzqQ8_QQ!=D_64@?Tu?c{&?fJh#wQLzux-FCR~R=2&~?J
zw%Tg$o_FWc%66C5e?Fc$EAIR6+CSBhzzqK@@FV`rXB(Z<pT557Vs!+C^l&4-&1ZlA
zy}Dxm{pdGLvw#12SI_;V!sg%R)Cb#(k2V1l&+bB*?e`e#*4kg6Y%t+(cM}gVAv1Wo
L`njxgN@xNA>8gQW

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/sub_grey.png b/b_asic/GUI/operation_icons/sub_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..8b32557a56f08e27adcd028f080d517f6ce5bdf2
GIT binary patch
literal 1208
zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>fw4K$**Ty%$lXc7)79C`(9+CI
z*GSKhfk9(p>BRHeE`cJ)?LRk9l<!Si7_rA>ic{T{BW{wMs=mon;%Cn^-+$#y#a{DE
zDXVitRn6*je{kQ>%3j(rWsT07PTtGK_s(4LF?wG1?&qENd!LuQ-<iO#c6G|bH_ahd
zi`ms`PW?Hw=uy>CPwuN*E{O*5=3aR}f7zm1+l0-JV*5i^f7)5UR!_(@WW!aC{uQ(4
z3C<Il7k55G>^+zH>dpX{A2B<kr*m`NkFMHrAYbe__n+jAay&hadjlgnrx)(%x!N*M
zXxjA0o~qZcJgQwY{fhhL;>|+0kKQ)mFlgj&)jTG7*xUZFQT5BjhPcQ;#r^v8?GLYd
zvdSns<zl3wUlY&WMe97jt~fH|MWCeTrm+A2zg_L%<r7(T{r7@bM@{Ul4s2!KnS1$<
zo!QwtoEGyQYQ>(4w{2SJe{s9_tmguEW)~F3UOH4=u#;8SKgxsuscQQUTe0Tv#u*U}
z%c57rYJFL=<NQly7V{^o^Ai3uSUufS{Zl-6OZr{2*?(69V}-N8BeIx*fm;}a85w5H
zkzin8y6x%W7*a9k?JYwul|T`Pi>nr{Nfa_P+YlU{aOrfRZr<X}kqzDw0zI)uS=@Nt
zdT$7?E_k_M#;XT&KCt#rkNI&>K0hz-pG^Ppc;*i$E&BE!ubgMk*M4}%H^YKFv)N}K
z_iycQC{`2TxP1NK-@ktg3JW(L)7`LY`t<1!fByV=`AxJ!d3pI^oAdwQYFLQ>{`)Ul
zVP1p)3zMV5a3d0<if(?akSW`JcarwQl<nKMA1>P+Tk?ESBhO(28(Ul5y+VFQzLy_0
zRwOwJaIiEw;3Ojcn7{w}_3I_=hbrOW;oa|JOePe{v;%=^_@Pxc8x=TMnjD53ap+6w
zhhM*}&V1ejN<6^8%iml$?|J{>hX(5k_~&droA&udCo3?`EIWPr^xxmVf1lsV-)Q{h
ayZqnxMoJ4FK6?X9`wX70elF{r5}E)^$E%zG

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/t.png b/b_asic/GUI/operation_icons/t.png
new file mode 100644
index 0000000000000000000000000000000000000000..f294072fc6d3b3567d8252aee881d035aa4913eb
GIT binary patch
literal 4369
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
Kd4=i4oBsnq$UU0?

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/operation_icons/t_grey.png b/b_asic/GUI/operation_icons/t_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..88af5760169b161fdd7aa5dce7c61cdb13b69231
GIT binary patch
literal 4304
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

literal 0
HcmV?d00001

diff --git a/b_asic/GUI/port_button.py b/b_asic/GUI/port_button.py
new file mode 100644
index 00000000..a856b618
--- /dev/null
+++ b/b_asic/GUI/port_button.py
@@ -0,0 +1,59 @@
+
+import sys
+
+from PySide2.QtWidgets import QPushButton, QMenu
+from PySide2.QtCore import Qt, Signal
+
+
+class PortButton(QPushButton):
+    connectionRequested = Signal(QPushButton)
+    moved = Signal()
+    def __init__(self, name, operation, port, window, parent=None):
+        super(PortButton, self).__init__(name, operation, parent)
+        self.pressed = False
+        self._window = window
+        self.port = port
+        self.operation = operation
+        self.clicked = 0
+        self._m_drag = False
+        self._m_press = False
+
+        self.setStyleSheet("background-color: white")
+        self.connectionRequested.connect(self._window._connect_button)
+
+    def contextMenuEvent(self, event):
+        menu = QMenu()
+        menu.addAction("Connect", lambda: self.connectionRequested.emit(self))
+        menu.exec_(self.cursor().pos())
+
+    def mousePressEvent(self, event):
+        if event.button() == Qt.LeftButton:
+            self.select_port(event.modifiers())
+
+        super(PortButton, self).mousePressEvent(event)
+
+    def mouseReleaseEvent(self, event):
+        super(PortButton, self).mouseReleaseEvent(event)
+
+    def _toggle_port(self, pressed=False):
+        self.pressed = not pressed
+        self.setStyleSheet(f"background-color: {'white' if not self.pressed else 'grey'}")
+
+    def select_port(self, modifiers=None):
+        if modifiers != Qt.ControlModifier:
+            for port in self._window.pressed_ports:
+                port._toggle_port(port.pressed)
+
+            self._toggle_port(self.pressed)
+            self._window.pressed_ports = [self]
+
+        else:
+            self._toggle_port(self.pressed)
+            if self in self._window.pressed_ports:
+                self._window.pressed_ports.remove(self)
+            else:
+                self._window.pressed_ports.append(self)
+
+        for signal in self._window.signalList:
+            signal.update()
+
diff --git a/b_asic/GUI/properties_window.py b/b_asic/GUI/properties_window.py
new file mode 100644
index 00000000..3c690517
--- /dev/null
+++ b/b_asic/GUI/properties_window.py
@@ -0,0 +1,146 @@
+from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\
+QLabel, QCheckBox, QGridLayout
+from PySide2.QtCore import Qt
+from PySide2.QtGui import QDoubleValidator
+
+
+class PropertiesWindow(QDialog):
+    def __init__(self, operation, main_window):
+        super(PropertiesWindow, self).__init__()
+        self.operation = operation
+        self._window = main_window
+        self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
+        self.setWindowTitle("Properties")
+
+        self.name_layout = QHBoxLayout()
+        self.name_layout.setSpacing(50)
+        self.name_label = QLabel("Name:")
+        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)
+
+        if hasattr(self.operation.operation, "value") or hasattr(self.operation.operation, "initial_value"):
+            self.constant_layout = QHBoxLayout()
+            self.constant_layout.setSpacing(50)
+            self.constant_value = QLabel("Value:")
+            if hasattr(self.operation.operation, "value"):
+                self.edit_constant = QLineEdit(str(self.operation.operation.value))
+            else:
+                self.edit_constant = QLineEdit(str(self.operation.operation.initial_value))
+
+            self.only_accept_float = QDoubleValidator()
+            self.edit_constant.setValidator(self.only_accept_float)
+            self.constant_layout.addWidget(self.constant_value)
+            self.constant_layout.addWidget(self.edit_constant)
+            self.vertical_layout.addLayout(self.constant_layout)
+
+        self.show_name_layout = QHBoxLayout()
+        self.check_show_name = QCheckBox("Show name?")
+        if self.operation.is_show_name:
+            self.check_show_name.setChecked(1)
+        else:
+            self.check_show_name.setChecked(0)
+        self.check_show_name.setLayoutDirection(Qt.RightToLeft)
+        self.check_show_name.setStyleSheet("spacing: 170px")
+        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 = QDoubleValidator()
+                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 = QDoubleValidator()
+                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)
+        self.setLayout(self.vertical_layout)
+
+    def save_properties(self):
+        self._window.logger.info(f"Saving properties of operation: {self.operation.name}.")
+        self.operation.name = self.edit_name.text()
+        self.operation.operation.name = self.edit_name.text()
+        self.operation.label.setPlainText(self.operation.name)
+        if hasattr(self.operation.operation, "value"):
+            self.operation.operation.value = float(self.edit_constant.text().replace(",", "."))
+        elif hasattr(self.operation.operation, "initial_value"):
+            self.operation.operation.initial_value = float(self.edit_constant.text().replace(",", "."))
+
+        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: float(self.latency_fields[port].text().replace(",", ".")) if self.latency_fields[port].text() and float(self.latency_fields[port].text().replace(",", ".")) > 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 00000000..1b70c2c2
--- /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
new file mode 100644
index 00000000..1cc87939
--- /dev/null
+++ b/b_asic/GUI/show_pc_window.py
@@ -0,0 +1,49 @@
+from b_asic.signal_flow_graph import SFG
+
+from PySide2.QtWidgets import QDialog, QPushButton, QVBoxLayout, QCheckBox,\
+QFrame, QFormLayout
+from PySide2.QtCore import Qt, Signal
+
+
+class ShowPCWindow(QDialog):
+    pc = Signal()
+
+    def __init__(self, window):
+        super(ShowPCWindow, self).__init__()
+        self._window = window
+        self.check_box_dict = dict()
+        self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
+        self.setWindowTitle("Show PC")
+
+        self.dialog_layout = QVBoxLayout()
+        self.pc_btn = QPushButton("Show PC")
+        self.pc_btn.clicked.connect(self.show_precedence_graph)
+        self.dialog_layout.addWidget(self.pc_btn)
+        self.setLayout(self.dialog_layout)
+
+    def add_sfg_to_dialog(self):
+        self.sfg_layout = QVBoxLayout()
+        self.options_layout = QFormLayout()
+
+        for sfg in self._window.sfg_dict:
+            check_box = QCheckBox()
+            self.options_layout.addRow(sfg, check_box)
+            self.check_box_dict[check_box] = sfg
+
+        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(self.sfg_layout)
+
+    def show_precedence_graph(self):
+        for check_box, sfg in self.check_box_dict.items():
+            if check_box.isChecked():
+                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()
diff --git a/b_asic/GUI/simulate_sfg_window.py b/b_asic/GUI/simulate_sfg_window.py
new file mode 100644
index 00000000..f787e1ce
--- /dev/null
+++ b/b_asic/GUI/simulate_sfg_window.py
@@ -0,0 +1,175 @@
+from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\
+QLabel, QCheckBox, QSpinBox, QGroupBox, QFrame, QFormLayout, QGridLayout, QSizePolicy, QFileDialog, QShortcut
+from PySide2.QtCore import Qt, Signal
+from PySide2.QtGui import QDoubleValidator, QKeySequence
+
+from matplotlib.backends import qt_compat
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.figure import Figure
+
+
+class SimulateSFGWindow(QDialog):
+    simulate = Signal()
+
+    def __init__(self, window):
+        super(SimulateSFGWindow, self).__init__()
+        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")
+
+        self.dialog_layout = QVBoxLayout()
+        self.simulate_btn = QPushButton("Simulate")
+        self.simulate_btn.clicked.connect(self.save_properties)
+        self.dialog_layout.addWidget(self.simulate_btn)
+        self.setLayout(self.dialog_layout)
+
+    def add_sfg_to_dialog(self, sfg):
+        sfg_layout = QVBoxLayout()
+        options_layout = QFormLayout()
+
+        name_label = QLabel(f"{sfg.name}")
+        sfg_layout.addWidget(name_label)
+
+        spin_box = QSpinBox()
+        spin_box.setRange(0, 2147483647)
+        options_layout.addRow("Iteration Count: ", spin_box)
+
+        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)
+
+        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("e.g 0, 0, 0")
+                input_value.setFixedWidth(100)
+                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)
+        frame.setFrameShadow(QFrame.Sunken)
+        self.dialog_layout.addWidget(frame)
+
+        self.sfg_to_layout[sfg] = sfg_layout
+        self.dialog_layout.addLayout(sfg_layout)
+
+    def parse_input_values(self, input_values):
+        _input_values = []
+        for _list in list(input_values):
+            _list_values = []
+            for val in _list:
+                val = val.strip()
+                try:
+                    if not val:
+                        val = 0
+
+                    _list_values.append(complex(val))
+                except ValueError:
+                    self._window.logger.warning(f"Skipping value: {val}, not a digit.")
+                    continue
+
+            _input_values.append(_list_values)
+
+        return _input_values
+
+    def save_properties(self):
+        for sfg, properties in self.input_fields.items():
+            input_values = self.parse_input_values(widget.text().split(",") if widget.text() else [0] for widget in self.input_fields[sfg]["input_values"])
+            if max(len(list_) for list_ in input_values) != min(len(list_) for list_ in input_values):
+                self._window.logger.error(f"Minimum length of input lists are not equal to maximum length of input lists: {max(len(list_) for list_ in input_values)} != {min(len(list_) for list_ in input_values)}.")
+            elif self.input_fields[sfg]["iteration_count"].value() > min(len(list_) for list_ in input_values):
+                self._window.logger.error(f"Minimum length of input lists are less than the iteration count: {self.input_fields[sfg]['iteration_count'].value()} > {min(len(list_) for list_ in input_values)}.")
+            else:
+                self.properties[sfg] = {
+                    "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": input_values
+                }
+
+                # If we plot we should also print the entire data, since you can't really interact with the graph.
+                if self.properties[sfg]["show_plot"]:
+                    self.properties[sfg]["all_results"] = True
+
+                continue
+
+            self._window.logger.info(f"Skipping simulation of sfg with name: {sfg.name}, due to previous errors.")
+
+        self.accept()
+        self.simulate.emit()
+
+
+class Plot(FigureCanvas):
+    def __init__(self, simulation, sfg, window, parent=None, width=5, height=4, dpi=100):
+        self.simulation = simulation
+        self.sfg = sfg
+        self.dpi = dpi
+        self._window = window
+
+        fig = Figure(figsize=(width, height), dpi=dpi)
+        fig.suptitle(sfg.name, fontsize=20)
+        self.axes = fig.add_subplot(111)
+
+        FigureCanvas.__init__(self, fig)
+        self.setParent(parent)
+
+        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
+        FigureCanvas.updateGeometry(self)
+        self.save_figure = QShortcut(QKeySequence("Ctrl+S"), self)
+        self.save_figure.activated.connect(self._save_plot_figure)
+        self._plot_values_sfg()
+
+    def _save_plot_figure(self):
+        self._window.logger.info(f"Saving plot of figure: {self.sfg.name}.")
+        file_choices = "PNG (*.png)|*.png"
+        path, ext = QFileDialog.getSaveFileName(self, "Save file", "", file_choices)
+        path = path.encode("utf-8")
+        if not path[-4:] == file_choices[-4:].encode("utf-8"):
+            path += file_choices[-4:].encode("utf-8")
+
+        if path:
+            self.print_figure(path.decode(), dpi=self.dpi)
+            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["0"])))
+        for _output in range(self.sfg.output_count):
+            y_axis = self.simulation.results[str(_output)]
+
+            self.axes.plot(x_axis, y_axis)
diff --git a/b_asic/GUI/utils.py b/b_asic/GUI/utils.py
new file mode 100644
index 00000000..5234c654
--- /dev/null
+++ b/b_asic/GUI/utils.py
@@ -0,0 +1,20 @@
+from PySide2.QtWidgets import QErrorMessage
+from traceback import format_exc
+
+def handle_error(fn):
+    def wrapper(self, *args, **kwargs):
+        try:
+            return fn(self, *args, **kwargs)
+        except Exception as e:
+            self._window.logger.error(f"Unexpected error: {format_exc()}")
+            QErrorMessage(self._window).showMessage(f"Unexpected error: {format_exc()}")
+
+    return wrapper
+
+def decorate_class(decorator):
+    def decorate(cls):
+        for attr in cls.__dict__:
+            if callable(getattr(cls, attr)):
+                setattr(cls, attr, decorator(getattr(cls, attr)))
+        return cls
+    return decorate
\ No newline at end of file
diff --git a/b_asic/__init__.py b/b_asic/__init__.py
index 7e40ad52..e69f0498 100644
--- a/b_asic/__init__.py
+++ b/b_asic/__init__.py
@@ -1,14 +1,19 @@
+"""B-ASIC - Better ASIC Toolbox.
+ASIC toolbox that simplifies circuit design and optimization.
 """
-Better ASIC Toolbox.
-TODO: More info.
-"""
+# Extension module (C++).
+# NOTE: If this import gives an error,
+# make sure the C++ module has been compiled and installed properly.
+# See the included README.md for more information on how to build/install.
+from _b_asic import *
+# Python modules.
 from b_asic.core_operations import *
 from b_asic.graph_component import *
-from b_asic.graph_id import *
 from b_asic.operation import *
-from b_asic.precedence_chart import *
 from b_asic.port import *
-from b_asic.schema import *
 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/core_operations.py b/b_asic/core_operations.py
index 8902b169..c822fe8f 100644
--- a/b_asic/core_operations.py
+++ b/b_asic/core_operations.py
@@ -1,337 +1,315 @@
-"""@package docstring
-B-ASIC Core Operations Module.
-TODO: More info.
+"""B-ASIC Core Operations Module.
+
+Contains some of the most commonly used mathematical operations.
 """
 
 from numbers import Number
-from typing import Any
+from typing import Optional, Dict
 from numpy import conjugate, sqrt, abs as np_abs
-from b_asic.port import InputPort, OutputPort
-from b_asic.graph_id import GraphIDType
+
+from b_asic.port import SignalSourceProvider, InputPort, OutputPort
 from b_asic.operation import AbstractOperation
 from b_asic.graph_component import Name, TypeName
 
 
-class Input(AbstractOperation):
-    """Input operation.
-    TODO: More info.
-    """
-
-    # TODO: Implement all functions.
-
-    @property
-    def type_name(self) -> TypeName:
-        return "in"
-
-
 class Constant(AbstractOperation):
     """Constant value operation.
-    TODO: More info.
+
+    Gives a specified value that remains constant for every iteration.
+
+    output(0): self.param("value")
     """
 
     def __init__(self, value: Number = 0, name: Name = ""):
-        super().__init__(name)
+        """Construct a Constant operation with the given value."""
+        super().__init__(input_count=0, output_count=1, name=name)
+        self.set_param("value", value)
 
-        self._output_ports = [OutputPort(0, self)]
-        self._parameters["value"] = value
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "c"
 
     def evaluate(self):
         return self.param("value")
 
     @property
-    def type_name(self) -> TypeName:
-        return "c"
+    def value(self) -> Number:
+        """Get the constant value of this operation."""
+        return self.param("value")
+
+    @value.setter
+    def value(self, value: Number) -> None:
+        """Set the constant value of this operation."""
+        return self.set_param("value", value)
 
 
 class Addition(AbstractOperation):
     """Binary addition operation.
-    TODO: More info.
-    """
 
-    def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
-        super().__init__(name)
+    Gives the result of adding two inputs.
+
+    output(0): input(0) + input(1)
+    """
 
-        self._input_ports = [InputPort(0, self), InputPort(1, self)]
-        self._output_ports = [OutputPort(0, self)]
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct an Addition operation."""
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
-        if source2 is not None:
-            self._input_ports[1].connect(source2)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "add"
 
     def evaluate(self, a, b):
         return a + b
 
-    @property
-    def type_name(self) -> TypeName:
-        return "add"
-
 
 class Subtraction(AbstractOperation):
     """Binary subtraction operation.
-    TODO: More info.
+
+    Gives the result of subtracting the second input from the first one.
+
+    output(0): input(0) - input(1)
     """
 
-    def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self), InputPort(1, self)]
-        self._output_ports = [OutputPort(0, self)]
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a Subtraction operation."""
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
-        if source2 is not None:
-            self._input_ports[1].connect(source2)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "sub"
 
     def evaluate(self, a, b):
         return a - b
 
-    @property
-    def type_name(self) -> TypeName:
-        return "sub"
-
 
 class Multiplication(AbstractOperation):
     """Binary multiplication operation.
-    TODO: More info.
+
+    Gives the result of multiplying two inputs.
+
+    output(0): input(0) * input(1)
     """
 
-    def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self), InputPort(1, self)]
-        self._output_ports = [OutputPort(0, self)]
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a Multiplication operation."""
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
-        if source2 is not None:
-            self._input_ports[1].connect(source2)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "mul"
 
     def evaluate(self, a, b):
         return a * b
 
-    @property
-    def type_name(self) -> TypeName:
-        return "mul"
-
 
 class Division(AbstractOperation):
     """Binary division operation.
-    TODO: More info.
-    """
 
-    def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self), InputPort(1, self)]
-        self._output_ports = [OutputPort(0, self)]
+    Gives the result of dividing the first input by the second one.
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
-        if source2 is not None:
-            self._input_ports[1].connect(source2)
+    output(0): input(0) / input(1)
+    """
 
-    def evaluate(self, a, b):
-        return a / b
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a Division operation."""
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
-    @property
-    def type_name(self) -> TypeName:
+    @classmethod
+    def type_name(cls) -> TypeName:
         return "div"
 
+    def evaluate(self, a, b):
+        return a / b
 
-class SquareRoot(AbstractOperation):
-    """Unary square root operation.
-    TODO: More info.
-    """
-
-    def __init__(self, source1: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self)]
-        self._output_ports = [OutputPort(0, self)]
-
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
 
-    def evaluate(self, a):
-        return sqrt((complex)(a))
-
-    @property
-    def type_name(self) -> TypeName:
-        return "sqrt"
+class Min(AbstractOperation):
+    """Binary min operation.
 
+    Gives the minimum value of two inputs.
+    NOTE: Non-real numbers are not supported.
 
-class ComplexConjugate(AbstractOperation):
-    """Unary complex conjugate operation.
-    TODO: More info.
+    output(0): min(input(0), input(1))
     """
 
-    def __init__(self, source1: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self)]
-        self._output_ports = [OutputPort(0, self)]
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a Min operation."""
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
-
-    def evaluate(self, a):
-        return conjugate(a)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "min"
 
-    @property
-    def type_name(self) -> TypeName:
-        return "conj"
+    def evaluate(self, a, b):
+        assert not isinstance(a, complex) and not isinstance(b, complex), \
+            ("core_operations.Min does not support complex numbers.")
+        return a if a < b else b
 
 
 class Max(AbstractOperation):
     """Binary max operation.
-    TODO: More info.
+
+    Gives the maximum value of two inputs.
+    NOTE: Non-real numbers are not supported.
+
+    output(0): max(input(0), input(1))
     """
 
-    def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self), InputPort(1, self)]
-        self._output_ports = [OutputPort(0, self)]
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a Max operation."""
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
-        if source2 is not None:
-            self._input_ports[1].connect(source2)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "max"
 
     def evaluate(self, a, b):
         assert not isinstance(a, complex) and not isinstance(b, complex), \
             ("core_operations.Max does not support complex numbers.")
         return a if a > b else b
 
-    @property
-    def type_name(self) -> TypeName:
-        return "max"
 
+class SquareRoot(AbstractOperation):
+    """Square root operation.
 
-class Min(AbstractOperation):
-    """Binary min operation.
-    TODO: More info.
+    Gives the square root of its input.
+
+    output(0): sqrt(input(0))
     """
 
-    def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self), InputPort(1, self)]
-        self._output_ports = [OutputPort(0, self)]
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a SquareRoot operation."""
+        super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
+                         latency=latency, latency_offsets=latency_offsets)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
-        if source2 is not None:
-            self._input_ports[1].connect(source2)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "sqrt"
 
-    def evaluate(self, a, b):
-        assert not isinstance(a, complex) and not isinstance(b, complex), \
-            ("core_operations.Min does not support complex numbers.")
-        return a if a < b else b
+    def evaluate(self, a):
+        return sqrt(complex(a))
 
-    @property
-    def type_name(self) -> TypeName:
-        return "min"
 
+class ComplexConjugate(AbstractOperation):
+    """Complex conjugate operation.
 
-class Absolute(AbstractOperation):
-    """Unary absolute value operation.
-    TODO: More info.
+    Gives the complex conjugate of its input.
+
+    output(0): conj(input(0))
     """
 
-    def __init__(self, source1: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self)]
-        self._output_ports = [OutputPort(0, self)]
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a ComplexConjugate operation."""
+        super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
+                         latency=latency, latency_offsets=latency_offsets)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "conj"
 
     def evaluate(self, a):
-        return np_abs(a)
+        return conjugate(a)
 
-    @property
-    def type_name(self) -> TypeName:
-        return "abs"
 
+class Absolute(AbstractOperation):
+    """Absolute value operation.
 
-class ConstantMultiplication(AbstractOperation):
-    """Unary constant multiplication operation.
-    TODO: More info.
+    Gives the absolute value of its input.
+
+    output(0): abs(input(0))
     """
 
-    def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self)]
-        self._output_ports = [OutputPort(0, self)]
-        self._parameters["coefficient"] = coefficient
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct an Absolute operation."""
+        super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
+                         latency=latency, latency_offsets=latency_offsets)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "abs"
 
     def evaluate(self, a):
-        return a * self.param("coefficient")
+        return np_abs(a)
 
-    @property
-    def type_name(self) -> TypeName:
-        return "cmul"
 
+class ConstantMultiplication(AbstractOperation):
+    """Constant multiplication operation.
+
+    Gives the result of multiplying its input by a specified value.
 
-class ConstantAddition(AbstractOperation):
-    """Unary constant addition operation.
-    TODO: More info.
+    output(0): self.param("value") * input(0)
     """
 
-    def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self)]
-        self._output_ports = [OutputPort(0, self)]
-        self._parameters["coefficient"] = coefficient
+    def __init__(self, value: Number = 0, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a ConstantMultiplication operation with the given value."""
+        super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
+                         latency=latency, latency_offsets=latency_offsets)
+        self.set_param("value", value)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "cmul"
 
     def evaluate(self, a):
-        return a + self.param("coefficient")
+        return a * self.param("value")
 
     @property
-    def type_name(self) -> TypeName:
-        return "cadd"
+    def value(self) -> Number:
+        """Get the constant value of this operation."""
+        return self.param("value")
+
+    @value.setter
+    def value(self, value: Number) -> None:
+        """Set the constant value of this operation."""
+        return self.set_param("value", value)
 
 
-class ConstantSubtraction(AbstractOperation):
-    """Unary constant subtraction operation.
-    TODO: More info.
+class Butterfly(AbstractOperation):
+    """Butterfly operation.
+
+    Gives the result of adding its two inputs, as well as the result of
+    subtracting the second input from the first one.
+
+    output(0): input(0) + input(1)
+    output(1): input(0) - input(1)
     """
 
-    def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self)]
-        self._output_ports = [OutputPort(0, self)]
-        self._parameters["coefficient"] = coefficient
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a Butterfly operation."""
+        super().__init__(input_count=2, output_count=2, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "bfly"
 
-    def evaluate(self, a):
-        return a - self.param("coefficient")
+    def evaluate(self, a, b):
+        return a + b, a - b
 
-    @property
-    def type_name(self) -> TypeName:
-        return "csub"
 
+class MAD(AbstractOperation):
+    """Multiply-add operation.
 
-class ConstantDivision(AbstractOperation):
-    """Unary constant division operation.
-    TODO: More info.
-    """
+    Gives the result of multiplying the first input by the second input and
+    then adding the third input.
 
-    def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""):
-        super().__init__(name)
-        self._input_ports = [InputPort(0, self)]
-        self._output_ports = [OutputPort(0, self)]
-        self._parameters["coefficient"] = coefficient
+    output(0): (input(0) * input(1)) + input(2)
+    """
 
-        if source1 is not None:
-            self._input_ports[0].connect(source1)
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, src2: Optional[SignalSourceProvider] = None, name: Name = "", latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct a MAD operation."""
+        super().__init__(input_count=3, output_count=1, name=name, input_sources=[src0, src1, src2],
+                         latency=latency, latency_offsets=latency_offsets)
 
-    def evaluate(self, a):
-        return a / self.param("coefficient")
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "mad"
 
-    @property
-    def type_name(self) -> TypeName:
-        return "cdiv"
+    def evaluate(self, a, b, c):
+        return a * b + c
diff --git a/b_asic/graph_component.py b/b_asic/graph_component.py
index 1987d449..8a0b4a9a 100644
--- a/b_asic/graph_component.py
+++ b/b_asic/graph_component.py
@@ -1,49 +1,125 @@
-"""@package docstring
-B-ASIC Operation Module.
-TODO: More info.
+"""B-ASIC Graph Component Module.
+
+Contains the base for all components with an ID in a signal flow graph.
 """
 
 from abc import ABC, abstractmethod
-from typing import NewType
+from collections import deque
+from copy import copy, deepcopy
+from typing import NewType, Any, Dict, Mapping, Iterable, Generator
+
 
 Name = NewType("Name", str)
 TypeName = NewType("TypeName", str)
+GraphID = NewType("GraphID", str)
+GraphIDNumber = NewType("GraphIDNumber", int)
 
 
 class GraphComponent(ABC):
     """Graph component interface.
-    TODO: More info.
+
+    Each graph component has a type name, a name and a unique ID.
+    Graph components also contain parameters and provide an interface for
+    copying that automatically copies each parameter in its default
+    implementation.
+
+    Graph components also provide an interface for traversing connected graph
+    components and accessing their direct neighbors.
     """
 
-    @property
+    @classmethod
     @abstractmethod
-    def type_name(self) -> TypeName:
-        """Return the type name of the graph component"""
+    def type_name(cls) -> TypeName:
+        """Get the type name of this graph component"""
         raise NotImplementedError
 
     @property
     @abstractmethod
     def name(self) -> Name:
-        """Return the name of the graph component."""
+        """Get the name of this graph component."""
         raise NotImplementedError
 
     @name.setter
     @abstractmethod
     def name(self, name: Name) -> None:
-        """Set the name of the graph component to the entered name."""
+        """Set the name of this graph component to the given name."""
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def graph_id(self) -> GraphID:
+        """Get the graph id of this graph component."""
+        raise NotImplementedError
+
+    @graph_id.setter
+    @abstractmethod
+    def graph_id(self, graph_id: GraphID) -> None:
+        """Set the graph id of this graph component to the given id.
+        Note that this id will be ignored if this component is used to create a new graph,
+        and that a new local id will be generated for it instead."""
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def params(self) -> Mapping[str, Any]:
+        """Get a dictionary of all parameter values."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def param(self, name: str) -> Any:
+        """Get the value of a parameter.
+        Returns None if the parameter is not defined.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def set_param(self, name: str, value: Any) -> None:
+        """Set the value of a parameter.
+        Adds the parameter if it is not already defined.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def copy_component(self, *args, **kwargs) -> "GraphComponent":
+        """Get a new instance of this graph component type with the same name, id and parameters."""
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def neighbors(self) -> Iterable["GraphComponent"]:
+        """Get all components that are directly connected to this operation."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def traverse(self) -> Generator["GraphComponent", None, None]:
+        """Get a generator that recursively iterates through all components that are connected to this operation,
+        as well as the ones that they are connected to.
+        """
         raise NotImplementedError
 
 
 class AbstractGraphComponent(GraphComponent):
-    """Abstract Graph Component class which is a component of a signal flow graph.
+    """Generic abstract graph component base class.
 
-    TODO: More info.
+    Concrete graph components should normally derive from this to get the
+    default behavior.
     """
 
     _name: Name
+    _graph_id: GraphID
+    _parameters: Dict[str, Any]
 
     def __init__(self, name: Name = ""):
+        """Construct a graph component."""
         self._name = name
+        self._graph_id = ""
+        self._parameters = {}
+
+    def __str__(self) -> str:
+        """Get a string representation of this graph component."""
+        return f"id: {self.graph_id if self.graph_id else 'no_id'}, \tname: {self.name if self.name else 'no_name'}" + \
+            "".join((f", \t{key}: {str(param)}" for key,
+                     param in self._parameters.items()))
 
     @property
     def name(self) -> Name:
@@ -52,3 +128,42 @@ class AbstractGraphComponent(GraphComponent):
     @name.setter
     def name(self, name: Name) -> None:
         self._name = name
+
+    @property
+    def graph_id(self) -> GraphID:
+        return self._graph_id
+
+    @graph_id.setter
+    def graph_id(self, graph_id: GraphID) -> None:
+        self._graph_id = graph_id
+
+    @property
+    def params(self) -> Mapping[str, Any]:
+        return self._parameters.copy()
+
+    def param(self, name: str) -> Any:
+        return self._parameters.get(name)
+
+    def set_param(self, name: str, value: Any) -> None:
+        self._parameters[name] = value
+
+    def copy_component(self, *args, **kwargs) -> GraphComponent:
+        new_component = self.__class__(*args, **kwargs)
+        new_component.name = copy(self.name)
+        new_component.graph_id = copy(self.graph_id)
+        for name, value in self.params.items():
+            new_component.set_param(copy(name), deepcopy(
+                value))  # pylint: disable=no-member
+        return new_component
+
+    def traverse(self) -> Generator[GraphComponent, None, None]:
+        # Breadth first search.
+        visited = {self}
+        fontier = deque([self])
+        while fontier:
+            component = fontier.popleft()
+            yield component
+            for neighbor in component.neighbors:
+                if neighbor not in visited:
+                    visited.add(neighbor)
+                    fontier.append(neighbor)
diff --git a/b_asic/graph_id.py b/b_asic/graph_id.py
deleted file mode 100644
index 8da6a9d4..00000000
--- a/b_asic/graph_id.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""@package docstring
-B-ASIC Graph ID module for handling IDs of different objects in a graph.
-TODO: More info
-"""
-
-from collections import defaultdict
-from typing import NewType, DefaultDict
-
-GraphID = NewType("GraphID", str)
-GraphIDType = NewType("GraphIDType", str)
-GraphIDNumber = NewType("GraphIDNumber", int)
-
-
-class GraphIDGenerator:
-    """A class that generates Graph IDs for objects."""
-
-    _next_id_number: DefaultDict[GraphIDType, GraphIDNumber]
-
-    def __init__(self):
-        self._next_id_number = defaultdict(lambda: 1)       # Initalises every key element to 1
-
-    def get_next_id(self, graph_id_type: GraphIDType) -> GraphID:
-        """Return the next graph id for a certain graph id type."""
-        graph_id = graph_id_type + str(self._next_id_number[graph_id_type])
-        self._next_id_number[graph_id_type] += 1            # Increase the current id number
-        return graph_id
diff --git a/b_asic/operation.py b/b_asic/operation.py
index 5578e3c4..2ce78396 100644
--- a/b_asic/operation.py
+++ b/b_asic/operation.py
@@ -1,268 +1,616 @@
-"""@package docstring
-B-ASIC Operation Module.
-TODO: More info.
+"""B-ASIC Operation Module.
+
+Contains the base for operations that are used by B-ASIC.
 """
 
+from b_asic.signal import Signal
+from b_asic.port import SignalSourceProvider, InputPort, OutputPort
+from b_asic.graph_component import GraphComponent, AbstractGraphComponent, Name
+import itertools as it
+from math import trunc
+import collections
+
 from abc import abstractmethod
 from numbers import Number
-from typing import List, Dict, Optional, Any, Set, TYPE_CHECKING
-from collections import deque
+from typing import NewType, List, Dict, Sequence, Iterable, Mapping, MutableMapping, Optional, Any, Set, Union
 
-from b_asic.graph_component import GraphComponent, AbstractGraphComponent, Name
-from b_asic.simulation import SimulationState, OperationState
-from b_asic.signal import Signal
 
-if TYPE_CHECKING:
-    from b_asic.port import InputPort, OutputPort
+ResultKey = NewType("ResultKey", str)
+ResultMap = Mapping[ResultKey, Optional[Number]]
+MutableResultMap = MutableMapping[ResultKey, Optional[Number]]
+DelayMap = Mapping[ResultKey, Number]
+MutableDelayMap = MutableMapping[ResultKey, Number]
 
 
-class Operation(GraphComponent):
+class Operation(GraphComponent, SignalSourceProvider):
     """Operation interface.
-    TODO: More info.
+
+    Operations are graph components that perform a certain function.
+    They are connected to eachother by signals through their input/output
+    ports.
+
+    Operations can be evaluated independently using evaluate_output().
+    Operations may specify how to truncate inputs through truncate_input().
     """
 
     @abstractmethod
-    def inputs(self) -> "List[InputPort]":
-        """Get a list of all input ports."""
+    def __add__(self, src: Union[SignalSourceProvider, Number]) -> "Addition":
+        """Overloads the addition operator to make it return a new Addition operation
+        object that is connected to the self and other objects.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def __radd__(self, src: Union[SignalSourceProvider, Number]) -> "Addition":
+        """Overloads the addition operator to make it return a new Addition operation
+        object that is connected to the self and other objects.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def __sub__(self, src: Union[SignalSourceProvider, Number]) -> "Subtraction":
+        """Overloads the subtraction operator to make it return a new Subtraction operation
+        object that is connected to the self and other objects.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def __rsub__(self, src: Union[SignalSourceProvider, Number]) -> "Subtraction":
+        """Overloads the subtraction operator to make it return a new Subtraction operation
+        object that is connected to the self and other objects.
+        """
         raise NotImplementedError
 
     @abstractmethod
-    def outputs(self) -> "List[OutputPort]":
-        """Get a list of all output ports."""
+    def __mul__(self, src: Union[SignalSourceProvider, Number]) -> "Union[Multiplication, ConstantMultiplication]":
+        """Overloads the multiplication operator to make it return a new Multiplication operation
+        object that is connected to the self and other objects. If other is a number then
+        returns a ConstantMultiplication operation object instead.
+        """
         raise NotImplementedError
 
+    @abstractmethod
+    def __rmul__(self, src: Union[SignalSourceProvider, Number]) -> "Union[Multiplication, ConstantMultiplication]":
+        """Overloads the multiplication operator to make it return a new Multiplication operation
+        object that is connected to the self and other objects. If other is a number then
+        returns a ConstantMultiplication operation object instead.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def __truediv__(self, src: Union[SignalSourceProvider, Number]) -> "Division":
+        """Overloads the division operator to make it return a new Division operation
+        object that is connected to the self and other objects.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def __rtruediv__(self, src: Union[SignalSourceProvider, Number]) -> "Division":
+        """Overloads the division operator to make it return a new Division operation
+        object that is connected to the self and other objects.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def __lshift__(self, src: SignalSourceProvider) -> Signal:
+        """Overloads the left shift operator to make it connect the provided signal source
+        to this operation's input, assuming it has exactly 1 input port.
+        Returns the new signal.
+        """
+        raise NotImplementedError
+
+    @property
     @abstractmethod
     def input_count(self) -> int:
         """Get the number of input ports."""
         raise NotImplementedError
 
+    @property
     @abstractmethod
     def output_count(self) -> int:
         """Get the number of output ports."""
         raise NotImplementedError
 
     @abstractmethod
-    def input(self, i: int) -> "InputPort":
-        """Get the input port at index i."""
+    def input(self, index: int) -> InputPort:
+        """Get the input port at the given index."""
         raise NotImplementedError
 
     @abstractmethod
-    def output(self, i: int) -> "OutputPort":
-        """Get the output port at index i."""
+    def output(self, index: int) -> OutputPort:
+        """Get the output port at the given index."""
         raise NotImplementedError
 
+    @property
     @abstractmethod
-    def params(self) -> Dict[str, Optional[Any]]:
-        """Get a dictionary of all parameter values."""
+    def inputs(self) -> Sequence[InputPort]:
+        """Get all input ports."""
         raise NotImplementedError
 
+    @property
+    @abstractmethod
+    def outputs(self) -> Sequence[OutputPort]:
+        """Get all output ports."""
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def input_signals(self) -> Iterable[Signal]:
+        """Get all the signals that are connected to this operation's input ports,
+        in no particular order.
+        """
+        raise NotImplementedError
+
+    @property
     @abstractmethod
-    def param(self, name: str) -> Optional[Any]:
-        """Get the value of a parameter.
-        Returns None if the parameter is not defined.
+    def output_signals(self) -> Iterable[Signal]:
+        """Get all the signals that are connected to this operation's output ports,
+        in no particular order.
         """
         raise NotImplementedError
 
     @abstractmethod
-    def set_param(self, name: str, value: Any) -> None:
-        """Set the value of a parameter.
-        The parameter must be defined.
+    def key(self, index: int, prefix: str = "") -> ResultKey:
+        """Get the key used to access the output of a certain output of this operation
+        from the output parameter passed to current_output(s) or evaluate_output(s).
         """
         raise NotImplementedError
 
     @abstractmethod
-    def evaluate_outputs(self, state: "SimulationState") -> List[Number]:
-        """Simulate the circuit until its iteration count matches that of the simulation state,
-        then return the resulting output vector.
+    def current_output(self, index: int, delays: Optional[DelayMap] = None, prefix: str = "") -> Optional[Number]:
+        """Get the current output at the given index of this operation, if available.
+        The delays parameter will be used for lookup.
+        The prefix parameter will be used as a prefix for the key string when looking for delays.
+        See also: current_outputs, evaluate_output, evaluate_outputs.
         """
         raise NotImplementedError
 
     @abstractmethod
-    def split(self) -> "List[Operation]":
+    def evaluate_output(self, index: int, input_values: Sequence[Number], results: Optional[MutableResultMap] = None, delays: Optional[MutableDelayMap] = None, prefix: str = "", bits_override: Optional[int] = None, truncate: bool = True) -> Number:
+        """Evaluate the output at the given index of this operation with the given input values.
+        The results parameter will be used to store any results (including intermediate results) for caching.
+        The delays parameter will be used to get the current value of any intermediate delays that are encountered, and be updated with their new values.
+        The prefix parameter will be used as a prefix for the key string when storing results/delays.
+        The bits_override parameter specifies a word length override when truncating inputs which ignores the word length specified by the input signal.
+        The truncate parameter specifies whether input truncation should be enabled in the first place. If set to False, input values will be used driectly without any bit truncation.
+        See also: evaluate_outputs, current_output, current_outputs.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def current_outputs(self, delays: Optional[DelayMap] = None, prefix: str = "") -> Sequence[Optional[Number]]:
+        """Get all current outputs of this operation, if available.
+        See current_output for more information.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def evaluate_outputs(self, input_values: Sequence[Number], results: Optional[MutableResultMap] = None, delays: Optional[MutableDelayMap] = None, prefix: str = "", bits_override: Optional[int] = None, truncate: bool = True) -> Sequence[Number]:
+        """Evaluate all outputs of this operation given the input values.
+        See evaluate_output for more information.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def split(self) -> Iterable["Operation"]:
         """Split the operation into multiple operations.
         If splitting is not possible, this may return a list containing only the operation itself.
         """
         raise NotImplementedError
 
+    @abstractmethod
+    def to_sfg(self) -> "SFG":
+        """Convert the operation into its corresponding SFG.
+        If the operation is composed by multiple operations, the operation will be split.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def inputs_required_for_output(self, output_index: int) -> Iterable[int]:
+        """Get the input indices of all inputs in this operation whose values are required in order to evaluate the output at the given output index."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def truncate_input(self, index: int, value: Number, bits: int) -> Number:
+        """Truncate the value to be used as input at the given index to a certain bit length."""
+        raise NotImplementedError
+
     @property
     @abstractmethod
-    def neighbors(self) -> "List[Operation]":
-        """Return all operations that are connected by signals to this operation.
-        If no neighbors are found, this returns an empty list.
+    def latency(self) -> int:
+        """Get the latency of the operation, which is the longest time it takes from one of
+        the operations inputport to one of the operations outputport.
+        """
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def latency_offsets(self) -> Sequence[Sequence[int]]:
+        """Get a nested list with all the operations ports latency-offsets, the first list contains the
+        latency-offsets of the operations input ports, the second list contains the latency-offsets of
+        the operations output ports.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def set_latency(self, latency: int) -> None:
+        """Sets the latency of the operation to the specified integer value  by setting the
+        latency-offsets of operations input ports to 0 and the latency-offsets of the operations
+        output ports to the specified value. The latency cannot be a negative integers.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def set_latency_offsets(self, latency_offsets: Dict[str, int]) -> None:
+        """Sets the latency-offsets for the operations ports specified in the latency_offsets dictionary.
+        The latency offsets dictionary should be {'in0': 2, 'out1': 4} if you want to set the latency offset
+        for the inport port with index 0 to 2, and the latency offset of the output port with index 1 to 4.
         """
         raise NotImplementedError
 
 
 class AbstractOperation(Operation, AbstractGraphComponent):
-    """Generic abstract operation class which most implementations will derive from.
-    TODO: More info.
+    """Generic abstract operation base class.
+
+    Concrete operations should normally derive from this to get the default
+    behavior.
     """
 
-    _input_ports: List["InputPort"]
-    _output_ports: List["OutputPort"]
-    _parameters: Dict[str, Optional[Any]]
+    _input_ports: List[InputPort]
+    _output_ports: List[OutputPort]
 
-    def __init__(self, name: Name = ""):
+    def __init__(self, input_count: int, output_count: int, name: Name = "", input_sources: Optional[Sequence[Optional[SignalSourceProvider]]] = None, latency: Optional[int] = None, latency_offsets: Optional[Dict[str, int]] = None):
+        """Construct an operation with the given input/output count.
+
+        A list of input sources may be specified to automatically connect
+        to the input ports.
+        If provided, the number of sources must match the number of inputs.
+
+        The latency offsets may also be specified to be initialized.
+        """
         super().__init__(name)
-        self._input_ports = []
-        self._output_ports = []
-        self._parameters = {}
+
+        self._input_ports = [InputPort(self, i) for i in range(input_count)]
+        self._output_ports = [OutputPort(self, i) for i in range(output_count)]
+
+        # Connect given input sources, if any.
+        if input_sources is not None:
+            source_count = len(input_sources)
+            if source_count != input_count:
+                raise ValueError(
+                    f"Wrong number of input sources supplied to Operation (expected {input_count}, got {source_count})")
+            for i, src in enumerate(input_sources):
+                if src is not None:
+                    self._input_ports[i].connect(src.source)
+
+        ports_without_latency_offset = set(([f"in{i}" for i in range(self.input_count)] +
+                                            [f"out{i}" for i in range(self.output_count)]))
+
+        if latency_offsets is not None:
+            self.set_latency_offsets(latency_offsets)
+
+        if latency is not None:
+            # Set the latency of the rest of ports with no latency_offset.
+            assert latency >= 0, "Negative latency entered"
+            for inp in self.inputs:
+                if inp.latency_offset is None:
+                    inp.latency_offset = 0
+            for outp in self.outputs:
+                if outp.latency_offset is None:
+                    outp.latency_offset = latency
 
     @abstractmethod
     def evaluate(self, *inputs) -> Any:  # pylint: disable=arguments-differ
-        """Evaluate the operation and generate a list of output values given a
-        list of input values.
-        """
+        """Evaluate the operation and generate a list of output values given a list of input values."""
         raise NotImplementedError
 
-    def inputs(self) -> List["InputPort"]:
-        return self._input_ports.copy()
+    def __add__(self, src: Union[SignalSourceProvider, Number]) -> "Addition":
+        # Import here to avoid circular imports.
+        from b_asic.core_operations import Constant, Addition
+        return Addition(self, Constant(src) if isinstance(src, Number) else src)
+
+    def __radd__(self, src: Union[SignalSourceProvider, Number]) -> "Addition":
+        # Import here to avoid circular imports.
+        from b_asic.core_operations import Constant, Addition
+        return Addition(Constant(src) if isinstance(src, Number) else src, self)
+
+    def __sub__(self, src: Union[SignalSourceProvider, Number]) -> "Subtraction":
+        # Import here to avoid circular imports.
+        from b_asic.core_operations import Constant, Subtraction
+        return Subtraction(self, Constant(src) if isinstance(src, Number) else src)
+
+    def __rsub__(self, src: Union[SignalSourceProvider, Number]) -> "Subtraction":
+        # Import here to avoid circular imports.
+        from b_asic.core_operations import Constant, Subtraction
+        return Subtraction(Constant(src) if isinstance(src, Number) else src, self)
+
+    def __mul__(self, src: Union[SignalSourceProvider, Number]) -> "Union[Multiplication, ConstantMultiplication]":
+        # Import here to avoid circular imports.
+        from b_asic.core_operations import Multiplication, ConstantMultiplication
+        return ConstantMultiplication(src, self) if isinstance(src, Number) else Multiplication(self, src)
 
-    def outputs(self) -> List["OutputPort"]:
-        return self._output_ports.copy()
+    def __rmul__(self, src: Union[SignalSourceProvider, Number]) -> "Union[Multiplication, ConstantMultiplication]":
+        # Import here to avoid circular imports.
+        from b_asic.core_operations import Multiplication, ConstantMultiplication
+        return ConstantMultiplication(src, self) if isinstance(src, Number) else Multiplication(src, self)
+
+    def __truediv__(self, src: Union[SignalSourceProvider, Number]) -> "Division":
+        # Import here to avoid circular imports.
+        from b_asic.core_operations import Constant, Division
+        return Division(self, Constant(src) if isinstance(src, Number) else src)
+
+    def __rtruediv__(self, src: Union[SignalSourceProvider, Number]) -> "Division":
+        # Import here to avoid circular imports.
+        from b_asic.core_operations import Constant, Division
+        return Division(Constant(src) if isinstance(src, Number) else src, self)
+
+    def __lshift__(self, src: SignalSourceProvider) -> Signal:
+        if self.input_count != 1:
+            diff = "more" if self.input_count > 1 else "less"
+            raise TypeError(
+                f"{self.__class__.__name__} cannot be used as a destination because it has {diff} than 1 input")
+        return self.input(0).connect(src)
+
+    def __str__(self) -> str:
+        """Get a string representation of this operation."""
+        inputs_dict = dict()
+        for i, port in enumerate(self.inputs):
+            if port.signal_count == 0:
+                inputs_dict[i] = '-'
+                break
+            dict_ele = []
+            for signal in port.signals:
+                if signal.source:
+                    if signal.source.operation.graph_id:
+                        dict_ele.append(signal.source.operation.graph_id)
+                    else:
+                        dict_ele.append("no_id")
+                else:
+                    if signal.graph_id:
+                        dict_ele.append(signal.graph_id)
+                    else:
+                        dict_ele.append("no_id")
+            inputs_dict[i] = dict_ele
+
+        outputs_dict = dict()
+        for i, port in enumerate(self.outputs):
+            if port.signal_count == 0:
+                outputs_dict[i] = '-'
+                break
+            dict_ele = []
+            for signal in port.signals:
+                if signal.destination:
+                    if signal.destination.operation.graph_id:
+                        dict_ele.append(signal.destination.operation.graph_id)
+                    else:
+                        dict_ele.append("no_id")
+                else:
+                    if signal.graph_id:
+                        dict_ele.append(signal.graph_id)
+                    else:
+                        dict_ele.append("no_id")
+            outputs_dict[i] = dict_ele
+
+        return super().__str__() + f", \tinputs: {str(inputs_dict)}, \toutputs: {str(outputs_dict)}"
 
+    @property
     def input_count(self) -> int:
         return len(self._input_ports)
 
+    @property
     def output_count(self) -> int:
         return len(self._output_ports)
 
-    def input(self, i: int) -> "InputPort":
-        return self._input_ports[i]
-
-    def output(self, i: int) -> "OutputPort":
-        return self._output_ports[i]
-
-    def params(self) -> Dict[str, Optional[Any]]:
-        return self._parameters.copy()
-
-    def param(self, name: str) -> Optional[Any]:
-        return self._parameters.get(name)
-
-    def set_param(self, name: str, value: Any) -> None:
-        assert name in self._parameters  # TODO: Error message.
-        self._parameters[name] = value
-
-    def evaluate_outputs(self, state: SimulationState) -> List[Number]:
-        # TODO: Check implementation.
-        input_count: int = self.input_count()
-        output_count: int = self.output_count()
-        assert input_count == len(self._input_ports)  # TODO: Error message.
-        assert output_count == len(self._output_ports)  # TODO: Error message.
-
-        self_state: OperationState = state.operation_states[self]
-
-        while self_state.iteration < state.iteration:
-            input_values: List[Number] = [0] * input_count
-            for i in range(input_count):
-                source: Signal = self._input_ports[i].signal
-                input_values[i] = source.operation.evaluate_outputs(state)[
-                    source.port_index]
-
-            self_state.output_values = self.evaluate(input_values)
-            # TODO: Error message.
-            assert len(self_state.output_values) == output_count
-            self_state.iteration += 1
-            for i in range(output_count):
-                for signal in self._output_ports[i].signals():
-                    destination: Signal = signal.destination
-                    destination.evaluate_outputs(state)
-
-        return self_state.output_values
-
-    def split(self) -> List[Operation]:
-        # TODO: Check implementation.
-        results = self.evaluate(self._input_ports)
-        if all(isinstance(e, Operation) for e in results):
-            return results
-        return [self]
+    def input(self, index: int) -> InputPort:
+        return self._input_ports[index]
+
+    def output(self, index: int) -> OutputPort:
+        return self._output_ports[index]
 
     @property
-    def neighbors(self) -> List[Operation]:
-        neighbors: List[Operation] = []
-        for port in self._input_ports:
-            for signal in port.signals:
-                neighbors.append(signal.source.operation)
+    def inputs(self) -> Sequence[InputPort]:
+        return self._input_ports
 
-        for port in self._output_ports:
-            for signal in port.signals:
-                neighbors.append(signal.destination.operation)
-
-        return neighbors
-
-    def traverse(self) -> Operation:
-        """Traverse the operation tree and return a generator with start point in the operation."""
-        return self._breadth_first_search()
-
-    def _breadth_first_search(self) -> Operation:
-        """Use breadth first search to traverse the operation tree."""
-        visited: Set[Operation] = {self}
-        queue = deque([self])
-        while queue:
-            operation = queue.popleft()
-            yield operation
-            for n_operation in operation.neighbors:
-                if n_operation not in visited:
-                    visited.add(n_operation)
-                    queue.append(n_operation)
-
-    def __add__(self, other):
-        """Overloads the addition operator to make it return a new Addition operation
-        object that is connected to the self and other objects. If other is a number then
-        returns a ConstantAddition operation object instead.
-        """
-        # Import here to avoid circular imports.
-        from b_asic.core_operations import Addition, ConstantAddition
+    @property
+    def outputs(self) -> Sequence[OutputPort]:
+        return self._output_ports
+
+    @property
+    def input_signals(self) -> Iterable[Signal]:
+        result = []
+        for p in self.inputs:
+            for s in p.signals:
+                result.append(s)
+        return result
 
-        if isinstance(other, Operation):
-            return Addition(self.output(0), other.output(0))
-        elif isinstance(other, Number):
-            return ConstantAddition(other, self.output(0))
+    @property
+    def output_signals(self) -> Iterable[Signal]:
+        result = []
+        for p in self.outputs:
+            for s in p.signals:
+                result.append(s)
+        return result
+
+    def key(self, index: int, prefix: str = "") -> ResultKey:
+        key = prefix
+        if self.output_count != 1:
+            if key:
+                key += "."
+            key += str(index)
+        elif not key:
+            key = str(index)
+        return key
+
+    def current_output(self, index: int, delays: Optional[DelayMap] = None, prefix: str = "") -> Optional[Number]:
+        return None
+
+    def evaluate_output(self, index: int, input_values: Sequence[Number], results: Optional[MutableResultMap] = None, delays: Optional[MutableDelayMap] = None, prefix: str = "", bits_override: Optional[int] = None, truncate: bool = True) -> Number:
+        if index < 0 or index >= self.output_count:
+            raise IndexError(
+                f"Output index out of range (expected 0-{self.output_count - 1}, got {index})")
+        if len(input_values) != self.input_count:
+            raise ValueError(
+                f"Wrong number of input values supplied to operation (expected {self.input_count}, got {len(input_values)})")
+
+        values = self.evaluate(
+            *(self.truncate_inputs(input_values, bits_override) if truncate else input_values))
+        if isinstance(values, collections.abc.Sequence):
+            if len(values) != self.output_count:
+                raise RuntimeError(
+                    f"Operation evaluated to incorrect number of outputs (expected {self.output_count}, got {len(values)})")
+        elif isinstance(values, Number):
+            if self.output_count != 1:
+                raise RuntimeError(
+                    f"Operation evaluated to incorrect number of outputs (expected {self.output_count}, got 1)")
+            values = (values,)
         else:
-            raise TypeError("Other type is not an Operation or a Number.")
+            raise RuntimeError(
+                f"Operation evaluated to invalid type (expected Sequence/Number, got {values.__class__.__name__})")
 
-    def __sub__(self, other):
-        """Overloads the subtraction operator to make it return a new Subtraction operation
-        object that is connected to the self and other objects. If other is a number then
-        returns a ConstantSubtraction operation object instead.
-        """
-        # Import here to avoid circular imports.
-        from b_asic.core_operations import Subtraction, ConstantSubtraction
+        if results is not None:
+            for i in range(self.output_count):
+                results[self.key(i, prefix)] = values[i]
+        return values[index]
 
-        if isinstance(other, Operation):
-            return Subtraction(self.output(0), other.output(0))
-        elif isinstance(other, Number):
-            return ConstantSubtraction(other, self.output(0))
-        else:
-            raise TypeError("Other type is not an Operation or a Number.")
+    def current_outputs(self, delays: Optional[DelayMap] = None, prefix: str = "") -> Sequence[Optional[Number]]:
+        return [self.current_output(i, delays, prefix) for i in range(self.output_count)]
 
-    def __mul__(self, other):
-        """Overloads the multiplication operator to make it return a new Multiplication operation
-        object that is connected to the self and other objects. If other is a number then
-        returns a ConstantMultiplication operation object instead.
-        """
-        # Import here to avoid circular imports.
-        from b_asic.core_operations import Multiplication, ConstantMultiplication
+    def evaluate_outputs(self, input_values: Sequence[Number], results: Optional[MutableResultMap] = None, delays: Optional[MutableDelayMap] = None, prefix: str = "", bits_override: Optional[int] = None, truncate: bool = True) -> Sequence[Number]:
+        return [self.evaluate_output(i, input_values, results, delays, prefix, bits_override, truncate) for i in range(self.output_count)]
 
-        if isinstance(other, Operation):
-            return Multiplication(self.output(0), other.output(0))
-        elif isinstance(other, Number):
-            return ConstantMultiplication(other, self.output(0))
-        else:
-            raise TypeError("Other type is not an Operation or a Number.")
+    def split(self) -> Iterable[Operation]:
+        # Import here to avoid circular imports.
+        from b_asic.special_operations import Input
+        try:
+            result = self.evaluate(*([Input()] * self.input_count))
+            if isinstance(result, collections.Sequence) and all(isinstance(e, Operation) for e in result):
+                return result
+            if isinstance(result, Operation):
+                return [result]
+        except TypeError:
+            pass
+        except ValueError:
+            pass
+        return [self]
 
-    def __truediv__(self, other):
-        """Overloads the division operator to make it return a new Division operation
-        object that is connected to the self and other objects. If other is a number then
-        returns a ConstantDivision operation object instead.
-        """
+    def to_sfg(self) -> "SFG":
         # Import here to avoid circular imports.
-        from b_asic.core_operations import Division, ConstantDivision
+        from b_asic.special_operations import Input, Output
+        from b_asic.signal_flow_graph import SFG
+
+        inputs = [Input() for i in range(self.input_count)]
+
+        try:
+            last_operations = self.evaluate(*inputs)
+            if isinstance(last_operations, Operation):
+                last_operations = [last_operations]
+            outputs = [Output(o) for o in last_operations]
+        except TypeError:
+            operation_copy: Operation = self.copy_component()
+            inputs = []
+            for i in range(self.input_count):
+                _input = Input()
+                operation_copy.input(i).connect(_input)
+                inputs.append(_input)
+
+            outputs = [Output(operation_copy)]
+
+        return SFG(inputs=inputs, outputs=outputs)
+
+    def copy_component(self, *args, **kwargs) -> GraphComponent:
+        new_component: Operation = super().copy_component(*args, **kwargs)
+        for i, inp in enumerate(self.inputs):
+            new_component.input(i).latency_offset = inp.latency_offset
+        for i, outp in enumerate(self.outputs):
+            new_component.output(i).latency_offset = outp.latency_offset
+        return new_component
+
+    def inputs_required_for_output(self, output_index: int) -> Iterable[int]:
+        if output_index < 0 or output_index >= self.output_count:
+            raise IndexError(
+                f"Output index out of range (expected 0-{self.output_count - 1}, got {output_index})")
+        # By default, assume each output depends on all inputs.
+        return [i for i in range(self.input_count)]
 
-        if isinstance(other, Operation):
-            return Division(self.output(0), other.output(0))
-        elif isinstance(other, Number):
-            return ConstantDivision(other, self.output(0))
-        else:
-            raise TypeError("Other type is not an Operation or a Number.")
+    @property
+    def neighbors(self) -> Iterable[GraphComponent]:
+        return list(self.input_signals) + list(self.output_signals)
 
+    @property
+    def preceding_operations(self) -> Iterable[Operation]:
+        """Returns an Iterable of all Operations that are connected to this Operations input ports."""
+        return [signal.source.operation for signal in self.input_signals if signal.source]
+
+    @property
+    def subsequent_operations(self) -> Iterable[Operation]:
+        """Returns an Iterable of all Operations that are connected to this Operations output ports."""
+        return [signal.destination.operation for signal in self.output_signals if signal.destination]
+
+    @property
+    def source(self) -> OutputPort:
+        if self.output_count != 1:
+            diff = "more" if self.output_count > 1 else "less"
+            raise TypeError(
+                f"{self.__class__.__name__} cannot be used as an input source because it has {diff} than 1 output")
+        return self.output(0)
+
+    def truncate_input(self, index: int, value: Number, bits: int) -> Number:
+        return int(value) & ((2 ** bits) - 1)
+
+    def truncate_inputs(self, input_values: Sequence[Number], bits_override: Optional[int] = None) -> Sequence[Number]:
+        """Truncate the values to be used as inputs to the bit lengths specified by the respective signals connected to each input."""
+        args = []
+        for i, input_port in enumerate(self.inputs):
+            value = input_values[i]
+            bits = bits_override
+            if bits_override is None and input_port.signal_count >= 1:
+                bits = input_port.signals[0].bits
+            if bits_override is not None:
+                if isinstance(value, complex):
+                    raise TypeError(
+                        "Complex value cannot be truncated to {bits} bits as requested by the signal connected to input #{i}")
+                value = self.truncate_input(i, value, bits)
+            args.append(value)
+        return args
+
+    @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)))
+
+    @property
+    def latency_offsets(self) -> Sequence[Sequence[int]]:
+        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(self, latency: int) -> None:
+        assert latency >= 0, "Negative latency entered."
+        for inport in self.inputs:
+            inport.latency_offset = 0
+        for outport in self.outputs:
+            outport.latency_offset = latency
+
+    def set_latency_offsets(self, latency_offsets: Dict[str, int]) -> None:
+        for port_str, latency_offset in latency_offsets.items():
+            port_str = port_str.lower()
+            if port_str.startswith("in"):
+                index_str = port_str[2:]
+                assert index_str.isdigit(), "Incorrectly formatted index in string, expected 'in' + index"
+                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"
+                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/port.py b/b_asic/port.py
index c22053df..25197891 100644
--- a/b_asic/port.py
+++ b/b_asic/port.py
@@ -1,84 +1,79 @@
-"""@package docstring
-B-ASIC Port Module.
-TODO: More info.
+"""B-ASIC Port Module.
+
+Contains classes for managing the ports of operations.
 """
 
 from abc import ABC, abstractmethod
-from typing import NewType, Optional, List
+from copy import copy
+from typing import Optional, List, Iterable, TYPE_CHECKING
 
-from b_asic.operation import Operation
 from b_asic.signal import Signal
+from b_asic.graph_component import Name
+
+if TYPE_CHECKING:
+    from b_asic.operation import Operation
 
-PortIndex = NewType("PortIndex", int)
 
 class Port(ABC):
-    """Port Interface.
+    """Port interface.
 
-    TODO: More documentaiton?
+    Ports serve as connection points for connecting signals between operations.
+    They also store information about the latency of the corresponding
+    calculations of the operation.
+
+    Aside from connected signals, each port also provides a reference to the
+    parent operation that "owns" it as well as the operation's port index at
+    which the port resides.
     """
 
     @property
     @abstractmethod
-    def operation(self) -> Operation:
+    def operation(self) -> "Operation":
         """Return the connected operation."""
         raise NotImplementedError
 
     @property
     @abstractmethod
-    def index(self) -> PortIndex:
-        """Return the unique PortIndex."""
+    def index(self) -> int:
+        """Return the index of the port."""
         raise NotImplementedError
 
     @property
     @abstractmethod
-    def signals(self) -> List[Signal]:
-        """Return a list of all connected signals."""
+    def latency_offset(self) -> int:
+        """Get the latency_offset of the port."""
         raise NotImplementedError
 
+    @latency_offset.setter
     @abstractmethod
-    def signal(self, i: int = 0) -> Signal:
-        """Return the connected signal at index i.
-
-        Keyword argumens:
-        i: integer index of the signal requsted.
-        """
+    def latency_offset(self, latency_offset: int) -> None:
+        """Set the latency_offset of the port to the integer specified value."""
         raise NotImplementedError
 
     @property
-    @abstractmethod
-    def connected_ports(self) -> List["Port"]:
-        """Return a list of all connected Ports."""
-        raise NotImplementedError
-
     @abstractmethod
     def signal_count(self) -> int:
         """Return the number of connected signals."""
         raise NotImplementedError
 
+    @property
     @abstractmethod
-    def connect(self, port: "Port") -> Signal:
-        """Create and return a signal that is connected to this port and the entered
-        port and connect this port to the signal and the entered port to the signal."""
+    def signals(self) -> Iterable[Signal]:
+        """Return all connected signals."""
         raise NotImplementedError
 
     @abstractmethod
     def add_signal(self, signal: Signal) -> None:
         """Connect this port to the entered signal. If the entered signal isn't connected to
-        this port then connect the entered signal to the port aswell."""
-        raise NotImplementedError
-
-    @abstractmethod
-    def disconnect(self, port: "Port") -> None:
-        """Disconnect the entered port from the port by removing it from the ports signal.
-        If the entered port is still connected to this ports signal then disconnect the entered
-        port from the signal aswell."""
+        this port then connect the entered signal to the port as well.
+        """
         raise NotImplementedError
 
     @abstractmethod
     def remove_signal(self, signal: Signal) -> None:
         """Remove the signal that was entered from the Ports signals.
         If the entered signal still is connected to this port then disconnect the
-        entered signal from the port aswell.
+        entered signal from the port as well.
 
         Keyword arguments:
         - signal: Signal to remove.
@@ -92,132 +87,146 @@ class Port(ABC):
 
 
 class AbstractPort(Port):
-    """Abstract port class.
+    """Generic abstract port base class.
 
-    Handles functionality for port id and saves the connection to the parent operation.
+    Concrete ports should normally derive from this to get the default
+    behavior.
     """
 
+    _operation: "Operation"
     _index: int
-    _operation: Operation
+    _latency_offset: Optional[int]
 
-    def __init__(self, index: int, operation: Operation):
-        self._index = index
+    def __init__(self, operation: "Operation", index: int, latency_offset: Optional[int] = None):
+        """Construct a port of the given operation at the given port index."""
         self._operation = operation
+        self._index = index
+        self._latency_offset = latency_offset
 
     @property
-    def operation(self) -> Operation:
+    def operation(self) -> "Operation":
         return self._operation
 
     @property
-    def index(self) -> PortIndex:
+    def index(self) -> int:
         return self._index
 
+    @property
+    def latency_offset(self) -> int:
+        return self._latency_offset
+
+    @latency_offset.setter
+    def latency_offset(self, latency_offset: int):
+        self._latency_offset = latency_offset
+
+
+class SignalSourceProvider(ABC):
+    """Signal source provider interface.
+
+    Signal source providers give access to a single output port that can be
+    used to connect signals from.
+    """
+
+    @property
+    @abstractmethod
+    def source(self) -> "OutputPort":
+        """Get the main source port provided by this object."""
+        raise NotImplementedError
+
 
 class InputPort(AbstractPort):
     """Input port.
-    TODO: More info.
+
+    May have one or zero signals connected to it.
     """
 
     _source_signal: Optional[Signal]
 
-    def __init__(self, port_id: PortIndex, operation: Operation):
-        super().__init__(port_id, operation)
+    def __init__(self, operation: "Operation", index: int):
+        """Construct an InputPort."""
+        super().__init__(operation, index)
         self._source_signal = None
 
     @property
-    def signals(self) -> List[Signal]:
-        return [] if self._source_signal is None else [self._source_signal]
-
-    def signal(self, i: int = 0) -> Signal:
-        assert 0 <= i < self.signal_count(), "Signal index out of bound."
-        assert self._source_signal is not None, "No Signal connect to InputPort."
-        return self._source_signal
-
-    @property
-    def connected_ports(self) -> List[Port]:
-        return [] if self._source_signal is None or self._source_signal.source is None \
-            else [self._source_signal.source]
-
     def signal_count(self) -> int:
         return 0 if self._source_signal is None else 1
 
-    def connect(self, port: "OutputPort") -> Signal:
-        assert self._source_signal is None, "Connecting new port to already connected input port."
-        return Signal(port, self)        # self._source_signal is set by the signal constructor
+    @property
+    def signals(self) -> Iterable[Signal]:
+        return [] if self._source_signal is None else [self._source_signal]
 
     def add_signal(self, signal: Signal) -> None:
-        assert self._source_signal is None, "Connecting new port to already connected input port."
-        self._source_signal: Signal = signal
-        if self is not signal.destination:
-            # Connect this inputport as destination for this signal if it isn't already.
-            signal.set_destination(self)
-
-    def disconnect(self, port: "OutputPort") -> None:
-        assert self._source_signal.source is port, "The entered port is not connected to this port."
-        self._source_signal.remove_source()
+        assert self._source_signal is None, "Input port may have only one signal added."
+        assert signal is not self._source_signal, "Attempted to add already connected signal."
+        self._source_signal = signal
+        signal.set_destination(self)
 
     def remove_signal(self, signal: Signal) -> None:
-        old_signal: Signal = self._source_signal
+        assert signal is self._source_signal, "Attempted to remove signal that is not connected."
         self._source_signal = None
-        if self is old_signal.destination:
-            # Disconnect the dest of the signal if this inputport currently is the dest
-            old_signal.remove_destination()
+        signal.remove_destination()
 
     def clear(self) -> None:
-        self.remove_signal(self._source_signal)
+        if self._source_signal is not None:
+            self.remove_signal(self._source_signal)
+
+    @property
+    def connected_source(self) -> Optional["OutputPort"]:
+        """Get the output port that is currently connected to this input port,
+        or None if it is unconnected.
+        """
+        return None if self._source_signal is None else self._source_signal.source
+
+    def connect(self, src: SignalSourceProvider, name: Name = "") -> Signal:
+        """Connect the provided signal source to this input port by creating a new signal.
+        Returns the new signal.
+        """
+        assert self._source_signal is None, "Attempted to connect already connected input port."
+        # self._source_signal is set by the signal constructor.
+        return Signal(source=src.source, destination=self, name=name)
+
+    def __lshift__(self, src: SignalSourceProvider) -> Signal:
+        """Overloads the left shift operator to make it connect the provided signal source to this input port.
+        Returns the new signal.
+        """
+        return self.connect(src)
+
 
-class OutputPort(AbstractPort):
+class OutputPort(AbstractPort, SignalSourceProvider):
     """Output port.
-    TODO: More info.
+
+    May have zero or more signals connected to it.
     """
 
     _destination_signals: List[Signal]
 
-    def __init__(self, port_id: PortIndex, operation: Operation):
-        super().__init__(port_id, operation)
+    def __init__(self, operation: "Operation", index: int):
+        """Construct an OutputPort."""
+        super().__init__(operation, index)
         self._destination_signals = []
 
     @property
-    def signals(self) -> List[Signal]:
-        return self._destination_signals.copy()
-
-    def signal(self, i: int = 0) -> Signal:
-        assert 0 <= i < self.signal_count(), "Signal index out of bounds."
-        return self._destination_signals[i]
-
-    @property
-    def connected_ports(self) -> List[Port]:
-        return [signal.destination for signal in self._destination_signals \
-            if signal.destination is not None]
-
     def signal_count(self) -> int:
         return len(self._destination_signals)
 
-    def connect(self, port: InputPort) -> Signal:
-        return Signal(self, port)      # Signal is added to self._destination_signals in signal constructor
+    @property
+    def signals(self) -> Iterable[Signal]:
+        return self._destination_signals
 
     def add_signal(self, signal: Signal) -> None:
-        assert signal not in self.signals, \
-                "Attempting to connect to Signal already connected."
+        assert signal not in self._destination_signals, "Attempted to add already connected signal."
         self._destination_signals.append(signal)
-        if self is not signal.source:
-            # Connect this outputport to the signal if it isn't already
-            signal.set_source(self)
-
-    def disconnect(self, port: InputPort) -> None:
-        assert port in self.connected_ports, "Attempting to disconnect port that isn't connected."
-        for sig in self._destination_signals:
-            if sig.destination is port:
-                sig.remove_destination()
-                break
+        signal.set_source(self)
 
     def remove_signal(self, signal: Signal) -> None:
-        i: int = self._destination_signals.index(signal)
-        old_signal: Signal = self._destination_signals[i]
-        del self._destination_signals[i]
-        if self is old_signal.source:
-            old_signal.remove_source()
+        assert signal in self._destination_signals, "Attempted to remove signal that is not connected."
+        self._destination_signals.remove(signal)
+        signal.remove_source()
 
     def clear(self) -> None:
-        for signal in self._destination_signals:
+        for signal in copy(self._destination_signals):
             self.remove_signal(signal)
+
+    @property
+    def source(self) -> "OutputPort":
+        return self
diff --git a/b_asic/precedence_chart.py b/b_asic/precedence_chart.py
deleted file mode 100644
index be55a123..00000000
--- a/b_asic/precedence_chart.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""@package docstring
-B-ASIC Precedence Chart Module.
-TODO: More info.
-"""
-
-from b_asic.signal_flow_graph import SFG
-
-
-class PrecedenceChart:
-    """Precedence chart constructed from a signal flow graph.
-    TODO: More info.
-    """
-
-    sfg: SFG
-    # TODO: More members.
-
-    def __init__(self, sfg: SFG):
-        self.sfg = sfg
-        # TODO: Implement.
-
-    # TODO: More stuff.
diff --git a/b_asic/save_load_structure.py b/b_asic/save_load_structure.py
new file mode 100644
index 00000000..222e4741
--- /dev/null
+++ b/b_asic/save_load_structure.py
@@ -0,0 +1,90 @@
+"""B-ASIC Save/Load Structure Module.
+
+Contains functions for saving/loading SFGs to/from strings that can be stored
+as files.
+"""
+
+from b_asic.signal_flow_graph import SFG
+from b_asic.graph_component import 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:
+    """Given an SFG structure try to serialize it for saving to a file."""
+    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"
+    # Keep track of already existing connections to avoid adding duplicates
+    connections = list()
+    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'
+    sfg_name_var = sfg_name.replace(" ", "_")
+    result += f"\n{sfg_name_var} = SFG(inputs={inputs}, outputs={outputs}, name='{sfg_name}')\n"
+    result += "\n# SFG Properties:\n" + \
+        "prop = {'name':" + f"{sfg_name_var}" + "}"
+
+    if suffix is not None:
+        result += "\n" + suffix + "\n"
+
+    return result
+
+
+def python_to_sfg(path: str) -> SFG:
+    """Given a serialized file try to deserialize it and load it to the library."""
+    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 {}
diff --git a/b_asic/schema.py b/b_asic/schema.py
index e5068cdc..25f18eb6 100644
--- a/b_asic/schema.py
+++ b/b_asic/schema.py
@@ -1,21 +1,112 @@
-"""@package docstring
-B-ASIC Schema Module.
-TODO: More info.
+"""B-ASIC Schema Module.
+
+Contains the schema class for scheduling operations in an SFG.
 """
 
-from b_asic.precedence_chart import PrecedenceChart
+from typing import Dict, List, Optional
+
+from b_asic.signal_flow_graph import SFG
+from b_asic.graph_component import GraphID
+from b_asic.operation import Operation
 
 
 class Schema:
-    """Schema constructed from a precedence chart.
-    TODO: More info.
-    """
+    """Schema of an SFG with scheduled Operations."""
+
+    _sfg: SFG
+    _start_times: Dict[GraphID, int]
+    _laps: Dict[GraphID, List[int]]
+    _schedule_time: int
+    _cyclic: bool
+    _resolution: int
+
+    def __init__(self, sfg: SFG, schedule_time: Optional[int] = None, cyclic: bool = False, resolution: int = 1, scheduling_alg: str = "ASAP"):
+        """Construct a Schema from an SFG."""
+        self._sfg = sfg
+        self._start_times = dict()
+        self._laps = dict()
+        self._cyclic = cyclic
+        self._resolution = resolution
+
+        if scheduling_alg == "ASAP":
+            self._schedule_asap()
+        else:
+            raise NotImplementedError(
+                f"No algorithm with name: {scheduling_alg} defined.")
+
+        max_end_time = 0
+        for op_id, op_start_time in self._start_times.items():
+            op = self._sfg.find_by_id(op_id)
+            for outport in op.outputs:
+                max_end_time = max(
+                    max_end_time, op_start_time + outport.latency_offset)
+
+        if not self._cyclic:
+            if schedule_time is None:
+                self._schedule_time = max_end_time
+            elif schedule_time < max_end_time:
+                raise ValueError(
+                    "Too short schedule time for non-cyclic Schedule entered.")
+            else:
+                self._schedule_time = schedule_time
+
+    def start_time_of_operation(self, op_id: GraphID) -> int:
+        """Get the start time of the operation with the specified by the op_id."""
+        assert op_id in self._start_times, "No operation with the specified op_id in this schema."
+        return self._start_times[op_id]
+
+    def forward_slack(self, op_id: GraphID) -> int:
+        raise NotImplementedError
+
+    def backward_slack(self, op_id: GraphID) -> int:
+        raise NotImplementedError
+
+    def print_slacks(self) -> None:
+        raise NotImplementedError
+
+    def _schedule_asap(self) -> None:
+        pl = self._sfg.get_precedence_list()
+
+        if len(pl) < 2:
+            print("Empty signal flow graph cannot be scheduled.")
+            return
+
+        non_schedulable_ops = set((outp.operation.graph_id for outp in pl[0]))
+
+        for outport in pl[1]:
+            op = outport.operation
+            if op not in self._start_times:
+                # Set start time of all operations in the first iter to 0
+                self._start_times[op.graph_id] = 0
+
+        for outports in pl[2:]:
+            for outport in outports:
+                op = outport.operation
+                if op.graph_id not in self._start_times:
+                    # Schedule the operation if it doesn't have a start time yet.
+                    op_start_time = 0
+                    for inport in op.inputs:
+                        print(inport.operation.graph_id)
+                        assert len(
+                            inport.signals) == 1, "Error in scheduling, dangling input port detected."
+                        assert inport.signals[0].source is not None, "Error in scheduling, signal with no source detected."
+                        source_port = inport.signals[0].source
+
+                        source_end_time = None
+                        if source_port.operation.graph_id in non_schedulable_ops:
+                            source_end_time = 0
+                        else:
+                            source_op_time = self._start_times[source_port.operation.graph_id]
+
+                            assert source_port.latency_offset is not None, f"Output port: {source_port.index} of operation: \
+                                    {source_port.operation.graph_id} has no latency-offset."
+                            assert inport.latency_offset is not None, f"Input port: {inport.index} of operation: \
+                                    {inport.operation.graph_id} has no latency-offset."
 
-    pc: PrecedenceChart
-    # TODO: More members.
+                            source_end_time = source_op_time + source_port.latency_offset
 
-    def __init__(self, pc: PrecedenceChart):
-        self.pc = pc
-        # TODO: Implement.
+                        op_start_time_from_in = source_end_time - inport.latency_offset
+                        op_start_time = max(
+                            op_start_time, op_start_time_from_in)
 
-    # TODO: More stuff.
+                    self._start_times[op.graph_id] = op_start_time
diff --git a/b_asic/signal.py b/b_asic/signal.py
index 64c25948..747b25fc 100644
--- a/b_asic/signal.py
+++ b/b_asic/signal.py
@@ -1,9 +1,10 @@
-"""@package docstring
-B-ASIC Signal Module.
+"""B-ASIC Signal Module.
+
+Contains the class for representing the connections between operations.
 """
-from typing import Optional, TYPE_CHECKING
+from typing import Optional, Iterable, TYPE_CHECKING
 
-from b_asic.graph_component import AbstractGraphComponent, TypeName, Name
+from b_asic.graph_component import GraphComponent, AbstractGraphComponent, TypeName, Name
 
 if TYPE_CHECKING:
     from b_asic.port import InputPort, OutputPort
@@ -12,30 +13,35 @@ if TYPE_CHECKING:
 class Signal(AbstractGraphComponent):
     """A connection between two ports."""
 
-    _source: "OutputPort"
-    _destination: "InputPort"
-
-    def __init__(self, source: Optional["OutputPort"] = None, \
-                destination: Optional["InputPort"] = None, name: Name = ""):
+    _source: Optional["OutputPort"]
+    _destination: Optional["InputPort"]
 
+    def __init__(self, source: Optional["OutputPort"] = None, destination: Optional["InputPort"] = None, bits: Optional[int] = None, name: Name = ""):
+        """Construct a Signal."""
         super().__init__(name)
-
-        self._source = source
-        self._destination = destination
-
+        self._source = None
+        self._destination = None
         if source is not None:
             self.set_source(source)
-
         if destination is not None:
             self.set_destination(destination)
+        self.set_param("bits", bits)
+
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "s"
 
     @property
-    def source(self) -> "OutputPort":
+    def neighbors(self) -> Iterable[GraphComponent]:
+        return [p.operation for p in [self.source, self.destination] if p is not None]
+
+    @property
+    def source(self) -> Optional["OutputPort"]:
         """Return the source OutputPort of the signal."""
         return self._source
 
     @property
-    def destination(self) -> "InputPort":
+    def destination(self) -> Optional["InputPort"]:
         """Return the destination "InputPort" of the signal."""
         return self._destination
 
@@ -47,11 +53,11 @@ class Signal(AbstractGraphComponent):
         Keyword arguments:
         - src: OutputPort to connect as source to the signal.
         """
-        self.remove_source()
-        self._source = src
-        if self not in src.signals:
-            # If the new source isn't connected to this signal then connect it.
-            src.add_signal(self)
+        if src is not self._source:
+            self.remove_source()
+            self._source = src
+            if self not in src.signals:
+                src.add_signal(self)
 
     def set_destination(self, dest: "InputPort") -> None:
         """Disconnect the previous destination InputPort of the signal and
@@ -61,36 +67,44 @@ class Signal(AbstractGraphComponent):
         Keywords argments:
         - dest: InputPort to connect as destination to the signal.
         """
-        self.remove_destination()
-        self._destination = dest
-        if self not in dest.signals:
-            # If the new destination isn't connected to tis signal then connect it.
-            dest.add_signal(self)
-
-    @property
-    def type_name(self) -> TypeName:
-        return "s"
+        if dest is not self._destination:
+            self.remove_destination()
+            self._destination = dest
+            if self not in dest.signals:
+                dest.add_signal(self)
 
     def remove_source(self) -> None:
         """Disconnect the source OutputPort of the signal. If the source port
         still is connected to this signal then also disconnect the source port."""
-        if self._source is not None:
-            old_source: "OutputPort" = self._source
+        src = self._source
+        if src is not None:
             self._source = None
-            if self in old_source.signals:
-                # If the old destination port still is connected to this signal, then disconnect it.
-                old_source.remove_signal(self)
+            if self in src.signals:
+                src.remove_signal(self)
 
     def remove_destination(self) -> None:
         """Disconnect the destination InputPort of the signal."""
-        if self._destination is not None:
-            old_destination: "InputPort" = self._destination
+        dest = self._destination
+        if dest is not None:
             self._destination = None
-            if self in old_destination.signals:
-                # If the old destination port still is connected to this signal, then disconnect it.
-                old_destination.remove_signal(self)
+            if self in dest.signals:
+                dest.remove_signal(self)
 
-    def is_connected(self) -> bool:
-        """Returns true if the signal is connected to both a source and a destination,
+    def dangling(self) -> bool:
+        """Returns true if the signal is missing either a source or a destination,
         else false."""
-        return self._source is not None and self._destination is not None
+        return self._source is None or self._destination is None
+
+    @property
+    def bits(self) -> Optional[int]:
+        """Get the number of bits that this operations using this signal as an input should truncate received values to.
+        None = unlimited."""
+        return self.param("bits")
+
+    @bits.setter
+    def bits(self, bits: Optional[int]) -> None:
+        """Set the number of bits that operations using this signal as an input should truncate received values to.
+        None = unlimited."""
+        assert bits is None or (isinstance(bits, int)
+                                and bits >= 0), "Bits must be non-negative."
+        self.set_param("bits", bits)
diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py
index 9c08aecc..1c0f4870 100644
--- a/b_asic/signal_flow_graph.py
+++ b/b_asic/signal_flow_graph.py
@@ -1,91 +1,833 @@
-"""@package docstring
-B-ASIC Signal Flow Graph Module.
-TODO: More info.
+"""B-ASIC Signal Flow Graph Module.
+
+Contains the signal flow graph operation.
 """
 
-from typing import List, Dict, Optional, DefaultDict
-from collections import defaultdict
+from typing import List, Iterable, Sequence, Dict, Optional, DefaultDict, MutableSet, Tuple
+from numbers import Number
+from collections import defaultdict, deque
+from io import StringIO
+from queue import PriorityQueue
+import itertools as it
+from graphviz import Digraph
 
-from b_asic.operation import Operation
-from b_asic.operation import AbstractOperation
+from b_asic.port import SignalSourceProvider, OutputPort
+from b_asic.operation import Operation, AbstractOperation, ResultKey, DelayMap, MutableResultMap, MutableDelayMap
 from b_asic.signal import Signal
-from b_asic.graph_id import GraphIDGenerator, GraphID
-from b_asic.graph_component import GraphComponent, Name, TypeName
+from b_asic.graph_component import GraphID, GraphIDNumber, GraphComponent, Name, TypeName
+from b_asic.special_operations import Input, Output, Delay
+
+
+DelayQueue = List[Tuple[str, ResultKey, OutputPort]]
+
+
+class GraphIDGenerator:
+    """Generates Graph IDs for objects."""
+
+    _next_id_number: DefaultDict[TypeName, GraphIDNumber]
+
+    def __init__(self, id_number_offset: GraphIDNumber = 0):
+        """Construct a GraphIDGenerator."""
+        self._next_id_number = defaultdict(lambda: id_number_offset)
+
+    def next_id(self, type_name: TypeName) -> GraphID:
+        """Get the next graph id for a certain graph id type."""
+        self._next_id_number[type_name] += 1
+        return type_name + str(self._next_id_number[type_name])
+
+    @property
+    def id_number_offset(self) -> GraphIDNumber:
+        """Get the graph id number offset of this generator."""
+        return self._next_id_number.default_factory()  # pylint: disable=not-callable
 
 
 class SFG(AbstractOperation):
     """Signal flow graph.
-    TODO: More info.
+
+    Contains a set of connected operations, forming a new operation.
+    Used as a base for simulation, scheduling, etc.
     """
 
-    _graph_components_by_id: Dict[GraphID, GraphComponent]
-    _graph_components_by_name: DefaultDict[Name, List[GraphComponent]]
+    _components_by_id: Dict[GraphID, GraphComponent]
+    _components_by_name: DefaultDict[Name, List[GraphComponent]]
+    _components_dfs_order: List[GraphComponent]
+    _operations_dfs_order: List[Operation]
+    _operations_topological_order: List[Operation]
     _graph_id_generator: GraphIDGenerator
+    _input_operations: List[Input]
+    _output_operations: List[Output]
+    _original_components_to_new: MutableSet[GraphComponent]
+    _original_input_signals_to_indices: Dict[Signal, int]
+    _original_output_signals_to_indices: Dict[Signal, int]
+    _precedence_list: Optional[List[List[OutputPort]]]
+
+    def __init__(self, inputs: Optional[Sequence[Input]] = None, outputs: Optional[Sequence[Output]] = None,
+                 input_signals: Optional[Sequence[Signal]] = None, output_signals: Optional[Sequence[Signal]] = None,
+                 id_number_offset: GraphIDNumber = 0, name: Name = "",
+                 input_sources: Optional[Sequence[Optional[SignalSourceProvider]]] = None):
+        """Construct an SFG given its inputs and outputs.
+
+        Inputs/outputs may be specified using either Input/Output operations
+        directly with the inputs/outputs parameters, or using signals with the
+        input_signals/output_signals parameters. If signals are used, the
+        corresponding Input/Output operations will be created automatically.
+
+        The id_number_offset parameter specifies what number graph IDs will be
+        offset by for each new graph component type. IDs start at 1 by default,
+        so the default offset of 0 will result in IDs like "c1", "c2", etc.
+        while an offset of 3 will result in "c4", "c5", etc.
+        """
+
+        input_signal_count = 0 if input_signals is None else len(input_signals)
+        input_operation_count = 0 if inputs is None else len(inputs)
+        output_signal_count = 0 if output_signals is None else len(
+            output_signals)
+        output_operation_count = 0 if outputs is None else len(outputs)
+        super().__init__(input_count=input_signal_count + input_operation_count,
+                         output_count=output_signal_count + output_operation_count,
+                         name=name, input_sources=input_sources)
+
+        self._components_by_id = dict()
+        self._components_by_name = defaultdict(list)
+        self._components_dfs_order = []
+        self._operations_dfs_order = []
+        self._operations_topological_order = []
+        self._graph_id_generator = GraphIDGenerator(id_number_offset)
+        self._input_operations = []
+        self._output_operations = []
+        self._original_components_to_new = {}
+        self._original_input_signals_to_indices = {}
+        self._original_output_signals_to_indices = {}
+        self._precedence_list = None
+
+        # Setup input signals.
+        if input_signals is not None:
+            for input_index, signal in enumerate(input_signals):
+                assert signal not in self._original_components_to_new, "Duplicate input signals supplied to SFG construcctor."
+                new_input_op = self._add_component_unconnected_copy(Input())
+                new_signal = self._add_component_unconnected_copy(signal)
+                new_signal.set_source(new_input_op.output(0))
+                self._input_operations.append(new_input_op)
+                self._original_input_signals_to_indices[signal] = input_index
 
-    def __init__(self, input_signals: List[Signal] = None, output_signals: List[Signal] = None, \
-                ops: List[Operation] = None, **kwds):
-        super().__init__(**kwds)
-        if input_signals is None:
-            input_signals = []
-        if output_signals is None:
-            output_signals = []
-        if ops is None:
-            ops = []
+        # Setup input operations, starting from indices ater input signals.
+        if inputs is not None:
+            for input_index, input_op in enumerate(inputs, input_signal_count):
+                assert input_op not in self._original_components_to_new, "Duplicate input operations supplied to SFG constructor."
+                new_input_op = self._add_component_unconnected_copy(input_op)
+                for signal in input_op.output(0).signals:
+                    assert signal not in self._original_components_to_new, "Duplicate input signals connected to input ports supplied to SFG construcctor."
+                    new_signal = self._add_component_unconnected_copy(signal)
+                    new_signal.set_source(new_input_op.output(0))
+                    self._original_input_signals_to_indices[signal] = input_index
 
-        self._graph_components_by_id = dict() # Maps Graph ID to objects
-        self._graph_components_by_name = defaultdict(list) # Maps Name to objects
-        self._graph_id_generator = GraphIDGenerator()
+                self._input_operations.append(new_input_op)
 
-        for operation in ops:
-            self._add_graph_component(operation)
+        # Setup output signals.
+        if output_signals is not None:
+            for output_index, signal in enumerate(output_signals):
+                new_output_op = self._add_component_unconnected_copy(Output())
+                if signal in self._original_components_to_new:
+                    # Signal was already added when setting up inputs.
+                    new_signal = self._original_components_to_new[signal]
+                    new_signal.set_destination(new_output_op.input(0))
+                else:
+                    # New signal has to be created.
+                    new_signal = self._add_component_unconnected_copy(signal)
+                    new_signal.set_destination(new_output_op.input(0))
 
-        for input_signal in input_signals:
-            self._add_graph_component(input_signal)
+                self._output_operations.append(new_output_op)
+                self._original_output_signals_to_indices[signal] = output_index
 
-        # TODO: Construct SFG based on what inputs that were given
-        # TODO: Traverse the graph between the inputs/outputs and add to self._operations.
-        # TODO: Connect ports with signals with appropriate IDs.
+        # Setup output operations, starting from indices after output signals.
+        if outputs is not None:
+            for output_index, output_op in enumerate(outputs, output_signal_count):
+                assert output_op not in self._original_components_to_new, "Duplicate output operations supplied to SFG constructor."
+                new_output_op = self._add_component_unconnected_copy(output_op)
+                for signal in output_op.input(0).signals:
+                    new_signal = None
+                    if signal in self._original_components_to_new:
+                        # Signal was already added when setting up inputs.
+                        new_signal = self._original_components_to_new[signal]
+                    else:
+                        # New signal has to be created.
+                        new_signal = self._add_component_unconnected_copy(
+                            signal)
 
-    def evaluate(self, *inputs) -> list:
-        return [] # TODO: Implement
+                    new_signal.set_destination(new_output_op.input(0))
+                    self._original_output_signals_to_indices[signal] = output_index
 
-    def _add_graph_component(self, graph_component: GraphComponent) -> GraphID:
-        """Add the entered graph component to the SFG's dictionary of graph objects and
-         return a generated GraphID for it.
+                self._output_operations.append(new_output_op)
+
+        output_operations_set = set(self._output_operations)
+
+        # Search the graph inwards from each input signal.
+        for signal, input_index in self._original_input_signals_to_indices.items():
+            # Check if already added destination.
+            new_signal = self._original_components_to_new[signal]
+            if new_signal.destination is None:
+                if signal.destination is None:
+                    raise ValueError(
+                        f"Input signal #{input_index} is missing destination in SFG")
+                if signal.destination.operation not in self._original_components_to_new:
+                    self._add_operation_connected_tree_copy(
+                        signal.destination.operation)
+            elif new_signal.destination.operation in output_operations_set:
+                # Add directly connected input to output to ordered list.
+                self._components_dfs_order.extend(
+                    [new_signal.source.operation, new_signal, new_signal.destination.operation])
+                self._operations_dfs_order.extend(
+                    [new_signal.source.operation, new_signal.destination.operation])
+
+        # Search the graph inwards from each output signal.
+        for signal, output_index in self._original_output_signals_to_indices.items():
+            # Check if already added source.
+            new_signal = self._original_components_to_new[signal]
+            if new_signal.source is None:
+                if signal.source is None:
+                    raise ValueError(
+                        f"Output signal #{output_index} is missing source in SFG")
+                if signal.source.operation not in self._original_components_to_new:
+                    self._add_operation_connected_tree_copy(
+                        signal.source.operation)
+
+    def __str__(self) -> str:
+        """Get a string representation of this SFG."""
+        string_io = StringIO()
+        string_io.write(super().__str__() + "\n")
+        string_io.write("Internal Operations:\n")
+        line = "-" * 100 + "\n"
+        string_io.write(line)
+
+        for operation in self.get_operations_topological_order():
+            string_io.write(str(operation) + "\n")
+
+        string_io.write(line)
+
+        return string_io.getvalue()
+
+    def __call__(self, *src: Optional[SignalSourceProvider], name: Name = "") -> "SFG":
+        """Get a new independent SFG instance that is identical to this SFG except without any of its external connections."""
+        return SFG(inputs=self._input_operations, outputs=self._output_operations,
+                   id_number_offset=self.id_number_offset, name=name, input_sources=src if src else None)
+
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "sfg"
+
+    def evaluate(self, *args):
+        result = self.evaluate_outputs(args)
+        n = len(result)
+        return None if n == 0 else result[0] if n == 1 else result
+
+    def evaluate_output(self, index: int, input_values: Sequence[Number], results: Optional[MutableResultMap] = None, delays: Optional[MutableDelayMap] = None, prefix: str = "", bits_override: Optional[int] = None, truncate: bool = True) -> Number:
+        if index < 0 or index >= self.output_count:
+            raise IndexError(
+                f"Output index out of range (expected 0-{self.output_count - 1}, got {index})")
+        if len(input_values) != self.input_count:
+            raise ValueError(
+                f"Wrong number of inputs supplied to SFG for evaluation (expected {self.input_count}, got {len(input_values)})")
+        if results is None:
+            results = {}
+        if delays is None:
+            delays = {}
+
+        # Set the values of our input operations to the given input values.
+        for op, arg in zip(self._input_operations, self.truncate_inputs(input_values, bits_override) if truncate else input_values):
+            op.value = arg
+
+        deferred_delays = []
+        value = self._evaluate_source(self._output_operations[index].input(
+            0).signals[0].source, results, delays, prefix, bits_override, truncate, deferred_delays)
+        while deferred_delays:
+            new_deferred_delays = []
+            for key_base, key, src in deferred_delays:
+                self._do_evaluate_source(
+                    key_base, key, src, results, delays, prefix, bits_override, truncate, new_deferred_delays)
+            deferred_delays = new_deferred_delays
+        results[self.key(index, prefix)] = value
+        return value
+
+    def connect_external_signals_to_components(self) -> bool:
+        """ Connects any external signals to this SFG's internal operations. This SFG becomes unconnected to the SFG
+        it is a component off, causing it to become invalid afterwards. Returns True if succesful, False otherwise. """
+        if len(self.inputs) != len(self.input_operations):
+            raise IndexError(
+                f"Number of inputs does not match the number of input_operations in SFG.")
+        if len(self.outputs) != len(self.output_operations):
+            raise IndexError(
+                f"Number of outputs does not match the number of output_operations SFG.")
+        if len(self.input_signals) == 0:
+            return False
+        if len(self.output_signals) == 0:
+            return False
+
+        # For each input_signal, connect it to the corresponding operation
+        for port, input_operation in zip(self.inputs, self.input_operations):
+            dest = input_operation.output(0).signals[0].destination
+            dest.clear()
+            port.signals[0].set_destination(dest)
+        # For each output_signal, connect it to the corresponding operation
+        for port, output_operation in zip(self.outputs, self.output_operations):
+            src = output_operation.input(0).signals[0].source
+            src.clear()
+            port.signals[0].set_source(src)
+        return True
+
+    @property
+    def input_operations(self) -> Sequence[Operation]:
+        """Get the internal input operations in the same order as their respective input ports."""
+        return self._input_operations
+
+    @property
+    def output_operations(self) -> Sequence[Operation]:
+        """Get the internal output operations in the same order as their respective output ports."""
+        return self._output_operations
+
+    def split(self) -> Iterable[Operation]:
+        return self.operations
+
+    def to_sfg(self) -> 'SFG':
+        return self
+
+    def inputs_required_for_output(self, output_index: int) -> Iterable[int]:
+        if output_index < 0 or output_index >= self.output_count:
+            raise IndexError(
+                f"Output index out of range (expected 0-{self.output_count - 1}, got {output_index})")
+
+        input_indexes_required = []
+        sfg_input_operations_to_indexes = {
+            input_op: index for index, input_op in enumerate(self._input_operations)}
+        output_op = self._output_operations[output_index]
+        queue = deque([output_op])
+        visited = set([output_op])
+        while queue:
+            op = queue.popleft()
+            if isinstance(op, Input):
+                if op in sfg_input_operations_to_indexes:
+                    input_indexes_required.append(
+                        sfg_input_operations_to_indexes[op])
+                    del sfg_input_operations_to_indexes[op]
+
+            for input_port in op.inputs:
+                for signal in input_port.signals:
+                    if signal.source is not None:
+                        new_op = signal.source.operation
+                        if new_op not in visited:
+                            queue.append(new_op)
+                            visited.add(new_op)
+
+        return input_indexes_required
+
+    def copy_component(self, *args, **kwargs) -> GraphComponent:
+        return super().copy_component(*args, **kwargs, inputs=self._input_operations, outputs=self._output_operations,
+                                      id_number_offset=self.id_number_offset, name=self.name)
+
+    @property
+    def id_number_offset(self) -> GraphIDNumber:
+        """Get the graph id number offset of the graph id generator for this SFG."""
+        return self._graph_id_generator.id_number_offset
+
+    @property
+    def components(self) -> Iterable[GraphComponent]:
+        """Get all components of this graph in depth-first order."""
+        return self._components_dfs_order
+
+    @property
+    def operations(self) -> Iterable[Operation]:
+        """Get all operations of this graph in depth-first order."""
+        return self._operations_dfs_order
+
+    def find_by_type_name(self, type_name: TypeName) -> Sequence[GraphComponent]:
+        """Find all components in this graph with the specified type name.
+        Returns an empty sequence if no components were found.
 
         Keyword arguments:
-        graph_component: Graph component to add to the graph.
+        type_name: The type_name of the desired components.
         """
-        # Add to name dict
-        self._graph_components_by_name[graph_component.name].append(graph_component)
+        i = self.id_number_offset + 1
+        components = []
+        found_comp = self.find_by_id(type_name + str(i))
+        while found_comp is not None:
+            components.append(found_comp)
+            i += 1
+            found_comp = self.find_by_id(type_name + str(i))
 
-        # Add to ID dict
-        graph_id: GraphID = self._graph_id_generator.get_next_id(graph_component.type_name)
-        self._graph_components_by_id[graph_id] = graph_component
-        return graph_id
+        return components
 
     def find_by_id(self, graph_id: GraphID) -> Optional[GraphComponent]:
-        """Find a graph object based on the entered Graph ID and return it. If no graph
-        object with the entered ID was found then return None.
+        """Find the graph component with the specified ID.
+        Returns None if the component was not found.
 
         Keyword arguments:
-        graph_id: Graph ID of the wanted object.
+        graph_id: Graph ID of the desired component.
         """
-        if graph_id in self._graph_components_by_id:
-            return self._graph_components_by_id[graph_id]
+        return self._components_by_id.get(graph_id, None)
 
-        return None
+    def find_by_name(self, name: Name) -> Sequence[GraphComponent]:
+        """Find all graph components with the specified name.
+        Returns an empty sequence if no components were found.
 
-    def find_by_name(self, name: Name) -> List[GraphComponent]:
-        """Find all graph objects that have the entered name and return them
-        in a list. If no graph object with the entered name was found then return an
-        empty list.
+        Keyword arguments:
+        name: Name of the desired component(s)
+        """
+        return self._components_by_name.get(name, [])
+
+    def find_result_keys_by_name(self, name: Name, output_index: int = 0) -> Sequence[ResultKey]:
+        """Find all graph components with the specified name and
+        return a sequence of the keys to use when fetching their results
+        from a simulation.
 
         Keyword arguments:
-        name: Name of the wanted object.
+        name: Name of the desired component(s)
+        output_index: The desired output index to get the result from
         """
-        return self._graph_components_by_name[name]
+        keys = []
+        for comp in self.find_by_name(name):
+            if isinstance(comp, Operation):
+                keys.append(comp.key(output_index, comp.graph_id))
+        return keys
 
-    @property
-    def type_name(self) -> TypeName:
-        return "sfg"
+    def replace_component(self, component: Operation, graph_id: GraphID) -> "SFG":
+        """Find and replace all components matching either on GraphID, Type or both.
+        Then return a new deepcopy of the sfg with the replaced component.
+
+        Arguments:
+        component: The new component(s), e.g Multiplication
+        graph_id: The GraphID to match the component to replace.
+        """
+
+        sfg_copy = self()  # Copy to not mess with this SFG.
+        component_copy = sfg_copy.find_by_id(graph_id)
+
+        assert component_copy is not None and isinstance(component_copy, Operation), \
+            "No operation matching the criteria found"
+        assert component_copy.output_count == component.output_count, \
+            "The output count may not differ between the operations"
+        assert component_copy.input_count == component.input_count, \
+            "The input count may not differ between the operations"
+
+        for index_in, inp in enumerate(component_copy.inputs):
+            for signal in inp.signals:
+                signal.remove_destination()
+                signal.set_destination(component.input(index_in))
+
+        for index_out, outp in enumerate(component_copy.outputs):
+            for signal in outp.signals:
+                signal.remove_source()
+                signal.set_source(component.output(index_out))
+
+        return sfg_copy()  # Copy again to update IDs.
+
+    def insert_operation(self, component: Operation, output_comp_id: GraphID) -> Optional["SFG"]:
+        """Insert an operation in the SFG after a given source operation.
+        The source operation output count must match the input count of the operation as well as the output
+        Then return a new deepcopy of the sfg with the inserted component.
+
+        Arguments:
+        component: The new component, e.g Multiplication.
+        output_comp_id: The source operation GraphID to connect from.
+        """
+
+        # Preserve the original SFG by creating a copy.
+        sfg_copy = self()
+        output_comp = sfg_copy.find_by_id(output_comp_id)
+        if output_comp is None:
+            return None
+
+        assert not isinstance(output_comp, Output), \
+            "Source operation can not be an output operation."
+        assert len(output_comp.output_signals) == component.input_count, \
+            "Source operation output count does not match input count for component."
+        assert len(output_comp.output_signals) == component.output_count, \
+            "Destination operation input count does not match output for component."
+
+        for index, signal_in in enumerate(output_comp.output_signals):
+            destination = signal_in.destination
+            signal_in.set_destination(component.input(index))
+            destination.connect(component.output(index))
+
+        # Recreate the newly coupled SFG so that all attributes are correct.
+        return sfg_copy()
+
+    def remove_operation(self, operation_id: GraphID) -> "SFG":
+        """Returns a version of the SFG where the operation with the specified GraphID removed.
+        The operation has to have the same amount of input- and output ports or a ValueError will 
+        be raised. If no operation with the entered operation_id is found then returns None and does nothing."""
+        sfg_copy = self()
+        operation = sfg_copy.find_by_id(operation_id)
+        if operation is None:
+            return None
+
+        if operation.input_count != operation.output_count:
+            raise ValueError(
+                "Different number of input and output ports of operation with the specified id")
+
+        for i, outport in enumerate(operation.outputs):
+            if outport.signal_count > 0:
+                if operation.input(i).signal_count > 0 and operation.input(i).signals[0].source is not None:
+                    in_sig = operation.input(i).signals[0]
+                    source_port = in_sig.source
+                    source_port.remove_signal(in_sig)
+                    operation.input(i).remove_signal(in_sig)
+                    for out_sig in outport.signals.copy():
+                        out_sig.set_source(source_port)
+                else:
+                    for out_sig in outport.signals.copy():
+                        out_sig.remove_source()
+            else:
+                if operation.input(i).signal_count > 0:
+                    in_sig = operation.input(i).signals[0]
+                    operation.input(i).remove_signal(in_sig)
+
+        return sfg_copy()
+
+    def get_precedence_list(self) -> Sequence[Sequence[OutputPort]]:
+        """Returns a Precedence list of the SFG where each element in n:th the list consists
+        of elements that are executed in the n:th step. If the precedence list already has been
+        calculated for the current SFG then returns the cached version."""
+        if self._precedence_list:
+            return self._precedence_list
+
+        # Find all operations with only outputs and no inputs.
+        no_input_ops = list(
+            filter(lambda op: op.input_count == 0, self.operations))
+        delay_ops = self.find_by_type_name(Delay.type_name())
+
+        # Find all first iter output ports for precedence
+        first_iter_ports = [op.output(i) for op in (
+            no_input_ops + delay_ops) for i in range(op.output_count)]
+
+        self._precedence_list = self._traverse_for_precedence_list(
+            first_iter_ports)
+
+        return self._precedence_list
+
+    def show_precedence_graph(self) -> None:
+        p_list = self.get_precedence_list()
+        pg = Digraph()
+        pg.attr(rankdir='LR')
+
+        # Creates nodes for each output port in the precedence list
+        for i in range(len(p_list)):
+            ports = p_list[i]
+            with pg.subgraph(name='cluster_' + str(i)) as sub:
+                sub.attr(label='N' + str(i + 1))
+                for port in ports:
+                    sub.node(port.operation.graph_id + '.' + str(port.index))
+        # Creates edges for each output port and creates nodes for each operation and edges for them as well
+        for i in range(len(p_list)):
+            ports = p_list[i]
+            for port in ports:
+                for signal in port.signals:
+                    pg.edge(port.operation.graph_id + '.' + str(port.index),
+                            signal.destination.operation.graph_id)
+                    pg.node(signal.destination.operation.graph_id,
+                            shape='square')
+                pg.edge(port.operation.graph_id,
+                        port.operation.graph_id + '.' + str(port.index))
+                pg.node(port.operation.graph_id, shape='square')
+
+        pg.view()
+
+    def print_precedence_graph(self) -> None:
+        """Prints a representation of the SFG's precedence list to the standard out.
+        If the precedence list already has been calculated then it uses the cached version,
+        otherwise it calculates the precedence list and then prints it."""
+        precedence_list = self.get_precedence_list()
+
+        line = "-" * 120
+        out_str = StringIO()
+        out_str.write(line)
+
+        printed_ops = set()
+
+        for iter_num, iter in enumerate(precedence_list, start=1):
+            for outport_num, outport in enumerate(iter, start=1):
+                if outport not in printed_ops:
+                    # Only print once per operation, even if it has multiple outports
+                    out_str.write("\n")
+                    out_str.write(str(iter_num))
+                    out_str.write(".")
+                    out_str.write(str(outport_num))
+                    out_str.write(" \t")
+                    out_str.write(str(outport.operation))
+                    printed_ops.add(outport)
+
+            out_str.write("\n")
+            out_str.write(line)
+
+        print(out_str.getvalue())
+
+    def get_operations_topological_order(self) -> Iterable[Operation]:
+        """Returns an Iterable of the Operations in the SFG in Topological Order.
+        Feedback loops makes an absolutely correct Topological order impossible, so an 
+        approximative Topological Order is returned in such cases in this implementation."""
+        if self._operations_topological_order:
+            return self._operations_topological_order
+
+        no_inputs_queue = deque(
+            list(filter(lambda op: op.input_count == 0, self.operations)))
+        remaining_inports_per_operation = {
+            op: op.input_count for op in self.operations}
+
+        # Maps number of input counts to a queue of seen objects with such a size.
+        seen_with_inputs_dict = defaultdict(deque)
+        seen = set()
+        top_order = []
+
+        assert len(
+            no_inputs_queue) > 0, "Illegal SFG state, dangling signals in SFG."
+
+        first_op = no_inputs_queue.popleft()
+        visited = set([first_op])
+        p_queue = PriorityQueue()
+        p_queue_entry_num = it.count()
+        # Negative priority as max-heap popping is wanted
+        p_queue.put((-first_op.output_count, -
+                     next(p_queue_entry_num), first_op))
+
+        operations_left = len(self.operations) - 1
+
+        seen_but_not_visited_count = 0
+
+        while operations_left > 0:
+            while not p_queue.empty():
+
+                op = p_queue.get()[2]
+
+                operations_left -= 1
+                top_order.append(op)
+                visited.add(op)
+
+                for neighbor_op in op.subsequent_operations:
+                    if neighbor_op not in visited:
+                        remaining_inports_per_operation[neighbor_op] -= 1
+                        remaining_inports = remaining_inports_per_operation[neighbor_op]
+
+                        if remaining_inports == 0:
+                            p_queue.put(
+                                (-neighbor_op.output_count, -next(p_queue_entry_num), neighbor_op))
+
+                        elif remaining_inports > 0:
+                            if neighbor_op in seen:
+                                seen_with_inputs_dict[remaining_inports +
+                                                      1].remove(neighbor_op)
+                            else:
+                                seen.add(neighbor_op)
+                                seen_but_not_visited_count += 1
+
+                            seen_with_inputs_dict[remaining_inports].append(
+                                neighbor_op)
+
+            # Check if have to fetch Operations from somewhere else since p_queue is empty
+            if operations_left > 0:
+                # First check if can fetch from Operations with no input ports
+                if no_inputs_queue:
+                    new_op = no_inputs_queue.popleft()
+                    p_queue.put((-new_op.output_count, -
+                                 next(p_queue_entry_num), new_op))
+
+                # Else fetch operation with lowest input count that is not zero
+                elif seen_but_not_visited_count > 0:
+                    for i in it.count(start=1):
+                        seen_inputs_queue = seen_with_inputs_dict[i]
+                        if seen_inputs_queue:
+                            new_op = seen_inputs_queue.popleft()
+                            p_queue.put((-new_op.output_count, -
+                                         next(p_queue_entry_num), new_op))
+                            seen_but_not_visited_count -= 1
+                            break
+                else:
+                    raise RuntimeError("Unallowed structure in SFG detected")
+
+        self._operations_topological_order = top_order
+        return self._operations_topological_order
+
+    def set_latency_of_type(self, type_name: TypeName, latency: int) -> None:
+        """Set the latency of all components with the given type name."""
+        for op in self.find_by_type_name(type_name):
+            op.set_latency(latency)
+
+    def set_latency_offsets_of_type(self, type_name: TypeName, latency_offsets: Dict[str, int]) -> None:
+        """Set the latency offset of all components with the given type name."""
+        for op in self.find_by_type_name(type_name):
+            op.set_latency_offsets(latency_offsets)
+
+    def _traverse_for_precedence_list(self, first_iter_ports: List[OutputPort]) -> List[List[OutputPort]]:
+        # Find dependencies of output ports and input ports.
+        remaining_inports_per_operation = {
+            op: op.input_count for op in self.operations}
+
+        # Traverse output ports for precedence
+        curr_iter_ports = first_iter_ports
+        precedence_list = []
+
+        while curr_iter_ports:
+            # Add the found ports to the current iter
+            precedence_list.append(curr_iter_ports)
+
+            next_iter_ports = []
+
+            for outport in curr_iter_ports:
+                for signal in outport.signals:
+                    new_inport = signal.destination
+                    # Don't traverse over delays.
+                    if new_inport is not None and not isinstance(new_inport.operation, Delay):
+                        new_op = new_inport.operation
+                        remaining_inports_per_operation[new_op] -= 1
+                        if remaining_inports_per_operation[new_op] == 0:
+                            next_iter_ports.extend(new_op.outputs)
+
+            curr_iter_ports = next_iter_ports
+
+        return precedence_list
+
+    def _add_component_unconnected_copy(self, original_component: GraphComponent) -> GraphComponent:
+        assert original_component not in self._original_components_to_new, "Tried to add duplicate SFG component"
+        new_component = original_component.copy_component()
+        self._original_components_to_new[original_component] = new_component
+        new_id = self._graph_id_generator.next_id(new_component.type_name())
+        new_component.graph_id = new_id
+        self._components_by_id[new_id] = new_component
+        self._components_by_name[new_component.name].append(new_component)
+        return new_component
+
+    def _add_operation_connected_tree_copy(self, start_op: Operation) -> None:
+        op_stack = deque([start_op])
+        while op_stack:
+            original_op = op_stack.pop()
+            # Add or get the new copy of the operation.
+            new_op = None
+            if original_op not in self._original_components_to_new:
+                new_op = self._add_component_unconnected_copy(original_op)
+                self._components_dfs_order.append(new_op)
+                self._operations_dfs_order.append(new_op)
+            else:
+                new_op = self._original_components_to_new[original_op]
+
+            # Connect input ports to new signals.
+            for original_input_port in original_op.inputs:
+                if original_input_port.signal_count < 1:
+                    raise ValueError("Unconnected input port in SFG")
+
+                for original_signal in original_input_port.signals:
+                    # Check if the signal is one of the SFG's input signals.
+                    if original_signal in self._original_input_signals_to_indices:
+                        # New signal already created during first step of constructor.
+                        new_signal = self._original_components_to_new[original_signal]
+                        new_signal.set_destination(
+                            new_op.input(original_input_port.index))
+
+                        self._components_dfs_order.extend(
+                            [new_signal, new_signal.source.operation])
+                        self._operations_dfs_order.append(
+                            new_signal.source.operation)
+
+                    # Check if the signal has not been added before.
+                    elif original_signal not in self._original_components_to_new:
+                        if original_signal.source is None:
+                            raise ValueError(
+                                "Dangling signal without source in SFG")
+
+                        new_signal = self._add_component_unconnected_copy(
+                            original_signal)
+                        new_signal.set_destination(
+                            new_op.input(original_input_port.index))
+
+                        self._components_dfs_order.append(new_signal)
+
+                        original_connected_op = original_signal.source.operation
+                        # Check if connected Operation has been added before.
+                        if original_connected_op in self._original_components_to_new:
+                            # Set source to the already added operations port.
+                            new_signal.set_source(self._original_components_to_new[original_connected_op].output(
+                                original_signal.source.index))
+                        else:
+                            # Create new operation, set signal source to it.
+                            new_connected_op = self._add_component_unconnected_copy(
+                                original_connected_op)
+                            new_signal.set_source(new_connected_op.output(
+                                original_signal.source.index))
+
+                            self._components_dfs_order.append(new_connected_op)
+                            self._operations_dfs_order.append(new_connected_op)
+
+                            # Add connected operation to queue of operations to visit.
+                            op_stack.append(original_connected_op)
+
+            # Connect output ports.
+            for original_output_port in original_op.outputs:
+                for original_signal in original_output_port.signals:
+                    # Check if the signal is one of the SFG's output signals.
+                    if original_signal in self._original_output_signals_to_indices:
+                        # New signal already created during first step of constructor.
+                        new_signal = self._original_components_to_new[original_signal]
+                        new_signal.set_source(
+                            new_op.output(original_output_port.index))
+
+                        self._components_dfs_order.extend(
+                            [new_signal, new_signal.destination.operation])
+                        self._operations_dfs_order.append(
+                            new_signal.destination.operation)
+
+                    # Check if signal has not been added before.
+                    elif original_signal not in self._original_components_to_new:
+                        if original_signal.source is None:
+                            raise ValueError(
+                                "Dangling signal without source in SFG")
+
+                        new_signal = self._add_component_unconnected_copy(
+                            original_signal)
+                        new_signal.set_source(
+                            new_op.output(original_output_port.index))
+
+                        self._components_dfs_order.append(new_signal)
+
+                        original_connected_op = original_signal.destination.operation
+                        # Check if connected operation has been added.
+                        if original_connected_op in self._original_components_to_new:
+                            # Set destination to the already connected operations port.
+                            new_signal.set_destination(self._original_components_to_new[original_connected_op].input(
+                                original_signal.destination.index))
+                        else:
+                            # Create new operation, set destination to it.
+                            new_connected_op = self._add_component_unconnected_copy(
+                                original_connected_op)
+                            new_signal.set_destination(new_connected_op.input(
+                                original_signal.destination.index))
+
+                            self._components_dfs_order.append(new_connected_op)
+                            self._operations_dfs_order.append(new_connected_op)
+
+                            # Add connected operation to the queue of operations to visit.
+                            op_stack.append(original_connected_op)
+
+    def _evaluate_source(self, src: OutputPort, results: MutableResultMap, delays: MutableDelayMap, prefix: str, bits_override: Optional[int], truncate: bool, deferred_delays: DelayQueue) -> Number:
+        key_base = (
+            prefix + "." + src.operation.graph_id) if prefix else src.operation.graph_id
+        key = src.operation.key(src.index, key_base)
+        if key in results:
+            value = results[key]
+            if value is None:
+                raise RuntimeError(
+                    "Direct feedback loop detected when evaluating operation.")
+            return value
+
+        value = src.operation.current_output(src.index, delays, key_base)
+        results[key] = value
+        if value is None:
+            value = self._do_evaluate_source(
+                key_base, key, src, results, delays, prefix, bits_override, truncate, deferred_delays)
+        else:
+            # Evaluate later. Use current value for now.
+            deferred_delays.append((key_base, key, src))
+        return value
+
+    def _do_evaluate_source(self, key_base: str, key: ResultKey, src: OutputPort, results: MutableResultMap, delays: MutableDelayMap, prefix: str, bits_override: Optional[int], truncate: bool, deferred_delays: DelayQueue) -> Number:
+        input_values = [self._evaluate_source(input_port.signals[0].source, results, delays, prefix,
+                                              bits_override, truncate, deferred_delays) for input_port in src.operation.inputs]
+        value = src.operation.evaluate_output(
+            src.index, input_values, results, delays, key_base, bits_override, truncate)
+        results[key] = value
+        return value
diff --git a/b_asic/simulation.py b/b_asic/simulation.py
index 50adaa52..5fece8d7 100644
--- a/b_asic/simulation.py
+++ b/b_asic/simulation.py
@@ -1,35 +1,125 @@
-"""@package docstring
-B-ASIC Simulation Module.
-TODO: More info.
+"""B-ASIC Simulation Module.
+
+Contains a class for simulating the result of an SFG given a set of input values.
 """
 
+import numpy as np
+
+from collections import defaultdict
 from numbers import Number
-from typing import List
+from typing import List, Dict, DefaultDict, Callable, Sequence, Mapping, Union, Optional, MutableSequence, MutableMapping
 
+from b_asic.operation import ResultKey, ResultMap, MutableResultMap, MutableDelayMap
+from b_asic.signal_flow_graph import SFG
 
-class OperationState:
-    """Simulation state of an operation.
-    TODO: More info.
-    """
 
-    output_values: List[Number]
-    iteration: int
+ResultArrayMap = Mapping[ResultKey, Sequence[Number]]
+MutableResultArrayMap = MutableMapping[ResultKey, MutableSequence[Number]]
+InputFunction = Callable[[int], Number]
+InputProvider = Union[Number, Sequence[Number], InputFunction]
 
-    def __init__(self):
-        self.output_values = []
-        self.iteration = 0
 
+class Simulation:
+    """Simulation of an SFG.
 
-class SimulationState:
-    """Simulation state.
-    TODO: More info.
+    Use FastSimulation (from the C++ extension module) for a more effective
+    simulation when running many iterations.
     """
 
-    # operation_states: Dict[OperationId, OperationState]
-    iteration: int
+    _sfg: SFG
+    _results: MutableResultArrayMap
+    _delays: MutableDelayMap
+    _iteration: int
+    _input_functions: Sequence[InputFunction]
+    _input_length: Optional[int]
+
+    def __init__(self, sfg: SFG, input_providers: Optional[Sequence[Optional[InputProvider]]] = None):
+        """Construct a Simulation of an SFG."""
+        self._sfg = sfg()  # Copy the SFG to make sure it's not modified from the outside.
+        self._results = defaultdict(list)
+        self._delays = {}
+        self._iteration = 0
+        self._input_functions = [
+            lambda _: 0 for _ in range(self._sfg.input_count)]
+        self._input_length = None
+        if input_providers is not None:
+            self.set_inputs(input_providers)
+
+    def set_input(self, index: int, input_provider: InputProvider) -> None:
+        """Set the input function used to get values for the specific input at the given index to the internal SFG."""
+        if index < 0 or index >= len(self._input_functions):
+            raise IndexError(
+                f"Input index out of range (expected 0-{len(self._input_functions) - 1}, got {index})")
+        if callable(input_provider):
+            self._input_functions[index] = input_provider
+        elif isinstance(input_provider, Number):
+            self._input_functions[index] = lambda _: input_provider
+        else:
+            if self._input_length is None:
+                self._input_length = len(input_provider)
+            elif self._input_length != len(input_provider):
+                raise ValueError(
+                    f"Inconsistent input length for simulation (was {self._input_length}, got {len(input_provider)})")
+            self._input_functions[index] = lambda n: input_provider[n]
+
+    def set_inputs(self, input_providers: Sequence[Optional[InputProvider]]) -> None:
+        """Set the input functions used to get values for the inputs to the internal SFG."""
+        if len(input_providers) != self._sfg.input_count:
+            raise ValueError(
+                f"Wrong number of inputs supplied to simulation (expected {self._sfg.input_count}, got {len(input_providers)})")
+        for index, input_provider in enumerate(input_providers):
+            if input_provider is not None:
+                self.set_input(index, input_provider)
+
+    def step(self, save_results: bool = True, bits_override: Optional[int] = None, truncate: bool = True) -> Sequence[Number]:
+        """Run one iteration of the simulation and return the resulting output values."""
+        return self.run_for(1, save_results, bits_override, truncate)
+
+    def run_until(self, iteration: int, save_results: bool = True, bits_override: Optional[int] = None, truncate: bool = True) -> Sequence[Number]:
+        """Run the simulation until its iteration is greater than or equal to the given iteration
+        and return the output values of the last iteration.
+        """
+        result = []
+        while self._iteration < iteration:
+            input_values = [self._input_functions[i](
+                self._iteration) for i in range(self._sfg.input_count)]
+            results = {}
+            result = self._sfg.evaluate_outputs(
+                input_values, results, self._delays, "", bits_override, truncate)
+            if save_results:
+                for key, value in results.items():
+                    self._results[key].append(value)
+            self._iteration += 1
+        return result
+
+    def run_for(self, iterations: int, save_results: bool = True, bits_override: Optional[int] = None, truncate: bool = True) -> Sequence[Number]:
+        """Run a given number of iterations of the simulation and return the output values of the last iteration."""
+        return self.run_until(self._iteration + iterations, save_results, bits_override, truncate)
+
+    def run(self, save_results: bool = True, bits_override: Optional[int] = None, truncate: bool = True) -> Sequence[Number]:
+        """Run the simulation until the end of its input arrays and return the output values of the last iteration."""
+        if self._input_length is None:
+            raise IndexError("Tried to run unlimited simulation")
+        return self.run_until(self._input_length, save_results, bits_override, truncate)
+
+    @property
+    def iteration(self) -> int:
+        """Get the current iteration number of the simulation."""
+        return self._iteration
+
+    @property
+    def results(self) -> ResultArrayMap:
+        """Get a mapping from result keys to numpy arrays containing all results, including intermediate values,
+        calculated for each iteration up until now that was run with save_results enabled.
+        The mapping is indexed using the key() method of Operation with the appropriate output index.
+        Example result after 3 iterations: {"c1": [3, 6, 7], "c2": [4, 5, 5], "bfly1.0": [7, 0, 0], "bfly1.1": [-1, 0, 2], "0": [7, -2, -1]}
+        """
+        return {key: np.array(value) for key, value in self._results.items()}
 
-    def __init__(self):
-        self.operation_states = {}
-        self.iteration = 0
+    def clear_results(self) -> None:
+        """Clear all results that were saved until now."""
+        self._results.clear()
 
-    # TODO: More stuff.
+    def clear_state(self) -> None:
+        """Clear all current state of the simulation, except for the results and iteration."""
+        self._delays.clear()
diff --git a/b_asic/special_operations.py b/b_asic/special_operations.py
new file mode 100644
index 00000000..dc84f0bc
--- /dev/null
+++ b/b_asic/special_operations.py
@@ -0,0 +1,117 @@
+"""B-ASIC Special Operations Module.
+
+Contains operations with special purposes that may be treated differently from
+normal operations in an SFG.
+"""
+
+from numbers import Number
+from typing import Optional, Sequence
+
+from b_asic.operation import AbstractOperation, ResultKey, DelayMap, MutableResultMap, MutableDelayMap
+from b_asic.graph_component import Name, TypeName
+from b_asic.port import SignalSourceProvider
+
+
+class Input(AbstractOperation):
+    """Input operation.
+
+    Marks an input port to an SFG.
+    Its value will be updated on each iteration when simulating the SFG.
+    """
+
+    def __init__(self, name: Name = ""):
+        """Construct an Input operation."""
+        super().__init__(input_count=0, output_count=1, name=name)
+        self.set_param("value", 0)
+
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "in"
+
+    def evaluate(self):
+        return self.param("value")
+
+    @property
+    def value(self) -> Number:
+        """Get the current value of this input."""
+        return self.param("value")
+
+    @value.setter
+    def value(self, value: Number) -> None:
+        """Set the current value of this input."""
+        self.set_param("value", value)
+
+
+class Output(AbstractOperation):
+    """Output operation.
+
+    Marks an output port to an SFG.
+    The SFG will forward its input to the corresponding output signal
+    destinations.
+    """
+
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""):
+        """Construct an Output operation."""
+        super().__init__(input_count=1, output_count=0,
+                         name=name, input_sources=[src0])
+
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "out"
+
+    def evaluate(self, _):
+        return None
+
+
+class Delay(AbstractOperation):
+    """Unit delay operation.
+
+    Represents one unit of delay in a circuit, typically a clock cycle.
+    Can be thought of as a register or a D flip-flop.
+    """
+
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, initial_value: Number = 0, name: Name = ""):
+        """Construct a Delay operation."""
+        super().__init__(input_count=1, output_count=1,
+                         name=name, input_sources=[src0])
+        self.set_param("initial_value", initial_value)
+
+    @classmethod
+    def type_name(cls) -> TypeName:
+        return "t"
+
+    def evaluate(self, a):
+        return self.param("initial_value")
+
+    def current_output(self, index: int, delays: Optional[DelayMap] = None, prefix: str = "") -> Optional[Number]:
+        if delays is not None:
+            return delays.get(self.key(index, prefix), self.param("initial_value"))
+        return self.param("initial_value")
+
+    def evaluate_output(self, index: int, input_values: Sequence[Number], results: Optional[MutableResultMap] = None, delays: Optional[MutableDelayMap] = None, prefix: str = "", bits_override: Optional[int] = None, truncate: bool = True) -> Number:
+        if index != 0:
+            raise IndexError(
+                f"Output index out of range (expected 0-0, got {index})")
+        if len(input_values) != 1:
+            raise ValueError(
+                f"Wrong number of inputs supplied to SFG for evaluation (expected 1, got {len(input_values)})")
+
+        key = self.key(index, prefix)
+        value = self.param("initial_value")
+        if delays is not None:
+            value = delays.get(key, value)
+            delays[key] = self.truncate_inputs(input_values, bits_override)[
+                0] if truncate else input_values[0]
+        if results is not None:
+            results[key] = value
+        return value
+
+    @property
+    def initial_value(self) -> Number:
+        """Get the initial value of this delay."""
+        return self.param("initial_value")
+
+    @initial_value.setter
+    def initial_value(self, value: Number) -> None:
+        """Set the initial value of this delay."""
+        self.set_param("initial_value", value)
diff --git a/legacy/README.md b/legacy/README.md
new file mode 100644
index 00000000..746d1efd
--- /dev/null
+++ b/legacy/README.md
@@ -0,0 +1,11 @@
+# Legacy files
+
+This folder contains currently unused code that is kept for acedemic purposes,
+or to be used as a refererence for future development.
+
+## simulation_oop
+
+This folder contains a C++ implementation of the Simulation class designed
+using Object-Oriented Programming, as opposed to the current version that uses
+Data-Oriented Design. They are functionally identical, but use different
+styles of programming and have different performance characteristics.
\ No newline at end of file
diff --git a/legacy/simulation_oop/core_operations.h b/legacy/simulation_oop/core_operations.h
new file mode 100644
index 00000000..09265729
--- /dev/null
+++ b/legacy/simulation_oop/core_operations.h
@@ -0,0 +1,236 @@
+#ifndef ASIC_SIMULATION_CORE_OPERATIONS_H
+#define ASIC_SIMULATION_CORE_OPERATIONS_H
+
+#define NOMINMAX
+#include "../debug.h"
+#include "../number.h"
+#include "operation.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <stdexcept>
+#include <utility>
+
+namespace asic {
+
+class constant_operation final : public abstract_operation {
+public:
+	constant_operation(result_key key, number value)
+		: abstract_operation(std::move(key))
+		, m_value(value) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const&) const final {
+		ASIC_DEBUG_MSG("Evaluating constant.");
+		return m_value;
+	}
+
+	number m_value;
+};
+
+class addition_operation final : public binary_operation {
+public:
+	explicit addition_operation(result_key key)
+		: binary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating addition.");
+		return this->evaluate_lhs(context) + this->evaluate_rhs(context);
+	}
+};
+
+class subtraction_operation final : public binary_operation {
+public:
+	explicit subtraction_operation(result_key key)
+		: binary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating subtraction.");
+		return this->evaluate_lhs(context) - this->evaluate_rhs(context);
+	}
+};
+
+class multiplication_operation final : public binary_operation {
+public:
+	explicit multiplication_operation(result_key key)
+		: binary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating multiplication.");
+		return this->evaluate_lhs(context) * this->evaluate_rhs(context);
+	}
+};
+
+class division_operation final : public binary_operation {
+public:
+	explicit division_operation(result_key key)
+		: binary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating division.");
+		return this->evaluate_lhs(context) / this->evaluate_rhs(context);
+	}
+};
+
+class min_operation final : public binary_operation {
+public:
+	explicit min_operation(result_key key)
+		: binary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating min.");
+		auto const lhs = this->evaluate_lhs(context);
+		if (lhs.imag() != 0) {
+			throw std::runtime_error{"Min does not support complex numbers."};
+		}
+		auto const rhs = this->evaluate_rhs(context);
+		if (rhs.imag() != 0) {
+			throw std::runtime_error{"Min does not support complex numbers."};
+		}
+		return std::min(lhs.real(), rhs.real());
+	}
+};
+
+class max_operation final : public binary_operation {
+public:
+	explicit max_operation(result_key key)
+		: binary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating max.");
+		auto const lhs = this->evaluate_lhs(context);
+		if (lhs.imag() != 0) {
+			throw std::runtime_error{"Max does not support complex numbers."};
+		}
+		auto const rhs = this->evaluate_rhs(context);
+		if (rhs.imag() != 0) {
+			throw std::runtime_error{"Max does not support complex numbers."};
+		}
+		return std::max(lhs.real(), rhs.real());
+	}
+};
+
+class square_root_operation final : public unary_operation {
+public:
+	explicit square_root_operation(result_key key)
+		: unary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating sqrt.");
+		return std::sqrt(this->evaluate_input(context));
+	}
+};
+
+class complex_conjugate_operation final : public unary_operation {
+public:
+	explicit complex_conjugate_operation(result_key key)
+		: unary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating conj.");
+		return std::conj(this->evaluate_input(context));
+	}
+};
+
+class absolute_operation final : public unary_operation {
+public:
+	explicit absolute_operation(result_key key)
+		: unary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating abs.");
+		return std::abs(this->evaluate_input(context));
+	}
+};
+
+class constant_multiplication_operation final : public unary_operation {
+public:
+	constant_multiplication_operation(result_key key, number value)
+		: unary_operation(std::move(key))
+		, m_value(value) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 1;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating cmul.");
+		return this->evaluate_input(context) * m_value;
+	}
+
+	number m_value;
+};
+
+class butterfly_operation final : public binary_operation {
+public:
+	explicit butterfly_operation(result_key key)
+		: binary_operation(std::move(key)) {}
+
+	[[nodiscard]] std::size_t output_count() const noexcept final {
+		return 2;
+	}
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final {
+		ASIC_DEBUG_MSG("Evaluating bfly.");
+		if (index == 0) {
+			return this->evaluate_lhs(context) + this->evaluate_rhs(context);
+		}
+		return this->evaluate_lhs(context) - this->evaluate_rhs(context);
+	}
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_CORE_OPERATIONS_H
\ No newline at end of file
diff --git a/legacy/simulation_oop/custom_operation.cpp b/legacy/simulation_oop/custom_operation.cpp
new file mode 100644
index 00000000..9153eb5b
--- /dev/null
+++ b/legacy/simulation_oop/custom_operation.cpp
@@ -0,0 +1,30 @@
+#include "custom_operation.h"
+
+#include <pybind11/stl.h>
+
+namespace py = pybind11;
+
+namespace asic {
+
+custom_operation::custom_operation(result_key key, pybind11::object evaluate_output, pybind11::object truncate_input,
+								   std::size_t output_count)
+	: nary_operation(std::move(key))
+	, m_evaluate_output(std::move(evaluate_output))
+	, m_truncate_input(std::move(truncate_input))
+	, m_output_count(output_count) {}
+
+std::size_t custom_operation::output_count() const noexcept {
+	return m_output_count;
+}
+
+number custom_operation::evaluate_output_impl(std::size_t index, evaluation_context const& context) const {
+	using namespace pybind11::literals;
+	auto input_values = this->evaluate_inputs(context);
+	return m_evaluate_output(index, std::move(input_values), "truncate"_a = false).cast<number>();
+}
+
+number custom_operation::truncate_input(std::size_t index, number value, std::size_t bits) const {
+	return m_truncate_input(index, value, bits).cast<number>();
+}
+
+} // namespace asic
\ No newline at end of file
diff --git a/legacy/simulation_oop/custom_operation.h b/legacy/simulation_oop/custom_operation.h
new file mode 100644
index 00000000..8a11aaac
--- /dev/null
+++ b/legacy/simulation_oop/custom_operation.h
@@ -0,0 +1,35 @@
+#ifndef ASIC_SIMULATION_CUSTOM_OPERATION_H
+#define ASIC_SIMULATION_CUSTOM_OPERATION_H
+
+#include "../algorithm.h"
+#include "../debug.h"
+#include "../number.h"
+#include "operation.h"
+
+#include <cstddef>
+#include <fmt/format.h>
+#include <functional>
+#include <pybind11/pybind11.h>
+#include <stdexcept>
+#include <utility>
+
+namespace asic {
+
+class custom_operation final : public nary_operation {
+public:
+	custom_operation(result_key key, pybind11::object evaluate_output, pybind11::object truncate_input, std::size_t output_count);
+
+	[[nodiscard]] std::size_t output_count() const noexcept final;
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
+	[[nodiscard]] number truncate_input(std::size_t index, number value, std::size_t bits) const final;
+
+	pybind11::object m_evaluate_output;
+	pybind11::object m_truncate_input;
+	std::size_t m_output_count;
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_CUSTOM_OPERATION_H
\ No newline at end of file
diff --git a/legacy/simulation_oop/operation.cpp b/legacy/simulation_oop/operation.cpp
new file mode 100644
index 00000000..a9738a0a
--- /dev/null
+++ b/legacy/simulation_oop/operation.cpp
@@ -0,0 +1,156 @@
+#include "operation.h"
+
+#include "../debug.h"
+
+#include <pybind11/pybind11.h>
+
+namespace py = pybind11;
+
+namespace asic {
+
+signal_source::signal_source(std::shared_ptr<const operation> op, std::size_t index, std::optional<std::size_t> bits)
+	: m_operation(std::move(op))
+	, m_index(index)
+	, m_bits(std::move(bits)) {}
+
+signal_source::operator bool() const noexcept {
+	return static_cast<bool>(m_operation);
+}
+
+std::optional<number> signal_source::current_output(delay_map const& delays) const {
+	ASIC_ASSERT(m_operation);
+	return m_operation->current_output(m_index, delays);
+}
+
+number signal_source::evaluate_output(evaluation_context const& context) const {
+	ASIC_ASSERT(m_operation);
+	return m_operation->evaluate_output(m_index, context);
+}
+
+std::optional<std::size_t> signal_source::bits() const noexcept {
+	return m_bits;
+}
+
+abstract_operation::abstract_operation(result_key key)
+	: m_key(std::move(key)) {}
+
+std::optional<number> abstract_operation::current_output(std::size_t, delay_map const&) const {
+	return std::nullopt;
+}
+
+number abstract_operation::evaluate_output(std::size_t index, evaluation_context const& context) const {
+	ASIC_ASSERT(index < this->output_count());
+	ASIC_ASSERT(context.results);
+	auto const key = this->key_of_output(index);
+	if (auto const it = context.results->find(key); it != context.results->end()) {
+		if (it->second) {
+			return *it->second;
+		}
+		throw std::runtime_error{"Direct feedback loop detected when evaluating simulation operation."};
+	}
+	auto& result = context.results->try_emplace(key, this->current_output(index, *context.delays))
+					   .first->second; // Use a reference to avoid potential iterator invalidation caused by evaluate_output_impl.
+	auto const value = this->evaluate_output_impl(index, context);
+	ASIC_ASSERT(&context.results->at(key) == &result);
+	result = value;
+	return value;
+}
+
+number abstract_operation::truncate_input(std::size_t index, number value, std::size_t bits) const {
+	if (value.imag() != 0) {
+		throw py::type_error{
+			fmt::format("Complex value cannot be truncated to {} bits as requested by the signal connected to input #{}", bits, index)};
+	}
+	if (bits > 64) {
+		throw py::value_error{
+			fmt::format("Cannot truncate to {} (more than 64) bits as requested by the singal connected to input #{}", bits, index)};
+	}
+	return number{static_cast<number::value_type>(static_cast<std::int64_t>(value.real()) & ((std::int64_t{1} << bits) - 1))};
+}
+
+result_key const& abstract_operation::key_base() const {
+	return m_key;
+}
+
+result_key abstract_operation::key_of_output(std::size_t index) const {
+	if (m_key.empty()) {
+		return fmt::to_string(index);
+	}
+	if (this->output_count() == 1) {
+		return m_key;
+	}
+	return fmt::format("{}.{}", m_key, index);
+}
+
+unary_operation::unary_operation(result_key key)
+	: abstract_operation(std::move(key)) {}
+
+void unary_operation::connect(signal_source in) {
+	m_in = std::move(in);
+}
+
+bool unary_operation::connected() const noexcept {
+	return static_cast<bool>(m_in);
+}
+
+signal_source const& unary_operation::input() const noexcept {
+	return m_in;
+}
+
+number unary_operation::evaluate_input(evaluation_context const& context) const {
+	auto const value = m_in.evaluate_output(context);
+	auto const bits = context.bits_override.value_or(m_in.bits().value_or(0));
+	return (context.truncate && bits) ? this->truncate_input(0, value, bits) : value;
+}
+
+binary_operation::binary_operation(result_key key)
+	: abstract_operation(std::move(key)) {}
+
+void binary_operation::connect(signal_source lhs, signal_source rhs) {
+	m_lhs = std::move(lhs);
+	m_rhs = std::move(rhs);
+}
+
+signal_source const& binary_operation::lhs() const noexcept {
+	return m_lhs;
+}
+
+signal_source const& binary_operation::rhs() const noexcept {
+	return m_rhs;
+}
+
+number binary_operation::evaluate_lhs(evaluation_context const& context) const {
+	auto const value = m_lhs.evaluate_output(context);
+	auto const bits = context.bits_override.value_or(m_lhs.bits().value_or(0));
+	return (context.truncate && bits) ? this->truncate_input(0, value, bits) : value;
+}
+
+number binary_operation::evaluate_rhs(evaluation_context const& context) const {
+	auto const value = m_rhs.evaluate_output(context);
+	auto const bits = context.bits_override.value_or(m_rhs.bits().value_or(0));
+	return (context.truncate && bits) ? this->truncate_input(0, value, bits) : value;
+}
+
+nary_operation::nary_operation(result_key key)
+	: abstract_operation(std::move(key)) {}
+
+void nary_operation::connect(std::vector<signal_source> inputs) {
+	m_inputs = std::move(inputs);
+}
+
+span<signal_source const> nary_operation::inputs() const noexcept {
+	return m_inputs;
+}
+
+std::vector<number> nary_operation::evaluate_inputs(evaluation_context const& context) const {
+	auto values = std::vector<number>{};
+	values.reserve(m_inputs.size());
+	for (auto const& input : m_inputs) {
+		auto const value = input.evaluate_output(context);
+		auto const bits = context.bits_override.value_or(input.bits().value_or(0));
+		values.push_back((context.truncate && bits) ? this->truncate_input(0, value, bits) : value);
+	}
+	return values;
+}
+
+} // namespace asic
\ No newline at end of file
diff --git a/legacy/simulation_oop/operation.h b/legacy/simulation_oop/operation.h
new file mode 100644
index 00000000..344eacc1
--- /dev/null
+++ b/legacy/simulation_oop/operation.h
@@ -0,0 +1,132 @@
+#ifndef ASIC_SIMULATION_OPERATION_H
+#define ASIC_SIMULATION_OPERATION_H
+
+#include "../number.h"
+#include "../span.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <fmt/format.h>
+#include <memory>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace asic {
+
+class operation;
+class signal_source;
+
+using result_key = std::string;
+using result_map = std::unordered_map<result_key, std::optional<number>>;
+using delay_map = std::unordered_map<result_key, number>;
+using delay_queue = std::vector<std::pair<result_key, signal_source const*>>;
+
+struct evaluation_context final {
+	result_map* results;
+	delay_map* delays;
+	delay_queue* deferred_delays;
+	std::optional<std::size_t> bits_override;
+	bool truncate;
+};
+
+class signal_source final {
+public:
+	signal_source() noexcept = default;
+	signal_source(std::shared_ptr<const operation> op, std::size_t index, std::optional<std::size_t> bits);
+
+	[[nodiscard]] explicit operator bool() const noexcept;
+
+	[[nodiscard]] std::optional<number> current_output(delay_map const& delays) const;
+	[[nodiscard]] number evaluate_output(evaluation_context const& context) const;
+
+	[[nodiscard]] std::optional<std::size_t> bits() const noexcept;
+
+private:
+	std::shared_ptr<const operation> m_operation;
+	std::size_t m_index = 0;
+	std::optional<std::size_t> m_bits;
+};
+
+class operation {
+public:
+	virtual ~operation() = default;
+	[[nodiscard]] virtual std::size_t output_count() const noexcept = 0;
+	[[nodiscard]] virtual std::optional<number> current_output(std::size_t index, delay_map const& delays) const = 0;
+	[[nodiscard]] virtual number evaluate_output(std::size_t index, evaluation_context const& context) const = 0;
+};
+
+class abstract_operation : public operation {
+public:
+	explicit abstract_operation(result_key key);
+	virtual ~abstract_operation() = default;
+
+	[[nodiscard]] std::optional<number> current_output(std::size_t, delay_map const&) const override;
+	[[nodiscard]] number evaluate_output(std::size_t index, evaluation_context const& context) const override;
+
+protected:
+	[[nodiscard]] virtual number evaluate_output_impl(std::size_t index, evaluation_context const& context) const = 0;
+	[[nodiscard]] virtual number truncate_input(std::size_t index, number value, std::size_t bits) const;
+
+	[[nodiscard]] result_key const& key_base() const;
+	[[nodiscard]] result_key key_of_output(std::size_t index) const;
+
+private:
+	result_key m_key;
+};
+
+class unary_operation : public abstract_operation {
+public:
+	explicit unary_operation(result_key key);
+	virtual ~unary_operation() = default;
+
+	void connect(signal_source in);
+
+protected:
+	[[nodiscard]] bool connected() const noexcept;
+	[[nodiscard]] signal_source const& input() const noexcept;
+	[[nodiscard]] number evaluate_input(evaluation_context const& context) const;
+
+private:
+	signal_source m_in;
+};
+
+class binary_operation : public abstract_operation {
+public:
+	explicit binary_operation(result_key key);
+	virtual ~binary_operation() = default;
+
+	void connect(signal_source lhs, signal_source rhs);
+
+protected:
+	[[nodiscard]] signal_source const& lhs() const noexcept;
+	[[nodiscard]] signal_source const& rhs() const noexcept;
+	[[nodiscard]] number evaluate_lhs(evaluation_context const& context) const;
+	[[nodiscard]] number evaluate_rhs(evaluation_context const& context) const;
+
+private:
+	signal_source m_lhs;
+	signal_source m_rhs;
+};
+
+class nary_operation : public abstract_operation {
+public:
+	explicit nary_operation(result_key key);
+	virtual ~nary_operation() = default;
+
+	void connect(std::vector<signal_source> inputs);
+
+protected:
+	[[nodiscard]] span<signal_source const> inputs() const noexcept;
+	[[nodiscard]] std::vector<number> evaluate_inputs(evaluation_context const& context) const;
+
+private:
+	std::vector<signal_source> m_inputs;
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_OPERATION_H
\ No newline at end of file
diff --git a/legacy/simulation_oop/signal_flow_graph.cpp b/legacy/simulation_oop/signal_flow_graph.cpp
new file mode 100644
index 00000000..4c3763c8
--- /dev/null
+++ b/legacy/simulation_oop/signal_flow_graph.cpp
@@ -0,0 +1,144 @@
+#include "signal_flow_graph.h"
+
+#include "../debug.h"
+
+namespace py = pybind11;
+
+namespace asic {
+
+signal_flow_graph_operation::signal_flow_graph_operation(result_key key)
+	: abstract_operation(std::move(key)) {}
+
+void signal_flow_graph_operation::create(pybind11::handle sfg, added_operation_cache& added) {
+	ASIC_DEBUG_MSG("Creating SFG.");
+	for (auto const& [i, op] : enumerate(sfg.attr("output_operations"))) {
+		ASIC_DEBUG_MSG("Adding output op.");
+		m_output_operations.emplace_back(this->key_of_output(i)).connect(make_source(op, 0, added, this->key_base()));
+	}
+	for (auto const& op : sfg.attr("input_operations")) {
+		ASIC_DEBUG_MSG("Adding input op.");
+		if (!m_input_operations.emplace_back(std::dynamic_pointer_cast<input_operation>(make_operation(op, added, this->key_base())))) {
+			throw py::value_error{"Invalid input operation in SFG."};
+		}
+	}
+}
+
+std::vector<std::shared_ptr<input_operation>> const& signal_flow_graph_operation::inputs() noexcept {
+	return m_input_operations;
+}
+
+std::size_t signal_flow_graph_operation::output_count() const noexcept {
+	return m_output_operations.size();
+}
+
+number signal_flow_graph_operation::evaluate_output(std::size_t index, evaluation_context const& context) const {
+	ASIC_DEBUG_MSG("Evaluating SFG.");
+	return m_output_operations.at(index).evaluate_output(0, context);
+}
+
+number signal_flow_graph_operation::evaluate_output_impl(std::size_t, evaluation_context const&) const {
+	return number{};
+}
+
+signal_source signal_flow_graph_operation::make_source(pybind11::handle op, std::size_t input_index, added_operation_cache& added,
+													   std::string_view prefix) {
+	auto const signal = py::object{op.attr("inputs")[py::int_{input_index}].attr("signals")[py::int_{0}]};
+	auto const src = py::handle{signal.attr("source")};
+	auto const operation = py::handle{src.attr("operation")};
+	auto const index = src.attr("index").cast<std::size_t>();
+	auto bits = std::optional<std::size_t>{};
+	if (!signal.attr("bits").is_none()) {
+		bits = signal.attr("bits").cast<std::size_t>();
+	}
+	return signal_source{make_operation(operation, added, prefix), index, bits};
+}
+
+std::shared_ptr<operation> signal_flow_graph_operation::add_signal_flow_graph_operation(pybind11::handle sfg, added_operation_cache& added,
+																						std::string_view prefix, result_key key) {
+	auto const new_op = add_operation<signal_flow_graph_operation>(sfg, added, std::move(key));
+	new_op->create(sfg, added);
+	for (auto&& [i, input] : enumerate(new_op->inputs())) {
+		input->connect(make_source(sfg, i, added, prefix));
+	}
+	return new_op;
+}
+
+std::shared_ptr<custom_operation> signal_flow_graph_operation::add_custom_operation(pybind11::handle op, added_operation_cache& added,
+																					std::string_view prefix, result_key key) {
+	auto const input_count = op.attr("input_count").cast<std::size_t>();
+	auto const output_count = op.attr("output_count").cast<std::size_t>();
+	auto const new_op = add_operation<custom_operation>(
+		op, added, key, op.attr("evaluate_output"), op.attr("truncate_input"), output_count);
+	auto inputs = std::vector<signal_source>{};
+	inputs.reserve(input_count);
+	for (auto const i : range(input_count)) {
+		inputs.push_back(make_source(op, i, added, prefix));
+	}
+	new_op->connect(std::move(inputs));
+	return new_op;
+}
+
+std::shared_ptr<operation> signal_flow_graph_operation::make_operation(pybind11::handle op, added_operation_cache& added,
+																	   std::string_view prefix) {
+	if (auto const it = added.find(op.ptr()); it != added.end()) {
+		ASIC_ASSERT(it->second);
+		return it->second;
+	}
+	auto const graph_id = op.attr("graph_id").cast<std::string_view>();
+	auto const type_name = op.attr("type_name")().cast<std::string_view>();
+	auto key = (prefix.empty()) ? result_key{graph_id} : fmt::format("{}.{}", prefix, graph_id);
+	if (type_name == "c") {
+		auto const value = op.attr("value").cast<number>();
+		return add_operation<constant_operation>(op, added, std::move(key), value);
+	}
+	if (type_name == "add") {
+		return add_binary_operation<addition_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "sub") {
+		return add_binary_operation<subtraction_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "mul") {
+		return add_binary_operation<multiplication_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "div") {
+		return add_binary_operation<division_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "min") {
+		return add_binary_operation<min_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "max") {
+		return add_binary_operation<max_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "sqrt") {
+		return add_unary_operation<square_root_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "conj") {
+		return add_unary_operation<complex_conjugate_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "abs") {
+		return add_unary_operation<absolute_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "cmul") {
+		auto const value = op.attr("value").cast<number>();
+		return add_unary_operation<constant_multiplication_operation>(op, added, prefix, std::move(key), value);
+	}
+	if (type_name == "bfly") {
+		return add_binary_operation<butterfly_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "in") {
+		return add_operation<input_operation>(op, added, std::move(key));
+	}
+	if (type_name == "out") {
+		return add_unary_operation<output_operation>(op, added, prefix, std::move(key));
+	}
+	if (type_name == "t") {
+		auto const initial_value = op.attr("initial_value").cast<number>();
+		return add_unary_operation<delay_operation>(op, added, prefix, std::move(key), initial_value);
+	}
+	if (type_name == "sfg") {
+		return add_signal_flow_graph_operation(op, added, prefix, std::move(key));
+	}
+	return add_custom_operation(op, added, prefix, std::move(key));
+}
+
+} // namespace asic
\ No newline at end of file
diff --git a/legacy/simulation_oop/signal_flow_graph.h b/legacy/simulation_oop/signal_flow_graph.h
new file mode 100644
index 00000000..f0678824
--- /dev/null
+++ b/legacy/simulation_oop/signal_flow_graph.h
@@ -0,0 +1,82 @@
+#ifndef ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_H
+#define ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_H
+
+#include "../algorithm.h"
+#include "../debug.h"
+#include "../number.h"
+#include "core_operations.h"
+#include "custom_operation.h"
+#include "operation.h"
+#include "special_operations.h"
+
+#include <Python.h>
+#include <cstddef>
+#include <fmt/format.h>
+#include <functional>
+#include <memory>
+#include <pybind11/pybind11.h>
+#include <stdexcept>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace asic {
+
+class signal_flow_graph_operation final : public abstract_operation {
+public:
+	using added_operation_cache = std::unordered_map<PyObject const*, std::shared_ptr<operation>>;
+
+	signal_flow_graph_operation(result_key key);
+
+	void create(pybind11::handle sfg, added_operation_cache& added);
+
+	[[nodiscard]] std::vector<std::shared_ptr<input_operation>> const& inputs() noexcept;
+	[[nodiscard]] std::size_t output_count() const noexcept final;
+
+	[[nodiscard]] number evaluate_output(std::size_t index, evaluation_context const& context) const final;
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
+
+	[[nodiscard]] static signal_source make_source(pybind11::handle op, std::size_t input_index, added_operation_cache& added,
+												   std::string_view prefix);
+
+	template <typename Operation, typename... Args>
+	[[nodiscard]] static std::shared_ptr<Operation> add_operation(pybind11::handle op, added_operation_cache& added, Args&&... args) {
+		return std::static_pointer_cast<Operation>(
+			added.try_emplace(op.ptr(), std::make_shared<Operation>(std::forward<Args>(args)...)).first->second);
+	}
+
+	template <typename Operation, typename... Args>
+	[[nodiscard]] static std::shared_ptr<Operation> add_unary_operation(pybind11::handle op, added_operation_cache& added,
+																		std::string_view prefix, Args&&... args) {
+		auto const new_op = add_operation<Operation>(op, added, std::forward<Args>(args)...);
+		new_op->connect(make_source(op, 0, added, prefix));
+		return new_op;
+	}
+
+	template <typename Operation, typename... Args>
+	[[nodiscard]] static std::shared_ptr<Operation> add_binary_operation(pybind11::handle op, added_operation_cache& added,
+																		 std::string_view prefix, Args&&... args) {
+		auto const new_op = add_operation<Operation>(op, added, std::forward<Args>(args)...);
+		new_op->connect(make_source(op, 0, added, prefix), make_source(op, 1, added, prefix));
+		return new_op;
+	}
+
+	[[nodiscard]] static std::shared_ptr<operation> add_signal_flow_graph_operation(pybind11::handle sfg, added_operation_cache& added,
+																					std::string_view prefix, result_key key);
+
+	[[nodiscard]] static std::shared_ptr<custom_operation> add_custom_operation(pybind11::handle op, added_operation_cache& added,
+																				std::string_view prefix, result_key key);
+
+	[[nodiscard]] static std::shared_ptr<operation> make_operation(pybind11::handle op, added_operation_cache& added,
+																   std::string_view prefix);
+
+	std::vector<output_operation> m_output_operations;
+	std::vector<std::shared_ptr<input_operation>> m_input_operations;
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_H
\ No newline at end of file
diff --git a/legacy/simulation_oop/simulation.cpp b/legacy/simulation_oop/simulation.cpp
new file mode 100644
index 00000000..4d6ff833
--- /dev/null
+++ b/legacy/simulation_oop/simulation.cpp
@@ -0,0 +1,138 @@
+#define NOMINMAX
+#include "simulation.h"
+
+#include "../debug.h"
+
+namespace py = pybind11;
+
+namespace asic {
+
+simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_t>>> input_providers)
+	: m_sfg("")
+	, m_input_functions(sfg.attr("input_count").cast<std::size_t>(), [](iteration_t) -> number { return number{}; }) {
+	if (input_providers) {
+		this->set_inputs(std::move(*input_providers));
+	}
+	auto added = signal_flow_graph_operation::added_operation_cache{};
+	m_sfg.create(sfg, added);
+}
+
+void simulation::set_input(std::size_t index, input_provider_t input_provider) {
+	if (index >= m_input_functions.size()) {
+		throw py::index_error{fmt::format("Input index out of range (expected 0-{}, got {})", m_input_functions.size() - 1, index)};
+	}
+	if (auto* const callable = std::get_if<input_function_t>(&input_provider)) {
+		m_input_functions[index] = std::move(*callable);
+	} else if (auto* const numeric = std::get_if<number>(&input_provider)) {
+		m_input_functions[index] = [value = *numeric](iteration_t) -> number {
+			return value;
+		};
+	} else if (auto* const list = std::get_if<std::vector<number>>(&input_provider)) {
+		if (!m_input_length) {
+			m_input_length = static_cast<iteration_t>(list->size());
+		} else if (*m_input_length != static_cast<iteration_t>(list->size())) {
+			throw py::value_error{fmt::format("Inconsistent input length for simulation (was {}, got {})", *m_input_length, list->size())};
+		}
+		m_input_functions[index] = [values = std::move(*list)](iteration_t n) -> number {
+			return values.at(n);
+		};
+	}
+}
+
+void simulation::set_inputs(std::vector<std::optional<input_provider_t>> input_providers) {
+	if (input_providers.size() != m_input_functions.size()) {
+		throw py::value_error{fmt::format(
+			"Wrong number of inputs supplied to simulation (expected {}, got {})", m_input_functions.size(), input_providers.size())};
+	}
+	for (auto&& [i, input_provider] : enumerate(input_providers)) {
+		if (input_provider) {
+			this->set_input(i, std::move(*input_provider));
+		}
+	}
+}
+
+std::vector<number> simulation::step(bool save_results, std::optional<std::size_t> bits_override, bool truncate) {
+	return this->run_for(1, save_results, bits_override, truncate);
+}
+
+std::vector<number> simulation::run_until(iteration_t iteration, bool save_results, std::optional<std::size_t> bits_override,
+										  bool truncate) {
+	auto result = std::vector<number>{};
+	while (m_iteration < iteration) {
+		ASIC_DEBUG_MSG("Running simulation iteration.");
+		for (auto&& [input, function] : zip(m_sfg.inputs(), m_input_functions)) {
+			input->value(function(m_iteration));
+		}
+
+		result.clear();
+		result.reserve(m_sfg.output_count());
+
+		auto results = result_map{};
+		auto deferred_delays = delay_queue{};
+		auto context = evaluation_context{};
+		context.results = &results;
+		context.delays = &m_delays;
+		context.deferred_delays = &deferred_delays;
+		context.bits_override = bits_override;
+		context.truncate = truncate;
+
+		for (auto const i : range(m_sfg.output_count())) {
+			result.push_back(m_sfg.evaluate_output(i, context));
+		}
+
+		while (!deferred_delays.empty()) {
+			auto new_deferred_delays = delay_queue{};
+			context.deferred_delays = &new_deferred_delays;
+			for (auto const& [key, src] : deferred_delays) {
+				ASIC_ASSERT(src);
+				m_delays[key] = src->evaluate_output(context);
+			}
+			deferred_delays = std::move(new_deferred_delays);
+		}
+
+		if (save_results) {
+			for (auto const& [key, value] : results) {
+				m_results[key].push_back(value.value());
+			}
+		}
+		++m_iteration;
+	}
+	return result;
+}
+
+std::vector<number> simulation::run_for(iteration_t iterations, bool save_results, std::optional<std::size_t> bits_override,
+										bool truncate) {
+	if (iterations > std::numeric_limits<iteration_t>::max() - m_iteration) {
+		throw py::value_error("Simulation iteration type overflow!");
+	}
+	return this->run_until(m_iteration + iterations, save_results, bits_override, truncate);
+}
+
+std::vector<number> simulation::run(bool save_results, std::optional<std::size_t> bits_override, bool truncate) {
+	if (m_input_length) {
+		return this->run_until(*m_input_length, save_results, bits_override, truncate);
+	}
+	throw py::index_error{"Tried to run unlimited simulation"};
+}
+
+iteration_t simulation::iteration() const noexcept {
+	return m_iteration;
+}
+
+pybind11::dict simulation::results() const noexcept {
+	auto results = py::dict{};
+	for (auto const& [key, values] : m_results) {
+		results[py::str{key}] = py::array{static_cast<py::ssize_t>(values.size()), values.data()};
+	}
+	return results;
+}
+
+void simulation::clear_results() noexcept {
+	m_results.clear();
+}
+
+void simulation::clear_state() noexcept {
+	m_delays.clear();
+}
+
+} // namespace asic
diff --git a/legacy/simulation_oop/simulation.h b/legacy/simulation_oop/simulation.h
new file mode 100644
index 00000000..38e2771b
--- /dev/null
+++ b/legacy/simulation_oop/simulation.h
@@ -0,0 +1,66 @@
+#ifndef ASIC_SIMULATION_OOP_H
+#define ASIC_SIMULATION_OOP_H
+
+#include "../number.h"
+#include "core_operations.h"
+#include "custom_operation.h"
+#include "operation.h"
+#include "signal_flow_graph.h"
+#include "special_operations.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <fmt/format.h>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <pybind11/functional.h>
+#include <pybind11/numpy.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace asic {
+
+using iteration_t = std::uint32_t;
+using result_array_map = std::unordered_map<std::string, std::vector<number>>;
+using input_function_t = std::function<number(iteration_t)>;
+using input_provider_t = std::variant<number, std::vector<number>, input_function_t>;
+
+class simulation final {
+public:
+	simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_t>>> input_providers = std::nullopt);
+
+	void set_input(std::size_t index, input_provider_t input_provider);
+	void set_inputs(std::vector<std::optional<input_provider_t>> input_providers);
+
+	[[nodiscard]] std::vector<number> step(bool save_results, std::optional<std::size_t> bits_override, bool truncate);
+	[[nodiscard]] std::vector<number> run_until(iteration_t iteration, bool save_results, std::optional<std::size_t> bits_override,
+												bool truncate);
+	[[nodiscard]] std::vector<number> run_for(iteration_t iterations, bool save_results, std::optional<std::size_t> bits_override,
+											  bool truncate);
+	[[nodiscard]] std::vector<number> run(bool save_results, std::optional<std::size_t> bits_override, bool truncate);
+
+	[[nodiscard]] iteration_t iteration() const noexcept;
+	[[nodiscard]] pybind11::dict results() const noexcept;
+
+	void clear_results() noexcept;
+	void clear_state() noexcept;
+
+private:
+	signal_flow_graph_operation m_sfg;
+	result_array_map m_results;
+	delay_map m_delays;
+	iteration_t m_iteration = 0;
+	std::vector<input_function_t> m_input_functions;
+	std::optional<iteration_t> m_input_length;
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_OOP_H
\ No newline at end of file
diff --git a/legacy/simulation_oop/special_operations.cpp b/legacy/simulation_oop/special_operations.cpp
new file mode 100644
index 00000000..1f7a6519
--- /dev/null
+++ b/legacy/simulation_oop/special_operations.cpp
@@ -0,0 +1,78 @@
+#include "special_operations.h"
+
+#include "../debug.h"
+
+namespace asic {
+
+input_operation::input_operation(result_key key)
+	: unary_operation(std::move(key)) {}
+
+std::size_t input_operation::output_count() const noexcept {
+	return 1;
+}
+
+number input_operation::value() const noexcept {
+	return m_value;
+}
+
+void input_operation::value(number value) noexcept {
+	m_value = value;
+}
+
+number input_operation::evaluate_output_impl(std::size_t, evaluation_context const& context) const {
+	ASIC_DEBUG_MSG("Evaluating input.");
+	if (this->connected()) {
+		return this->evaluate_input(context);
+	}
+	return m_value;
+}
+
+output_operation::output_operation(result_key key)
+	: unary_operation(std::move(key)) {}
+
+std::size_t output_operation::output_count() const noexcept {
+	return 1;
+}
+
+number output_operation::evaluate_output_impl(std::size_t, evaluation_context const& context) const {
+	ASIC_DEBUG_MSG("Evaluating output.");
+	return this->evaluate_input(context);
+}
+
+delay_operation::delay_operation(result_key key, number initial_value)
+	: unary_operation(std::move(key))
+	, m_initial_value(initial_value) {}
+
+std::size_t delay_operation::output_count() const noexcept {
+	return 1;
+}
+
+std::optional<number> delay_operation::current_output(std::size_t index, delay_map const& delays) const {
+	auto const key = this->key_of_output(index);
+	if (auto const it = delays.find(key); it != delays.end()) {
+		return it->second;
+	}
+	return m_initial_value;
+}
+
+number delay_operation::evaluate_output(std::size_t index, evaluation_context const& context) const {
+	ASIC_DEBUG_MSG("Evaluating delay.");
+	ASIC_ASSERT(index == 0);
+	ASIC_ASSERT(context.results);
+	ASIC_ASSERT(context.delays);
+	ASIC_ASSERT(context.deferred_delays);
+	auto key = this->key_of_output(index);
+	auto const value = context.delays->try_emplace(key, m_initial_value).first->second;
+	auto const& [it, inserted] = context.results->try_emplace(key, value);
+	if (inserted) {
+		context.deferred_delays->emplace_back(std::move(key), &this->input());
+		return value;
+	}
+	return it->second.value();
+}
+
+[[nodiscard]] number delay_operation::evaluate_output_impl(std::size_t, evaluation_context const&) const {
+	return number{};
+}
+
+} // namespace asic
diff --git a/legacy/simulation_oop/special_operations.h b/legacy/simulation_oop/special_operations.h
new file mode 100644
index 00000000..88fb087e
--- /dev/null
+++ b/legacy/simulation_oop/special_operations.h
@@ -0,0 +1,55 @@
+#ifndef ASIC_SIMULATION_SPECIAL_OPERATIONS_H
+#define ASIC_SIMULATION_SPECIAL_OPERATIONS_H
+
+#include "../debug.h"
+#include "../number.h"
+#include "operation.h"
+
+#include <cassert>
+#include <cstddef>
+#include <utility>
+
+namespace asic {
+
+class input_operation final : public unary_operation {
+public:
+	explicit input_operation(result_key key);
+
+	[[nodiscard]] std::size_t output_count() const noexcept final;
+	[[nodiscard]] number value() const noexcept;
+	void value(number value) noexcept;
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
+
+	number m_value{};
+};
+
+class output_operation final : public unary_operation {
+public:
+	explicit output_operation(result_key key);
+
+	[[nodiscard]] std::size_t output_count() const noexcept final;
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
+};
+
+class delay_operation final : public unary_operation {
+public:
+	delay_operation(result_key key, number initial_value);
+
+	[[nodiscard]] std::size_t output_count() const noexcept final;
+
+	[[nodiscard]] std::optional<number> current_output(std::size_t index, delay_map const& delays) const final;
+	[[nodiscard]] number evaluate_output(std::size_t index, evaluation_context const& context) const final;
+
+private:
+	[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
+
+	number m_initial_value;
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_SPECIAL_OPERATIONS_H
\ No newline at end of file
diff --git a/logo_tiny.png b/logo_tiny.png
new file mode 100644
index 0000000000000000000000000000000000000000..ae5359b69cdb0b04f1be87da750bccc9fbe16ac2
GIT binary patch
literal 10071
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

literal 0
HcmV?d00001

diff --git a/setup.py b/setup.py
index 43d55d40..e5860344 100644
--- a/setup.py
+++ b/setup.py
@@ -6,25 +6,30 @@ import setuptools
 from setuptools import Extension
 from setuptools.command.build_ext import build_ext
 
-CMAKE_EXE = os.environ.get('CMAKE_EXE', shutil.which('cmake'))
+CMAKE_EXE = os.environ.get("CMAKE_EXE", shutil.which("cmake"))
+
 
 class CMakeExtension(Extension):
-    def __init__(self, name, sourcedir = ""):
+    def __init__(self, name, sourcedir=""):
         super().__init__(name, sources=[])
         self.sourcedir = os.path.abspath(sourcedir)
 
+
 class CMakeBuild(build_ext):
     def build_extension(self, ext):
         if not isinstance(ext, CMakeExtension):
             return super().build_extension(ext)
 
         if not CMAKE_EXE:
-            raise RuntimeError(f"Cannot build extension {ext.name}: CMake executable not found! Set the CMAKE_EXE environment variable or update your path.")
+            raise RuntimeError(
+                f"Cannot build extension {ext.name}: CMake executable not found! Set the CMAKE_EXE environment variable or update your path.")
 
         cmake_build_type = "Debug" if self.debug else "Release"
-        cmake_output_dir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
+        cmake_output_dir = os.path.abspath(
+            os.path.dirname(self.get_ext_fullpath(ext.name)))
         cmake_configure_argv = [
             CMAKE_EXE, ext.sourcedir,
+            "-DASIC_BUILDING_PYTHON_DISTRIBUTION=true",
             "-DCMAKE_BUILD_TYPE=" + cmake_build_type,
             "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + cmake_output_dir,
             "-DPYTHON_EXECUTABLE=" + sys.executable,
@@ -36,13 +41,14 @@ class CMakeBuild(build_ext):
 
         if not os.path.exists(self.build_temp):
             os.makedirs(self.build_temp)
-        
+
         env = os.environ.copy()
-        
+
         print(f"=== Configuring {ext.name} ===")
         print(f"Temp dir: {self.build_temp}")
         print(f"Output dir: {cmake_output_dir}")
-        subprocess.check_call(cmake_configure_argv, cwd=self.build_temp, env=env)
+        subprocess.check_call(cmake_configure_argv,
+                              cwd=self.build_temp, env=env)
 
         print(f"=== Building {ext.name} ===")
         print(f"Temp dir: {self.build_temp}")
@@ -52,29 +58,33 @@ class CMakeBuild(build_ext):
 
         print()
 
+
 setuptools.setup(
-    name = "b-asic",
-    version = "0.0.1",
-    author = "Adam Jakobsson, Angus Lothian, Arvid Westerlund, Felix Goding, Ivar Härnqvist, Jacob Wahlman, Kevin Scott, Rasmus Karlsson",
-    author_email = "adaja901@student.liu.se, anglo547@student.liu.se, arvwe160@student.liu.se, felgo673@student.liu.se, ivaha717@student.liu.se, jacwa448@student.liu.se, kevsc634@student.liu.se, raska119@student.liu.se",
-    description = "Better ASIC Toolbox",
-    long_description = open("README.md", "r").read(),
-    long_description_content_type = "text/markdown",
-    url = "https://gitlab.liu.se/PUM_TDDD96/B-ASIC",
-    license = "MIT",
-    classifiers = [
+    name="b-asic",
+    version="1.0.0",
+    author="Adam Jakobsson, Angus Lothian, Arvid Westerlund, Felix Goding, Ivar Härnqvist, Jacob Wahlman, Kevin Scott, Rasmus Karlsson",
+    author_email="adaja901@student.liu.se, anglo547@student.liu.se, arvwe160@student.liu.se, felgo673@student.liu.se, ivaha717@student.liu.se, jacwa448@student.liu.se, kevsc634@student.liu.se, raska119@student.liu.se",
+    description="Better ASIC Toolbox",
+    long_description=open("README.md", "r").read(),
+    long_description_content_type="text/markdown",
+    url="https://gitlab.liu.se/PUM_TDDD96/B-ASIC",
+    license="MIT",
+    classifiers=[
         "Programming Language :: Python :: 3",
         "License :: OSI Approved :: MIT License",
         "Operating System :: OS Independent",
     ],
-    python_requires = ">=3.6",
-    install_requires = [
+    python_requires=">=3.6",
+    install_requires=[
         "pybind11>=2.3.0",
         "numpy",
-        "install_qt_binding"
+        "pyside2",
+        "graphviz",
+        "matplotlib"
     ],
-    packages = ["b_asic"],
-    ext_modules = [CMakeExtension("b_asic")],
-    cmdclass = {"build_ext": CMakeBuild},
-    zip_safe = False
-)
\ No newline at end of file
+    packages=["b_asic", "b_asic/GUI"],
+    ext_modules=[CMakeExtension("b_asic")],
+    cmdclass={"build_ext": CMakeBuild},
+    zip_safe=False,
+    include_package_data=True
+)
diff --git a/small_logo.png b/small_logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..689a38192b9fc4c6ed490e1e0001fd5dd264c968
GIT binary patch
literal 40475
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
P0Y4hIb#9fRY{LE@k7LaA

literal 0
HcmV?d00001

diff --git a/src/algorithm.h b/src/algorithm.h
new file mode 100644
index 00000000..c86275d1
--- /dev/null
+++ b/src/algorithm.h
@@ -0,0 +1,325 @@
+#ifndef ASIC_ALGORITHM_H
+#define ASIC_ALGORITHM_H
+
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+namespace asic {
+namespace detail {
+
+template <typename Reference>
+class arrow_proxy final {
+public:
+	template <typename Ref>
+	constexpr explicit arrow_proxy(Ref&& r) : m_r(std::forward<Ref>(r)) {}
+
+	Reference* operator->() {
+		return std::addressof(m_r);
+	}
+
+private:
+	Reference m_r;
+};
+
+template <typename T>
+struct range_view final {
+	class iterator final {
+	public:
+		using difference_type = std::ptrdiff_t;
+		using value_type = T const;
+		using reference = value_type&;
+		using pointer = value_type*;
+		using iterator_category = std::random_access_iterator_tag;
+
+		constexpr iterator() noexcept = default;
+		constexpr explicit iterator(T value) noexcept : m_value(value) {}
+
+		[[nodiscard]] constexpr bool operator==(iterator const& other) const noexcept {
+			return m_value == other.m_value;
+		}
+
+		[[nodiscard]] constexpr bool operator!=(iterator const& other) const noexcept {
+			return m_value != other.m_value;
+		}
+
+		[[nodiscard]] constexpr bool operator<(iterator const& other) const noexcept {
+			return m_value < other.m_value;
+		}
+
+		[[nodiscard]] constexpr bool operator>(iterator const& other) const noexcept {
+			return m_value > other.m_value;
+		}
+
+		[[nodiscard]] constexpr bool operator<=(iterator const& other) const noexcept {
+			return m_value <= other.m_value;
+		}
+
+		[[nodiscard]] constexpr bool operator>=(iterator const& other) const noexcept {
+			return m_value >= other.m_value;
+		}
+
+		[[nodiscard]] constexpr reference operator*() const noexcept {
+			return m_value;
+		}
+
+		[[nodiscard]] constexpr pointer operator->() const noexcept {
+			return std::addressof(**this);
+		}
+
+		constexpr iterator& operator++() noexcept {
+			++m_value;
+			return *this;
+		}
+
+		constexpr iterator operator++(int) noexcept {
+			return iterator{m_value++};
+		}
+
+		constexpr iterator& operator--() noexcept {
+			--m_value;
+			return *this;
+		}
+
+		constexpr iterator operator--(int) noexcept {
+			return iterator{m_value--};
+		}
+
+		constexpr iterator& operator+=(difference_type n) noexcept {
+			m_value += n;
+			return *this;
+		}
+
+		constexpr iterator& operator-=(difference_type n) noexcept {
+			m_value -= n;
+			return *this;
+		}
+
+		[[nodiscard]] constexpr T operator[](difference_type n) noexcept {
+			return m_value + static_cast<T>(n);
+		}
+
+		[[nodiscard]] constexpr friend iterator operator+(iterator const& lhs, difference_type rhs) noexcept {
+			return iterator{lhs.m_value + rhs};
+		}
+
+		[[nodiscard]] constexpr friend iterator operator+(difference_type lhs, iterator const& rhs) noexcept {
+			return iterator{lhs + rhs.m_value};
+		}
+
+		[[nodiscard]] constexpr friend iterator operator-(iterator const& lhs, difference_type rhs) noexcept {
+			return iterator{lhs.m_value - rhs};
+		}
+
+		[[nodiscard]] constexpr friend difference_type operator-(iterator const& lhs, iterator const& rhs) noexcept {
+			return static_cast<difference_type>(lhs.m_value - rhs.m_value);
+		}
+
+	private:
+		T m_value{};
+	};
+
+	using sentinel = iterator;
+
+	template <typename First, typename Last>
+	constexpr range_view(First&& first, Last&& last) noexcept : m_begin(std::forward<First>(first)), m_end(std::forward<Last>(last)) {}
+
+	[[nodiscard]] constexpr iterator begin() const noexcept {
+		return m_begin;
+	}
+	[[nodiscard]] constexpr sentinel end() const noexcept {
+		return m_end;
+	}
+
+	iterator m_begin;
+	sentinel m_end;
+};
+
+template <typename Range, typename Iterator, typename Sentinel>
+struct enumerate_view final {
+	using sentinel = Sentinel;
+
+	class iterator final {
+	public:
+		using difference_type = typename std::iterator_traits<Iterator>::difference_type;
+		using value_type = typename std::iterator_traits<Iterator>::value_type;
+		using reference = std::pair<std::size_t const&, decltype(*std::declval<Iterator const>())>;
+		using pointer = arrow_proxy<reference>;
+		using iterator_category =
+			std::common_type_t<typename std::iterator_traits<Iterator>::iterator_category, std::bidirectional_iterator_tag>;
+
+		constexpr iterator() = default;
+
+		constexpr iterator(Iterator it, std::size_t index) : m_it(std::move(it)), m_index(index) {}
+
+		[[nodiscard]] constexpr bool operator==(iterator const& other) const {
+			return m_it == other.m_it;
+		}
+
+		[[nodiscard]] constexpr bool operator!=(iterator const& other) const {
+			return m_it != other.m_it;
+		}
+
+		[[nodiscard]] constexpr bool operator==(sentinel const& other) const {
+			return m_it == other;
+		}
+
+		[[nodiscard]] constexpr bool operator!=(sentinel const& other) const {
+			return m_it != other;
+		}
+
+		[[nodiscard]] constexpr reference operator*() const {
+			return reference{m_index, *m_it};
+		}
+
+		[[nodiscard]] constexpr pointer operator->() const {
+			return pointer{**this};
+		}
+
+		constexpr iterator& operator++() {
+			++m_it;
+			++m_index;
+			return *this;
+		}
+
+		constexpr iterator operator++(int) {
+			return iterator{m_it++, m_index++};
+		}
+
+		constexpr iterator& operator--() {
+			--m_it;
+			--m_index;
+			return *this;
+		}
+
+		constexpr iterator operator--(int) {
+			return iterator{m_it--, m_index--};
+		}
+
+	private:
+		Iterator m_it;
+		std::size_t m_index = 0;
+	};
+
+	constexpr iterator begin() const {
+		return iterator{std::begin(m_range), 0};
+	}
+
+	constexpr sentinel end() const {
+		return std::end(m_range);
+	}
+
+	Range m_range;
+};
+
+template <typename Range1, typename Range2, typename Iterator1, typename Iterator2, typename Sentinel1, typename Sentinel2>
+struct zip_view final {
+	using sentinel = std::pair<Sentinel1, Sentinel2>;
+
+	class iterator final {
+	public:
+		using difference_type = std::common_type_t<typename std::iterator_traits<Iterator1>::difference_type,
+			typename std::iterator_traits<Iterator2>::difference_type>;
+		using value_type =
+			std::pair<typename std::iterator_traits<Iterator1>::value_type, typename std::iterator_traits<Iterator2>::value_type>;
+		using reference = std::pair<decltype(*std::declval<Iterator1 const>()), decltype(*std::declval<Iterator2 const>())>;
+		using pointer = arrow_proxy<reference>;
+		using iterator_category = std::common_type_t<typename std::iterator_traits<Iterator1>::iterator_category,
+			typename std::iterator_traits<Iterator2>::iterator_category, std::bidirectional_iterator_tag>;
+
+		constexpr iterator() = default;
+
+		constexpr iterator(Iterator1 it1, Iterator2 it2) : m_it1(std::move(it1)), m_it2(std::move(it2)) {}
+
+		[[nodiscard]] constexpr bool operator==(iterator const& other) const {
+			return m_it1 == other.m_it1 && m_it2 == other.m_it2;
+		}
+
+		[[nodiscard]] constexpr bool operator!=(iterator const& other) const {
+			return !(*this == other);
+		}
+
+		[[nodiscard]] constexpr bool operator==(sentinel const& other) const {
+			return m_it1 == other.first || m_it2 == other.second;
+		}
+
+		[[nodiscard]] constexpr bool operator!=(sentinel const& other) const {
+			return !(*this == other);
+		}
+
+		[[nodiscard]] constexpr reference operator*() const {
+			return reference{*m_it1, *m_it2};
+		}
+
+		[[nodiscard]] constexpr pointer operator->() const {
+			return pointer{**this};
+		}
+
+		constexpr iterator& operator++() {
+			++m_it1;
+			++m_it2;
+			return *this;
+		}
+
+		constexpr iterator operator++(int) {
+			return iterator{m_it1++, m_it2++};
+		}
+
+		constexpr iterator& operator--() {
+			--m_it1;
+			--m_it2;
+			return *this;
+		}
+
+		constexpr iterator operator--(int) {
+			return iterator{m_it1--, m_it2--};
+		}
+
+	private:
+		Iterator1 m_it1;
+		Iterator2 m_it2;
+	};
+
+	constexpr iterator begin() const {
+		return iterator{std::begin(m_range1), std::begin(m_range2)};
+	}
+
+	constexpr sentinel end() const {
+		return sentinel{std::end(m_range1), std::end(m_range2)};
+	}
+
+	Range1 m_range1;
+	Range2 m_range2;
+};
+
+} // namespace detail
+
+template <typename First, typename Last, typename T = std::remove_cv_t<std::remove_reference_t<First>>>
+[[nodiscard]] constexpr auto range(First&& first, Last&& last) {
+	return detail::range_view<T>{std::forward<First>(first), std::forward<Last>(last)};
+}
+
+template <typename Last, typename T = std::remove_cv_t<std::remove_reference_t<Last>>>
+[[nodiscard]] constexpr auto range(Last&& last) {
+	return detail::range_view<T>{T{}, std::forward<Last>(last)};
+}
+
+template <typename Range, typename Iterator = decltype(std::begin(std::declval<Range>())),
+	typename Sentinel = decltype(std::end(std::declval<Range>()))>
+[[nodiscard]] constexpr auto enumerate(Range&& range) {
+	return detail::enumerate_view<Range, Iterator, Sentinel>{std::forward<Range>(range)};
+}
+
+template <typename Range1, typename Range2, typename Iterator1 = decltype(std::begin(std::declval<Range1>())),
+	typename Iterator2 = decltype(std::begin(std::declval<Range2>())), typename Sentinel1 = decltype(std::end(std::declval<Range1>())),
+	typename Sentinel2 = decltype(std::end(std::declval<Range2>()))>
+[[nodiscard]] constexpr auto zip(Range1&& range1, Range2&& range2) {
+	return detail::zip_view<Range1, Range2, Iterator1, Iterator2, Sentinel1, Sentinel2>{
+		std::forward<Range1>(range1), std::forward<Range2>(range2)};
+}
+
+} // namespace asic
+
+#endif // ASIC_ALGORITHM_H
\ No newline at end of file
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 00000000..a11aa057
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,80 @@
+#ifndef ASIC_DEBUG_H
+#define ASIC_DEBUG_H
+
+#ifndef NDEBUG
+#define ASIC_ENABLE_DEBUG_LOGGING 1
+#define ASIC_ENABLE_ASSERTS 1
+#else
+#define ASIC_ENABLE_DEBUG_LOGGING 0
+#define ASIC_ENABLE_ASSERTS 0
+#endif // NDEBUG
+
+#if ASIC_ENABLE_DEBUG_LOGGING
+#include <filesystem>
+#include <fmt/format.h>
+#include <fstream>
+#include <ostream>
+#include <string_view>
+#include <utility>
+#endif // ASIC_ENABLE_DEBUG_LOGGING
+
+#if ASIC_ENABLE_ASSERTS
+#include <filesystem>
+#include <cstdlib>
+#include <cstdio>
+#include <string_view>
+#include <fmt/format.h>
+#endif // ASIC_ENABLE_ASSERTS
+
+namespace asic {
+
+constexpr auto debug_log_filename = "_b_asic_debug_log.txt";
+
+namespace detail {
+
+#if ASIC_ENABLE_DEBUG_LOGGING
+inline void log_debug_msg_string(std::string_view file, int line, std::string_view string) {
+	static auto log_file = std::ofstream{debug_log_filename, std::ios::trunc};
+	log_file << fmt::format("{:<40}: {}", fmt::format("{}:{}", std::filesystem::path{file}.filename().generic_string(), line), string)
+			 << std::endl;
+}
+
+template <typename Format, typename... Args>
+inline void log_debug_msg(std::string_view file, int line, Format&& format, Args&&... args) {
+	log_debug_msg_string(file, line, fmt::format(std::forward<Format>(format), std::forward<Args>(args)...));
+}
+#endif // ASIC_ENABLE_DEBUG_LOGGING
+
+#if ASIC_ENABLE_ASSERTS
+inline void fail_assert(std::string_view file, int line, std::string_view condition_string) {
+#if ASIC_ENABLE_DEBUG_LOGGING
+	log_debug_msg(file, line, "Assertion failed: {}", condition_string);
+#endif // ASIC_ENABLE_DEBUG_LOGGING
+	fmt::print(stderr, "{}:{}: Assertion failed: {}\n", std::filesystem::path{file}.filename().generic_string(), line, condition_string);
+	std::abort();
+}
+
+template <typename BoolConvertible>
+inline void check_assert(std::string_view file, int line, std::string_view condition_string, BoolConvertible&& condition) {
+	if (!static_cast<bool>(condition)) {
+		fail_assert(file, line, condition_string);
+	}
+}
+#endif // ASIC_ENABLE_ASSERTS
+
+} // namespace detail
+} // namespace asic
+
+#if ASIC_ENABLE_DEBUG_LOGGING
+#define ASIC_DEBUG_MSG(...) (asic::detail::log_debug_msg(__FILE__, __LINE__, __VA_ARGS__))
+#else
+#define ASIC_DEBUG_MSG(...) ((void)0)
+#endif // ASIC_ENABLE_DEBUG_LOGGING
+
+#if ASIC_ENABLE_ASSERTS
+#define ASIC_ASSERT(condition) (asic::detail::check_assert(__FILE__, __LINE__, #condition, (condition)))
+#else
+#define ASIC_ASSERT(condition) ((void)0)
+#endif
+
+#endif // ASIC_DEBUG_H
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 75a77ef5..f5c4be53 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,21 +1,9 @@
-#include <pybind11/pybind11.h>
-
-namespace py = pybind11;
-
-namespace asic {
-
-int add(int a, int b) {
-	return a + b;
-}
-
-int sub(int a, int b) {
-	return a - b;
-}
-
-} // namespace asic
-
-PYBIND11_MODULE(_b_asic, m) {
-	m.doc() = "Better ASIC Toolbox Extension Module.";
-	m.def("add", &asic::add, "A function which adds two numbers.", py::arg("a"), py::arg("b"));
-	m.def("sub", &asic::sub, "A function which subtracts two numbers.", py::arg("a"), py::arg("b"));
+#include "simulation.h"
+#include <pybind11/pybind11.h>
+
+namespace py = pybind11;
+
+PYBIND11_MODULE(_b_asic, module) {
+	module.doc() = "Better ASIC Toolbox Extension Module.";
+	asic::define_simulation_class(module);
 }
\ No newline at end of file
diff --git a/src/number.h b/src/number.h
new file mode 100644
index 00000000..9cb5b42f
--- /dev/null
+++ b/src/number.h
@@ -0,0 +1,13 @@
+#ifndef ASIC_NUMBER_H
+#define ASIC_NUMBER_H
+
+#include <complex>
+#include <pybind11/complex.h>
+
+namespace asic {
+
+using number = std::complex<double>;
+
+} // namespace asic
+
+#endif // ASIC_NUMBER_H
\ No newline at end of file
diff --git a/src/simulation.cpp b/src/simulation.cpp
new file mode 100644
index 00000000..33280f60
--- /dev/null
+++ b/src/simulation.cpp
@@ -0,0 +1,61 @@
+#include "simulation.h"
+#include "simulation/simulation.h"
+
+namespace py = pybind11;
+
+namespace asic {
+
+void define_simulation_class(pybind11::module& module) {
+	// clang-format off
+	py::class_<simulation>(module, "FastSimulation")
+		.def(py::init<py::handle>(),
+			py::arg("sfg"),
+			"SFG Constructor.")
+
+		.def(py::init<py::handle, std::optional<std::vector<std::optional<input_provider_t>>>>(),
+			py::arg("sfg"), py::arg("input_providers"),
+			"SFG Constructor.")
+
+		.def("set_input", &simulation::set_input,
+			py::arg("index"), py::arg("input_provider"),
+			"Set the input function used to get values for the specific input at the given index to the internal SFG.")
+
+		.def("set_inputs", &simulation::set_inputs,
+			py::arg("input_providers"),
+			"Set the input functions used to get values for the inputs to the internal SFG.")
+
+		.def("step", &simulation::step,
+			py::arg("save_results") = true, py::arg("bits_override") = py::none{}, py::arg("truncate") = true,
+			"Run one iteration of the simulation and return the resulting output values.")
+
+		.def("run_until", &simulation::run_until,
+			py::arg("iteration"), py::arg("save_results") = true, py::arg("bits_override") = py::none{}, py::arg("truncate") = true,
+			"Run the simulation until its iteration is greater than or equal to the given iteration\n"
+			"and return the output values of the last iteration.")
+
+		.def("run_for", &simulation::run_for,
+			py::arg("iterations"), py::arg("save_results") = true, py::arg("bits_override") = py::none{}, py::arg("truncate") = true,
+			"Run a given number of iterations of the simulation and return the output values of the last iteration.")
+
+		.def("run", &simulation::run,
+			py::arg("save_results") = true, py::arg("bits_override") = py::none{}, py::arg("truncate") = true,
+			"Run the simulation until the end of its input arrays and return the output values of the last iteration.")
+
+		.def_property_readonly("iteration", &simulation::iteration,
+			"Get the current iteration number of the simulation.")
+
+		.def_property_readonly("results", &simulation::results,
+			"Get a mapping from result keys to numpy arrays containing all results, including intermediate values,\n"
+			"calculated for each iteration up until now that was run with save_results enabled.\n"
+			"The mapping is indexed using the key() method of Operation with the appropriate output index.\n"
+			"Example result after 3 iterations: {\"c1\": [3, 6, 7], \"c2\": [4, 5, 5], \"bfly1.0\": [7, 0, 0], \"bfly1.1\": [-1, 0, 2], \"0\": [7, -2, -1]}")
+
+		.def("clear_results", &simulation::clear_results,
+			"Clear all results that were saved until now.")
+
+		.def("clear_state", &simulation::clear_state,
+			"Clear all current state of the simulation, except for the results and iteration.");
+	// clang-format on
+}
+
+} // namespace asic
\ No newline at end of file
diff --git a/src/simulation.h b/src/simulation.h
new file mode 100644
index 00000000..aefa3a4e
--- /dev/null
+++ b/src/simulation.h
@@ -0,0 +1,12 @@
+#ifndef ASIC_SIMULATION_H
+#define ASIC_SIMULATION_H
+
+#include <pybind11/pybind11.h>
+
+namespace asic {
+
+void define_simulation_class(pybind11::module& module);
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_H
\ No newline at end of file
diff --git a/src/simulation/compile.cpp b/src/simulation/compile.cpp
new file mode 100644
index 00000000..7fa7ac67
--- /dev/null
+++ b/src/simulation/compile.cpp
@@ -0,0 +1,313 @@
+#define NOMINMAX
+#include "compile.h"
+
+#include "../algorithm.h"
+#include "../debug.h"
+#include "../span.h"
+#include "format_code.h"
+
+#include <Python.h>
+#include <fmt/format.h>
+#include <limits>
+#include <optional>
+#include <string_view>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+
+namespace py = pybind11;
+
+namespace asic {
+
+[[nodiscard]] static result_key key_base(py::handle op, std::string_view prefix) {
+	auto const graph_id = op.attr("graph_id").cast<std::string_view>();
+	return (prefix.empty()) ? result_key{graph_id} : fmt::format("{}.{}", prefix, graph_id);
+}
+
+[[nodiscard]] static result_key key_of_output(py::handle op, std::size_t output_index, std::string_view prefix) {
+	auto const base = key_base(op, prefix);
+	if (base.empty()) {
+		return fmt::to_string(output_index);
+	}
+	if (op.attr("output_count").cast<std::size_t>() == 1) {
+		return base;
+	}
+	return fmt::format("{}.{}", base, output_index);
+}
+
+class compiler final {
+public:
+	simulation_code compile(py::handle sfg) {
+		ASIC_DEBUG_MSG("Compiling code...");
+		this->initialize_code(sfg.attr("input_count").cast<std::size_t>(), sfg.attr("output_count").cast<std::size_t>());
+		auto deferred_delays = delay_queue{};
+		this->add_outputs(sfg, deferred_delays);
+		this->add_deferred_delays(std::move(deferred_delays));
+		this->resolve_invalid_result_indices();
+		ASIC_DEBUG_MSG("Compiled code:\n{}\n", format_compiled_simulation_code(m_code));
+		return std::move(m_code);
+	}
+
+private:
+	struct sfg_info final {
+		py::handle sfg;
+		std::size_t prefix_length;
+
+		sfg_info(py::handle sfg, std::size_t prefix_length)
+			: sfg(sfg)
+			, prefix_length(prefix_length) {}
+
+		[[nodiscard]] std::size_t find_input_operation_index(py::handle op) const {
+			for (auto const& [i, in] : enumerate(sfg.attr("input_operations"))) {
+				if (in.is(op)) {
+					return i;
+				}
+			}
+			throw py::value_error{"Stray Input operation in simulation SFG"};
+		}
+	};
+
+	using sfg_info_stack = std::vector<sfg_info>;
+	using delay_queue = std::vector<std::tuple<std::size_t, py::handle, std::string, sfg_info_stack>>;
+	using added_output_cache = std::unordered_set<PyObject const*>;
+	using added_result_cache = std::unordered_map<PyObject const*, result_index_t>;
+	using added_custom_operation_cache = std::unordered_map<PyObject const*, std::size_t>;
+
+	static constexpr auto no_result_index = std::numeric_limits<result_index_t>::max();
+
+	void initialize_code(std::size_t input_count, std::size_t output_count) {
+		m_code.required_stack_size = 0;
+		m_code.input_count = input_count;
+		m_code.output_count = output_count;
+	}
+
+	void add_outputs(py::handle sfg, delay_queue& deferred_delays) {
+		for (auto const i : range(m_code.output_count)) {
+			this->add_operation_output(sfg, i, std::string_view{}, sfg_info_stack{}, deferred_delays);
+		}
+	}
+
+	void add_deferred_delays(delay_queue&& deferred_delays) {
+		while (!deferred_delays.empty()) {
+			auto new_deferred_delays = delay_queue{};
+			for (auto const& [delay_index, op, prefix, sfg_stack] : deferred_delays) {
+				this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
+				this->add_instruction(instruction_type::update_delay, no_result_index, -1).index = delay_index;
+			}
+			deferred_delays = new_deferred_delays;
+		}
+	}
+
+	void resolve_invalid_result_indices() {
+		for (auto& instruction : m_code.instructions) {
+			if (instruction.result_index == no_result_index) {
+				instruction.result_index = static_cast<result_index_t>(m_code.result_keys.size());
+			}
+		}
+	}
+
+	[[nodiscard]] static sfg_info_stack push_sfg(sfg_info_stack const& sfg_stack, py::handle sfg, std::size_t prefix_length) {
+		auto const new_size = static_cast<std::size_t>(sfg_stack.size() + 1);
+		auto new_sfg_stack = sfg_info_stack{};
+		new_sfg_stack.reserve(new_size);
+		for (auto const& info : sfg_stack) {
+			new_sfg_stack.push_back(info);
+		}
+		new_sfg_stack.emplace_back(sfg, prefix_length);
+		return new_sfg_stack;
+	}
+
+	[[nodiscard]] static sfg_info_stack pop_sfg(sfg_info_stack const& sfg_stack) {
+		ASIC_ASSERT(!sfg_stack.empty());
+		auto const new_size = static_cast<std::size_t>(sfg_stack.size() - 1);
+		auto new_sfg_stack = sfg_info_stack{};
+		new_sfg_stack.reserve(new_size);
+		for (auto const& info : span{sfg_stack}.first(new_size)) {
+			new_sfg_stack.push_back(info);
+		}
+		return new_sfg_stack;
+	}
+
+	instruction& add_instruction(instruction_type type, result_index_t result_index, std::ptrdiff_t stack_diff) {
+		m_stack_depth += stack_diff;
+		if (m_stack_depth < 0) {
+			throw py::value_error{"Detected input/output count mismatch in simulation SFG"};
+		}
+		if (auto const stack_size = static_cast<std::size_t>(m_stack_depth); stack_size > m_code.required_stack_size) {
+			m_code.required_stack_size = stack_size;
+		}
+		auto& instruction = m_code.instructions.emplace_back();
+		instruction.type = type;
+		instruction.result_index = result_index;
+		return instruction;
+	}
+
+	[[nodiscard]] std::optional<result_index_t> begin_operation_output(py::handle op, std::size_t output_index, std::string_view prefix) {
+		auto const pointer = op.attr("outputs")[py::int_{output_index}].ptr();
+		if (m_incomplete_outputs.count(pointer) != 0) {
+			// Make sure the output doesn't depend on its own value, unless it's a delay operation.
+			if (op.attr("type_name")().cast<std::string_view>() != "t") {
+				throw py::value_error{"Direct feedback loop detected in simulation SFG"};
+			}
+		}
+		// Try to add a new result.
+		auto const [it, inserted] = m_added_results.try_emplace(pointer, static_cast<result_index_t>(m_code.result_keys.size()));
+		if (inserted) {
+			if (m_code.result_keys.size() >= static_cast<std::size_t>(std::numeric_limits<result_index_t>::max())) {
+				throw py::value_error{fmt::format("Simulation SFG requires too many outputs to be stored (limit: {})",
+												  std::numeric_limits<result_index_t>::max())};
+			}
+			m_code.result_keys.push_back(key_of_output(op, output_index, prefix));
+			m_incomplete_outputs.insert(pointer);
+			return it->second;
+		}
+		// If the result has already been added, we re-use the old result and
+		// return std::nullopt to indicate that we don't need to add all the required instructions again.
+		this->add_instruction(instruction_type::push_result, it->second, 1).index = static_cast<std::size_t>(it->second);
+		return std::nullopt;
+	}
+
+	void end_operation_output(py::handle op, std::size_t output_index) {
+		auto const pointer = op.attr("outputs")[py::int_{output_index}].ptr();
+		[[maybe_unused]] auto const erased = m_incomplete_outputs.erase(pointer);
+		ASIC_ASSERT(erased == 1);
+	}
+
+	[[nodiscard]] std::size_t try_add_custom_operation(py::handle op) {
+		auto const [it, inserted] = m_added_custom_operations.try_emplace(op.ptr(), m_added_custom_operations.size());
+		if (inserted) {
+			auto& custom_operation = m_code.custom_operations.emplace_back();
+			custom_operation.evaluate_output = op.attr("evaluate_output");
+			custom_operation.input_count = op.attr("input_count").cast<std::size_t>();
+			custom_operation.output_count = op.attr("output_count").cast<std::size_t>();
+		}
+		return it->second;
+	}
+
+	[[nodiscard]] std::size_t add_delay_info(number initial_value, result_index_t result_index) {
+		auto const delay_index = m_code.delays.size();
+		auto& delay = m_code.delays.emplace_back();
+		delay.initial_value = initial_value;
+		delay.result_index = result_index;
+		return delay_index;
+	}
+
+	void add_source(py::handle op, std::size_t input_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
+					delay_queue& deferred_delays) {
+		auto const signal = py::object{op.attr("inputs")[py::int_{input_index}].attr("signals")[py::int_{0}]};
+		auto const src = py::handle{signal.attr("source")};
+		auto const operation = py::handle{src.attr("operation")};
+		auto const index = src.attr("index").cast<std::size_t>();
+		this->add_operation_output(operation, index, prefix, sfg_stack, deferred_delays);
+		if (!signal.attr("bits").is_none()) {
+			auto const bits = signal.attr("bits").cast<std::size_t>();
+			if (bits > 64) {
+				throw py::value_error{"Cannot truncate to more than 64 bits"};
+			}
+			this->add_instruction(instruction_type::truncate, no_result_index, 0).bit_mask = static_cast<std::int64_t>(
+				(std::int64_t{1} << bits) - 1);
+		}
+	}
+
+	void add_unary_operation_output(py::handle op, result_index_t result_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
+									delay_queue& deferred_delays, instruction_type type) {
+		this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
+		this->add_instruction(type, result_index, 0);
+	}
+
+	void add_binary_operation_output(py::handle op, result_index_t result_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
+									 delay_queue& deferred_delays, instruction_type type) {
+		this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
+		this->add_source(op, 1, prefix, sfg_stack, deferred_delays);
+		this->add_instruction(type, result_index, -1);
+	}
+
+	void add_operation_output(py::handle op, std::size_t output_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
+							  delay_queue& deferred_delays) {
+		auto const type_name = op.attr("type_name")().cast<std::string_view>();
+		if (type_name == "out") {
+			this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
+		} else if (auto const result_index = this->begin_operation_output(op, output_index, prefix)) {
+			if (type_name == "c") {
+				this->add_instruction(instruction_type::push_constant, *result_index, 1).value = op.attr("value").cast<number>();
+			} else if (type_name == "add") {
+				this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::addition);
+			} else if (type_name == "sub") {
+				this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::subtraction);
+			} else if (type_name == "mul") {
+				this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::multiplication);
+			} else if (type_name == "div") {
+				this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::division);
+			} else if (type_name == "min") {
+				this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::min);
+			} else if (type_name == "max") {
+				this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::max);
+			} else if (type_name == "sqrt") {
+				this->add_unary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::square_root);
+			} else if (type_name == "conj") {
+				this->add_unary_operation_output(
+					op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::complex_conjugate);
+			} else if (type_name == "abs") {
+				this->add_unary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::absolute);
+			} else if (type_name == "cmul") {
+				this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
+				this->add_instruction(instruction_type::constant_multiplication, *result_index, 0).value = op.attr("value").cast<number>();
+			} else if (type_name == "bfly") {
+				if (output_index == 0) {
+					this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
+					this->add_source(op, 1, prefix, sfg_stack, deferred_delays);
+					this->add_instruction(instruction_type::addition, *result_index, -1);
+				} else {
+					this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
+					this->add_source(op, 1, prefix, sfg_stack, deferred_delays);
+					this->add_instruction(instruction_type::subtraction, *result_index, -1);
+				}
+			} else if (type_name == "in") {
+				if (sfg_stack.empty()) {
+					throw py::value_error{"Encountered Input operation outside SFG in simulation"};
+				}
+				auto const& info = sfg_stack.back();
+				auto const input_index = info.find_input_operation_index(op);
+				if (sfg_stack.size() == 1) {
+					this->add_instruction(instruction_type::push_input, *result_index, 1).index = input_index;
+				} else {
+					this->add_source(info.sfg, input_index, prefix.substr(0, info.prefix_length), pop_sfg(sfg_stack), deferred_delays);
+					this->add_instruction(instruction_type::forward_value, *result_index, 0);
+				}
+			} else if (type_name == "t") {
+				auto const delay_index = this->add_delay_info(op.attr("initial_value").cast<number>(), *result_index);
+				deferred_delays.emplace_back(delay_index, op, std::string{prefix}, sfg_stack);
+				this->add_instruction(instruction_type::push_delay, *result_index, 1).index = delay_index;
+			} else if (type_name == "sfg") {
+				auto const output_op = py::handle{op.attr("output_operations")[py::int_{output_index}]};
+				this->add_source(output_op, 0, key_base(op, prefix), push_sfg(sfg_stack, op, prefix.size()), deferred_delays);
+				this->add_instruction(instruction_type::forward_value, *result_index, 0);
+			} else {
+				auto const custom_operation_index = this->try_add_custom_operation(op);
+				auto const& custom_operation = m_code.custom_operations[custom_operation_index];
+				for (auto const i : range(custom_operation.input_count)) {
+					this->add_source(op, i, prefix, sfg_stack, deferred_delays);
+				}
+				auto const custom_source_index = m_code.custom_sources.size();
+				auto& custom_source = m_code.custom_sources.emplace_back();
+				custom_source.custom_operation_index = custom_operation_index;
+				custom_source.output_index = output_index;
+				auto const stack_diff = std::ptrdiff_t{1} - static_cast<std::ptrdiff_t>(custom_operation.input_count);
+				this->add_instruction(instruction_type::custom, *result_index, stack_diff).index = custom_source_index;
+			}
+			this->end_operation_output(op, output_index);
+		}
+	}
+
+	simulation_code m_code;
+	added_output_cache m_incomplete_outputs;
+	added_result_cache m_added_results;
+	added_custom_operation_cache m_added_custom_operations;
+	std::ptrdiff_t m_stack_depth = 0;
+};
+
+simulation_code compile_simulation(pybind11::handle sfg) {
+	return compiler{}.compile(sfg);
+}
+
+} // namespace asic
\ No newline at end of file
diff --git a/src/simulation/compile.h b/src/simulation/compile.h
new file mode 100644
index 00000000..883f4c58
--- /dev/null
+++ b/src/simulation/compile.h
@@ -0,0 +1,61 @@
+#ifndef ASIC_SIMULATION_COMPILE_H
+#define ASIC_SIMULATION_COMPILE_H
+
+#include "instruction.h"
+
+#include <cstddef>
+#include <pybind11/pybind11.h>
+#include <string>
+#include <vector>
+
+namespace asic {
+
+using result_key = std::string;
+
+struct simulation_code final {
+	struct custom_operation final {
+		// Python function used to evaluate the custom operation.
+		pybind11::object evaluate_output;
+		// Number of inputs that the custom operation takes.
+		std::size_t input_count;
+		// Number of outputs that the custom operation gives.
+		std::size_t output_count;
+	};
+
+	struct custom_source final {
+		// Index into custom_operations where the custom_operation corresponding to this custom_source is located.
+		std::size_t custom_operation_index;
+		// Output index of the custom_operation that this source gets it value from.
+		std::size_t output_index;
+	};
+
+	struct delay_info final {
+		// Initial value to set at the start of the simulation.
+		number initial_value;
+		// The result index where the current value should be stored at the start of each iteration.
+		result_index_t result_index;
+	};
+
+	// Instructions to execute for one full iteration of the simulation.
+	std::vector<instruction> instructions;
+	// Custom operations used by the simulation.
+	std::vector<custom_operation> custom_operations;
+	// Signal sources that use custom operations.
+	std::vector<custom_source> custom_sources;
+	// Info about the delay operations used in the simulation.
+	std::vector<delay_info> delays;
+	// Keys for each result produced by the simulation. The index of the key matches the index of the result in the simulation state.
+	std::vector<result_key> result_keys;
+	// Number of values expected as input to the simulation.
+	std::size_t input_count;
+	// Number of values given as output from the simulation. This will be the number of values left on the stack after a full iteration of the simulation has been run.
+	std::size_t output_count;
+	// Maximum number of values that need to be able to fit on the stack in order to run a full iteration of the simulation.
+	std::size_t required_stack_size;
+};
+
+[[nodiscard]] simulation_code compile_simulation(pybind11::handle sfg);
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_COMPILE_H
\ No newline at end of file
diff --git a/src/simulation/format_code.h b/src/simulation/format_code.h
new file mode 100644
index 00000000..5ebbb95d
--- /dev/null
+++ b/src/simulation/format_code.h
@@ -0,0 +1,129 @@
+#ifndef ASIC_SIMULATION_FORMAT_CODE_H
+#define ASIC_SIMULATION_FORMAT_CODE_H
+
+#include "../algorithm.h"
+#include "../debug.h"
+#include "../number.h"
+#include "compile.h"
+#include "instruction.h"
+
+#include <fmt/format.h>
+#include <string>
+
+namespace asic {
+
+[[nodiscard]] inline std::string format_number(number const& value) {
+	if (value.imag() == 0) {
+		return fmt::to_string(value.real());
+	}
+	if (value.real() == 0) {
+		return fmt::format("{}j", value.imag());
+	}
+	if (value.imag() < 0) {
+		return fmt::format("{}-{}j", value.real(), -value.imag());
+	}
+	return fmt::format("{}+{}j", value.real(), value.imag());
+}
+
+[[nodiscard]] inline std::string format_compiled_simulation_code_result_keys(simulation_code const& code) {
+	auto result = std::string{};
+	for (auto const& [i, result_key] : enumerate(code.result_keys)) {
+		result += fmt::format("{:>2}: \"{}\"\n", i, result_key);
+	}
+	return result;
+}
+
+[[nodiscard]] inline std::string format_compiled_simulation_code_delays(simulation_code const& code) {
+	auto result = std::string{};
+	for (auto const& [i, delay] : enumerate(code.delays)) {
+		ASIC_ASSERT(delay.result_index < code.result_keys.size());
+		result += fmt::format("{:>2}: Initial value: {}, Result: {}: \"{}\"\n",
+							  i,
+							  format_number(delay.initial_value),
+							  delay.result_index,
+							  code.result_keys[delay.result_index]);
+	}
+	return result;
+}
+
+[[nodiscard]] inline std::string format_compiled_simulation_code_instruction(instruction const& instruction) {
+	switch (instruction.type) {
+		// clang-format off
+		case instruction_type::push_input:              return fmt::format("push_input inputs[{}]", instruction.index);
+		case instruction_type::push_result:             return fmt::format("push_result results[{}]", instruction.index);
+		case instruction_type::push_delay:              return fmt::format("push_delay delays[{}]", instruction.index);
+		case instruction_type::push_constant:           return fmt::format("push_constant {}", format_number(instruction.value));
+		case instruction_type::truncate:                return fmt::format("truncate {:#018x}", instruction.bit_mask);
+		case instruction_type::addition:                return "addition";
+		case instruction_type::subtraction:             return "subtraction";
+		case instruction_type::multiplication:          return "multiplication";
+		case instruction_type::division:                return "division";
+		case instruction_type::min:                     return "min";
+		case instruction_type::max:                     return "max";
+		case instruction_type::square_root:             return "square_root";
+		case instruction_type::complex_conjugate:       return "complex_conjugate";
+		case instruction_type::absolute:                return "absolute";
+		case instruction_type::constant_multiplication: return fmt::format("constant_multiplication {}", format_number(instruction.value));
+		case instruction_type::update_delay:            return fmt::format("update_delay delays[{}]", instruction.index);
+		case instruction_type::custom:                  return fmt::format("custom custom_sources[{}]", instruction.index);
+		case instruction_type::forward_value:           return "forward_value";
+		// clang-format on
+	}
+	return std::string{};
+}
+
+[[nodiscard]] inline std::string format_compiled_simulation_code_instructions(simulation_code const& code) {
+	auto result = std::string{};
+	for (auto const& [i, instruction] : enumerate(code.instructions)) {
+		auto instruction_string = format_compiled_simulation_code_instruction(instruction);
+		if (instruction.result_index < code.result_keys.size()) {
+			instruction_string = fmt::format(
+				"{:<26} -> {}: \"{}\"", instruction_string, instruction.result_index, code.result_keys[instruction.result_index]);
+		}
+		result += fmt::format("{:>2}: {}\n", i, instruction_string);
+	}
+	return result;
+}
+
+[[nodiscard]] inline std::string format_compiled_simulation_code(simulation_code const& code) {
+	return fmt::format(
+		"==============================================\n"
+		"> Code stats\n"
+		"==============================================\n"
+		"Input count: {}\n"
+		"Output count: {}\n"
+		"Instruction count: {}\n"
+		"Required stack size: {}\n"
+		"Delay count: {}\n"
+		"Result count: {}\n"
+		"Custom operation count: {}\n"
+		"Custom source count: {}\n"
+		"==============================================\n"
+		"> Delays\n"
+		"==============================================\n"
+		"{}"
+		"==============================================\n"
+		"> Result keys\n"
+		"==============================================\n"
+		"{}"
+		"==============================================\n"
+		"> Instructions\n"
+		"==============================================\n"
+		"{}"
+		"==============================================",
+		code.input_count,
+		code.output_count,
+		code.instructions.size(),
+		code.required_stack_size,
+		code.delays.size(),
+		code.result_keys.size(),
+		code.custom_operations.size(),
+		code.custom_sources.size(),
+		format_compiled_simulation_code_delays(code),
+		format_compiled_simulation_code_result_keys(code),
+		format_compiled_simulation_code_instructions(code));
+}
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_FORMAT_CODE
\ No newline at end of file
diff --git a/src/simulation/instruction.h b/src/simulation/instruction.h
new file mode 100644
index 00000000..d650c651
--- /dev/null
+++ b/src/simulation/instruction.h
@@ -0,0 +1,57 @@
+#ifndef ASIC_SIMULATION_INSTRUCTION_H
+#define ASIC_SIMULATION_INSTRUCTION_H
+
+#include "../number.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+
+namespace asic {
+
+enum class instruction_type : std::uint8_t {
+	push_input,              // push(inputs[index])
+	push_result,             // push(results[index])
+	push_delay,              // push(delays[index])
+	push_constant,           // push(value)
+	truncate,                // push(trunc(pop(), bit_mask))
+	addition,                // push(pop() + pop())
+	subtraction,             // push(pop() - pop())
+	multiplication,          // push(pop() * pop())
+	division,                // push(pop() / pop())
+	min,                     // push(min(pop(), pop()))
+	max,                     // push(max(pop(), pop()))
+	square_root,             // push(sqrt(pop()))
+	complex_conjugate,       // push(conj(pop()))
+	absolute,                // push(abs(pop()))
+	constant_multiplication, // push(pop() * value)
+	update_delay,            // delays[index] = pop()
+	custom,                  // Custom operation. Uses custom_source[index].
+	forward_value            // Forward the current value on the stack (push(pop()), i.e. do nothing).
+};
+
+using result_index_t = std::uint16_t;
+
+struct instruction final {
+	constexpr instruction() noexcept
+		: index(0)
+		, result_index(0)
+		, type(instruction_type::forward_value) {}
+
+	union {
+		// Index used by push_input, push_result, delay and custom.
+		std::size_t index;
+		// Bit mask used by truncate.
+		std::int64_t bit_mask;
+		// Constant value used by push_constant and constant_multiplication.
+		number value;
+	};
+	// Index into where the result of the instruction will be stored. If the result should be ignored, this index will be one past the last valid result index.
+	result_index_t result_index;
+	// Specifies what kind of operation the instruction should execute.
+	instruction_type type;
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_INSTRUCTION_H
\ No newline at end of file
diff --git a/src/simulation/run.cpp b/src/simulation/run.cpp
new file mode 100644
index 00000000..c14fa192
--- /dev/null
+++ b/src/simulation/run.cpp
@@ -0,0 +1,176 @@
+#define NOMINMAX
+#include "run.h"
+
+#include "../algorithm.h"
+#include "../debug.h"
+#include "format_code.h"
+
+#include <algorithm>
+#include <complex>
+#include <cstddef>
+#include <fmt/format.h>
+#include <iterator>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <stdexcept>
+
+namespace py = pybind11;
+
+namespace asic {
+
+[[nodiscard]] static number truncate_value(number value, std::int64_t bit_mask) {
+	if (value.imag() != 0) {
+		throw py::type_error{"Complex value cannot be truncated"};
+	}
+	return number{static_cast<number::value_type>(static_cast<std::int64_t>(value.real()) & bit_mask)};
+}
+
+[[nodiscard]] static std::int64_t setup_truncation_parameters(bool& truncate, std::optional<std::uint8_t>& bits_override) {
+	if (truncate && bits_override) {
+		truncate = false; // Ignore truncate instructions, they will be truncated using bits_override instead.
+		if (*bits_override > 64) {
+			throw py::value_error{"Cannot truncate to more than 64 bits"};
+		}
+		return static_cast<std::int64_t>((std::int64_t{1} << *bits_override) - 1); // Return the bit mask override to use.
+	}
+	bits_override.reset(); // Don't use bits_override if truncate is false.
+	return std::int64_t{};
+}
+
+simulation_state run_simulation(simulation_code const& code, span<number const> inputs, span<number> delays,
+								std::optional<std::uint8_t> bits_override, bool truncate) {
+	ASIC_ASSERT(inputs.size() == code.input_count);
+	ASIC_ASSERT(delays.size() == code.delays.size());
+	ASIC_ASSERT(code.output_count <= code.required_stack_size);
+
+	auto state = simulation_state{};
+
+	// Setup results.
+	state.results.resize(code.result_keys.size() + 1); // Add one space to store ignored results.
+	// Initialize delay results to their current values.
+	for (auto const& [i, delay] : enumerate(code.delays)) {
+		state.results[delay.result_index] = delays[i];
+	}
+
+	// Setup stack.
+	state.stack.resize(code.required_stack_size);
+	auto stack_pointer = state.stack.data();
+
+	// Utility functions to make the stack manipulation code below more readable.
+	// Should hopefully be inlined by the compiler.
+	auto const push = [&](number value) -> void {
+		ASIC_ASSERT(std::distance(state.stack.data(), stack_pointer) < static_cast<std::ptrdiff_t>(state.stack.size()));
+		*stack_pointer++ = value;
+	};
+	auto const pop = [&]() -> number {
+		ASIC_ASSERT(std::distance(state.stack.data(), stack_pointer) > std::ptrdiff_t{0});
+		return *--stack_pointer;
+	};
+	auto const peek = [&]() -> number {
+		ASIC_ASSERT(std::distance(state.stack.data(), stack_pointer) > std::ptrdiff_t{0});
+		ASIC_ASSERT(std::distance(state.stack.data(), stack_pointer) <= static_cast<std::ptrdiff_t>(state.stack.size()));
+		return *(stack_pointer - 1);
+	};
+
+	// Check if results should be truncated.
+	auto const bit_mask_override = setup_truncation_parameters(truncate, bits_override);
+
+	// Hot instruction evaluation loop.
+	for (auto const& instruction : code.instructions) {
+		ASIC_DEBUG_MSG("Evaluating {}.", format_compiled_simulation_code_instruction(instruction));
+		// Execute the instruction.
+		switch (instruction.type) {
+			case instruction_type::push_input:
+				push(inputs[instruction.index]);
+				break;
+			case instruction_type::push_result:
+				push(state.results[instruction.index]);
+				break;
+			case instruction_type::push_delay:
+				push(delays[instruction.index]);
+				break;
+			case instruction_type::push_constant:
+				push(instruction.value);
+				break;
+			case instruction_type::truncate:
+				if (truncate) {
+					push(truncate_value(pop(), instruction.bit_mask));
+				}
+				break;
+			case instruction_type::addition:
+				push(pop() + pop());
+				break;
+			case instruction_type::subtraction:
+				push(pop() - pop());
+				break;
+			case instruction_type::multiplication:
+				push(pop() * pop());
+				break;
+			case instruction_type::division:
+				push(pop() / pop());
+				break;
+			case instruction_type::min: {
+				auto const lhs = pop();
+				auto const rhs = pop();
+				if (lhs.imag() != 0 || rhs.imag() != 0) {
+					throw std::runtime_error{"Min does not support complex numbers."};
+				}
+				push(std::min(lhs.real(), rhs.real()));
+				break;
+			}
+			case instruction_type::max: {
+				auto const lhs = pop();
+				auto const rhs = pop();
+				if (lhs.imag() != 0 || rhs.imag() != 0) {
+					throw std::runtime_error{"Max does not support complex numbers."};
+				}
+				push(std::max(lhs.real(), rhs.real()));
+				break;
+			}
+			case instruction_type::square_root:
+				push(std::sqrt(pop()));
+				break;
+			case instruction_type::complex_conjugate:
+				push(std::conj(pop()));
+				break;
+			case instruction_type::absolute:
+				push(number{std::abs(pop())});
+				break;
+			case instruction_type::constant_multiplication:
+				push(pop() * instruction.value);
+				break;
+			case instruction_type::update_delay:
+				delays[instruction.index] = pop();
+				break;
+			case instruction_type::custom: {
+				using namespace pybind11::literals;
+				auto const& src = code.custom_sources[instruction.index];
+				auto const& op = code.custom_operations[src.custom_operation_index];
+				auto input_values = std::vector<number>{};
+				input_values.reserve(op.input_count);
+				for (auto i = std::size_t{0}; i < op.input_count; ++i) {
+					input_values.push_back(pop());
+				}
+				push(op.evaluate_output(src.output_index, std::move(input_values), "truncate"_a = truncate).cast<number>());
+				break;
+			}
+			case instruction_type::forward_value:
+				// Do nothing, since doing push(pop()) would be pointless.
+				break;
+		}
+		// If we've been given a global override for how many bits to use, always truncate the result.
+		if (bits_override) {
+			push(truncate_value(pop(), bit_mask_override));
+		}
+		// Store the result.
+		state.results[instruction.result_index] = peek();
+	}
+
+	// Remove the space that we used for ignored results.
+	state.results.pop_back();
+	// Erase the portion of the stack that does not contain the output values.
+	state.stack.erase(state.stack.begin() + static_cast<std::ptrdiff_t>(code.output_count), state.stack.end());
+	return state;
+}
+
+} // namespace asic
\ No newline at end of file
diff --git a/src/simulation/run.h b/src/simulation/run.h
new file mode 100644
index 00000000..2174c571
--- /dev/null
+++ b/src/simulation/run.h
@@ -0,0 +1,23 @@
+#ifndef ASIC_SIMULATION_RUN_H
+#define ASIC_SIMULATION_RUN_H
+
+#include "../number.h"
+#include "../span.h"
+#include "compile.h"
+
+#include <cstdint>
+#include <vector>
+
+namespace asic {
+
+struct simulation_state final {
+	std::vector<number> stack;
+	std::vector<number> results;
+};
+
+simulation_state run_simulation(simulation_code const& code, span<number const> inputs, span<number> delays,
+								std::optional<std::uint8_t> bits_override, bool truncate);
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_RUN_H
\ No newline at end of file
diff --git a/src/simulation/simulation.cpp b/src/simulation/simulation.cpp
new file mode 100644
index 00000000..3af24c10
--- /dev/null
+++ b/src/simulation/simulation.cpp
@@ -0,0 +1,129 @@
+#define NOMINMAX
+#include "simulation.h"
+
+#include "../algorithm.h"
+#include "../debug.h"
+#include "compile.h"
+#include "run.h"
+
+#include <fmt/format.h>
+#include <limits>
+#include <pybind11/numpy.h>
+#include <utility>
+
+namespace py = pybind11;
+
+namespace asic {
+
+simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_t>>> input_providers)
+	: m_code(compile_simulation(sfg))
+	, m_input_functions(sfg.attr("input_count").cast<std::size_t>(), [](iteration_t) -> number { return number{}; }) {
+	m_delays.reserve(m_code.delays.size());
+	for (auto const& delay : m_code.delays) {
+		m_delays.push_back(delay.initial_value);
+	}
+	if (input_providers) {
+		this->set_inputs(std::move(*input_providers));
+	}
+}
+
+void simulation::set_input(std::size_t index, input_provider_t input_provider) {
+	if (index >= m_input_functions.size()) {
+		throw py::index_error{fmt::format("Input index out of range (expected 0-{}, got {})", m_input_functions.size() - 1, index)};
+	}
+	if (auto* const callable = std::get_if<input_function_t>(&input_provider)) {
+		m_input_functions[index] = std::move(*callable);
+	} else if (auto* const numeric = std::get_if<number>(&input_provider)) {
+		m_input_functions[index] = [value = *numeric](iteration_t) -> number {
+			return value;
+		};
+	} else if (auto* const list = std::get_if<std::vector<number>>(&input_provider)) {
+		if (!m_input_length) {
+			m_input_length = static_cast<iteration_t>(list->size());
+		} else if (*m_input_length != static_cast<iteration_t>(list->size())) {
+			throw py::value_error{fmt::format("Inconsistent input length for simulation (was {}, got {})", *m_input_length, list->size())};
+		}
+		m_input_functions[index] = [values = std::move(*list)](iteration_t n) -> number {
+			return values.at(n);
+		};
+	}
+}
+
+void simulation::set_inputs(std::vector<std::optional<input_provider_t>> input_providers) {
+	if (input_providers.size() != m_input_functions.size()) {
+		throw py::value_error{fmt::format(
+			"Wrong number of inputs supplied to simulation (expected {}, got {})", m_input_functions.size(), input_providers.size())};
+	}
+	for (auto&& [i, input_provider] : enumerate(input_providers)) {
+		if (input_provider) {
+			this->set_input(i, std::move(*input_provider));
+		}
+	}
+}
+
+std::vector<number> simulation::step(bool save_results, std::optional<std::uint8_t> bits_override, bool truncate) {
+	return this->run_for(1, save_results, bits_override, truncate);
+}
+
+std::vector<number> simulation::run_until(iteration_t iteration, bool save_results, std::optional<std::uint8_t> bits_override,
+										  bool truncate) {
+	auto result = std::vector<number>{};
+	while (m_iteration < iteration) {
+		ASIC_DEBUG_MSG("Running simulation iteration.");
+		auto inputs = std::vector<number>(m_code.input_count);
+		for (auto&& [input, function] : zip(inputs, m_input_functions)) {
+			input = function(m_iteration);
+		}
+		auto state = run_simulation(m_code, inputs, m_delays, bits_override, truncate);
+		result = std::move(state.stack);
+		if (save_results) {
+			m_results.push_back(std::move(state.results));
+		}
+		++m_iteration;
+	}
+	return result;
+}
+
+std::vector<number> simulation::run_for(iteration_t iterations, bool save_results, std::optional<std::uint8_t> bits_override,
+										bool truncate) {
+	if (iterations > std::numeric_limits<iteration_t>::max() - m_iteration) {
+		throw py::value_error("Simulation iteration type overflow!");
+	}
+	return this->run_until(m_iteration + iterations, save_results, bits_override, truncate);
+}
+
+std::vector<number> simulation::run(bool save_results, std::optional<std::uint8_t> bits_override, bool truncate) {
+	if (m_input_length) {
+		return this->run_until(*m_input_length, save_results, bits_override, truncate);
+	}
+	throw py::index_error{"Tried to run unlimited simulation"};
+}
+
+iteration_t simulation::iteration() const noexcept {
+	return m_iteration;
+}
+
+pybind11::dict simulation::results() const noexcept {
+	auto results = py::dict{};
+	if (!m_results.empty()) {
+		for (auto const& [i, key] : enumerate(m_code.result_keys)) {
+			auto values = std::vector<number>{};
+			values.reserve(m_results.size());
+			for (auto const& result : m_results) {
+				values.push_back(result[i]);
+			}
+			results[py::str{key}] = py::array{static_cast<py::ssize_t>(values.size()), values.data()};
+		}
+	}
+	return results;
+}
+
+void simulation::clear_results() noexcept {
+	m_results.clear();
+}
+
+void simulation::clear_state() noexcept {
+	m_delays.clear();
+}
+
+} // namespace asic
diff --git a/src/simulation/simulation.h b/src/simulation/simulation.h
new file mode 100644
index 00000000..c1a36cbc
--- /dev/null
+++ b/src/simulation/simulation.h
@@ -0,0 +1,54 @@
+#ifndef ASIC_SIMULATION_DOD_H
+#define ASIC_SIMULATION_DOD_H
+
+#include "../number.h"
+#include "compile.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <optional>
+#include <pybind11/functional.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <variant>
+#include <vector>
+
+namespace asic {
+
+using iteration_t = std::uint32_t;
+using input_function_t = std::function<number(iteration_t)>;
+using input_provider_t = std::variant<number, std::vector<number>, input_function_t>;
+
+class simulation final {
+public:
+	simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_t>>> input_providers = std::nullopt);
+
+	void set_input(std::size_t index, input_provider_t input_provider);
+	void set_inputs(std::vector<std::optional<input_provider_t>> input_providers);
+
+	[[nodiscard]] std::vector<number> step(bool save_results, std::optional<std::uint8_t> bits_override, bool truncate);
+	[[nodiscard]] std::vector<number> run_until(iteration_t iteration, bool save_results, std::optional<std::uint8_t> bits_override,
+												bool truncate);
+	[[nodiscard]] std::vector<number> run_for(iteration_t iterations, bool save_results, std::optional<std::uint8_t> bits_override,
+											  bool truncate);
+	[[nodiscard]] std::vector<number> run(bool save_results, std::optional<std::uint8_t> bits_override, bool truncate);
+
+	[[nodiscard]] iteration_t iteration() const noexcept;
+	[[nodiscard]] pybind11::dict results() const noexcept;
+
+	void clear_results() noexcept;
+	void clear_state() noexcept;
+
+private:
+	simulation_code m_code;
+	std::vector<number> m_delays;
+	std::vector<input_function_t> m_input_functions;
+	std::optional<iteration_t> m_input_length;
+	iteration_t m_iteration = 0;
+	std::vector<std::vector<number>> m_results;
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_DOD_H
\ No newline at end of file
diff --git a/src/span.h b/src/span.h
new file mode 100644
index 00000000..2ad454e1
--- /dev/null
+++ b/src/span.h
@@ -0,0 +1,314 @@
+#ifndef ASIC_SPAN_H
+#define ASIC_SPAN_H
+
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+#include <iterator>
+#include <limits>
+#include <array>
+#include <algorithm>
+#include <cassert>
+
+namespace asic {
+
+constexpr auto dynamic_size = static_cast<std::size_t>(-1);
+
+// C++17-compatible std::span substitute.
+template <typename T, std::size_t Size = dynamic_size>
+class span;
+
+namespace detail {
+
+template <typename T>
+struct is_span_impl : std::false_type {};
+
+template <typename T, std::size_t Size>
+struct is_span_impl<span<T, Size>> : std::true_type {};
+
+template <typename T>
+struct is_span : is_span_impl<std::remove_cv_t<T>> {};
+
+template <typename T>
+constexpr auto is_span_v = is_span<T>::value;
+
+template <typename T>
+struct is_std_array_impl : std::false_type {};
+
+template <typename T, std::size_t Size>
+struct is_std_array_impl<std::array<T, Size>> : std::true_type {};
+
+template <typename T>
+struct is_std_array : is_std_array_impl<std::remove_cv_t<T>> {};
+
+template <typename T>
+constexpr auto is_std_array_v = is_std_array<T>::value;
+
+template <std::size_t From, std::size_t To>
+struct is_size_convertible : std::bool_constant<From == To || From == dynamic_size || To == dynamic_size> {};
+
+template <std::size_t From, std::size_t To>
+constexpr auto is_size_convertible_v = is_size_convertible<From, To>::value;
+
+template <typename From, typename To>
+struct is_element_type_convertible : std::bool_constant<std::is_convertible_v<From(*)[], To(*)[]>> {};
+
+template <typename From, typename To>
+constexpr auto is_element_type_convertible_v = is_element_type_convertible<From, To>::value;
+
+template <typename T, std::size_t Size>
+struct span_base {
+	using element_type	= T;
+	using pointer		= element_type*;
+	using size_type		= std::size_t;
+
+	constexpr span_base() noexcept = default;
+	constexpr span_base(pointer data, [[maybe_unused]] size_type size) : m_data(data) { assert(size == Size); }
+
+	template <size_type N>
+	constexpr span_base(span_base<T, N> other) : m_data(other.data()) {
+		static_assert(N == Size || N == dynamic_size);
+		assert(other.size() == Size);
+	}
+
+	[[nodiscard]] constexpr pointer data() const noexcept	{ return m_data; }
+	[[nodiscard]] constexpr size_type size() const noexcept	{ return Size; }
+
+private:
+	pointer m_data = nullptr;
+};
+
+template <typename T>
+struct span_base<T, dynamic_size> {
+	using element_type	= T;
+	using pointer		= element_type*;
+	using size_type		= std::size_t;
+
+	constexpr span_base() noexcept = default;
+	constexpr span_base(pointer data, size_type size) : m_data(data), m_size(size) {}
+
+	template <size_type N>
+	explicit constexpr span_base(span_base<T, N> other) : m_data(other.data()), m_size(other.size()) {}
+
+	[[nodiscard]] constexpr pointer data() const noexcept	{ return m_data; }
+	[[nodiscard]] constexpr size_type size() const noexcept	{ return m_size; }
+
+private:
+	pointer		m_data = nullptr;
+	size_type	m_size = 0;
+};
+
+template <typename T, std::size_t Size, std::size_t Offset, std::size_t N>
+struct subspan_type {
+	using type = span<
+		T,
+		(N != dynamic_size) ?
+			N :
+			(Size != dynamic_size) ?
+				Size - Offset :
+				Size
+	>;
+};
+
+template <typename T, std::size_t Size, std::size_t Offset, std::size_t Count>
+using subspan_type_t = typename subspan_type<T, Size, Offset, Count>::type;
+
+} // namespace detail
+
+template <typename T, std::size_t Size>
+class span final : public detail::span_base<T, Size> {
+public:
+	using element_type				= typename detail::span_base<T, Size>::element_type;
+	using pointer					= typename detail::span_base<T, Size>::pointer;
+	using size_type					= typename detail::span_base<T, Size>::size_type;
+	using value_type				= std::remove_cv_t<element_type>;
+	using reference					= element_type&;
+	using iterator					= element_type*;
+	using const_iterator			= const element_type*;
+	using reverse_iterator			= std::reverse_iterator<iterator>;
+	using const_reverse_iterator	= std::reverse_iterator<const_iterator>;
+
+	// Default constructor.
+	constexpr span() noexcept = default;
+
+	// Construct from pointer, size.
+	constexpr span(pointer data, size_type size) : detail::span_base<T, Size>(data, size) {}
+
+	// Copy constructor.
+	template <
+		typename U, std::size_t N,
+		typename = std::enable_if_t<detail::is_size_convertible_v<N, Size>>,
+		typename = std::enable_if_t<detail::is_element_type_convertible_v<U, T>>
+	>
+	constexpr span(span<U, N> const& other) : span(other.data(), other.size()) {}
+
+	// Copy assignment.
+	constexpr span& operator=(span const&) noexcept = default;
+
+	// Destructor.
+	~span() = default;
+
+	// Construct from begin, end.
+	constexpr span(pointer begin, pointer end) : span(begin, end - begin) {}
+
+	// Construct from C array.
+	template <std::size_t N>
+	constexpr span(element_type(&arr)[N]) noexcept : span(std::data(arr), N) {}
+
+	// Construct from std::array.
+	template <
+		std::size_t N,
+		typename = std::enable_if_t<N != 0>
+	>
+	constexpr span(std::array<value_type, N>& arr) noexcept : span(std::data(arr), N) {}
+
+	// Construct from empty std::array.
+	constexpr span(std::array<value_type, 0>&) noexcept : span() {}
+
+	// Construct from const std::array.
+	template <
+		std::size_t N,
+		typename = std::enable_if_t<N != 0>
+	>
+	constexpr span(std::array<value_type, N> const& arr) noexcept : span(std::data(arr), N) {}
+
+	// Construct from empty const std::array.
+	constexpr span(std::array<value_type, 0> const&) noexcept : span() {}
+
+	// Construct from other container.
+	template <
+		typename Container,
+		typename = std::enable_if_t<!detail::is_span_v<Container>>,
+		typename = std::enable_if_t<!detail::is_std_array_v<Container>>,
+		typename = decltype(std::data(std::declval<Container>())),
+		typename = decltype(std::size(std::declval<Container>())),
+		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, pointer>>,
+		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, decltype(std::data(std::declval<Container>()))>>
+	>
+	constexpr span(Container& container) : span(std::data(container), std::size(container)) {}
+
+	// Construct from other const container.
+	template <
+		typename Container,
+		typename Element = element_type,
+		typename = std::enable_if_t<std::is_const_v<Element>>,
+		typename = std::enable_if_t<!detail::is_span_v<Container>>,
+		typename = std::enable_if_t<!detail::is_std_array_v<Container>>,
+		typename = decltype(std::data(std::declval<Container>())),
+		typename = decltype(std::size(std::declval<Container>())),
+		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, pointer>>,
+		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, decltype(std::data(std::declval<Container>()))>>
+	>
+	constexpr span(Container const& container) : span(std::data(container), std::size(container)) {}
+
+	[[nodiscard]] constexpr iterator begin() const noexcept						{ return this->data(); }
+	[[nodiscard]] constexpr const_iterator cbegin() const noexcept				{ return this->data(); }
+	[[nodiscard]] constexpr iterator end() const noexcept						{ return this->data() + this->size(); }
+	[[nodiscard]] constexpr const_iterator cend() const noexcept				{ return this->data() + this->size(); }
+	[[nodiscard]] constexpr reverse_iterator rbegin() const noexcept			{ return std::make_reverse_iterator(this->end()); }
+	[[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept		{ return std::make_reverse_iterator(this->cend()); }
+	[[nodiscard]] constexpr reverse_iterator rend() const noexcept				{ return std::make_reverse_iterator(this->begin()); }
+	[[nodiscard]] constexpr const_reverse_iterator crend() const noexcept		{ return std::make_reverse_iterator(this->cbegin()); }
+
+	[[nodiscard]] constexpr reference operator[](size_type i) const noexcept	{ assert(i < this->size()); return this->data()[i]; }
+	[[nodiscard]] constexpr reference operator()(size_type i) const noexcept	{ assert(i < this->size()); return this->data()[i]; }
+
+	[[nodiscard]] constexpr size_type size_bytes() const noexcept				{ return this->size() * sizeof(element_type); }
+	[[nodiscard]] constexpr bool empty() const noexcept							{ return this->size() == 0; }
+
+	[[nodiscard]] constexpr reference front() const noexcept					{ assert(!this->empty()); return this->data()[0]; }
+	[[nodiscard]] constexpr reference back() const noexcept						{ assert(!this->empty()); return this->data()[this->size() - 1]; }
+
+	template <std::size_t N>
+	[[nodiscard]] constexpr span<T, N> first() const {
+		static_assert(N != dynamic_size && N <= Size);
+		return {this->data(), N};
+	}
+
+	template <std::size_t N>
+	[[nodiscard]] constexpr span<T, N> last() const {
+		static_assert(N != dynamic_size && N <= Size);
+		return {this->data() + (Size - N), N};
+	}
+
+	template <std::size_t Offset, std::size_t N = dynamic_size>
+	[[nodiscard]] constexpr auto subspan() const -> detail::subspan_type_t<T, Size, Offset, N> {
+		static_assert(Offset <= Size);
+		return {this->data() + Offset, (N == dynamic_size) ? this->size() - Offset : N};
+	}
+
+	[[nodiscard]] constexpr span<T, dynamic_size> first(size_type n) const {
+		assert(n <= this->size());
+		return { this->data(), n };
+	}
+
+	[[nodiscard]] constexpr span<T, dynamic_size> last(size_type n) const {
+		return this->subspan(this->size() - n);
+	}
+
+	[[nodiscard]] constexpr span<T, dynamic_size> subspan(size_type offset, size_type n = dynamic_size) const {
+		if constexpr (Size == dynamic_size) {
+			assert(offset <= this->size());
+			if (n == dynamic_size) {
+				return { this->data() + offset, this->size() - offset };
+			}
+			assert(n <= this->size());
+			assert(offset + n <= this->size());
+			return {this->data() + offset, n};
+		} else {
+			return span<T, dynamic_size>{*this}.subspan(offset, n);
+		}
+	}
+};
+
+template <typename T, std::size_t LhsSize, std::size_t RhsSize>
+[[nodiscard]] constexpr bool operator==(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
+	return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+}
+
+template <typename T, std::size_t LhsSize, std::size_t RhsSize>
+[[nodiscard]] constexpr bool operator!=(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
+	return !(lhs == rhs);
+}
+
+template <typename T, std::size_t LhsSize, std::size_t RhsSize>
+[[nodiscard]] constexpr bool operator<(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
+	return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+}
+
+template <typename T, std::size_t LhsSize, std::size_t RhsSize>
+[[nodiscard]] constexpr bool operator<=(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
+	return !(lhs > rhs);
+}
+
+template <typename T, std::size_t LhsSize, std::size_t RhsSize>
+[[nodiscard]] constexpr bool operator>(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
+	return rhs < lhs;
+}
+
+template <typename T, std::size_t LhsSize, std::size_t RhsSize>
+[[nodiscard]] constexpr bool operator>=(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
+	return !(lhs < rhs);
+}
+
+template <typename Container>
+span(Container&) -> span<typename Container::value_type>;
+
+template <typename Container>
+span(Container const&) -> span<typename Container::value_type const>;
+
+template <typename T, std::size_t N>
+span(T(&)[N]) -> span<T, N>;
+
+template <typename T, std::size_t N>
+span(std::array<T, N>&) -> span<T, N>;
+
+template <typename T, std::size_t N>
+span(std::array<T, N> const&) -> span<T const, N>;
+
+template <typename T, typename Dummy>
+span(T, Dummy&&) -> span<std::remove_reference_t<decltype(std::declval<T>()[0])>>;
+
+} // namespace asic
+
+#endif // ASIC_SPAN_H
\ No newline at end of file
diff --git a/test/conftest.py b/test/conftest.py
index 64f39843..63cf5ce1 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,4 +1,5 @@
 from test.fixtures.signal import signal, signals
 from test.fixtures.operation_tree import *
 from test.fixtures.port import *
-import pytest
+from test.fixtures.signal_flow_graph import *
+import pytest
\ No newline at end of file
diff --git a/test/fixtures/operation_tree.py b/test/fixtures/operation_tree.py
index df3fcac3..695979c6 100644
--- a/test/fixtures/operation_tree.py
+++ b/test/fixtures/operation_tree.py
@@ -1,58 +1,95 @@
-from b_asic.core_operations import Addition, Constant
-from b_asic.signal import Signal
-
 import pytest
 
+from b_asic import Addition, Constant, Signal, Butterfly
+
+
 @pytest.fixture
 def operation():
     return Constant(2)
 
-def create_operation(_type, dest_oper, index, **kwargs):
-    oper = _type(**kwargs)
-    oper_signal = Signal()
-    oper._output_ports[0].add_signal(oper_signal)
-
-    dest_oper._input_ports[index].add_signal(oper_signal)
-    return oper
-
 @pytest.fixture
 def operation_tree():
-    """Return a addition operation connected with 2 constants.
-    ---C---+
-           ---A
-    ---C---+
+    """Valid addition operation connected with 2 constants.
+    2---+
+        |
+        v
+       add = 2 + 3 = 5
+        ^
+        |
+    3---+
     """
-    add_oper = Addition()
-    create_operation(Constant, add_oper, 0, value=2)
-    create_operation(Constant, add_oper, 1, value=3)
-    return add_oper
+    return Addition(Constant(2), Constant(3))
 
 @pytest.fixture
 def large_operation_tree():
-    """Return a constant operation connected with a large operation tree with 3 other constants and 3 additions.
-    ---C---+
-           ---A---+
-    ---C---+      |
-                  +---A
-    ---C---+      |
-           ---A---+
-    ---C---+
+    """Valid addition operation connected with a large operation tree with 2 other additions and 4 constants.
+    2---+
+        |
+        v
+       add---+
+        ^    |
+        |    |
+    3---+    v
+            add = (2 + 3) + (4 + 5) = 14
+    4---+    ^
+        |    |
+        v    |
+       add---+
+        ^
+        |
+    5---+
     """
-    add_oper = Addition()
-    add_oper_2 = Addition()
-
-    const_oper = create_operation(Constant, add_oper, 0, value=2)
-    create_operation(Constant, add_oper, 1, value=3)
+    return Addition(Addition(Constant(2), Constant(3)), Addition(Constant(4), Constant(5)))
 
-    create_operation(Constant, add_oper_2, 0, value=4)
-    create_operation(Constant, add_oper_2, 1, value=5)
+@pytest.fixture
+def large_operation_tree_names():
+    """Valid addition operation connected with a large operation tree with 2 other additions and 4 constants.
+    With names.
+    2---+
+        |
+        v
+       add---+
+        ^    |
+        |    |
+    3---+    v
+            add = (2 + 3) + (4 + 5) = 14
+    4---+    ^
+        |    |
+        v    |
+       add---+
+        ^
+        |
+    5---+
+    """
+    return Addition(Addition(Constant(2, name="constant2"), Constant(3, name="constant3")), Addition(Constant(4, name="constant4"), Constant(5, name="constant5")))
 
-    add_oper_3 = Addition()
-    add_oper_signal = Signal(add_oper.output(0), add_oper_3.output(0))
-    add_oper._output_ports[0].add_signal(add_oper_signal)
-    add_oper_3._input_ports[0].add_signal(add_oper_signal)
+@pytest.fixture
+def butterfly_operation_tree():
+    """Valid butterfly operations connected to eachother with 3 butterfly operations and 2 constants as inputs and 2 outputs.
+    2 ---+       +--- (2 + 4) ---+       +--- (6 + (-2)) ---+       +--- (4 + 8) ---> out1 = 12
+         |       |               |       |                  |       |
+         v       ^               v       ^                  v       ^
+         butterfly               butterfly                  butterfly
+         ^       v               ^       v                  ^       v
+         |       |               |       |                  |       |               
+    4 ---+       +--- (2 - 4) ---+       +--- (6 - (-2)) ---+       +--- (4 - 8) ---> out2 = -4
+    """
+    return Butterfly(*(Butterfly(*(Butterfly(Constant(2), Constant(4), name="bfly3").outputs), name="bfly2").outputs), name="bfly1")
 
-    add_oper_2_signal = Signal(add_oper_2.output(0), add_oper_3.output(0))
-    add_oper_2._output_ports[0].add_signal(add_oper_2_signal)
-    add_oper_3._input_ports[1].add_signal(add_oper_2_signal)
-    return const_oper
+@pytest.fixture
+def operation_graph_with_cycle():
+    """Invalid addition operation connected with an operation graph containing a cycle.
+     +-+
+     | |
+     v |
+    add+---+
+     ^     |
+     |     v
+     7    add = (? + 7) + 6 = ?
+           ^
+           |
+           6
+    """
+    add1 = Addition(None, Constant(7))
+    add1.input(0).connect(add1)
+    return Addition(add1, Constant(6))
diff --git a/test/fixtures/port.py b/test/fixtures/port.py
index 4019b3a2..4cce4f69 100644
--- a/test/fixtures/port.py
+++ b/test/fixtures/port.py
@@ -1,10 +1,20 @@
 import pytest
-from b_asic.port import InputPort, OutputPort
+
+from b_asic import InputPort, OutputPort
+
 
 @pytest.fixture
 def input_port():
-    return InputPort(0, None)
+    return InputPort(None, 0)
 
 @pytest.fixture
 def output_port():
-    return OutputPort(0, None)
+    return OutputPort(None, 0)
+
+@pytest.fixture
+def list_of_input_ports():
+    return [InputPort(None, i) for i in range(0, 3)]
+
+@pytest.fixture
+def list_of_output_ports():
+    return [OutputPort(None, i) for i in range(0, 3)]
diff --git a/test/fixtures/signal.py b/test/fixtures/signal.py
index 7b13c978..4dba99e2 100644
--- a/test/fixtures/signal.py
+++ b/test/fixtures/signal.py
@@ -1,6 +1,8 @@
 import pytest
+
 from b_asic import Signal
 
+
 @pytest.fixture
 def signal():
     """Return a signal with no connections."""
@@ -9,4 +11,4 @@ def signal():
 @pytest.fixture
 def signals():
     """Return 3 signals with no connections."""
-    return [Signal() for _ in range(0,3)]
+    return [Signal() for _ in range(0, 3)]
diff --git a/test/fixtures/signal_flow_graph.py b/test/fixtures/signal_flow_graph.py
new file mode 100644
index 00000000..a2c25ec9
--- /dev/null
+++ b/test/fixtures/signal_flow_graph.py
@@ -0,0 +1,245 @@
+import pytest
+
+from b_asic import SFG, Input, Output, Constant, Delay, Addition, ConstantMultiplication, Butterfly, AbstractOperation, Name, TypeName, SignalSourceProvider
+from typing import Optional
+
+
+@pytest.fixture
+def sfg_two_inputs_two_outputs():
+    """Valid SFG with two inputs and two outputs.
+         .               .
+    in1-------+  +--------->out1
+         .    |  |       .
+         .    v  |       .
+         .   add1+--+    .
+         .    ^     |    .
+         .    |     v    .
+    in2+------+    add2---->out2
+       | .          ^    .
+       | .          |    .
+       +------------+    .
+         .               .
+    out1 = in1 + in2
+    out2 = in1 + 2 * in2
+    """
+    in1 = Input("IN1")
+    in2 = Input("IN2")
+    add1 = Addition(in1, in2, "ADD1")
+    add2 = Addition(add1, in2, "ADD2")
+    out1 = Output(add1, "OUT1")
+    out2 = Output(add2, "OUT2")
+    return SFG(inputs=[in1, in2], outputs=[out1, out2])
+
+
+@pytest.fixture
+def sfg_two_inputs_two_outputs_independent():
+    """Valid SFG with two inputs and two outputs, where the first output only depends
+    on the first input and the second output only depends on the second input.
+         .               .
+    in1-------------------->out1
+         .               .
+         .               .
+         .      c1--+    .
+         .          |    .
+         .          v    .
+    in2------+     add1---->out2
+         .   |      ^    .
+         .   |      |    .
+         .   +------+    .
+         .               .
+    out1 = in1
+    out2 = in2 + 3
+    """
+    in1 = Input("IN1")
+    in2 = Input("IN2")
+    c1 = Constant(3, "C1")
+    add1 = Addition(in2, c1, "ADD1")
+    out1 = Output(in1, "OUT1")
+    out2 = Output(add1, "OUT2")
+    return SFG(inputs=[in1, in2], outputs=[out1, out2])
+
+
+@pytest.fixture
+def sfg_two_inputs_two_outputs_independent_with_cmul():
+    """Valid SFG with two inputs and two outputs, where the first output only depends
+    on the first input and the second output only depends on the second input.
+        .                 .
+    in1--->cmul1--->cmul2--->out1
+        .                 .
+        .                 .
+        .  c1             .
+        .   |             .
+        .   v             .
+    in2--->add1---->cmul3--->out2
+        .                 .
+    """
+    in1 = Input("IN1")
+    in2 = Input("IN2")
+    c1 = Constant(3, "C1")
+    add1 = Addition(in2, c1, "ADD1", 7)
+    cmul3 = ConstantMultiplication(2, add1, "CMUL3", 3)
+    cmul1 = ConstantMultiplication(5, in1, "CMUL1", 5)
+    cmul2 = ConstantMultiplication(4, cmul1, "CMUL2", 4)
+    out1 = Output(cmul2, "OUT1")
+    out2 = Output(cmul3, "OUT2")
+    return SFG(inputs=[in1, in2], outputs=[out1, out2])
+
+
+@pytest.fixture
+def sfg_nested():
+    """Valid SFG with two inputs and one output.
+    out1 = in1 + (in1 + in1 * in2) * (in1 + in2 * (in1 + in1 * in2))
+    """
+    mac_in1 = Input()
+    mac_in2 = Input()
+    mac_in3 = Input()
+    mac_out1 = Output(mac_in1 + mac_in2 * mac_in3)
+    MAC = SFG(inputs=[mac_in1, mac_in2, mac_in3], outputs=[mac_out1])
+
+    in1 = Input()
+    in2 = Input()
+    mac1 = MAC(in1, in1, in2)
+    mac2 = MAC(in1, in2, mac1)
+    mac3 = MAC(in1, mac1, mac2)
+    out1 = Output(mac3)
+    return SFG(inputs=[in1, in2], outputs=[out1])
+
+
+@pytest.fixture
+def sfg_delay():
+    """Valid SFG with one input and one output.
+    out1 = in1'
+    """
+    in1 = Input()
+    t1 = Delay(in1)
+    out1 = Output(t1)
+    return SFG(inputs = [in1], outputs = [out1])
+
+@pytest.fixture
+def sfg_accumulator():
+    """Valid SFG with two inputs and one output.
+    data_out = (data_in' + data_in) * (1 - reset)
+    """
+    data_in = Input()
+    reset = Input()
+    t = Delay()
+    t << (t + data_in) * (1 - reset)
+    data_out = Output(t)
+    return SFG(inputs = [data_in, reset], outputs = [data_out])
+
+
+@pytest.fixture
+def sfg_simple_accumulator():
+    """Valid SFG with two inputs and one output.
+         .                .
+    in1----->add1-----+----->out1
+         .    ^       |   .
+         .    |       |   .
+         .    +--t1<--+   .
+         .                .
+    """
+    in1 = Input()
+    t1 = Delay()
+    add1 = in1 + t1
+    t1 << add1
+    out1 = Output(add1)
+    return SFG(inputs = [in1], outputs = [out1])
+
+@pytest.fixture
+def sfg_simple_filter():
+    """A valid SFG that is used as a filter in the first lab for TSTE87.
+         .                 .
+         .   +--cmul1<--+  .
+         .   |          |  .
+         .   v          |  .
+    in1---->add1----->t1+---->out1
+         .                 .
+    """
+    in1 = Input("IN1")
+    cmul1 = ConstantMultiplication(0.5, name="CMUL1")
+    add1 = Addition(in1, cmul1, "ADD1")
+    add1.input(1).signals[0].name = "S2"
+    t1 = Delay(add1, name="T1")
+    cmul1.input(0).connect(t1, "S1")
+    out1 = Output(t1, "OUT1")
+    return SFG(inputs=[in1], outputs=[out1], name="simple_filter")
+
+@pytest.fixture
+def sfg_custom_operation():
+    """A valid SFG containing a custom operation."""
+    class CustomOperation(AbstractOperation):
+        def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""):
+            super().__init__(input_count = 1, output_count = 2, name = name, input_sources = [src0])
+        
+        @classmethod
+        def type_name(self) -> TypeName:
+            return "custom"
+
+        def evaluate(self, a):
+            return a * 2, 2 ** a
+
+    in1 = Input()
+    custom1 = CustomOperation(in1)
+    out1 = Output(custom1.output(0))
+    out2 = Output(custom1.output(1))
+    return SFG(inputs=[in1], outputs=[out1, out2])
+
+
+@pytest.fixture
+def precedence_sfg_delays():
+    """A sfg with delays and interesting layout for precednce list generation.
+         .                                          .
+    IN1>--->C0>--->ADD1>--->Q1>---+--->A0>--->ADD4>--->OUT1
+         .           ^            |            ^    .
+         .           |            T1           |    .
+         .           |            |            |    .
+         .         ADD2<---<B1<---+--->A1>--->ADD3  .
+         .           ^            |            ^    .
+         .           |            T2           |    .
+         .           |            |            |    .
+         .           +-----<B2<---+--->A2>-----+    .
+    """
+    in1 = Input("IN1")
+    c0 = ConstantMultiplication(5, in1, "C0")
+    add1 = Addition(c0, None, "ADD1")
+    # Not sure what operation "Q" is supposed to be in the example
+    Q1 = ConstantMultiplication(1, add1, "Q1")
+    T1 = Delay(Q1, 0, "T1")
+    T2 = Delay(T1, 0, "T2")
+    b2 = ConstantMultiplication(2, T2, "B2")
+    b1 = ConstantMultiplication(3, T1, "B1")
+    add2 = Addition(b1, b2, "ADD2")
+    add1.input(1).connect(add2)
+    a1 = ConstantMultiplication(4, T1, "A1")
+    a2 = ConstantMultiplication(6, T2, "A2")
+    add3 = Addition(a1, a2, "ADD3")
+    a0 = ConstantMultiplication(7, Q1, "A0")
+    add4 = Addition(a0, add3, "ADD4")
+    out1 = Output(add4, "OUT1")
+
+    return SFG(inputs=[in1], outputs=[out1], name="SFG")
+
+
+@pytest.fixture
+def precedence_sfg_delays_and_constants():
+    in1 = Input("IN1")
+    c0 = ConstantMultiplication(5, in1, "C0")
+    add1 = Addition(c0, None, "ADD1")
+    # Not sure what operation "Q" is supposed to be in the example
+    Q1 = ConstantMultiplication(1, add1, "Q1")
+    T1 = Delay(Q1, 0, "T1")
+    const1 = Constant(10, "CONST1")  # Replace T2 delay with a constant
+    b2 = ConstantMultiplication(2, const1, "B2")
+    b1 = ConstantMultiplication(3, T1, "B1")
+    add2 = Addition(b1, b2, "ADD2")
+    add1.input(1).connect(add2)
+    a1 = ConstantMultiplication(4, T1, "A1")
+    a2 = ConstantMultiplication(10, const1, "A2")
+    add3 = Addition(a1, a2, "ADD3")
+    a0 = ConstantMultiplication(7, Q1, "A0")
+    # Replace ADD4 with a butterfly to test multiple output ports
+    bfly1 = Butterfly(a0, add3, "BFLY1")
+    out1 = Output(bfly1.output(0), "OUT1")
+    Output(bfly1.output(1), "OUT2")
+
+    return SFG(inputs=[in1], outputs=[out1], name="SFG")
diff --git a/test/operation/test_abstract_operation.py b/test/operation/test_abstract_operation.py
deleted file mode 100644
index 626a2dc3..00000000
--- a/test/operation/test_abstract_operation.py
+++ /dev/null
@@ -1,77 +0,0 @@
-"""
-B-ASIC test suite for the AbstractOperation class.
-"""
-
-from b_asic.core_operations import Addition, ConstantAddition, Subtraction, ConstantSubtraction, \
-    Multiplication, ConstantMultiplication, Division, ConstantDivision
-
-import pytest
-
-
-def test_addition_overload():
-    """Tests addition overloading for both operation and number argument."""
-    add1 = Addition(None, None, "add1")
-    add2 = Addition(None, None, "add2")
-
-    add3 = add1 + add2
-
-    assert isinstance(add3, Addition)
-    assert add3.input(0).signals == add1.output(0).signals
-    assert add3.input(1).signals == add2.output(0).signals
-
-    add4 = add3 + 5
-
-    assert isinstance(add4, ConstantAddition)
-    assert add4.input(0).signals == add3.output(0).signals
-
-
-def test_subtraction_overload():
-    """Tests subtraction overloading for both operation and number argument."""
-    add1 = Addition(None, None, "add1")
-    add2 = Addition(None, None, "add2")
-
-    sub1 = add1 - add2
-
-    assert isinstance(sub1, Subtraction)
-    assert sub1.input(0).signals == add1.output(0).signals
-    assert sub1.input(1).signals == add2.output(0).signals
-
-    sub2 = sub1 - 5
-
-    assert isinstance(sub2, ConstantSubtraction)
-    assert sub2.input(0).signals == sub1.output(0).signals
-
-
-def test_multiplication_overload():
-    """Tests multiplication overloading for both operation and number argument."""
-    add1 = Addition(None, None, "add1")
-    add2 = Addition(None, None, "add2")
-
-    mul1 = add1 * add2
-
-    assert isinstance(mul1, Multiplication)
-    assert mul1.input(0).signals == add1.output(0).signals
-    assert mul1.input(1).signals == add2.output(0).signals
-
-    mul2 = mul1 * 5
-
-    assert isinstance(mul2, ConstantMultiplication)
-    assert mul2.input(0).signals == mul1.output(0).signals
-
-
-def test_division_overload():
-    """Tests division overloading for both operation and number argument."""
-    add1 = Addition(None, None, "add1")
-    add2 = Addition(None, None, "add2")
-
-    div1 = add1 / add2
-
-    assert isinstance(div1, Division)
-    assert div1.input(0).signals == add1.output(0).signals
-    assert div1.input(1).signals == add2.output(0).signals
-
-    div2 = div1 / 5
-
-    assert isinstance(div2, ConstantDivision)
-    assert div2.input(0).signals == div1.output(0).signals
-
diff --git a/test/test_core_operations.py b/test/test_core_operations.py
index b176b2a6..6a0493c6 100644
--- a/test/test_core_operations.py
+++ b/test/test_core_operations.py
@@ -2,226 +2,175 @@
 B-ASIC test suite for the core operations.
 """
 
-from b_asic.core_operations import Constant, Addition, Subtraction, Multiplication, Division, SquareRoot, ComplexConjugate, Max, Min, Absolute, ConstantMultiplication, ConstantAddition, ConstantSubtraction, ConstantDivision
-
-# Constant tests.
-def test_constant():
-    constant_operation = Constant(3)
-    assert constant_operation.evaluate() == 3
-
-def test_constant_negative():
-    constant_operation = Constant(-3)
-    assert constant_operation.evaluate() == -3
-
-def test_constant_complex():
-    constant_operation = Constant(3+4j)
-    assert constant_operation.evaluate() == 3+4j
-
-# Addition tests.
-def test_addition():
-    test_operation = Addition()
-    constant_operation = Constant(3)
-    constant_operation_2 = Constant(5)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 8
-
-def test_addition_negative():
-    test_operation = Addition()
-    constant_operation = Constant(-3)
-    constant_operation_2 = Constant(-5)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == -8
-
-def test_addition_complex():
-    test_operation = Addition()
-    constant_operation = Constant((3+5j))
-    constant_operation_2 = Constant((4+6j))
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == (7+11j)
-
-# Subtraction tests.
-def test_subtraction():
-    test_operation = Subtraction()
-    constant_operation = Constant(5)
-    constant_operation_2 = Constant(3)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 2
-
-def test_subtraction_negative():
-    test_operation = Subtraction()
-    constant_operation = Constant(-5)
-    constant_operation_2 = Constant(-3)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == -2
-
-def test_subtraction_complex():
-    test_operation = Subtraction()
-    constant_operation = Constant((3+5j))
-    constant_operation_2 = Constant((4+6j))
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == (-1-1j)
-
-# Multiplication tests.
-def test_multiplication():
-    test_operation = Multiplication()
-    constant_operation = Constant(5)
-    constant_operation_2 = Constant(3)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 15
-
-def test_multiplication_negative():
-    test_operation = Multiplication()
-    constant_operation = Constant(-5)
-    constant_operation_2 = Constant(-3)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 15
-
-def test_multiplication_complex():
-    test_operation = Multiplication()
-    constant_operation = Constant((3+5j))
-    constant_operation_2 = Constant((4+6j))
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == (-18+38j)
-
-# Division tests.
-def test_division():
-    test_operation = Division()
-    constant_operation = Constant(30)
-    constant_operation_2 = Constant(5)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 6
-
-def test_division_negative():
-    test_operation = Division()
-    constant_operation = Constant(-30)
-    constant_operation_2 = Constant(-5)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 6
-
-def test_division_complex():
-    test_operation = Division()
-    constant_operation = Constant((60+40j))
-    constant_operation_2 = Constant((10+20j))
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == (2.8-1.6j)
-
-# SquareRoot tests.
-def test_squareroot():
-    test_operation = SquareRoot()
-    constant_operation = Constant(36)
-    assert test_operation.evaluate(constant_operation.evaluate()) == 6
-
-def test_squareroot_negative():
-    test_operation = SquareRoot()
-    constant_operation = Constant(-36)
-    assert test_operation.evaluate(constant_operation.evaluate()) == 6j
-
-def test_squareroot_complex():
-    test_operation = SquareRoot()
-    constant_operation = Constant((48+64j))
-    assert test_operation.evaluate(constant_operation.evaluate()) == (8+4j)
-
-# ComplexConjugate tests.
-def test_complexconjugate():
-    test_operation = ComplexConjugate()
-    constant_operation = Constant(3+4j)
-    assert test_operation.evaluate(constant_operation.evaluate()) == (3-4j)
-
-def test_test_complexconjugate_negative():
-    test_operation = ComplexConjugate()
-    constant_operation = Constant(-3-4j)
-    assert test_operation.evaluate(constant_operation.evaluate()) == (-3+4j)
-
-# Max tests.
-def test_max():
-    test_operation = Max()
-    constant_operation = Constant(30)
-    constant_operation_2 = Constant(5)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 30
-
-def test_max_negative():
-    test_operation = Max()
-    constant_operation = Constant(-30)
-    constant_operation_2 = Constant(-5)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == -5
-
-# Min tests.
-def test_min():
-    test_operation = Min()
-    constant_operation = Constant(30)
-    constant_operation_2 = Constant(5)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 5
-
-def test_min_negative():
-    test_operation = Min()
-    constant_operation = Constant(-30)
-    constant_operation_2 = Constant(-5)
-    assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == -30
-
-# Absolute tests.
-def test_absolute():
-    test_operation = Absolute()
-    constant_operation = Constant(30)
-    assert test_operation.evaluate(constant_operation.evaluate()) == 30
-
-def test_absolute_negative():
-    test_operation = Absolute()
-    constant_operation = Constant(-5)
-    assert test_operation.evaluate(constant_operation.evaluate()) == 5
-
-def test_absolute_complex():
-    test_operation = Absolute()
-    constant_operation = Constant((3+4j))
-    assert test_operation.evaluate(constant_operation.evaluate()) == 5.0
-
-# ConstantMultiplication tests.
-def test_constantmultiplication():
-    test_operation = ConstantMultiplication(5)
-    constant_operation = Constant(20)
-    assert test_operation.evaluate(constant_operation.evaluate()) == 100
-
-def test_constantmultiplication_negative():
-    test_operation = ConstantMultiplication(5)
-    constant_operation = Constant(-5)
-    assert test_operation.evaluate(constant_operation.evaluate()) == -25
-
-def test_constantmultiplication_complex():
-    test_operation = ConstantMultiplication(3+2j)
-    constant_operation = Constant((3+4j))
-    assert test_operation.evaluate(constant_operation.evaluate()) == (1+18j)
-
-# ConstantAddition tests.
-def test_constantaddition():
-    test_operation = ConstantAddition(5)
-    constant_operation = Constant(20)
-    assert test_operation.evaluate(constant_operation.evaluate()) == 25
-
-def test_constantaddition_negative():
-    test_operation = ConstantAddition(4)
-    constant_operation = Constant(-5)
-    assert test_operation.evaluate(constant_operation.evaluate()) == -1
-
-def test_constantaddition_complex():
-    test_operation = ConstantAddition(3+2j)
-    constant_operation = Constant((3+4j))
-    assert test_operation.evaluate(constant_operation.evaluate()) == (6+6j)
-
-# ConstantSubtraction tests.
-def test_constantsubtraction():
-    test_operation = ConstantSubtraction(5)
-    constant_operation = Constant(20)
-    assert test_operation.evaluate(constant_operation.evaluate()) == 15
-
-def test_constantsubtraction_negative():
-    test_operation = ConstantSubtraction(4)
-    constant_operation = Constant(-5)
-    assert test_operation.evaluate(constant_operation.evaluate()) == -9
-
-def test_constantsubtraction_complex():
-    test_operation = ConstantSubtraction(4+6j)
-    constant_operation = Constant((3+4j))
-    assert test_operation.evaluate(constant_operation.evaluate()) == (-1-2j)
-
-# ConstantDivision tests.
-def test_constantdivision():
-    test_operation = ConstantDivision(5)
-    constant_operation = Constant(20)
-    assert test_operation.evaluate(constant_operation.evaluate()) == 4
-
-def test_constantdivision_negative():
-    test_operation = ConstantDivision(4)
-    constant_operation = Constant(-20)
-    assert test_operation.evaluate(constant_operation.evaluate()) == -5
-
-def test_constantdivision_complex():
-    test_operation = ConstantDivision(2+2j)
-    constant_operation = Constant((10+10j))
-    assert test_operation.evaluate(constant_operation.evaluate()) == (5+0j)
+from b_asic import \
+    Constant, Addition, Subtraction, Multiplication, ConstantMultiplication, Division, \
+    SquareRoot, ComplexConjugate, Max, Min, Absolute, Butterfly
+
+class TestConstant:
+    def test_constant_positive(self):
+        test_operation = Constant(3)
+        assert test_operation.evaluate_output(0, []) == 3
+
+    def test_constant_negative(self):
+        test_operation = Constant(-3)
+        assert test_operation.evaluate_output(0, []) == -3
+
+    def test_constant_complex(self):
+        test_operation = Constant(3+4j)
+        assert test_operation.evaluate_output(0, []) == 3+4j
+
+
+class TestAddition:
+    def test_addition_positive(self):
+        test_operation = Addition()
+        assert test_operation.evaluate_output(0, [3, 5]) == 8
+
+    def test_addition_negative(self):
+        test_operation = Addition()
+        assert test_operation.evaluate_output(0, [-3, -5]) == -8
+
+    def test_addition_complex(self):
+        test_operation = Addition()
+        assert test_operation.evaluate_output(0, [3+5j, 4+6j]) == 7+11j
+
+
+class TestSubtraction:
+    def test_subtraction_positive(self):
+        test_operation = Subtraction()
+        assert test_operation.evaluate_output(0, [5, 3]) == 2
+
+    def test_subtraction_negative(self):
+        test_operation = Subtraction()
+        assert test_operation.evaluate_output(0, [-5, -3]) == -2
+
+    def test_subtraction_complex(self):
+        test_operation = Subtraction()
+        assert test_operation.evaluate_output(0, [3+5j, 4+6j]) == -1-1j
+
+
+class TestMultiplication:
+    def test_multiplication_positive(self):
+        test_operation = Multiplication()
+        assert test_operation.evaluate_output(0, [5, 3]) == 15
+
+    def test_multiplication_negative(self):
+        test_operation = Multiplication()
+        assert test_operation.evaluate_output(0, [-5, -3]) == 15
+
+    def test_multiplication_complex(self):
+        test_operation = Multiplication()
+        assert test_operation.evaluate_output(0, [3+5j, 4+6j]) == -18+38j
+
+
+class TestDivision:
+    def test_division_positive(self):
+        test_operation = Division()
+        assert test_operation.evaluate_output(0, [30, 5]) == 6
+
+    def test_division_negative(self):
+        test_operation = Division()
+        assert test_operation.evaluate_output(0, [-30, -5]) == 6
+
+    def test_division_complex(self):
+        test_operation = Division()
+        assert test_operation.evaluate_output(0, [60+40j, 10+20j]) == 2.8-1.6j
+
+
+class TestSquareRoot:
+    def test_squareroot_positive(self):
+        test_operation = SquareRoot()
+        assert test_operation.evaluate_output(0, [36]) == 6
+
+    def test_squareroot_negative(self):
+        test_operation = SquareRoot()
+        assert test_operation.evaluate_output(0, [-36]) == 6j
+
+    def test_squareroot_complex(self):
+        test_operation = SquareRoot()
+        assert test_operation.evaluate_output(0, [48+64j]) == 8+4j
+
+
+class TestComplexConjugate:
+    def test_complexconjugate_positive(self):
+        test_operation = ComplexConjugate()
+        assert test_operation.evaluate_output(0, [3+4j]) == 3-4j
+
+    def test_test_complexconjugate_negative(self):
+        test_operation = ComplexConjugate()
+        assert test_operation.evaluate_output(0, [-3-4j]) == -3+4j
+
+
+class TestMax:
+    def test_max_positive(self):
+        test_operation = Max()
+        assert test_operation.evaluate_output(0, [30, 5]) == 30
+
+    def test_max_negative(self):
+        test_operation = Max()
+        assert test_operation.evaluate_output(0, [-30, -5]) == -5
+
+
+class TestMin:
+    def test_min_positive(self):
+        test_operation = Min()
+        assert test_operation.evaluate_output(0, [30, 5]) == 5
+
+    def test_min_negative(self):
+        test_operation = Min()
+        assert test_operation.evaluate_output(0, [-30, -5]) == -30
+
+
+class TestAbsolute:
+    def test_absolute_positive(self):
+        test_operation = Absolute()
+        assert test_operation.evaluate_output(0, [30]) == 30
+
+    def test_absolute_negative(self):
+        test_operation = Absolute()
+        assert test_operation.evaluate_output(0, [-5]) == 5
+
+    def test_absolute_complex(self):
+        test_operation = Absolute()
+        assert test_operation.evaluate_output(0, [3+4j]) == 5.0
+
+
+class TestConstantMultiplication:
+    def test_constantmultiplication_positive(self):
+        test_operation = ConstantMultiplication(5)
+        assert test_operation.evaluate_output(0, [20]) == 100
+
+    def test_constantmultiplication_negative(self):
+        test_operation = ConstantMultiplication(5)
+        assert test_operation.evaluate_output(0, [-5]) == -25
+
+    def test_constantmultiplication_complex(self):
+        test_operation = ConstantMultiplication(3+2j)
+        assert test_operation.evaluate_output(0, [3+4j]) == 1+18j
+
+
+class TestButterfly:
+    def test_butterfly_positive(self):
+        test_operation = Butterfly()
+        assert test_operation.evaluate_output(0, [2, 3]) == 5
+        assert test_operation.evaluate_output(1, [2, 3]) == -1
+
+    def test_butterfly_negative(self):
+        test_operation = Butterfly()
+        assert test_operation.evaluate_output(0, [-2, -3]) == -5
+        assert test_operation.evaluate_output(1, [-2, -3]) == 1
+
+    def test_buttefly_complex(self):
+        test_operation = Butterfly()
+        assert test_operation.evaluate_output(0, [2+1j, 3-2j]) == 5-1j
+        assert test_operation.evaluate_output(1, [2+1j, 3-2j]) == -1+3j
+
+
+class TestDepends:
+    def test_depends_addition(self):
+        add1 = Addition()
+        assert set(add1.inputs_required_for_output(0)) == {0, 1}
+
+    def test_depends_butterfly(self):
+        bfly1 = Butterfly()
+        assert set(bfly1.inputs_required_for_output(0)) == {0, 1}
+        assert set(bfly1.inputs_required_for_output(1)) == {0, 1}
diff --git a/test/test_fast_simulation.py b/test/test_fast_simulation.py
new file mode 100644
index 00000000..acdea48d
--- /dev/null
+++ b/test/test_fast_simulation.py
@@ -0,0 +1,232 @@
+import pytest
+import numpy as np
+
+from b_asic import SFG, Output, FastSimulation, Addition, Subtraction, Constant, Butterfly
+
+
+class TestRunFor:
+    def test_with_lambdas_as_input(self, sfg_two_inputs_two_outputs):
+        simulation = FastSimulation(sfg_two_inputs_two_outputs, [lambda n: n + 3, lambda n: 1 + n * 2])
+
+        output = simulation.run_for(101, save_results = True)
+
+        assert output[0] == 304
+        assert output[1] == 505
+
+        assert simulation.results["0"][100] == 304
+        assert simulation.results["1"][100] == 505
+
+        assert simulation.results["in1"][0] == 3
+        assert simulation.results["in2"][0] == 1
+        assert simulation.results["add1"][0] == 4
+        assert simulation.results["add2"][0] == 5
+        assert simulation.results["0"][0] == 4
+        assert simulation.results["1"][0] == 5
+
+        assert simulation.results["in1"][1] == 4
+        assert simulation.results["in2"][1] == 3
+        assert simulation.results["add1"][1] == 7
+        assert simulation.results["add2"][1] == 10
+        assert simulation.results["0"][1] == 7
+        assert simulation.results["1"][1] == 10
+
+        assert simulation.results["in1"][2] == 5
+        assert simulation.results["in2"][2] == 5
+        assert simulation.results["add1"][2] == 10
+        assert simulation.results["add2"][2] == 15
+        assert simulation.results["0"][2] == 10
+        assert simulation.results["1"][2] == 15
+
+        assert simulation.results["in1"][3] == 6
+        assert simulation.results["in2"][3] == 7
+        assert simulation.results["add1"][3] == 13
+        assert simulation.results["add2"][3] == 20
+        assert simulation.results["0"][3] == 13
+        assert simulation.results["1"][3] == 20
+
+    def test_with_numpy_arrays_as_input(self, sfg_two_inputs_two_outputs):
+        input0 = np.array([5, 9, 25, -5, 7])
+        input1 = np.array([7, 3, 3,  54, 2])
+        simulation = FastSimulation(sfg_two_inputs_two_outputs, [input0, input1])
+
+        output = simulation.run_for(5, save_results = True)
+
+        assert output[0] == 9
+        assert output[1] == 11
+
+        assert isinstance(simulation.results["in1"], np.ndarray)
+        assert isinstance(simulation.results["in2"], np.ndarray)
+        assert isinstance(simulation.results["add1"], np.ndarray)
+        assert isinstance(simulation.results["add2"], np.ndarray)
+        assert isinstance(simulation.results["0"], np.ndarray)
+        assert isinstance(simulation.results["1"], np.ndarray)
+
+        assert simulation.results["in1"][0] == 5
+        assert simulation.results["in2"][0] == 7
+        assert simulation.results["add1"][0] == 12
+        assert simulation.results["add2"][0] == 19
+        assert simulation.results["0"][0] == 12
+        assert simulation.results["1"][0] == 19
+
+        assert simulation.results["in1"][1] == 9
+        assert simulation.results["in2"][1] == 3
+        assert simulation.results["add1"][1] == 12
+        assert simulation.results["add2"][1] == 15
+        assert simulation.results["0"][1] == 12
+        assert simulation.results["1"][1] == 15
+
+        assert simulation.results["in1"][2] == 25
+        assert simulation.results["in2"][2] == 3
+        assert simulation.results["add1"][2] == 28
+        assert simulation.results["add2"][2] == 31
+        assert simulation.results["0"][2] == 28
+        assert simulation.results["1"][2] == 31
+
+        assert simulation.results["in1"][3] == -5
+        assert simulation.results["in2"][3] == 54
+        assert simulation.results["add1"][3] == 49
+        assert simulation.results["add2"][3] == 103
+        assert simulation.results["0"][3] == 49
+        assert simulation.results["1"][3] == 103
+
+        assert simulation.results["0"][4] == 9
+        assert simulation.results["1"][4] == 11
+    
+    def test_with_numpy_array_overflow(self, sfg_two_inputs_two_outputs):
+        input0 = np.array([5, 9, 25, -5, 7])
+        input1 = np.array([7, 3, 3,  54, 2])
+        simulation = FastSimulation(sfg_two_inputs_two_outputs, [input0, input1])
+        simulation.run_for(5)
+        with pytest.raises(IndexError):
+            simulation.step()
+
+    def test_run_whole_numpy_array(self, sfg_two_inputs_two_outputs):
+        input0 = np.array([5, 9, 25, -5, 7])
+        input1 = np.array([7, 3, 3,  54, 2])
+        simulation = FastSimulation(sfg_two_inputs_two_outputs, [input0, input1])
+        simulation.run()
+        assert len(simulation.results["0"]) == 5
+        assert len(simulation.results["1"]) == 5
+        with pytest.raises(IndexError):
+            simulation.step()
+
+    def test_delay(self, sfg_delay):
+        simulation = FastSimulation(sfg_delay)
+        simulation.set_input(0, [5, -2, 25, -6, 7, 0])
+        simulation.run_for(6, save_results = True)
+
+        assert simulation.results["0"][0] == 0
+        assert simulation.results["0"][1] == 5
+        assert simulation.results["0"][2] == -2
+        assert simulation.results["0"][3] == 25
+        assert simulation.results["0"][4] == -6
+        assert simulation.results["0"][5] == 7
+
+    def test_find_result_key(self, precedence_sfg_delays):
+        sim = FastSimulation(precedence_sfg_delays, [[0, 4, 542, 42, 31.314, 534.123, -453415, 5431]])
+        sim.run()
+        assert sim.results[precedence_sfg_delays.find_result_keys_by_name("ADD2")[0]][4] == 31220
+        assert sim.results[precedence_sfg_delays.find_result_keys_by_name("A1")[0]][2] == 80
+
+class TestRun:
+    def test_save_results(self, sfg_two_inputs_two_outputs):
+        simulation = FastSimulation(sfg_two_inputs_two_outputs, [2, 3])
+        assert not simulation.results
+        simulation.run_for(10, save_results = False)
+        assert not simulation.results
+        simulation.run_for(10)
+        assert len(simulation.results["0"]) == 10
+        assert len(simulation.results["1"]) == 10
+        simulation.run_for(10, save_results = True)
+        assert len(simulation.results["0"]) == 20
+        assert len(simulation.results["1"]) == 20
+        simulation.run_for(10, save_results = False)
+        assert len(simulation.results["0"]) == 20
+        assert len(simulation.results["1"]) == 20
+        simulation.run_for(13, save_results = True)
+        assert len(simulation.results["0"]) == 33
+        assert len(simulation.results["1"]) == 33
+        simulation.step(save_results = False)
+        assert len(simulation.results["0"]) == 33
+        assert len(simulation.results["1"]) == 33
+        simulation.step()
+        assert len(simulation.results["0"]) == 34
+        assert len(simulation.results["1"]) == 34
+        simulation.clear_results()
+        assert not simulation.results
+
+    def test_nested(self, sfg_nested):
+        input0 = np.array([5, 9])
+        input1 = np.array([7, 3])
+        simulation = FastSimulation(sfg_nested, [input0, input1])
+
+        output0 = simulation.step()
+        output1 = simulation.step()
+
+        assert output0[0] == 11405
+        assert output1[0] == 4221
+    
+    def test_accumulator(self, sfg_accumulator):
+        data_in = np.array([5, -2, 25, -6, 7, 0])
+        reset   = np.array([0, 0,  0,  1,  0, 0])
+        simulation = FastSimulation(sfg_accumulator, [data_in, reset])
+        output0 = simulation.step()
+        output1 = simulation.step()
+        output2 = simulation.step()
+        output3 = simulation.step()
+        output4 = simulation.step()
+        output5 = simulation.step()
+        assert output0[0] == 0
+        assert output1[0] == 5
+        assert output2[0] == 3
+        assert output3[0] == 28
+        assert output4[0] == 0
+        assert output5[0] == 7
+
+    def test_simple_accumulator(self, sfg_simple_accumulator):
+        data_in = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+        simulation = FastSimulation(sfg_simple_accumulator, [data_in])
+        simulation.run()
+        assert list(simulation.results["0"]) == [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
+        
+    def test_simple_filter(self, sfg_simple_filter):
+        input0 = np.array([1, 2, 3, 4, 5])
+        simulation = FastSimulation(sfg_simple_filter, [input0])
+        simulation.run_for(len(input0), save_results = True)
+        assert all(simulation.results["0"] == np.array([0, 1.0, 2.5, 4.25, 6.125]))
+
+    def test_custom_operation(self, sfg_custom_operation):
+        simulation = FastSimulation(sfg_custom_operation, [lambda n: n + 1])
+        simulation.run_for(5)
+        assert all(simulation.results["0"] == np.array([2, 4, 6, 8, 10]))
+        assert all(simulation.results["1"] == np.array([2, 4, 8, 16, 32]))
+
+
+class TestLarge:
+    def test_1k_additions(self):
+        prev_op = Addition(Constant(1), Constant(1))
+        for _ in range(999):
+            prev_op = Addition(prev_op, Constant(2))
+        sfg = SFG(outputs=[Output(prev_op)])
+        simulation = FastSimulation(sfg, [])
+        assert simulation.step()[0] == 2000
+        
+    def test_1k_subtractions(self):
+        prev_op = Subtraction(Constant(0), Constant(2))
+        for _ in range(999):
+            prev_op = Subtraction(prev_op, Constant(2))
+        sfg = SFG(outputs=[Output(prev_op)])
+        simulation = FastSimulation(sfg, [])
+        assert simulation.step()[0] == -2000
+        
+    def test_1k_butterfly(self):
+        prev_op_add = Addition(Constant(1), Constant(1))
+        prev_op_sub = Subtraction(Constant(-1), Constant(1))
+        for _ in range(499):
+            prev_op_add = Addition(prev_op_add, Constant(2))
+        for _ in range(499):
+            prev_op_sub = Subtraction(prev_op_sub, Constant(2))
+        butterfly = Butterfly(prev_op_add, prev_op_sub)
+        sfg = SFG(outputs=[Output(butterfly.output(0)), Output(butterfly.output(1))])
+        simulation = FastSimulation(sfg, [])
+        assert list(simulation.step()) == [0, 2000]
\ No newline at end of file
diff --git a/test/test_graph_id_generator.py b/test/test_graph_id_generator.py
index b14597ea..72c923b6 100644
--- a/test/test_graph_id_generator.py
+++ b/test/test_graph_id_generator.py
@@ -2,9 +2,10 @@
 B-ASIC test suite for graph id generator.
 """
 
-from b_asic.graph_id import GraphIDGenerator, GraphID
 import pytest
 
+from b_asic import GraphIDGenerator, GraphID
+
 @pytest.fixture
 def graph_id_generator():
     return GraphIDGenerator()
@@ -12,17 +13,17 @@ def graph_id_generator():
 class TestGetNextId:
     def test_empty_string_generator(self, graph_id_generator):
         """Test the graph id generator for an empty string type."""
-        assert graph_id_generator.get_next_id("") == "1"
-        assert graph_id_generator.get_next_id("") == "2"
+        assert graph_id_generator.next_id("") == "1"
+        assert graph_id_generator.next_id("") == "2"
 
     def test_normal_string_generator(self, graph_id_generator):
         """"Test the graph id generator for a normal string type."""
-        assert graph_id_generator.get_next_id("add") == "add1"
-        assert graph_id_generator.get_next_id("add") == "add2"
+        assert graph_id_generator.next_id("add") == "add1"
+        assert graph_id_generator.next_id("add") == "add2"
 
     def test_different_strings_generator(self, graph_id_generator):
         """Test the graph id generator for different strings."""
-        assert graph_id_generator.get_next_id("sub") == "sub1"
-        assert graph_id_generator.get_next_id("mul") == "mul1"
-        assert graph_id_generator.get_next_id("sub") == "sub2"
-        assert graph_id_generator.get_next_id("mul") == "mul2"
+        assert graph_id_generator.next_id("sub") == "sub1"
+        assert graph_id_generator.next_id("mul") == "mul1"
+        assert graph_id_generator.next_id("sub") == "sub2"
+        assert graph_id_generator.next_id("mul") == "mul2"
diff --git a/test/test_inputport.py b/test/test_inputport.py
index a4324069..f4668938 100644
--- a/test/test_inputport.py
+++ b/test/test_inputport.py
@@ -4,92 +4,64 @@ B-ASIC test suite for Inputport
 
 import pytest
 
-from b_asic import InputPort, OutputPort
-from b_asic import Signal
+from b_asic import InputPort, OutputPort, Signal
 
 @pytest.fixture
-def inp_port():
-    return InputPort(0, None)
-
-@pytest.fixture
-def out_port():
-    return OutputPort(0, None)
-
-@pytest.fixture
-def out_port2():
-    return OutputPort(1, None)
+def output_port2():
+    return OutputPort(None, 1)
 
 @pytest.fixture
 def dangling_sig():
     return Signal()
 
 @pytest.fixture
-def s_w_source():
-    out_port = OutputPort(0, None)
-    return Signal(source=out_port)
+def s_w_source(output_port):
+    return Signal(source=output_port)
 
 @pytest.fixture
-def sig_with_dest():
-    inp_port = InputPort(0, None)
-    return Signal(destination=out_port)
+def sig_with_dest(inp_port):
+    return Signal(destination=inp_port)
 
 @pytest.fixture
-def connected_sig():
-    out_port = OutputPort(0, None)
-    inp_port = InputPort(0, None)
-    return Signal(source=out_port, destination=inp_port)
+def connected_sig(inp_port, output_port):
+    return Signal(source=output_port, destination=inp_port)
 
-def test_connect_then_disconnect(inp_port, out_port):
+def test_connect_then_disconnect(input_port, output_port):
     """Test connect unused port to port."""
-    s1 = inp_port.connect(out_port)
-
-    assert inp_port.connected_ports == [out_port]
-    assert out_port.connected_ports == [inp_port]
-    assert inp_port.signals == [s1]
-    assert out_port.signals == [s1]
-    assert s1.source is out_port
-    assert s1.destination is inp_port
-
-    inp_port.remove_signal(s1)
-
-    assert inp_port.connected_ports == []
-    assert out_port.connected_ports == []
-    assert inp_port.signals == []
-    assert out_port.signals == [s1]
-    assert s1.source is out_port
+    s1 = input_port.connect(output_port)
+
+    assert input_port.connected_source == output_port
+    assert input_port.signals == [s1]
+    assert output_port.signals == [s1]
+    assert s1.source is output_port
+    assert s1.destination is input_port
+
+    input_port.remove_signal(s1)
+
+    assert input_port.connected_source is None
+    assert input_port.signals == []
+    assert output_port.signals == [s1]
+    assert s1.source is output_port
     assert s1.destination is None
 
-def test_connect_used_port_to_new_port(inp_port, out_port, out_port2):
-    """Does connecting multiple ports to an inputport throw error?"""
-    inp_port.connect(out_port)
-    with pytest.raises(AssertionError):
-        inp_port.connect(out_port2)
+def test_connect_used_port_to_new_port(input_port, output_port, output_port2):
+    """Multiple connections to an input port should throw an error."""
+    input_port.connect(output_port)
+    with pytest.raises(Exception):
+        input_port.connect(output_port2)
 
-def test_add_signal_then_disconnect(inp_port, s_w_source):
+def test_add_signal_then_disconnect(input_port, s_w_source):
     """Can signal be connected then disconnected properly?"""
-    inp_port.add_signal(s_w_source)
+    input_port.add_signal(s_w_source)
 
-    assert inp_port.connected_ports == [s_w_source.source]
-    assert s_w_source.source.connected_ports == [inp_port]
-    assert inp_port.signals == [s_w_source]
+    assert input_port.connected_source == s_w_source.source
+    assert input_port.signals == [s_w_source]
     assert s_w_source.source.signals == [s_w_source]
-    assert s_w_source.destination is inp_port
+    assert s_w_source.destination is input_port
 
-    inp_port.remove_signal(s_w_source)
+    input_port.remove_signal(s_w_source)
 
-    assert inp_port.connected_ports == []
-    assert s_w_source.source.connected_ports == []
-    assert inp_port.signals == []
+    assert input_port.connected_source is None
+    assert input_port.signals == []
     assert s_w_source.source.signals == [s_w_source]
     assert s_w_source.destination is None
-
-def test_connect_then_disconnect(inp_port, out_port):
-    """Can port be connected and then disconnected properly?"""
-    inp_port.connect(out_port)
-
-    inp_port.disconnect(out_port)
-
-    print("outport signals:", out_port.signals, "count:", out_port.signal_count())
-    assert inp_port.signal_count() == 1
-    assert len(inp_port.connected_ports) == 0
-    assert out_port.signal_count() == 0
diff --git a/test/test_operation.py b/test/test_operation.py
index 6c37e30b..f4af81b5 100644
--- a/test/test_operation.py
+++ b/test/test_operation.py
@@ -1,9 +1,95 @@
-from b_asic.core_operations import Constant, Addition
-from b_asic.signal import Signal
-from b_asic.port import InputPort, OutputPort
+"""
+B-ASIC test suite for the AbstractOperation class.
+"""
 
 import pytest
 
+from b_asic import Addition, Subtraction, Multiplication, ConstantMultiplication, Division, Constant, Butterfly, \
+    MAD, SquareRoot
+
+
+class TestOperationOverloading:
+    def test_addition_overload(self):
+        """Tests addition overloading for both operation and number argument."""
+        add1 = Addition(None, None, "add1")
+        add2 = Addition(None, None, "add2")
+
+        add3 = add1 + add2
+        assert isinstance(add3, Addition)
+        assert add3.input(0).signals == add1.output(0).signals
+        assert add3.input(1).signals == add2.output(0).signals
+
+        add4 = add3 + 5
+        assert isinstance(add4, Addition)
+        assert add4.input(0).signals == add3.output(0).signals
+        assert add4.input(1).signals[0].source.operation.value == 5
+
+        add5 = 5 + add4
+        assert isinstance(add5, Addition)
+        assert add5.input(0).signals[0].source.operation.value == 5
+        assert add5.input(1).signals == add4.output(0).signals
+
+    def test_subtraction_overload(self):
+        """Tests subtraction overloading for both operation and number argument."""
+        add1 = Addition(None, None, "add1")
+        add2 = Addition(None, None, "add2")
+
+        sub1 = add1 - add2
+        assert isinstance(sub1, Subtraction)
+        assert sub1.input(0).signals == add1.output(0).signals
+        assert sub1.input(1).signals == add2.output(0).signals
+
+        sub2 = sub1 - 5
+        assert isinstance(sub2, Subtraction)
+        assert sub2.input(0).signals == sub1.output(0).signals
+        assert sub2.input(1).signals[0].source.operation.value == 5
+
+        sub3 = 5 - sub2
+        assert isinstance(sub3, Subtraction)
+        assert sub3.input(0).signals[0].source.operation.value == 5
+        assert sub3.input(1).signals == sub2.output(0).signals
+
+    def test_multiplication_overload(self):
+        """Tests multiplication overloading for both operation and number argument."""
+        add1 = Addition(None, None, "add1")
+        add2 = Addition(None, None, "add2")
+
+        mul1 = add1 * add2
+        assert isinstance(mul1, Multiplication)
+        assert mul1.input(0).signals == add1.output(0).signals
+        assert mul1.input(1).signals == add2.output(0).signals
+
+        mul2 = mul1 * 5
+        assert isinstance(mul2, ConstantMultiplication)
+        assert mul2.input(0).signals == mul1.output(0).signals
+        assert mul2.value == 5
+
+        mul3 = 5 * mul2
+        assert isinstance(mul3, ConstantMultiplication)
+        assert mul3.input(0).signals == mul2.output(0).signals
+        assert mul3.value == 5
+
+    def test_division_overload(self):
+        """Tests division overloading for both operation and number argument."""
+        add1 = Addition(None, None, "add1")
+        add2 = Addition(None, None, "add2")
+
+        div1 = add1 / add2
+        assert isinstance(div1, Division)
+        assert div1.input(0).signals == add1.output(0).signals
+        assert div1.input(1).signals == add2.output(0).signals
+
+        div2 = div1 / 5
+        assert isinstance(div2, Division)
+        assert div2.input(0).signals == div1.output(0).signals
+        assert div2.input(1).signals[0].source.operation.value == 5
+
+        div3 = 5 / div2
+        assert isinstance(div3, Division)
+        assert div3.input(0).signals[0].source.operation.value == 5
+        assert div3.input(1).signals == div2.output(0).signals
+
+
 class TestTraverse:
     def test_traverse_single_tree(self, operation):
         """Traverse a tree consisting of one operation."""
@@ -12,20 +98,89 @@ class TestTraverse:
 
     def test_traverse_tree(self, operation_tree):
         """Traverse a basic addition tree with two constants."""
-        assert len(list(operation_tree.traverse())) == 3
+        assert len(list(operation_tree.traverse())) == 5
 
     def test_traverse_large_tree(self, large_operation_tree):
         """Traverse a larger tree."""
-        assert len(list(large_operation_tree.traverse())) == 7
+        assert len(list(large_operation_tree.traverse())) == 13
 
     def test_traverse_type(self, large_operation_tree):
-        traverse = list(large_operation_tree.traverse())
-        assert len(list(filter(lambda type_: isinstance(type_, Addition), traverse))) == 3
-        assert len(list(filter(lambda type_: isinstance(type_, Constant), traverse))) == 4
-
-    def test_traverse_loop(self, operation_tree):
-        add_oper_signal = Signal()
-        operation_tree._output_ports[0].add_signal(add_oper_signal)
-        operation_tree._input_ports[0].remove_signal(add_oper_signal)
-        operation_tree._input_ports[0].add_signal(add_oper_signal)
-        assert len(list(operation_tree.traverse())) == 2
+        result = list(large_operation_tree.traverse())
+        assert len(list(filter(lambda type_: isinstance(type_, Addition), result))) == 3
+        assert len(list(filter(lambda type_: isinstance(type_, Constant), result))) == 4
+
+    def test_traverse_loop(self, operation_graph_with_cycle):
+        assert len(list(operation_graph_with_cycle.traverse())) == 8
+
+
+class TestToSfg:
+    def test_convert_mad_to_sfg(self):
+        mad1 = MAD()
+        mad1_sfg = mad1.to_sfg()
+
+        assert mad1.evaluate(1, 1, 1) == mad1_sfg.evaluate(1, 1, 1)
+        assert len(mad1_sfg.operations) == 6
+
+    def test_butterfly_to_sfg(self):
+        but1 = Butterfly()
+        but1_sfg = but1.to_sfg()
+
+        assert but1.evaluate(1, 1)[0] == but1_sfg.evaluate(1, 1)[0]
+        assert but1.evaluate(1, 1)[1] == but1_sfg.evaluate(1, 1)[1]
+        assert len(but1_sfg.operations) == 8
+
+    def test_add_to_sfg(self):
+        add1 = Addition()
+        add1_sfg = add1.to_sfg()
+
+        assert len(add1_sfg.operations) == 4
+
+    def test_sqrt_to_sfg(self):
+        sqrt1 = SquareRoot()
+        sqrt1_sfg = sqrt1.to_sfg()
+
+        assert len(sqrt1_sfg.operations) == 3
+
+
+class TestLatency:
+    def test_latency_constructor(self):
+        bfly = Butterfly(latency=5)
+
+        assert bfly.latency == 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 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 bfly.latency_offsets == {"in0": 0, "in1": 2, "out0": 9, "out1": 5}
+
+    def test_set_latency(self):
+        bfly = Butterfly()
+
+        bfly.set_latency(9)
+
+        assert bfly.latency == 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 bfly.latency_offsets == {'in0': 3, "in1": None, "out0": None, 'out1': 5}
+
+
+class TestCopyOperation:
+    def test_copy_buttefly_latency_offsets(self):
+        bfly = Butterfly(latency_offsets={'in0': 4, 'in1': 2, 'out0': 10, 'out1': 9})
+
+        bfly_copy = bfly.copy_component()
+
+        assert bfly_copy.latency_offsets == {'in0': 4, 'in1': 2, 'out0': 10, 'out1': 9}
diff --git a/test/test_outputport.py b/test/test_outputport.py
index deed7a1e..7cc250ee 100644
--- a/test/test_outputport.py
+++ b/test/test_outputport.py
@@ -1,80 +1,70 @@
 """
 B-ASIC test suite for OutputPort.
 """
-from b_asic import OutputPort, InputPort, Signal
 import pytest
 
-@pytest.fixture
-def output_port():
-    return OutputPort(0, None)
-
-@pytest.fixture
-def input_port():
-    return InputPort(0, None)
-
-@pytest.fixture
-def list_of_input_ports():
-    return [InputPort(_, None) for _ in range(0,3)]
+from b_asic import OutputPort, InputPort, Signal
 
 class TestConnect:
     def test_multiple_ports(self, output_port, list_of_input_ports):
-        """Can multiple ports connect to an output port?"""
+        """Multiple connections to an output port should be possible."""
         for port in list_of_input_ports:
-            output_port.connect(port)
+            port.connect(output_port)
 
-        assert output_port.signal_count() == len(list_of_input_ports)
+        assert output_port.signal_count == len(list_of_input_ports)
 
-    def test_same_port(self, output_port, list_of_input_ports):
+    def test_same_port(self, output_port, input_port):
         """Check error handing."""
-        output_port.connect(list_of_input_ports[0])
-        with pytest.raises(AssertionError):
-            output_port.connect(list_of_input_ports[0])
+        input_port.connect(output_port)
+        with pytest.raises(Exception):
+            input_port.connect(output_port)
 
-        assert output_port.signal_count() == 2
+        assert output_port.signal_count == 1
 
 class TestAddSignal:
     def test_dangling(self, output_port):
         s = Signal()
         output_port.add_signal(s)
 
-        assert output_port.signal_count() == 1
-
-    def test_with_destination(self, output_port, input_port):
-        s = Signal(destination=input_port)
-        output_port.add_signal(s)
+        assert output_port.signal_count == 1
+        assert output_port.signals == [s]
 
-        assert output_port.connected_ports == [s.destination]
+class TestClear:
+    def test_others_clear(self, output_port, list_of_input_ports):
+        for port in list_of_input_ports:
+            port.connect(output_port)
 
-class TestDisconnect:
-    def test_multiple_ports(self, output_port, list_of_input_ports):
-        """Can multiple ports disconnect from OutputPort?"""
         for port in list_of_input_ports:
-            output_port.connect(port)
+            port.clear()
+
+        assert output_port.signal_count == 3
+        assert all(s.dangling() for s in output_port.signals)
 
+    def test_self_clear(self, output_port, list_of_input_ports):
         for port in list_of_input_ports:
-            output_port.disconnect(port)
+            port.connect(output_port)
 
-        assert output_port.signal_count() == 3
-        assert output_port.connected_ports == []
+        output_port.clear()
+
+        assert output_port.signal_count == 0
+        assert output_port.signals == []
 
 class TestRemoveSignal:
     def test_one_signal(self, output_port, input_port):
-        s = output_port.connect(input_port)
+        s = input_port.connect(output_port)
         output_port.remove_signal(s)
 
-        assert output_port.signal_count() == 0
+        assert output_port.signal_count == 0
         assert output_port.signals == []
-        assert output_port.connected_ports == []
 
     def test_multiple_signals(self, output_port, list_of_input_ports):
-        """Can multiple signals disconnect from OutputPort?"""
         sigs = []
 
         for port in list_of_input_ports:
-            sigs.append(output_port.connect(port))
+            sigs.append(port.connect(output_port))
 
-        for sig in sigs:
-            output_port.remove_signal(sig)
+        for s in sigs:
+            output_port.remove_signal(s)
 
-        assert output_port.signal_count() == 0
+        assert output_port.signal_count == 0
         assert output_port.signals == []
diff --git a/test/test_schema.py b/test/test_schema.py
new file mode 100644
index 00000000..78a713a9
--- /dev/null
+++ b/test/test_schema.py
@@ -0,0 +1,67 @@
+"""
+B-ASIC test suite for the schema module and Schema class.
+"""
+
+from b_asic import Schema, Addition, ConstantMultiplication
+
+
+class TestInit:
+    def test_simple_filter_normal_latency(self, sfg_simple_filter):
+        sfg_simple_filter.set_latency_of_type(Addition.type_name(), 5)
+        sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 4)
+
+        schema = Schema(sfg_simple_filter)
+
+        assert schema._start_times == {"add1": 4, "cmul1": 0}
+
+    def test_complicated_single_outputs_normal_latency(self, precedence_sfg_delays):
+        precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4)
+        precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
+
+        schema = Schema(precedence_sfg_delays, scheduling_alg="ASAP")
+
+        for op in schema._sfg.get_operations_topological_order():
+            print(op.latency_offsets)
+
+        start_times_names = dict()
+        for op_id, start_time in schema._start_times.items():
+            op_name = precedence_sfg_delays.find_by_id(op_id).name
+            start_times_names[op_name] = start_time
+
+        assert start_times_names == {"C0": 0, "B1": 0, "B2": 0, "ADD2": 3, "ADD1": 7, "Q1": 11,
+                                     "A0": 14, "A1": 0, "A2": 0, "ADD3": 3, "ADD4": 17}
+
+    def test_complicated_single_outputs_complex_latencies(self, precedence_sfg_delays):
+        precedence_sfg_delays.set_latency_offsets_of_type(ConstantMultiplication.type_name(), {'in0': 3, 'out0': 5})
+
+        precedence_sfg_delays.find_by_name("B1")[0].set_latency_offsets({'in0': 4, 'out0': 7})
+        precedence_sfg_delays.find_by_name("B2")[0].set_latency_offsets({'in0': 1, 'out0': 4})
+        precedence_sfg_delays.find_by_name("ADD2")[0].set_latency_offsets({'in0': 4, 'in1': 2, 'out0': 4})
+        precedence_sfg_delays.find_by_name("ADD1")[0].set_latency_offsets({'in0': 1, 'in1': 2, 'out0': 4})
+        precedence_sfg_delays.find_by_name("Q1")[0].set_latency_offsets({'in0': 3, 'out0': 6})
+        precedence_sfg_delays.find_by_name("A0")[0].set_latency_offsets({'in0': 0, 'out0': 2})
+
+        precedence_sfg_delays.find_by_name("A1")[0].set_latency_offsets({'in0': 0, 'out0': 5})
+        precedence_sfg_delays.find_by_name("A2")[0].set_latency_offsets({'in0': 2, 'out0': 3})
+        precedence_sfg_delays.find_by_name("ADD3")[0].set_latency_offsets({'in0': 2, 'in1': 1, 'out0': 4})
+        precedence_sfg_delays.find_by_name("ADD4")[0].set_latency_offsets({'in0': 6, 'in1': 7, 'out0': 9})
+
+        schema = Schema(precedence_sfg_delays, scheduling_alg="ASAP")
+
+        start_times_names = dict()
+        for op_id, start_time in schema._start_times.items():
+            op_name = precedence_sfg_delays.find_by_id(op_id).name
+            start_times_names[op_name] = start_time
+
+        assert start_times_names == {'C0': 0, 'B1': 0, 'B2': 0, 'ADD2': 3, 'ADD1': 5, 'Q1': 6, 'A0': 12,
+                                     'A1': 0, 'A2': 0, 'ADD3': 3, 'ADD4': 8}
+
+    def test_independent_sfg(self, sfg_two_inputs_two_outputs_independent_with_cmul):
+        schema = Schema(sfg_two_inputs_two_outputs_independent_with_cmul, scheduling_alg="ASAP")
+
+        start_times_names = dict()
+        for op_id, start_time in schema._start_times.items():
+            op_name = sfg_two_inputs_two_outputs_independent_with_cmul.find_by_id(op_id).name
+            start_times_names[op_name] = start_time
+
+        assert start_times_names == {'CMUL1': 0, 'CMUL2': 5, "ADD1": 0, "CMUL3": 7}
diff --git a/test/test_sfg.py b/test/test_sfg.py
new file mode 100644
index 00000000..2124daa9
--- /dev/null
+++ b/test/test_sfg.py
@@ -0,0 +1,1018 @@
+from os import path, remove
+import pytest
+import random
+import string
+import io
+import sys
+
+from b_asic import SFG, Signal, Input, Output, Delay, FastSimulation
+from b_asic.core_operations import Constant, Addition, Subtraction, Multiplication, \
+    Division, Min, Max, SquareRoot, ComplexConjugate, Absolute, ConstantMultiplication, \
+    Butterfly
+
+from b_asic.save_load_structure import sfg_to_python, python_to_sfg
+
+
+class TestInit:
+    def test_direct_input_to_output_sfg_construction(self):
+        in1 = Input("IN1")
+        out1 = Output(None, "OUT1")
+        out1.input(0).connect(in1, "S1")
+
+        sfg = SFG(inputs=[in1], outputs=[out1])  # in1 ---s1---> out1
+
+        assert len(list(sfg.components)) == 3
+        assert len(list(sfg.operations)) == 2
+        assert sfg.input_count == 1
+        assert sfg.output_count == 1
+
+    def test_same_signal_input_and_output_sfg_construction(self):
+        add1 = Addition(None, None, "ADD1")
+        add2 = Addition(None, None, "ADD2")
+
+        s1 = add2.input(0).connect(add1, "S1")
+
+        # in1 ---s1---> out1
+        sfg = SFG(input_signals=[s1], output_signals=[s1])
+
+        assert len(list(sfg.components)) == 3
+        assert len(list(sfg.operations)) == 2
+        assert sfg.input_count == 1
+        assert sfg.output_count == 1
+
+    def test_outputs_construction(self, operation_tree):
+        sfg = SFG(outputs=[Output(operation_tree)])
+
+        assert len(list(sfg.components)) == 7
+        assert len(list(sfg.operations)) == 4
+        assert sfg.input_count == 0
+        assert sfg.output_count == 1
+
+    def test_signals_construction(self, operation_tree):
+        sfg = SFG(output_signals=[Signal(source=operation_tree.output(0))])
+
+        assert len(list(sfg.components)) == 7
+        assert len(list(sfg.operations)) == 4
+        assert sfg.input_count == 0
+        assert sfg.output_count == 1
+
+
+class TestPrintSfg:
+    def test_one_addition(self):
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        add1 = Addition(inp1, inp2, "ADD1")
+        out1 = Output(add1, "OUT1")
+        sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="SFG1")
+
+        assert sfg.__str__() == \
+            "id: no_id, \tname: SFG1, \tinputs: {0: '-'}, \toutputs: {0: '-'}\n" + \
+            "Internal Operations:\n" + \
+            "----------------------------------------------------------------------------------------------------\n" + \
+            str(sfg.find_by_name("INP1")[0]) + "\n" + \
+            str(sfg.find_by_name("INP2")[0]) + "\n" + \
+            str(sfg.find_by_name("ADD1")[0]) + "\n" + \
+            str(sfg.find_by_name("OUT1")[0]) + "\n" + \
+            "----------------------------------------------------------------------------------------------------\n"
+
+    def test_add_mul(self):
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        inp3 = Input("INP3")
+        add1 = Addition(inp1, inp2, "ADD1")
+        mul1 = Multiplication(add1, inp3, "MUL1")
+        out1 = Output(mul1, "OUT1")
+        sfg = SFG(inputs=[inp1, inp2, inp3], outputs=[out1], name="mac_sfg")
+
+        assert sfg.__str__() == \
+            "id: no_id, \tname: mac_sfg, \tinputs: {0: '-'}, \toutputs: {0: '-'}\n" + \
+            "Internal Operations:\n" + \
+            "----------------------------------------------------------------------------------------------------\n" + \
+            str(sfg.find_by_name("INP1")[0]) + "\n" + \
+            str(sfg.find_by_name("INP2")[0]) + "\n" + \
+            str(sfg.find_by_name("ADD1")[0]) + "\n" + \
+            str(sfg.find_by_name("INP3")[0]) + "\n" + \
+            str(sfg.find_by_name("MUL1")[0]) + "\n" + \
+            str(sfg.find_by_name("OUT1")[0]) + "\n" + \
+            "----------------------------------------------------------------------------------------------------\n"
+
+    def test_constant(self):
+        inp1 = Input("INP1")
+        const1 = Constant(3, "CONST")
+        add1 = Addition(const1, inp1, "ADD1")
+        out1 = Output(add1, "OUT1")
+
+        sfg = SFG(inputs=[inp1], outputs=[out1], name="sfg")
+
+        assert sfg.__str__() == \
+            "id: no_id, \tname: sfg, \tinputs: {0: '-'}, \toutputs: {0: '-'}\n" + \
+            "Internal Operations:\n" + \
+            "----------------------------------------------------------------------------------------------------\n" + \
+            str(sfg.find_by_name("CONST")[0]) + "\n" + \
+            str(sfg.find_by_name("INP1")[0]) + "\n" + \
+            str(sfg.find_by_name("ADD1")[0]) + "\n" + \
+            str(sfg.find_by_name("OUT1")[0]) + "\n" + \
+            "----------------------------------------------------------------------------------------------------\n"
+
+    def test_simple_filter(self, sfg_simple_filter):
+        assert sfg_simple_filter.__str__() == \
+            "id: no_id, \tname: simple_filter, \tinputs: {0: '-'}, \toutputs: {0: '-'}\n" + \
+            "Internal Operations:\n" + \
+            "----------------------------------------------------------------------------------------------------\n" + \
+            str(sfg_simple_filter.find_by_name("IN1")[0]) + "\n" + \
+            str(sfg_simple_filter.find_by_name("ADD1")[0]) + "\n" + \
+            str(sfg_simple_filter.find_by_name("T1")[0]) + "\n" + \
+            str(sfg_simple_filter.find_by_name("CMUL1")[0]) + "\n" + \
+            str(sfg_simple_filter.find_by_name("OUT1")[0]) + "\n" + \
+            "----------------------------------------------------------------------------------------------------\n"
+
+
+class TestDeepCopy:
+    def test_deep_copy_no_duplicates(self):
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        inp3 = Input("INP3")
+        add1 = Addition(inp1, inp2, "ADD1")
+        mul1 = Multiplication(add1, inp3, "MUL1")
+        out1 = Output(mul1, "OUT1")
+
+        mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="mac_sfg")
+        mac_sfg_new = mac_sfg()
+
+        assert mac_sfg.name == "mac_sfg"
+        assert mac_sfg_new.name == ""
+
+        for g_id, component in mac_sfg._components_by_id.items():
+            component_copy = mac_sfg_new.find_by_id(g_id)
+            assert component.name == component_copy.name
+
+    def test_deep_copy(self):
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        inp3 = Input("INP3")
+        add1 = Addition(None, None, "ADD1")
+        add2 = Addition(None, None, "ADD2")
+        mul1 = Multiplication(None, None, "MUL1")
+        out1 = Output(None, "OUT1")
+
+        add1.input(0).connect(inp1, "S1")
+        add1.input(1).connect(inp2, "S2")
+        add2.input(0).connect(add1, "S4")
+        add2.input(1).connect(inp3, "S3")
+        mul1.input(0).connect(add1, "S5")
+        mul1.input(1).connect(add2, "S6")
+        out1.input(0).connect(mul1, "S7")
+
+        mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1],
+                      id_number_offset=100, name="mac_sfg")
+        mac_sfg_new = mac_sfg(name="mac_sfg2")
+
+        assert mac_sfg.name == "mac_sfg"
+        assert mac_sfg_new.name == "mac_sfg2"
+        assert mac_sfg.id_number_offset == 100
+        assert mac_sfg_new.id_number_offset == 100
+
+        for g_id, component in mac_sfg._components_by_id.items():
+            component_copy = mac_sfg_new.find_by_id(g_id)
+            assert component.name == component_copy.name
+
+    def test_deep_copy_with_new_sources(self):
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        inp3 = Input("INP3")
+        add1 = Addition(inp1, inp2, "ADD1")
+        mul1 = Multiplication(add1, inp3, "MUL1")
+        out1 = Output(mul1, "OUT1")
+
+        mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="mac_sfg")
+
+        a = Addition(Constant(3), Constant(5))
+        b = Constant(2)
+        mac_sfg_new = mac_sfg(a, b)
+        assert mac_sfg_new.input(0).signals[0].source.operation is a
+        assert mac_sfg_new.input(1).signals[0].source.operation is b
+
+
+class TestEvaluateOutput:
+    def test_evaluate_output(self, operation_tree):
+        sfg = SFG(outputs=[Output(operation_tree)])
+        assert sfg.evaluate_output(0, []) == 5
+
+    def test_evaluate_output_large(self, large_operation_tree):
+        sfg = SFG(outputs=[Output(large_operation_tree)])
+        assert sfg.evaluate_output(0, []) == 14
+
+    def test_evaluate_output_cycle(self, operation_graph_with_cycle):
+        sfg = SFG(outputs=[Output(operation_graph_with_cycle)])
+        with pytest.raises(Exception):
+            sfg.evaluate_output(0, [])
+
+
+class TestComponents:
+    def test_advanced_components(self):
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        inp3 = Input("INP3")
+        add1 = Addition(None, None, "ADD1")
+        add2 = Addition(None, None, "ADD2")
+        mul1 = Multiplication(None, None, "MUL1")
+        out1 = Output(None, "OUT1")
+
+        add1.input(0).connect(inp1, "S1")
+        add1.input(1).connect(inp2, "S2")
+        add2.input(0).connect(add1, "S4")
+        add2.input(1).connect(inp3, "S3")
+        mul1.input(0).connect(add1, "S5")
+        mul1.input(1).connect(add2, "S6")
+        out1.input(0).connect(mul1, "S7")
+
+        mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="mac_sfg")
+
+        assert set([comp.name for comp in mac_sfg.components]) == {
+            "INP1", "INP2", "INP3", "ADD1", "ADD2", "MUL1", "OUT1", "S1", "S2", "S3", "S4", "S5", "S6", "S7"}
+
+
+class TestReplaceComponents:
+    def test_replace_addition_by_id(self, operation_tree):
+        sfg = SFG(outputs=[Output(operation_tree)])
+        component_id = "add1"
+
+        sfg = sfg.replace_component(
+            Multiplication(name="Multi"), graph_id=component_id)
+        assert component_id not in sfg._components_by_id.keys()
+        assert "Multi" in sfg._components_by_name.keys()
+
+    def test_replace_addition_large_tree(self, large_operation_tree):
+        sfg = SFG(outputs=[Output(large_operation_tree)])
+        component_id = "add3"
+
+        sfg = sfg.replace_component(
+            Multiplication(name="Multi"), graph_id=component_id)
+        assert "Multi" in sfg._components_by_name.keys()
+        assert component_id not in sfg._components_by_id.keys()
+
+    def test_replace_no_input_component(self, operation_tree):
+        sfg = SFG(outputs=[Output(operation_tree)])
+        component_id = "c1"
+        const_ = sfg.find_by_id(component_id)
+
+        sfg = sfg.replace_component(Constant(1), graph_id=component_id)
+        assert const_ is not sfg.find_by_id(component_id)
+
+    def test_no_match_on_replace(self, large_operation_tree):
+        sfg = SFG(outputs=[Output(large_operation_tree)])
+        component_id = "addd1"
+
+        try:
+            sfg = sfg.replace_component(
+                Multiplication(name="Multi"), graph_id=component_id)
+        except AssertionError:
+            assert True
+        else:
+            assert False
+
+    def test_not_equal_input(self, large_operation_tree):
+        sfg = SFG(outputs=[Output(large_operation_tree)])
+        component_id = "c1"
+
+        try:
+            sfg = sfg.replace_component(
+                Multiplication(name="Multi"), graph_id=component_id)
+        except AssertionError:
+            assert True
+        else:
+            assert False
+
+
+class TestConstructSFG:
+
+    def test_1k_additions(self):
+        prev_op = Addition(Constant(1), Constant(1))
+        for _ in range(999):
+            prev_op = Addition(prev_op, Constant(2))
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == 2000
+
+    def test_1k_subtractions(self):
+        prev_op = Subtraction(Constant(0), Constant(2))
+        for _ in range(999):
+            prev_op = Subtraction(prev_op, Constant(2))
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == -2000
+
+    def test_1k_butterfly(self):
+        prev_op_add = Addition(Constant(1), Constant(1))
+        prev_op_sub = Subtraction(Constant(-1), Constant(1))
+        for _ in range(499):
+            prev_op_add = Addition(prev_op_add, Constant(2))
+        for _ in range(499):
+            prev_op_sub = Subtraction(prev_op_sub, Constant(2))
+        butterfly = Butterfly(prev_op_add, prev_op_sub)
+        sfg = SFG(outputs=[Output(butterfly.output(0)),
+                           Output(butterfly.output(1))])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == 0
+        assert sim.results["1"][0].real == 2000
+
+    def test_1k_multiplications(self):
+        prev_op = Multiplication(Constant(3), Constant(0.5))
+        for _ in range(999):
+            prev_op = Multiplication(prev_op, Constant(1.01))
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == 31127.458868040336
+
+    def test_1k_divisions(self):
+        prev_op = Division(Constant(3), Constant(0.5))
+        for _ in range(999):
+            prev_op = Division(prev_op, Constant(1.01))
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == 0.00028913378500165966
+
+    def test_1k_mins(self):
+        prev_op = Min(Constant(3.14159), Constant(43.14123843))
+        for _ in range(999):
+            prev_op = Min(prev_op, Constant(43.14123843))
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == 3.14159
+
+    def test_1k_maxs(self):
+        prev_op = Max(Constant(3.14159), Constant(43.14123843))
+        for _ in range(999):
+            prev_op = Max(prev_op, Constant(3.14159))
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == 43.14123843
+
+    def test_1k_square_roots(self):
+        prev_op = SquareRoot(Constant(1000000))
+        for _ in range(4):
+            prev_op = SquareRoot(prev_op)
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == 1.539926526059492
+
+    def test_1k_complex_conjugates(self):
+        prev_op = ComplexConjugate(Constant(10+5j))
+        for _ in range(999):
+            prev_op = ComplexConjugate(prev_op)
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"] == [10+5j]
+
+    def test_1k_absolutes(self):
+        prev_op = Absolute(Constant(-3.14159))
+        for _ in range(999):
+            prev_op = Absolute(prev_op)
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == 3.14159
+
+    def test_1k_constant_multiplications(self):
+        prev_op = ConstantMultiplication(1.02, Constant(3.14159))
+        for _ in range(999):
+            prev_op = ConstantMultiplication(1.02, prev_op)
+        sfg = SFG(outputs=[Output(prev_op)])
+        sim = FastSimulation(sfg)
+        sim.step()
+        assert sim.results["0"][0].real == 1251184247.0026844
+
+
+class TestInsertComponent:
+
+    def test_insert_component_in_sfg(self, large_operation_tree_names):
+        sfg = SFG(outputs=[Output(large_operation_tree_names)])
+        sqrt = SquareRoot()
+
+        _sfg = sfg.insert_operation(
+            sqrt, sfg.find_by_name("constant4")[0].graph_id)
+        assert _sfg.evaluate() != sfg.evaluate()
+
+        assert any([isinstance(comp, SquareRoot) for comp in _sfg.operations])
+        assert not any([isinstance(comp, SquareRoot)
+                        for comp in sfg.operations])
+
+        assert not isinstance(sfg.find_by_name("constant4")[0].output(
+            0).signals[0].destination.operation, SquareRoot)
+        assert isinstance(_sfg.find_by_name("constant4")[0].output(
+            0).signals[0].destination.operation, SquareRoot)
+
+        assert sfg.find_by_name("constant4")[0].output(
+            0).signals[0].destination.operation is sfg.find_by_id("add3")
+        assert _sfg.find_by_name("constant4")[0].output(
+            0).signals[0].destination.operation is not _sfg.find_by_id("add3")
+        assert _sfg.find_by_id("sqrt1").output(
+            0).signals[0].destination.operation is _sfg.find_by_id("add3")
+
+    def test_insert_invalid_component_in_sfg(self, large_operation_tree):
+        sfg = SFG(outputs=[Output(large_operation_tree)])
+
+        # Should raise an exception for not matching input count to output count.
+        add4 = Addition()
+        with pytest.raises(Exception):
+            sfg.insert_operation(add4, "c1")
+
+    def test_insert_at_output(self, large_operation_tree):
+        sfg = SFG(outputs=[Output(large_operation_tree)])
+
+        # Should raise an exception for trying to insert an operation after an output.
+        sqrt = SquareRoot()
+        with pytest.raises(Exception):
+            _sfg = sfg.insert_operation(sqrt, "out1")
+
+    def test_insert_multiple_output_ports(self, butterfly_operation_tree):
+        sfg = SFG(outputs=list(map(Output, butterfly_operation_tree.outputs)))
+        _sfg = sfg.insert_operation(Butterfly(name="n_bfly"), "bfly3")
+
+        assert sfg.evaluate() != _sfg.evaluate()
+
+        assert len(sfg.find_by_name("n_bfly")) == 0
+        assert len(_sfg.find_by_name("n_bfly")) == 1
+
+        # Correctly connected old output -> new input
+        assert _sfg.find_by_name("bfly3")[0].output(
+            0).signals[0].destination.operation is _sfg.find_by_name("n_bfly")[0]
+        assert _sfg.find_by_name("bfly3")[0].output(
+            1).signals[0].destination.operation is _sfg.find_by_name("n_bfly")[0]
+
+        # Correctly connected new input -> old output
+        assert _sfg.find_by_name("n_bfly")[0].input(
+            0).signals[0].source.operation is _sfg.find_by_name("bfly3")[0]
+        assert _sfg.find_by_name("n_bfly")[0].input(
+            1).signals[0].source.operation is _sfg.find_by_name("bfly3")[0]
+
+        # Correctly connected new output -> next input
+        assert _sfg.find_by_name("n_bfly")[0].output(
+            0).signals[0].destination.operation is _sfg.find_by_name("bfly2")[0]
+        assert _sfg.find_by_name("n_bfly")[0].output(
+            1).signals[0].destination.operation is _sfg.find_by_name("bfly2")[0]
+
+        # Correctly connected next input -> new output
+        assert _sfg.find_by_name("bfly2")[0].input(
+            0).signals[0].source.operation is _sfg.find_by_name("n_bfly")[0]
+        assert _sfg.find_by_name("bfly2")[0].input(
+            1).signals[0].source.operation is _sfg.find_by_name("n_bfly")[0]
+
+
+class TestFindComponentsWithTypeName:
+    def test_mac_components(self):
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        inp3 = Input("INP3")
+        add1 = Addition(None, None, "ADD1")
+        add2 = Addition(None, None, "ADD2")
+        mul1 = Multiplication(None, None, "MUL1")
+        out1 = Output(None, "OUT1")
+
+        add1.input(0).connect(inp1, "S1")
+        add1.input(1).connect(inp2, "S2")
+        add2.input(0).connect(add1, "S4")
+        add2.input(1).connect(inp3, "S3")
+        mul1.input(0).connect(add1, "S5")
+        mul1.input(1).connect(add2, "S6")
+        out1.input(0).connect(mul1, "S7")
+
+        mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="mac_sfg")
+
+        assert {comp.name for comp in mac_sfg.find_by_type_name(
+            inp1.type_name())} == {"INP1", "INP2", "INP3"}
+
+        assert {comp.name for comp in mac_sfg.find_by_type_name(
+            add1.type_name())} == {"ADD1", "ADD2"}
+
+        assert {comp.name for comp in mac_sfg.find_by_type_name(
+            mul1.type_name())} == {"MUL1"}
+
+        assert {comp.name for comp in mac_sfg.find_by_type_name(
+            out1.type_name())} == {"OUT1"}
+
+        assert {comp.name for comp in mac_sfg.find_by_type_name(
+            Signal.type_name())} == {"S1", "S2", "S3", "S4", "S5", "S6", "S7"}
+
+
+class TestGetPrecedenceList:
+
+    def test_inputs_delays(self, precedence_sfg_delays):
+
+        precedence_list = precedence_sfg_delays.get_precedence_list()
+
+        assert len(precedence_list) == 7
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[0]]) == {"IN1", "T1", "T2"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[1]]) == {"C0", "B1", "B2", "A1", "A2"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[2]]) == {"ADD2", "ADD3"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[3]]) == {"ADD1"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[4]]) == {"Q1"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[5]]) == {"A0"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[6]]) == {"ADD4"}
+
+    def test_inputs_constants_delays_multiple_outputs(self, precedence_sfg_delays_and_constants):
+
+        precedence_list = precedence_sfg_delays_and_constants.get_precedence_list()
+
+        assert len(precedence_list) == 7
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[0]]) == {"IN1", "T1", "CONST1"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[1]]) == {"C0", "B1", "B2", "A1", "A2"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[2]]) == {"ADD2", "ADD3"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[3]]) == {"ADD1"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[4]]) == {"Q1"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[5]]) == {"A0"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[6]]) == {"BFLY1.0", "BFLY1.1"}
+
+    def test_precedence_multiple_outputs_same_precedence(self, sfg_two_inputs_two_outputs):
+        sfg_two_inputs_two_outputs.name = "NESTED_SFG"
+
+        in1 = Input("IN1")
+        sfg_two_inputs_two_outputs.input(0).connect(in1, "S1")
+        in2 = Input("IN2")
+        cmul1 = ConstantMultiplication(10, None, "CMUL1")
+        cmul1.input(0).connect(in2, "S2")
+        sfg_two_inputs_two_outputs.input(1).connect(cmul1, "S3")
+
+        out1 = Output(sfg_two_inputs_two_outputs.output(0), "OUT1")
+        out2 = Output(sfg_two_inputs_two_outputs.output(1), "OUT2")
+
+        sfg = SFG(inputs=[in1, in2], outputs=[out1, out2])
+
+        precedence_list = sfg.get_precedence_list()
+
+        assert len(precedence_list) == 3
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[0]]) == {"IN1", "IN2"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[1]]) == {"CMUL1"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[2]]) == {"NESTED_SFG.0", "NESTED_SFG.1"}
+
+    def test_precedence_sfg_multiple_outputs_different_precedences(self, sfg_two_inputs_two_outputs_independent):
+        sfg_two_inputs_two_outputs_independent.name = "NESTED_SFG"
+
+        in1 = Input("IN1")
+        in2 = Input("IN2")
+        sfg_two_inputs_two_outputs_independent.input(0).connect(in1, "S1")
+        cmul1 = ConstantMultiplication(10, None, "CMUL1")
+        cmul1.input(0).connect(in2, "S2")
+        sfg_two_inputs_two_outputs_independent.input(1).connect(cmul1, "S3")
+        out1 = Output(sfg_two_inputs_two_outputs_independent.output(0), "OUT1")
+        out2 = Output(sfg_two_inputs_two_outputs_independent.output(1), "OUT2")
+
+        sfg = SFG(inputs=[in1, in2], outputs=[out1, out2])
+
+        precedence_list = sfg.get_precedence_list()
+
+        assert len(precedence_list) == 3
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[0]]) == {"IN1", "IN2"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[1]]) == {"CMUL1"}
+
+        assert set([port.operation.key(port.index, port.operation.name)
+                    for port in precedence_list[2]]) == {"NESTED_SFG.0", "NESTED_SFG.1"}
+
+
+class TestPrintPrecedence:
+    def test_delays(self, precedence_sfg_delays):
+        sfg = precedence_sfg_delays
+
+        captured_output = io.StringIO()
+        sys.stdout = captured_output
+
+        sfg.print_precedence_graph()
+
+        sys.stdout = sys.__stdout__
+
+        captured_output = captured_output.getvalue()
+
+        assert captured_output == \
+            "-" * 120 + "\n" + \
+            "1.1 \t" + str(sfg.find_by_name("IN1")[0]) + "\n" + \
+            "1.2 \t" + str(sfg.find_by_name("T1")[0]) + "\n" + \
+            "1.3 \t" + str(sfg.find_by_name("T2")[0]) + "\n" + \
+            "-" * 120 + "\n" + \
+            "2.1 \t" + str(sfg.find_by_name("C0")[0]) + "\n" + \
+            "2.2 \t" + str(sfg.find_by_name("A1")[0]) + "\n" + \
+            "2.3 \t" + str(sfg.find_by_name("B1")[0]) + "\n" + \
+            "2.4 \t" + str(sfg.find_by_name("A2")[0]) + "\n" + \
+            "2.5 \t" + str(sfg.find_by_name("B2")[0]) + "\n" + \
+            "-" * 120 + "\n" + \
+            "3.1 \t" + str(sfg.find_by_name("ADD3")[0]) + "\n" + \
+            "3.2 \t" + str(sfg.find_by_name("ADD2")[0]) + "\n" + \
+            "-" * 120 + "\n" + \
+            "4.1 \t" + str(sfg.find_by_name("ADD1")[0]) + "\n" + \
+            "-" * 120 + "\n" + \
+            "5.1 \t" + str(sfg.find_by_name("Q1")[0]) + "\n" + \
+            "-" * 120 + "\n" + \
+            "6.1 \t" + str(sfg.find_by_name("A0")[0]) + "\n" + \
+            "-" * 120 + "\n" + \
+            "7.1 \t" + str(sfg.find_by_name("ADD4")[0]) + "\n" + \
+            "-" * 120 + "\n"
+
+
+class TestDepends:
+    def test_depends_sfg(self, sfg_two_inputs_two_outputs):
+        assert set(sfg_two_inputs_two_outputs.inputs_required_for_output(0)) == {
+            0, 1}
+        assert set(sfg_two_inputs_two_outputs.inputs_required_for_output(1)) == {
+            0, 1}
+
+    def test_depends_sfg_independent(self, sfg_two_inputs_two_outputs_independent):
+        assert set(
+            sfg_two_inputs_two_outputs_independent.inputs_required_for_output(0)) == {0}
+        assert set(
+            sfg_two_inputs_two_outputs_independent.inputs_required_for_output(1)) == {1}
+
+
+class TestConnectExternalSignalsToComponentsSoloComp:
+
+    def test_connect_external_signals_to_components_mac(self):
+        """ Replace a MAC with inner components in an SFG """
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        inp3 = Input("INP3")
+        add1 = Addition(None, None, "ADD1")
+        add2 = Addition(None, None, "ADD2")
+        mul1 = Multiplication(None, None, "MUL1")
+        out1 = Output(None, "OUT1")
+
+        add1.input(0).connect(inp1, "S1")
+        add1.input(1).connect(inp2, "S2")
+        add2.input(0).connect(add1, "S3")
+        add2.input(1).connect(inp3, "S4")
+        mul1.input(0).connect(add1, "S5")
+        mul1.input(1).connect(add2, "S6")
+        out1.input(0).connect(mul1, "S7")
+
+        mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1])
+
+        inp4 = Input("INP4")
+        inp5 = Input("INP5")
+        out2 = Output(None, "OUT2")
+
+        mac_sfg.input(0).connect(inp4, "S8")
+        mac_sfg.input(1).connect(inp5, "S9")
+        out2.input(0).connect(mac_sfg.outputs[0], "S10")
+
+        test_sfg = SFG(inputs=[inp4, inp5], outputs=[out2])
+        assert test_sfg.evaluate(1, 2) == 9
+        mac_sfg.connect_external_signals_to_components()
+        assert test_sfg.evaluate(1, 2) == 9
+        assert not test_sfg.connect_external_signals_to_components()
+
+    def test_connect_external_signals_to_components_operation_tree(self, operation_tree):
+        """ Replaces an SFG with only a operation_tree component with its inner components """
+        sfg1 = SFG(outputs=[Output(operation_tree)])
+        out1 = Output(None, "OUT1")
+        out1.input(0).connect(sfg1.outputs[0], "S1")
+        test_sfg = SFG(outputs=[out1])
+        assert test_sfg.evaluate_output(0, []) == 5
+        sfg1.connect_external_signals_to_components()
+        assert test_sfg.evaluate_output(0, []) == 5
+        assert not test_sfg.connect_external_signals_to_components()
+
+    def test_connect_external_signals_to_components_large_operation_tree(self, large_operation_tree):
+        """ Replaces an SFG with only a large_operation_tree component with its inner components """
+        sfg1 = SFG(outputs=[Output(large_operation_tree)])
+        out1 = Output(None, "OUT1")
+        out1.input(0).connect(sfg1.outputs[0], "S1")
+        test_sfg = SFG(outputs=[out1])
+        assert test_sfg.evaluate_output(0, []) == 14
+        sfg1.connect_external_signals_to_components()
+        assert test_sfg.evaluate_output(0, []) == 14
+        assert not test_sfg.connect_external_signals_to_components()
+
+
+class TestConnectExternalSignalsToComponentsMultipleComp:
+
+    def test_connect_external_signals_to_components_operation_tree(self, operation_tree):
+        """ Replaces a operation_tree in an SFG with other components """
+        sfg1 = SFG(outputs=[Output(operation_tree)])
+
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        out1 = Output(None, "OUT1")
+
+        add1 = Addition(None, None, "ADD1")
+        add2 = Addition(None, None, "ADD2")
+
+        add1.input(0).connect(inp1, "S1")
+        add1.input(1).connect(inp2, "S2")
+        add2.input(0).connect(add1, "S3")
+        add2.input(1).connect(sfg1.outputs[0], "S4")
+        out1.input(0).connect(add2, "S5")
+
+        test_sfg = SFG(inputs=[inp1, inp2], outputs=[out1])
+        assert test_sfg.evaluate(1, 2) == 8
+        sfg1.connect_external_signals_to_components()
+        assert test_sfg.evaluate(1, 2) == 8
+        assert not test_sfg.connect_external_signals_to_components()
+
+    def test_connect_external_signals_to_components_large_operation_tree(self, large_operation_tree):
+        """ Replaces a large_operation_tree in an SFG with other components """
+        sfg1 = SFG(outputs=[Output(large_operation_tree)])
+
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        out1 = Output(None, "OUT1")
+        add1 = Addition(None, None, "ADD1")
+        add2 = Addition(None, None, "ADD2")
+
+        add1.input(0).connect(inp1, "S1")
+        add1.input(1).connect(inp2, "S2")
+        add2.input(0).connect(add1, "S3")
+        add2.input(1).connect(sfg1.outputs[0], "S4")
+        out1.input(0).connect(add2, "S5")
+
+        test_sfg = SFG(inputs=[inp1, inp2], outputs=[out1])
+        assert test_sfg.evaluate(1, 2) == 17
+        sfg1.connect_external_signals_to_components()
+        assert test_sfg.evaluate(1, 2) == 17
+        assert not test_sfg.connect_external_signals_to_components()
+
+    def create_sfg(self, op_tree):
+        """ Create a simple SFG with either operation_tree or large_operation_tree """
+        sfg1 = SFG(outputs=[Output(op_tree)])
+
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        out1 = Output(None, "OUT1")
+        add1 = Addition(None, None, "ADD1")
+        add2 = Addition(None, None, "ADD2")
+
+        add1.input(0).connect(inp1, "S1")
+        add1.input(1).connect(inp2, "S2")
+        add2.input(0).connect(add1, "S3")
+        add2.input(1).connect(sfg1.outputs[0], "S4")
+        out1.input(0).connect(add2, "S5")
+
+        return SFG(inputs=[inp1, inp2], outputs=[out1])
+
+    def test_connect_external_signals_to_components_many_op(self, large_operation_tree):
+        """ Replaces an sfg component in a larger SFG with several component operations """
+        inp1 = Input("INP1")
+        inp2 = Input("INP2")
+        inp3 = Input("INP3")
+        inp4 = Input("INP4")
+        out1 = Output(None, "OUT1")
+        add1 = Addition(None, None, "ADD1")
+        sub1 = Subtraction(None, None, "SUB1")
+
+        add1.input(0).connect(inp1, "S1")
+        add1.input(1).connect(inp2, "S2")
+
+        sfg1 = self.create_sfg(large_operation_tree)
+
+        sfg1.input(0).connect(add1, "S3")
+        sfg1.input(1).connect(inp3, "S4")
+        sub1.input(0).connect(sfg1.outputs[0], "S5")
+        sub1.input(1).connect(inp4, "S6")
+        out1.input(0).connect(sub1, "S7")
+
+        test_sfg = SFG(inputs=[inp1, inp2, inp3, inp4], outputs=[out1])
+
+        assert test_sfg.evaluate(1, 2, 3, 4) == 16
+        sfg1.connect_external_signals_to_components()
+        assert test_sfg.evaluate(1, 2, 3, 4) == 16
+        assert not test_sfg.connect_external_signals_to_components()
+
+
+class TestTopologicalOrderOperations:
+    def test_feedback_sfg(self, sfg_simple_filter):
+        topological_order = sfg_simple_filter.get_operations_topological_order()
+
+        assert [comp.name for comp in topological_order] == [
+            "IN1", "ADD1", "T1", "CMUL1", "OUT1"]
+
+    def test_multiple_independent_inputs(self, sfg_two_inputs_two_outputs_independent):
+        topological_order = sfg_two_inputs_two_outputs_independent.get_operations_topological_order()
+
+        assert [comp.name for comp in topological_order] == [
+            "IN1", "OUT1", "IN2", "C1", "ADD1", "OUT2"]
+
+    def test_complex_graph(self, precedence_sfg_delays):
+        topological_order = precedence_sfg_delays.get_operations_topological_order()
+
+        assert [comp.name for comp in topological_order] == \
+            ['IN1', 'C0', 'ADD1', 'Q1', 'A0', 'T1', 'B1', 'A1',
+                'T2', 'B2', 'ADD2', 'A2', 'ADD3', 'ADD4', 'OUT1']
+
+
+class TestRemove:
+    def test_remove_single_input_outputs(self, sfg_simple_filter):
+        new_sfg = sfg_simple_filter.remove_operation("cmul1")
+
+        assert set(op.name for op in sfg_simple_filter.find_by_name(
+            "T1")[0].subsequent_operations) == {"CMUL1", "OUT1"}
+        assert set(op.name for op in new_sfg.find_by_name("T1")[
+                   0].subsequent_operations) == {"ADD1", "OUT1"}
+
+        assert set(op.name for op in sfg_simple_filter.find_by_name(
+            "ADD1")[0].preceding_operations) == {"CMUL1", "IN1"}
+        assert set(op.name for op in new_sfg.find_by_name(
+            "ADD1")[0].preceding_operations) == {"T1", "IN1"}
+
+        assert "S1" in set(
+            [sig.name for sig in sfg_simple_filter.find_by_name("T1")[0].output(0).signals])
+        assert "S2" in set(
+            [sig.name for sig in new_sfg.find_by_name("T1")[0].output(0).signals])
+
+    def test_remove_multiple_inputs_outputs(self, butterfly_operation_tree):
+        out1 = Output(butterfly_operation_tree.output(0), "OUT1")
+        out2 = Output(butterfly_operation_tree.output(1), "OUT2")
+
+        sfg = SFG(outputs=[out1, out2])
+
+        new_sfg = sfg.remove_operation(sfg.find_by_name("bfly2")[0].graph_id)
+
+        assert sfg.find_by_name("bfly3")[0].output(0).signal_count == 1
+        assert new_sfg.find_by_name("bfly3")[0].output(0).signal_count == 1
+
+        sfg_dest_0 = sfg.find_by_name(
+            "bfly3")[0].output(0).signals[0].destination
+        new_sfg_dest_0 = new_sfg.find_by_name(
+            "bfly3")[0].output(0).signals[0].destination
+
+        assert sfg_dest_0.index == 0
+        assert new_sfg_dest_0.index == 0
+        assert sfg_dest_0.operation.name == "bfly2"
+        assert new_sfg_dest_0.operation.name == "bfly1"
+
+        assert sfg.find_by_name("bfly3")[0].output(1).signal_count == 1
+        assert new_sfg.find_by_name("bfly3")[0].output(1).signal_count == 1
+
+        sfg_dest_1 = sfg.find_by_name(
+            "bfly3")[0].output(1).signals[0].destination
+        new_sfg_dest_1 = new_sfg.find_by_name(
+            "bfly3")[0].output(1).signals[0].destination
+
+        assert sfg_dest_1.index == 1
+        assert new_sfg_dest_1.index == 1
+        assert sfg_dest_1.operation.name == "bfly2"
+        assert new_sfg_dest_1.operation.name == "bfly1"
+
+        assert sfg.find_by_name("bfly1")[0].input(0).signal_count == 1
+        assert new_sfg.find_by_name("bfly1")[0].input(0).signal_count == 1
+
+        sfg_source_0 = sfg.find_by_name("bfly1")[0].input(0).signals[0].source
+        new_sfg_source_0 = new_sfg.find_by_name(
+            "bfly1")[0].input(0).signals[0].source
+
+        assert sfg_source_0.index == 0
+        assert new_sfg_source_0.index == 0
+        assert sfg_source_0.operation.name == "bfly2"
+        assert new_sfg_source_0.operation.name == "bfly3"
+
+        sfg_source_1 = sfg.find_by_name("bfly1")[0].input(1).signals[0].source
+        new_sfg_source_1 = new_sfg.find_by_name(
+            "bfly1")[0].input(1).signals[0].source
+
+        assert sfg_source_1.index == 1
+        assert new_sfg_source_1.index == 1
+        assert sfg_source_1.operation.name == "bfly2"
+        assert new_sfg_source_1.operation.name == "bfly3"
+
+        assert "bfly2" not in set(op.name for op in new_sfg.operations)
+
+    def remove_different_number_inputs_outputs(self, sfg_simple_filter):
+        with pytest.raises(ValueError):
+            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.find_by_type_name(Multiplication.type_name())] \
+            == []
+
+    def test_get_multple_operations_of_type(self, sfg_two_inputs_two_outputs):
+        assert [op.name for op in sfg_two_inputs_two_outputs.find_by_type_name(Addition.type_name())] \
+            == ["ADD1", "ADD2"]
+
+        assert [op.name for op in sfg_two_inputs_two_outputs.find_by_type_name(Input.type_name())] \
+            == ["IN1", "IN2"]
+
+        assert [op.name for op in sfg_two_inputs_two_outputs.find_by_type_name(Output.type_name())] \
+            == ["OUT1", "OUT2"]
diff --git a/test/test_signal.py b/test/test_signal.py
index ab07eb77..42086d4d 100644
--- a/test/test_signal.py
+++ b/test/test_signal.py
@@ -2,14 +2,14 @@
 B-ASIC test suit for the signal module which consists of the Signal class.
 """
 
-from b_asic.port import InputPort, OutputPort
-from b_asic.signal import Signal
-
 import pytest
 
+from b_asic import InputPort, OutputPort, Signal
+
+
 def test_signal_creation_and_disconnction_and_connection_changing():
-    in_port = InputPort(0, None)
-    out_port = OutputPort(1, None)
+    in_port = InputPort(None, 0)
+    out_port = OutputPort(None, 1)
     s = Signal(out_port, in_port)
 
     assert in_port.signals == [s]
@@ -17,7 +17,7 @@ def test_signal_creation_and_disconnction_and_connection_changing():
     assert s.source is out_port
     assert s.destination is in_port
 
-    in_port1 = InputPort(0, None)
+    in_port1 = InputPort(None, 0)
     s.set_destination(in_port1)
 
     assert in_port.signals == []
@@ -40,7 +40,7 @@ def test_signal_creation_and_disconnction_and_connection_changing():
     assert s.source is None
     assert s.destination is None
 
-    out_port1 = OutputPort(0, None)
+    out_port1 = OutputPort(None, 0)
     s.set_source(out_port1)
 
     assert out_port1.signals == [s]
@@ -60,3 +60,29 @@ def test_signal_creation_and_disconnction_and_connection_changing():
     assert in_port.signals == [s]
     assert s.source is out_port
     assert s.destination is in_port
+
+class Bits:
+    def test_pos_int(self, signal):
+        signal.bits = 10
+        assert signal.bits == 10
+
+    def test_bits_zero(self, signal):
+        signal.bits = 0
+        assert signal.bits == 0
+
+    def test_bits_neg_int(self, signal):
+        with pytest.raises(Exception):
+            signal.bits = -10
+
+    def test_bits_complex(self, signal):
+        with pytest.raises(Exception):
+            signal.bits = (2+4j)
+
+    def test_bits_float(self, signal):
+        with pytest.raises(Exception):
+            signal.bits = 3.2
+
+    def test_bits_pos_then_none(self, signal):
+        signal.bits = 10
+        signal.bits = None
+        assert signal.bits is None
\ No newline at end of file
diff --git a/test/test_simulation.py b/test/test_simulation.py
new file mode 100644
index 00000000..8ca19e47
--- /dev/null
+++ b/test/test_simulation.py
@@ -0,0 +1,202 @@
+import pytest
+import numpy as np
+
+from b_asic import SFG, Output, Simulation
+
+
+class TestRunFor:
+    def test_with_lambdas_as_input(self, sfg_two_inputs_two_outputs):
+        simulation = Simulation(sfg_two_inputs_two_outputs, [lambda n: n + 3, lambda n: 1 + n * 2])
+
+        output = simulation.run_for(101, save_results = True)
+
+        assert output[0] == 304
+        assert output[1] == 505
+
+        assert simulation.results["0"][100] == 304
+        assert simulation.results["1"][100] == 505
+
+        assert simulation.results["in1"][0] == 3
+        assert simulation.results["in2"][0] == 1
+        assert simulation.results["add1"][0] == 4
+        assert simulation.results["add2"][0] == 5
+        assert simulation.results["0"][0] == 4
+        assert simulation.results["1"][0] == 5
+
+        assert simulation.results["in1"][1] == 4
+        assert simulation.results["in2"][1] == 3
+        assert simulation.results["add1"][1] == 7
+        assert simulation.results["add2"][1] == 10
+        assert simulation.results["0"][1] == 7
+        assert simulation.results["1"][1] == 10
+
+        assert simulation.results["in1"][2] == 5
+        assert simulation.results["in2"][2] == 5
+        assert simulation.results["add1"][2] == 10
+        assert simulation.results["add2"][2] == 15
+        assert simulation.results["0"][2] == 10
+        assert simulation.results["1"][2] == 15
+
+        assert simulation.results["in1"][3] == 6
+        assert simulation.results["in2"][3] == 7
+        assert simulation.results["add1"][3] == 13
+        assert simulation.results["add2"][3] == 20
+        assert simulation.results["0"][3] == 13
+        assert simulation.results["1"][3] == 20
+
+    def test_with_numpy_arrays_as_input(self, sfg_two_inputs_two_outputs):
+        input0 = np.array([5, 9, 25, -5, 7])
+        input1 = np.array([7, 3, 3,  54, 2])
+        simulation = Simulation(sfg_two_inputs_two_outputs, [input0, input1])
+
+        output = simulation.run_for(5, save_results = True)
+
+        assert output[0] == 9
+        assert output[1] == 11
+
+        assert isinstance(simulation.results["in1"], np.ndarray)
+        assert isinstance(simulation.results["in2"], np.ndarray)
+        assert isinstance(simulation.results["add1"], np.ndarray)
+        assert isinstance(simulation.results["add2"], np.ndarray)
+        assert isinstance(simulation.results["0"], np.ndarray)
+        assert isinstance(simulation.results["1"], np.ndarray)
+
+        assert simulation.results["in1"][0] == 5
+        assert simulation.results["in2"][0] == 7
+        assert simulation.results["add1"][0] == 12
+        assert simulation.results["add2"][0] == 19
+        assert simulation.results["0"][0] == 12
+        assert simulation.results["1"][0] == 19
+
+        assert simulation.results["in1"][1] == 9
+        assert simulation.results["in2"][1] == 3
+        assert simulation.results["add1"][1] == 12
+        assert simulation.results["add2"][1] == 15
+        assert simulation.results["0"][1] == 12
+        assert simulation.results["1"][1] == 15
+
+        assert simulation.results["in1"][2] == 25
+        assert simulation.results["in2"][2] == 3
+        assert simulation.results["add1"][2] == 28
+        assert simulation.results["add2"][2] == 31
+        assert simulation.results["0"][2] == 28
+        assert simulation.results["1"][2] == 31
+
+        assert simulation.results["in1"][3] == -5
+        assert simulation.results["in2"][3] == 54
+        assert simulation.results["add1"][3] == 49
+        assert simulation.results["add2"][3] == 103
+        assert simulation.results["0"][3] == 49
+        assert simulation.results["1"][3] == 103
+
+        assert simulation.results["0"][4] == 9
+        assert simulation.results["1"][4] == 11
+    
+    def test_with_numpy_array_overflow(self, sfg_two_inputs_two_outputs):
+        input0 = np.array([5, 9, 25, -5, 7])
+        input1 = np.array([7, 3, 3,  54, 2])
+        simulation = Simulation(sfg_two_inputs_two_outputs, [input0, input1])
+        simulation.run_for(5)
+        with pytest.raises(IndexError):
+            simulation.step()
+
+    def test_run_whole_numpy_array(self, sfg_two_inputs_two_outputs):
+        input0 = np.array([5, 9, 25, -5, 7])
+        input1 = np.array([7, 3, 3,  54, 2])
+        simulation = Simulation(sfg_two_inputs_two_outputs, [input0, input1])
+        simulation.run()
+        assert len(simulation.results["0"]) == 5
+        assert len(simulation.results["1"]) == 5
+        with pytest.raises(IndexError):
+            simulation.step()
+
+    def test_delay(self, sfg_delay):
+        simulation = Simulation(sfg_delay)
+        simulation.set_input(0, [5, -2, 25, -6, 7, 0])
+        simulation.run_for(6, save_results = True)
+
+        assert simulation.results["0"][0] == 0
+        assert simulation.results["0"][1] == 5
+        assert simulation.results["0"][2] == -2
+        assert simulation.results["0"][3] == 25
+        assert simulation.results["0"][4] == -6
+        assert simulation.results["0"][5] == 7
+
+    def test_find_result_key(self, precedence_sfg_delays):
+        sim = Simulation(precedence_sfg_delays, [[0, 4, 542, 42, 31.314, 534.123, -453415, 5431]])
+        sim.run()
+        assert sim.results[precedence_sfg_delays.find_result_keys_by_name("ADD2")[0]][4] == 31220
+        assert sim.results[precedence_sfg_delays.find_result_keys_by_name("A1")[0]][2] == 80
+
+class TestRun:
+    def test_save_results(self, sfg_two_inputs_two_outputs):
+        simulation = Simulation(sfg_two_inputs_two_outputs, [2, 3])
+        assert not simulation.results
+        simulation.run_for(10, save_results = False)
+        assert not simulation.results
+        simulation.run_for(10)
+        assert len(simulation.results["0"]) == 10
+        assert len(simulation.results["1"]) == 10
+        simulation.run_for(10, save_results = True)
+        assert len(simulation.results["0"]) == 20
+        assert len(simulation.results["1"]) == 20
+        simulation.run_for(10, save_results = False)
+        assert len(simulation.results["0"]) == 20
+        assert len(simulation.results["1"]) == 20
+        simulation.run_for(13, save_results = True)
+        assert len(simulation.results["0"]) == 33
+        assert len(simulation.results["1"]) == 33
+        simulation.step(save_results = False)
+        assert len(simulation.results["0"]) == 33
+        assert len(simulation.results["1"]) == 33
+        simulation.step()
+        assert len(simulation.results["0"]) == 34
+        assert len(simulation.results["1"]) == 34
+        simulation.clear_results()
+        assert not simulation.results
+
+    def test_nested(self, sfg_nested):
+        input0 = np.array([5, 9])
+        input1 = np.array([7, 3])
+        simulation = Simulation(sfg_nested, [input0, input1])
+
+        output0 = simulation.step()
+        output1 = simulation.step()
+
+        assert output0[0] == 11405
+        assert output1[0] == 4221
+    
+    def test_accumulator(self, sfg_accumulator):
+        data_in = np.array([5, -2, 25, -6, 7, 0])
+        reset   = np.array([0, 0,  0,  1,  0, 0])
+        simulation = Simulation(sfg_accumulator, [data_in, reset])
+        output0 = simulation.step()
+        output1 = simulation.step()
+        output2 = simulation.step()
+        output3 = simulation.step()
+        output4 = simulation.step()
+        output5 = simulation.step()
+        assert output0[0] == 0
+        assert output1[0] == 5
+        assert output2[0] == 3
+        assert output3[0] == 28
+        assert output4[0] == 0
+        assert output5[0] == 7
+
+    def test_simple_accumulator(self, sfg_simple_accumulator):
+        data_in = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+        simulation = Simulation(sfg_simple_accumulator, [data_in])
+        simulation.run()
+        assert list(simulation.results["0"]) == [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
+        
+    def test_simple_filter(self, sfg_simple_filter):
+        input0 = np.array([1, 2, 3, 4, 5])
+        simulation = Simulation(sfg_simple_filter, [input0])
+        simulation.run_for(len(input0), save_results = True)
+        assert all(simulation.results["0"] == np.array([0, 1.0, 2.5, 4.25, 6.125]))
+
+    def test_custom_operation(self, sfg_custom_operation):
+        simulation = Simulation(sfg_custom_operation, [lambda n: n + 1])
+        simulation.run_for(5)
+        assert all(simulation.results["0"] == np.array([2, 4, 6, 8, 10]))
+        assert all(simulation.results["1"] == np.array([2, 4, 8, 16, 32]))
-- 
GitLab