diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000000000000000000000000000000000000..7548f76b9b80cc6c1725505ab0492be1d7e3317a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,151 @@
+AccessModifierOffset: -4
+
+AlignAfterOpenBracket: DontAlign
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignConsecutiveMacros: true
+AlignEscapedNewlines: DontAlign
+AlignOperands: false
+AlignTrailingComments: true
+
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLambdasOnASingleLine: Inline
+AllowShortLoopsOnASingleLine: false
+
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+
+BinPackArguments: false
+BinPackParameters: true
+
+BreakBeforeBraces: Custom
+BraceWrapping:
+  AfterCaseLabel: false
+  AfterClass: false
+  AfterControlStatement: false
+  AfterEnum: false
+  AfterFunction: false
+  AfterNamespace: false
+  AfterObjCDeclaration: false
+  AfterStruct: false
+  AfterUnion: false
+  AfterExternBlock: false
+  BeforeCatch: false
+  BeforeElse: false
+  IndentBraces: false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: false
+  SplitEmptyNamespace: true
+
+BreakBeforeBinaryOperators: None
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializers: BeforeComma
+BreakInheritanceList: BeforeComma
+BreakStringLiterals: true
+
+ColumnLimit: 140
+
+CommentPragmas: ''
+
+CompactNamespaces: false
+
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+
+ContinuationIndentWidth: 4
+
+Cpp11BracedListStyle: true
+
+DerivePointerAlignment: false
+
+DisableFormat: false
+
+FixNamespaceComments: true
+
+ForEachMacros:
+  - Q_FOREACH
+  - BOOST_FOREACH
+  - FOREACH
+  - FOR_EACH
+
+IncludeBlocks: Regroup
+IncludeCategories:
+  - Regex: '^<'
+    Priority: 2
+  - Regex: '.*'
+    Priority: 1
+IncludeIsMainRegex: '(_test)?$'
+
+IndentCaseLabels: true
+IndentGotoLabels: false
+IndentPPDirectives: None
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+
+KeepEmptyLinesAtTheStartOfBlocks: false
+
+Language: Cpp
+
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+
+MaxEmptyLinesToKeep: 1
+
+NamespaceIndentation: None
+NamespaceMacros:
+  - NAMESPACE
+
+PenaltyBreakAssignment: 100
+PenaltyBreakBeforeFirstCallParameter: 10
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 100
+PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10000
+PenaltyExcessCharacter: 999999
+PenaltyReturnTypeOnItsOwnLine: 10000
+
+PointerAlignment: Left
+
+ReflowComments: false
+
+SortIncludes: true
+SortUsingDeclarations: true
+
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInContainerLiterals: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+
+Standard: Cpp11
+
+StatementMacros:
+  - Q_UNUSED
+
+TabWidth: 4
+
+TypenameMacros:
+  - STACK_OF
+  - LIST
+  - LIST_ENTRY
+
+UseTab: ForContinuationAndIndentation
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..0bdf5476787722ba2007a85c8c73255d56178262
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+.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
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6c51653c1a03a663601c9d49afbcd898f7fc86b9..3b2095bbac834a11ff4b8d3d5a13ad0f9903a090 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,10 +1,16 @@
+image: python:3.6
+
 stages:
-  - build
+ - test
+
+before_script:
+  - apt-get update --yes
+  - apt-get install --yes build-essential cmake libfmt-dev pybind11-dev
+  - pip3 install pytest pytest-cov
+  - pip3 install .
+  - pip3 show b_asic
 
-PythonBuild:
-  stage: build
-  artifacts:
-    untracked: true
+run tests:
+  stage: test
   script:
-    - apt-get update && apt-get install python3 -y
-    - bash build.sh
\ No newline at end of file
+    - pytest test
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3471fb4f473b069f9a4f77efec5d7aeb0fc84a79
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 3.8)
+
+project(
+	"B-ASIC"
+	VERSION 0.0.1
+	DESCRIPTION "Better ASIC Toolbox for python3"
+	LANGUAGES C CXX
+)
+
+find_package(fmt 5.2.1 REQUIRED)
+find_package(pybind11 CONFIG REQUIRED)
+
+set(LIBRARY_NAME "b_asic")
+set(TARGET_NAME "_${LIBRARY_NAME}")
+
+if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
+	include(GNUInstallDirs)
+	set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_INSTALL_LIBDIR}")
+endif()
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
+set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
+set(CMAKE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
+set(CMAKE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
+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}")
+
+pybind11_add_module(
+	"${TARGET_NAME}"
+	"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp"
+)
+
+target_include_directories(
+	"${TARGET_NAME}"
+    PRIVATE
+        "${CMAKE_CURRENT_SOURCE_DIR}/src"
+)
+
+target_compile_features(
+	"${TARGET_NAME}"
+	PRIVATE
+		cxx_std_17
+)
+target_compile_options(
+	"${TARGET_NAME}"
+	PRIVATE
+		$<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>:
+			-W -Wall -Wextra -Werror -Wno-psabi
+			$<$<CONFIG:Debug>:-g>
+			$<$<NOT:$<CONFIG:Debug>>:-O3>
+		>
+		$<$<CXX_COMPILER_ID:MSVC>:
+			/W3 /WX /permissive- /utf-8
+			$<$<CONFIG:Debug>:/Od>
+			$<$<NOT:$<CONFIG:Debug>>:/Ot>
+		>
+)
+
+target_link_libraries(
+	"${TARGET_NAME}"
+	PRIVATE
+		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
diff --git a/LICENSE b/LICENSE
index 17010bd5c1e2eb344384a5fa1f4fa6bfc4b72e63..669ce41e6f7c3cc001e8f9bb88a86c0dfaf755d9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2020 TDDD96
+Copyright (c) 2020 TDDD96 PUM4
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000000000000000000000000000000000000..ce996f6c4ffcc6a70d095c24ed296ec3b69e5f43
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include README.md
+include LICENSE
+include CMakeLists.txt
+recursive-include src *.cpp *.h
diff --git a/README.md b/README.md
index 20c8c4a23579933ca86a74fade71c015da9a79cf..fd98f919202588942a3e8d394b0b461ef63cfe54 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,119 @@
-<img src="https://files.slack.com/files-pri/TSHPRJY83-FTTRW9MQ8/b-asic-logo-opaque.png"  width="318" height="100">
-<br>
-<h3>B-ASIC is an ASIC toolbox for Python 3 that simplifies circuit design and optimization.<h3>
\ No newline at end of file
+<img src="logo.png" width="278" height="100">
+
+# B-ASIC - Better ASIC Toolbox
+B-ASIC is an ASIC toolbox for Python 3 that simplifies circuit design and optimization.
+
+## Development
+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+
+* python 3.6+
+  * setuptools
+  * wheel
+  * pybind11
+  * numpy
+  * pyside2/pyqt5
+
+### Using CMake directly
+How to build using CMake.
+
+#### Configuring
+In `B-ASIC`:
+```
+mkdir build
+cd build
+cmake ..
+```
+
+#### Building (Debug)
+In `B-ASIC/build`:
+```
+cmake --build .
+```
+The output gets written to `B-ASIC/build/lib`.
+
+#### Building (Release)
+In `B-ASIC/build`:
+```
+cmake --build . --config Release
+```
+The output gets written to `B-ASIC/build/lib`.
+
+### Using setuptools to create a package
+How to create a package using setuptools that can be installed using pip.
+
+#### Setup (Binary distribution)
+In `B-ASIC`:
+```
+python3 setup.py bdist_wheel
+```
+The output gets written to `B-ASIC/dist/b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl`.
+
+#### Setup (Source distribution)
+In `B-ASIC`:
+```
+python3 setup.py sdist
+```
+The output gets written to `B-ASIC/dist/b-asic-<version>.tar.gz`.
+
+#### Installation (Binary distribution)
+In `B-ASIC/dist`:
+```
+pip install b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl
+```
+
+#### Installation (Source distribution)
+In `B-ASIC/dist`:
+```
+pip install b-asic-<version>.tar.gz
+```
+
+### Running tests
+How to run the tests using pytest in a virtual environment.
+
+#### Linux/OS X
+In `B-ASIC`:
+```
+python3 -m venv env
+source env/bin/activate
+pip install .
+pytest
+```
+
+#### Windows
+In `B-ASIC` (as admin):
+```
+python3 -m venv env
+.\env\Scripts\activate.bat
+pip install .
+pytest
+```
+
+#### Test with coverage
+```
+pytest --cov=b_asic --cov-report html test
+```
+
+## Usage
+How to build and use the library as a user.
+
+### Installation
+```
+pip install b_asic
+```
+
+### Importing
+```
+python3
+>>> import b_asic as asic
+>>> help(asic)
+```
+
+## License
+B-ASIC is distributed under the MIT license.
+See the included LICENSE file for more information.
diff --git a/b_asic/__init__.py b/b_asic/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e40ad52cdc51da3e1d91964ad55cfc90e12a34a
--- /dev/null
+++ b/b_asic/__init__.py
@@ -0,0 +1,14 @@
+"""
+Better ASIC Toolbox.
+TODO: More info.
+"""
+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 *
diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py
new file mode 100644
index 0000000000000000000000000000000000000000..8902b169c07e600a843e526d44452fb9386ff7a9
--- /dev/null
+++ b/b_asic/core_operations.py
@@ -0,0 +1,337 @@
+"""@package docstring
+B-ASIC Core Operations Module.
+TODO: More info.
+"""
+
+from numbers import Number
+from typing import Any
+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.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.
+    """
+
+    def __init__(self, value: Number = 0, name: Name = ""):
+        super().__init__(name)
+
+        self._output_ports = [OutputPort(0, self)]
+        self._parameters["value"] = value
+
+    def evaluate(self):
+        return self.param("value")
+
+    @property
+    def type_name(self) -> TypeName:
+        return "c"
+
+
+class Addition(AbstractOperation):
+    """Binary addition 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)]
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+        if source2 is not None:
+            self._input_ports[1].connect(source2)
+
+    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.
+    """
+
+    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)]
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+        if source2 is not None:
+            self._input_ports[1].connect(source2)
+
+    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.
+    """
+
+    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)]
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+        if source2 is not None:
+            self._input_ports[1].connect(source2)
+
+    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)]
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+        if source2 is not None:
+            self._input_ports[1].connect(source2)
+
+    def evaluate(self, a, b):
+        return a / b
+
+    @property
+    def type_name(self) -> TypeName:
+        return "div"
+
+
+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 ComplexConjugate(AbstractOperation):
+    """Unary complex conjugate 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 conjugate(a)
+
+    @property
+    def type_name(self) -> TypeName:
+        return "conj"
+
+
+class Max(AbstractOperation):
+    """Binary max 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)]
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+        if source2 is not None:
+            self._input_ports[1].connect(source2)
+
+    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 Min(AbstractOperation):
+    """Binary min 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)]
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+        if source2 is not None:
+            self._input_ports[1].connect(source2)
+
+    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
+
+    @property
+    def type_name(self) -> TypeName:
+        return "min"
+
+
+class Absolute(AbstractOperation):
+    """Unary absolute value 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 np_abs(a)
+
+    @property
+    def type_name(self) -> TypeName:
+        return "abs"
+
+
+class ConstantMultiplication(AbstractOperation):
+    """Unary constant multiplication operation.
+    TODO: More info.
+    """
+
+    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
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+
+    def evaluate(self, a):
+        return a * self.param("coefficient")
+
+    @property
+    def type_name(self) -> TypeName:
+        return "cmul"
+
+
+class ConstantAddition(AbstractOperation):
+    """Unary constant addition operation.
+    TODO: More info.
+    """
+
+    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
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+
+    def evaluate(self, a):
+        return a + self.param("coefficient")
+
+    @property
+    def type_name(self) -> TypeName:
+        return "cadd"
+
+
+class ConstantSubtraction(AbstractOperation):
+    """Unary constant subtraction operation.
+    TODO: More info.
+    """
+
+    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
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+
+    def evaluate(self, a):
+        return a - self.param("coefficient")
+
+    @property
+    def type_name(self) -> TypeName:
+        return "csub"
+
+
+class ConstantDivision(AbstractOperation):
+    """Unary constant division operation.
+    TODO: More info.
+    """
+
+    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
+
+        if source1 is not None:
+            self._input_ports[0].connect(source1)
+
+    def evaluate(self, a):
+        return a / self.param("coefficient")
+
+    @property
+    def type_name(self) -> TypeName:
+        return "cdiv"
diff --git a/b_asic/graph_component.py b/b_asic/graph_component.py
new file mode 100644
index 0000000000000000000000000000000000000000..1987d4491e12089fec401eedc14fb91a7274252e
--- /dev/null
+++ b/b_asic/graph_component.py
@@ -0,0 +1,54 @@
+"""@package docstring
+B-ASIC Operation Module.
+TODO: More info.
+"""
+
+from abc import ABC, abstractmethod
+from typing import NewType
+
+Name = NewType("Name", str)
+TypeName = NewType("TypeName", str)
+
+
+class GraphComponent(ABC):
+    """Graph component interface.
+    TODO: More info.
+    """
+
+    @property
+    @abstractmethod
+    def type_name(self) -> TypeName:
+        """Return the type name of the graph component"""
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def name(self) -> Name:
+        """Return the name of the graph component."""
+        raise NotImplementedError
+
+    @name.setter
+    @abstractmethod
+    def name(self, name: Name) -> None:
+        """Set the name of the graph component to the entered name."""
+        raise NotImplementedError
+
+
+class AbstractGraphComponent(GraphComponent):
+    """Abstract Graph Component class which is a component of a signal flow graph.
+
+    TODO: More info.
+    """
+
+    _name: Name
+
+    def __init__(self, name: Name = ""):
+        self._name = name
+
+    @property
+    def name(self) -> Name:
+        return self._name
+
+    @name.setter
+    def name(self, name: Name) -> None:
+        self._name = name
diff --git a/b_asic/graph_id.py b/b_asic/graph_id.py
new file mode 100644
index 0000000000000000000000000000000000000000..8da6a9d4af6a1bee25125904527a2fd3a374ab90
--- /dev/null
+++ b/b_asic/graph_id.py
@@ -0,0 +1,26 @@
+"""@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
new file mode 100644
index 0000000000000000000000000000000000000000..5578e3c48edcf15594d6d1cd71e71a17521eca25
--- /dev/null
+++ b/b_asic/operation.py
@@ -0,0 +1,268 @@
+"""@package docstring
+B-ASIC Operation Module.
+TODO: More info.
+"""
+
+from abc import abstractmethod
+from numbers import Number
+from typing import List, Dict, Optional, Any, Set, TYPE_CHECKING
+from collections import deque
+
+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
+
+
+class Operation(GraphComponent):
+    """Operation interface.
+    TODO: More info.
+    """
+
+    @abstractmethod
+    def inputs(self) -> "List[InputPort]":
+        """Get a list of all input ports."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def outputs(self) -> "List[OutputPort]":
+        """Get a list of all output ports."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def input_count(self) -> int:
+        """Get the number of input ports."""
+        raise NotImplementedError
+
+    @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."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def output(self, i: int) -> "OutputPort":
+        """Get the output port at index i."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def params(self) -> Dict[str, Optional[Any]]:
+        """Get a dictionary of all parameter values."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def param(self, name: str) -> Optional[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.
+        The parameter must be defined.
+        """
+        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.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def split(self) -> "List[Operation]":
+        """Split the operation into multiple operations.
+        If splitting is not possible, this may return a list containing only the operation itself.
+        """
+        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.
+        """
+        raise NotImplementedError
+
+
+class AbstractOperation(Operation, AbstractGraphComponent):
+    """Generic abstract operation class which most implementations will derive from.
+    TODO: More info.
+    """
+
+    _input_ports: List["InputPort"]
+    _output_ports: List["OutputPort"]
+    _parameters: Dict[str, Optional[Any]]
+
+    def __init__(self, name: Name = ""):
+        super().__init__(name)
+        self._input_ports = []
+        self._output_ports = []
+        self._parameters = {}
+
+    @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.
+        """
+        raise NotImplementedError
+
+    def inputs(self) -> List["InputPort"]:
+        return self._input_ports.copy()
+
+    def outputs(self) -> List["OutputPort"]:
+        return self._output_ports.copy()
+
+    def input_count(self) -> int:
+        return len(self._input_ports)
+
+    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]
+
+    @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)
+
+        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
+
+        if isinstance(other, Operation):
+            return Addition(self.output(0), other.output(0))
+        elif isinstance(other, Number):
+            return ConstantAddition(other, self.output(0))
+        else:
+            raise TypeError("Other type is not an Operation or a Number.")
+
+    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 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 __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
+
+        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 __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.
+        """
+        # Import here to avoid circular imports.
+        from b_asic.core_operations import Division, ConstantDivision
+
+        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.")
+
diff --git a/b_asic/port.py b/b_asic/port.py
new file mode 100644
index 0000000000000000000000000000000000000000..c22053df1928cf2f6ea32c4880816d551b85836a
--- /dev/null
+++ b/b_asic/port.py
@@ -0,0 +1,223 @@
+"""@package docstring
+B-ASIC Port Module.
+TODO: More info.
+"""
+
+from abc import ABC, abstractmethod
+from typing import NewType, Optional, List
+
+from b_asic.operation import Operation
+from b_asic.signal import Signal
+
+PortIndex = NewType("PortIndex", int)
+
+class Port(ABC):
+    """Port Interface.
+
+    TODO: More documentaiton?
+    """
+
+    @property
+    @abstractmethod
+    def operation(self) -> Operation:
+        """Return the connected operation."""
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def index(self) -> PortIndex:
+        """Return the unique PortIndex."""
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def signals(self) -> List[Signal]:
+        """Return a list of all connected signals."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def signal(self, i: int = 0) -> Signal:
+        """Return the connected signal at index i.
+
+        Keyword argumens:
+        i: integer index of the signal requsted.
+        """
+        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
+
+    @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."""
+        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."""
+        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.
+
+        Keyword arguments:
+        - signal: Signal to remove.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def clear(self) -> None:
+        """Removes all connected signals from the Port."""
+        raise NotImplementedError
+
+
+class AbstractPort(Port):
+    """Abstract port class.
+
+    Handles functionality for port id and saves the connection to the parent operation.
+    """
+
+    _index: int
+    _operation: Operation
+
+    def __init__(self, index: int, operation: Operation):
+        self._index = index
+        self._operation = operation
+
+    @property
+    def operation(self) -> Operation:
+        return self._operation
+
+    @property
+    def index(self) -> PortIndex:
+        return self._index
+
+
+class InputPort(AbstractPort):
+    """Input port.
+    TODO: More info.
+    """
+
+    _source_signal: Optional[Signal]
+
+    def __init__(self, port_id: PortIndex, operation: Operation):
+        super().__init__(port_id, operation)
+        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
+
+    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()
+
+    def remove_signal(self, signal: Signal) -> None:
+        old_signal: Signal = self._source_signal
+        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()
+
+    def clear(self) -> None:
+        self.remove_signal(self._source_signal)
+
+class OutputPort(AbstractPort):
+    """Output port.
+    TODO: More info.
+    """
+
+    _destination_signals: List[Signal]
+
+    def __init__(self, port_id: PortIndex, operation: Operation):
+        super().__init__(port_id, operation)
+        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
+
+    def add_signal(self, signal: Signal) -> None:
+        assert signal not in self.signals, \
+                "Attempting to connect to Signal already connected."
+        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
+
+    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()
+
+    def clear(self) -> None:
+        for signal in self._destination_signals:
+            self.remove_signal(signal)
diff --git a/b_asic/precedence_chart.py b/b_asic/precedence_chart.py
new file mode 100644
index 0000000000000000000000000000000000000000..be55a123e0ab4330057c0bb62581e45195f5e5ba
--- /dev/null
+++ b/b_asic/precedence_chart.py
@@ -0,0 +1,21 @@
+"""@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/schema.py b/b_asic/schema.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5068cdc080c5c5004c44c885ac48f52ba44c1f3
--- /dev/null
+++ b/b_asic/schema.py
@@ -0,0 +1,21 @@
+"""@package docstring
+B-ASIC Schema Module.
+TODO: More info.
+"""
+
+from b_asic.precedence_chart import PrecedenceChart
+
+
+class Schema:
+    """Schema constructed from a precedence chart.
+    TODO: More info.
+    """
+
+    pc: PrecedenceChart
+    # TODO: More members.
+
+    def __init__(self, pc: PrecedenceChart):
+        self.pc = pc
+        # TODO: Implement.
+
+    # TODO: More stuff.
diff --git a/b_asic/signal.py b/b_asic/signal.py
new file mode 100644
index 0000000000000000000000000000000000000000..64c259486abd78e3b18ea824b58bfabe271f50d8
--- /dev/null
+++ b/b_asic/signal.py
@@ -0,0 +1,96 @@
+"""@package docstring
+B-ASIC Signal Module.
+"""
+from typing import Optional, TYPE_CHECKING
+
+from b_asic.graph_component import AbstractGraphComponent, TypeName, Name
+
+if TYPE_CHECKING:
+    from b_asic.port import InputPort, OutputPort
+
+
+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 = ""):
+
+        super().__init__(name)
+
+        self._source = source
+        self._destination = destination
+
+        if source is not None:
+            self.set_source(source)
+
+        if destination is not None:
+            self.set_destination(destination)
+
+    @property
+    def source(self) -> "OutputPort":
+        """Return the source OutputPort of the signal."""
+        return self._source
+
+    @property
+    def destination(self) -> "InputPort":
+        """Return the destination "InputPort" of the signal."""
+        return self._destination
+
+    def set_source(self, src: "OutputPort") -> None:
+        """Disconnect the previous source OutputPort of the signal and
+        connect to the entered source OutputPort. Also connect the entered
+        source port to the signal if it hasn't already been connected.
+
+        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)
+
+    def set_destination(self, dest: "InputPort") -> None:
+        """Disconnect the previous destination InputPort of the signal and
+        connect to the entered destination InputPort. Also connect the entered
+        destination port to the signal if it hasn't already been connected.
+
+        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"
+
+    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
+            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)
+
+    def remove_destination(self) -> None:
+        """Disconnect the destination InputPort of the signal."""
+        if self._destination is not None:
+            old_destination: "InputPort" = self._destination
+            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)
+
+    def is_connected(self) -> bool:
+        """Returns true if the signal is connected to both a source and a destination,
+        else false."""
+        return self._source is not None and self._destination is not None
diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c08aecc40ff77b8fe90051b6ea165c0f1703b9b
--- /dev/null
+++ b/b_asic/signal_flow_graph.py
@@ -0,0 +1,91 @@
+"""@package docstring
+B-ASIC Signal Flow Graph Module.
+TODO: More info.
+"""
+
+from typing import List, Dict, Optional, DefaultDict
+from collections import defaultdict
+
+from b_asic.operation import Operation
+from b_asic.operation import AbstractOperation
+from b_asic.signal import Signal
+from b_asic.graph_id import GraphIDGenerator, GraphID
+from b_asic.graph_component import GraphComponent, Name, TypeName
+
+
+class SFG(AbstractOperation):
+    """Signal flow graph.
+    TODO: More info.
+    """
+
+    _graph_components_by_id: Dict[GraphID, GraphComponent]
+    _graph_components_by_name: DefaultDict[Name, List[GraphComponent]]
+    _graph_id_generator: GraphIDGenerator
+
+    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 = []
+
+        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()
+
+        for operation in ops:
+            self._add_graph_component(operation)
+
+        for input_signal in input_signals:
+            self._add_graph_component(input_signal)
+
+        # 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.
+
+    def evaluate(self, *inputs) -> list:
+        return [] # TODO: Implement
+
+    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.
+
+        Keyword arguments:
+        graph_component: Graph component to add to the graph.
+        """
+        # Add to name dict
+        self._graph_components_by_name[graph_component.name].append(graph_component)
+
+        # 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
+
+    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.
+
+        Keyword arguments:
+        graph_id: Graph ID of the wanted object.
+        """
+        if graph_id in self._graph_components_by_id:
+            return self._graph_components_by_id[graph_id]
+
+        return None
+
+    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 wanted object.
+        """
+        return self._graph_components_by_name[name]
+
+    @property
+    def type_name(self) -> TypeName:
+        return "sfg"
diff --git a/b_asic/simulation.py b/b_asic/simulation.py
new file mode 100644
index 0000000000000000000000000000000000000000..50adaa522b6d685b428354a9f84689330b7fd40f
--- /dev/null
+++ b/b_asic/simulation.py
@@ -0,0 +1,35 @@
+"""@package docstring
+B-ASIC Simulation Module.
+TODO: More info.
+"""
+
+from numbers import Number
+from typing import List
+
+
+class OperationState:
+    """Simulation state of an operation.
+    TODO: More info.
+    """
+
+    output_values: List[Number]
+    iteration: int
+
+    def __init__(self):
+        self.output_values = []
+        self.iteration = 0
+
+
+class SimulationState:
+    """Simulation state.
+    TODO: More info.
+    """
+
+    # operation_states: Dict[OperationId, OperationState]
+    iteration: int
+
+    def __init__(self):
+        self.operation_states = {}
+        self.iteration = 0
+
+    # TODO: More stuff.
diff --git a/build.sh b/build.sh
deleted file mode 100644
index 1015ced245f71f0f85cad0da8ae562acb0d7a550..0000000000000000000000000000000000000000
--- a/build.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#! bin/sh
-for n in `find . -name "*.py"`
-do
-  python3 $n
-done
\ No newline at end of file
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..b1bd78fe7eb8a737ac8153576078d61492c2e9a3
Binary files /dev/null and b/logo.png differ
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..43d55d40a95212196facb973ebc97a1bdc5e7f42
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,80 @@
+import os
+import sys
+import shutil
+import subprocess
+import setuptools
+from setuptools import Extension
+from setuptools.command.build_ext import build_ext
+
+CMAKE_EXE = os.environ.get('CMAKE_EXE', shutil.which('cmake'))
+
+class CMakeExtension(Extension):
+    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.")
+
+        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_configure_argv = [
+            CMAKE_EXE, ext.sourcedir,
+            "-DCMAKE_BUILD_TYPE=" + cmake_build_type,
+            "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + cmake_output_dir,
+            "-DPYTHON_EXECUTABLE=" + sys.executable,
+        ]
+        cmake_build_argv = [
+            CMAKE_EXE, "--build", ".",
+            "--config", cmake_build_type
+        ]
+
+        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)
+
+        print(f"=== Building {ext.name} ===")
+        print(f"Temp dir: {self.build_temp}")
+        print(f"Output dir: {cmake_output_dir}")
+        print(f"Build type: {cmake_build_type}")
+        subprocess.check_call(cmake_build_argv, cwd=self.build_temp, env=env)
+
+        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 = [
+        "Programming Language :: Python :: 3",
+        "License :: OSI Approved :: MIT License",
+        "Operating System :: OS Independent",
+    ],
+    python_requires = ">=3.6",
+    install_requires = [
+        "pybind11>=2.3.0",
+        "numpy",
+        "install_qt_binding"
+    ],
+    packages = ["b_asic"],
+    ext_modules = [CMakeExtension("b_asic")],
+    cmdclass = {"build_ext": CMakeBuild},
+    zip_safe = False
+)
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..75a77ef58b86cd29238205a078cec780a6ba9a36
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,21 @@
+#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"));
+}
\ No newline at end of file
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/conftest.py b/test/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..64f39843c53a4369781a269fd7fc30ad9aa1d255
--- /dev/null
+++ b/test/conftest.py
@@ -0,0 +1,4 @@
+from test.fixtures.signal import signal, signals
+from test.fixtures.operation_tree import *
+from test.fixtures.port import *
+import pytest
diff --git a/test/fixtures/operation_tree.py b/test/fixtures/operation_tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..df3fcac35cc495d14bed06ccdfc2a3ebed25616e
--- /dev/null
+++ b/test/fixtures/operation_tree.py
@@ -0,0 +1,58 @@
+from b_asic.core_operations import Addition, Constant
+from b_asic.signal import Signal
+
+import pytest
+
+@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---+
+    """
+    add_oper = Addition()
+    create_operation(Constant, add_oper, 0, value=2)
+    create_operation(Constant, add_oper, 1, value=3)
+    return add_oper
+
+@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---+
+    """
+    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)
+
+    create_operation(Constant, add_oper_2, 0, value=4)
+    create_operation(Constant, add_oper_2, 1, value=5)
+
+    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)
+
+    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
diff --git a/test/fixtures/port.py b/test/fixtures/port.py
new file mode 100644
index 0000000000000000000000000000000000000000..4019b3a2016aa418daeca771f9a2d8bcc4ca6652
--- /dev/null
+++ b/test/fixtures/port.py
@@ -0,0 +1,10 @@
+import pytest
+from b_asic.port import InputPort, OutputPort
+
+@pytest.fixture
+def input_port():
+    return InputPort(0, None)
+
+@pytest.fixture
+def output_port():
+    return OutputPort(0, None)
diff --git a/test/fixtures/signal.py b/test/fixtures/signal.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b13c9789f94c33338d08b48373391398bd9f71d
--- /dev/null
+++ b/test/fixtures/signal.py
@@ -0,0 +1,12 @@
+import pytest
+from b_asic import Signal
+
+@pytest.fixture
+def signal():
+    """Return a signal with no connections."""
+    return Signal()
+
+@pytest.fixture
+def signals():
+    """Return 3 signals with no connections."""
+    return [Signal() for _ in range(0,3)]
diff --git a/test/operation/test_abstract_operation.py b/test/operation/test_abstract_operation.py
new file mode 100644
index 0000000000000000000000000000000000000000..626a2dc3e5e26fb76d9266dcdd31940681df5c6e
--- /dev/null
+++ b/test/operation/test_abstract_operation.py
@@ -0,0 +1,77 @@
+"""
+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
new file mode 100644
index 0000000000000000000000000000000000000000..b176b2a6506cc5a1297813f6ddcb6d3589492838
--- /dev/null
+++ b/test/test_core_operations.py
@@ -0,0 +1,227 @@
+"""
+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)
diff --git a/test/test_graph_id_generator.py b/test/test_graph_id_generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..b14597eabe6c15695c5c452f69f3deeab56e36d5
--- /dev/null
+++ b/test/test_graph_id_generator.py
@@ -0,0 +1,28 @@
+"""
+B-ASIC test suite for graph id generator.
+"""
+
+from b_asic.graph_id import GraphIDGenerator, GraphID
+import pytest
+
+@pytest.fixture
+def graph_id_generator():
+    return GraphIDGenerator()
+
+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"
+
+    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"
+
+    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"
diff --git a/test/test_inputport.py b/test/test_inputport.py
new file mode 100644
index 0000000000000000000000000000000000000000..a43240693ac632b48461023536ff46b0ea379c5c
--- /dev/null
+++ b/test/test_inputport.py
@@ -0,0 +1,95 @@
+"""
+B-ASIC test suite for Inputport
+"""
+
+import pytest
+
+from b_asic import InputPort, OutputPort
+from b_asic import 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)
+
+@pytest.fixture
+def dangling_sig():
+    return Signal()
+
+@pytest.fixture
+def s_w_source():
+    out_port = OutputPort(0, None)
+    return Signal(source=out_port)
+
+@pytest.fixture
+def sig_with_dest():
+    inp_port = InputPort(0, None)
+    return Signal(destination=out_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 test_connect_then_disconnect(inp_port, out_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
+    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_add_signal_then_disconnect(inp_port, s_w_source):
+    """Can signal be connected then disconnected properly?"""
+    inp_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 s_w_source.source.signals == [s_w_source]
+    assert s_w_source.destination is inp_port
+
+    inp_port.remove_signal(s_w_source)
+
+    assert inp_port.connected_ports == []
+    assert s_w_source.source.connected_ports == []
+    assert inp_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
new file mode 100644
index 0000000000000000000000000000000000000000..6c37e30bddd0b55ea69ae5b95a341c1ddeb56847
--- /dev/null
+++ b/test/test_operation.py
@@ -0,0 +1,31 @@
+from b_asic.core_operations import Constant, Addition
+from b_asic.signal import Signal
+from b_asic.port import InputPort, OutputPort
+
+import pytest
+
+class TestTraverse:
+    def test_traverse_single_tree(self, operation):
+        """Traverse a tree consisting of one operation."""
+        constant = Constant(None)
+        assert list(constant.traverse()) == [constant]
+
+    def test_traverse_tree(self, operation_tree):
+        """Traverse a basic addition tree with two constants."""
+        assert len(list(operation_tree.traverse())) == 3
+
+    def test_traverse_large_tree(self, large_operation_tree):
+        """Traverse a larger tree."""
+        assert len(list(large_operation_tree.traverse())) == 7
+
+    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
diff --git a/test/test_outputport.py b/test/test_outputport.py
new file mode 100644
index 0000000000000000000000000000000000000000..deed7a1e06836600254e3903b8b45a3d05f17cbe
--- /dev/null
+++ b/test/test_outputport.py
@@ -0,0 +1,80 @@
+"""
+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)]
+
+class TestConnect:
+    def test_multiple_ports(self, output_port, list_of_input_ports):
+        """Can multiple ports connect to an output port?"""
+        for port in list_of_input_ports:
+            output_port.connect(port)
+
+        assert output_port.signal_count() == len(list_of_input_ports)
+
+    def test_same_port(self, output_port, list_of_input_ports):
+        """Check error handing."""
+        output_port.connect(list_of_input_ports[0])
+        with pytest.raises(AssertionError):
+            output_port.connect(list_of_input_ports[0])
+
+        assert output_port.signal_count() == 2
+
+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.connected_ports == [s.destination]
+
+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)
+
+        for port in list_of_input_ports:
+            output_port.disconnect(port)
+
+        assert output_port.signal_count() == 3
+        assert output_port.connected_ports == []
+
+class TestRemoveSignal:
+    def test_one_signal(self, output_port, input_port):
+        s = output_port.connect(input_port)
+        output_port.remove_signal(s)
+
+        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))
+
+        for sig in sigs:
+            output_port.remove_signal(sig)
+
+        assert output_port.signal_count() == 0
+        assert output_port.signals == []
diff --git a/test/test_signal.py b/test/test_signal.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab07eb778ddb693bfc9cfabf6aeb7804038312d5
--- /dev/null
+++ b/test/test_signal.py
@@ -0,0 +1,62 @@
+"""
+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
+
+def test_signal_creation_and_disconnction_and_connection_changing():
+    in_port = InputPort(0, None)
+    out_port = OutputPort(1, None)
+    s = Signal(out_port, in_port)
+
+    assert in_port.signals == [s]
+    assert out_port.signals == [s]
+    assert s.source is out_port
+    assert s.destination is in_port
+
+    in_port1 = InputPort(0, None)
+    s.set_destination(in_port1)
+
+    assert in_port.signals == []
+    assert in_port1.signals == [s]
+    assert out_port.signals == [s]
+    assert s.source is out_port
+    assert s.destination is in_port1
+
+    s.remove_source()
+
+    assert out_port.signals == []
+    assert in_port1.signals == [s]
+    assert s.source is None
+    assert s.destination is in_port1
+
+    s.remove_destination()
+
+    assert out_port.signals == []
+    assert in_port1.signals == []
+    assert s.source is None
+    assert s.destination is None
+
+    out_port1 = OutputPort(0, None)
+    s.set_source(out_port1)
+
+    assert out_port1.signals == [s]
+    assert s.source is out_port1
+    assert s.destination is None
+
+    s.set_source(out_port)
+
+    assert out_port.signals == [s]
+    assert out_port1.signals == []
+    assert s.source is out_port
+    assert s.destination is None
+
+    s.set_destination(in_port)
+
+    assert out_port.signals == [s]
+    assert in_port.signals == [s]
+    assert s.source is out_port
+    assert s.destination is in_port