diff --git a/README.md b/README.md
index b2972f30828f19038e5efe612831f989b8f5ffd5..d28399ce800d0240881c14405c892eb65d23b072 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,15 @@ To run the test suite, the following additional packages are required:
   * pytest
   * pytest-cov (for testing with coverage)
 
+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)
+
 ### Using CMake directly
 How to build using CMake.
 
diff --git a/b_asic/__init__.py b/b_asic/__init__.py
index b35d0c1be97e5dc2758e8647147e918916103d40..8dc848b23ed11edf63d8ff88c3aa2e553a32e095 100644
--- a/b_asic/__init__.py
+++ b/b_asic/__init__.py
@@ -10,3 +10,4 @@ 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.schema import *
diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py
index a70c86b7d966197850604ad7a269242d364eca36..9a11d91943e9621e43daa3c88ddfa67d3441060c 100644
--- a/b_asic/core_operations.py
+++ b/b_asic/core_operations.py
@@ -4,7 +4,7 @@ TODO: More info.
 """
 
 from numbers import Number
-from typing import Optional
+from typing import Optional, Dict
 from numpy import conjugate, sqrt, abs as np_abs
 
 from b_asic.port import SignalSourceProvider, InputPort, OutputPort
@@ -44,9 +44,9 @@ class Addition(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=2, output_count=1,
-                         name=name, input_sources=[src0, src1])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -61,9 +61,9 @@ class Subtraction(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=2, output_count=1,
-                         name=name, input_sources=[src0, src1])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -78,9 +78,9 @@ class Multiplication(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=2, output_count=1,
-                         name=name, input_sources=[src0, src1])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -95,9 +95,9 @@ class Division(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=2, output_count=1,
-                         name=name, input_sources=[src0, src1])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -112,9 +112,9 @@ class Min(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=2, output_count=1,
-                         name=name, input_sources=[src0, src1])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -131,9 +131,9 @@ class Max(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=2, output_count=1,
-                         name=name, input_sources=[src0, src1])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -150,9 +150,9 @@ class SquareRoot(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=1, output_count=1,
-                         name=name, input_sources=[src0])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -167,9 +167,9 @@ class ComplexConjugate(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=1, output_count=1,
-                         name=name, input_sources=[src0])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -184,9 +184,9 @@ class Absolute(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=1, output_count=1,
-                         name=name, input_sources=[src0])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -201,9 +201,9 @@ class ConstantMultiplication(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, value: Number = 0, src0: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=1, output_count=1,
-                         name=name, input_sources=[src0])
+    def __init__(self, value: Number = 0, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
+                         latency=latency, latency_offsets=latency_offsets)
         self.set_param("value", value)
 
     @classmethod
@@ -230,9 +230,9 @@ class Butterfly(AbstractOperation):
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count=2, output_count=2,
-                         name=name, input_sources=[src0, src1])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=2, output_count=2, name=name, input_sources=[src0, src1],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
@@ -241,13 +241,15 @@ class Butterfly(AbstractOperation):
     def evaluate(self, a, b):
         return a + b, a - b
 
+
 class MAD(AbstractOperation):
     """Multiply-and-add operation.
     TODO: More info.
     """
 
-    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, src2: Optional[SignalSourceProvider] = None, name: Name = ""):
-        super().__init__(input_count = 3, output_count = 1, name = name, input_sources = [src0, src1, src2])
+    def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, src2: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
+        super().__init__(input_count=3, output_count=1, name=name, input_sources=[src0, src1, src2],
+                         latency=latency, latency_offsets=latency_offsets)
 
     @classmethod
     def type_name(cls) -> TypeName:
diff --git a/b_asic/operation.py b/b_asic/operation.py
index 02ba1aa50682448e931a0694d24e03e20eadd399..d1a820fbe49592e5545ff5624dc5fc121c77b71f 100644
--- a/b_asic/operation.py
+++ b/b_asic/operation.py
@@ -3,16 +3,17 @@ B-ASIC Operation Module.
 TODO: More info.
 """
 
+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 NewType, List, Sequence, Iterable, Mapping, MutableMapping, Optional, Any, Set, Union
-from math import trunc
+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.port import SignalSourceProvider, InputPort, OutputPort
-from b_asic.signal import Signal
 
 OutputKey = NewType("OutputKey", str)
 OutputMap = Mapping[OutputKey, Optional[Number]]
@@ -193,6 +194,39 @@ class Operation(GraphComponent, SignalSourceProvider):
         """
         raise NotImplementedError
 
+    @property
+    @abstractmethod
+    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.
@@ -202,7 +236,7 @@ class AbstractOperation(Operation, AbstractGraphComponent):
     _input_ports: List[InputPort]
     _output_ports: List[OutputPort]
 
-    def __init__(self, input_count: int, output_count: int, name: Name = "", input_sources: Optional[Sequence[Optional[SignalSourceProvider]]] = None):
+    def __init__(self, input_count: int, output_count: int, name: Name = "", input_sources: Optional[Sequence[Optional[SignalSourceProvider]]] = None, latency: int = None, latency_offsets: Dict[str, int] = None):
         super().__init__(name)
 
         self._input_ports = [InputPort(self, i) for i in range(input_count)]
@@ -218,6 +252,22 @@ class AbstractOperation(Operation, AbstractGraphComponent):
                 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."""
@@ -433,6 +483,14 @@ class AbstractOperation(Operation, AbstractGraphComponent):
 
         return SFG(inputs=inputs, outputs=outputs)
 
+    def copy_component(self, *args, **kwargs) -> Operation:
+        new_component = 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})")
@@ -483,3 +541,32 @@ class AbstractOperation(Operation, AbstractGraphComponent):
             else:
                 args.append(input_values[i])
         return args
+
+    @property
+    def latency(self) -> int:
+        return max(((outp.latency_offset - inp.latency_offset) for outp, inp in it.product(self.outputs, self.inputs)))
+
+    def set_latency(self, latency: int) -> None:
+        assert latency >= 0, "Negative latency entered."
+        for inport in self.inputs:
+            inport.latency_offset = 0
+        for outport in self.outputs:
+            outport.latency_offset = latency
+
+    @property
+    def latency_offsets(self) -> Sequence[Sequence[int]]:
+        return ([inp.latency_offset for inp in self.inputs], [outp.latency_offset for outp in self.outputs])
+
+    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 20783d5df0962b034aee2b6e934255a9fc9cd6e6..fb3f64177e74f2623d02284243a03529cf05b6e4 100644
--- a/b_asic/port.py
+++ b/b_asic/port.py
@@ -32,6 +32,18 @@ class Port(ABC):
         """Return the index of the port."""
         raise NotImplementedError
 
+    @property
+    @abstractmethod
+    def latency_offset(self) -> int:
+        """Get the latency_offset of the port."""
+        raise NotImplementedError
+
+    @latency_offset.setter
+    @abstractmethod
+    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 signal_count(self) -> int:
@@ -76,10 +88,12 @@ class AbstractPort(Port):
 
     _operation: "Operation"
     _index: int
+    _latency_offset: Optional[int]
 
-    def __init__(self, operation: "Operation", index: int):
+    def __init__(self, operation: "Operation", index: int, latency_offset: int = None):
         self._operation = operation
         self._index = index
+        self._latency_offset = latency_offset
 
     @property
     def operation(self) -> "Operation":
@@ -89,6 +103,14 @@ class AbstractPort(Port):
     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.
diff --git a/b_asic/schema.py b/b_asic/schema.py
new file mode 100644
index 0000000000000000000000000000000000000000..157ed3b8ec57a35ffe66f1de476e2d77c3e28abd
--- /dev/null
+++ b/b_asic/schema.py
@@ -0,0 +1,107 @@
+"""@package docstring
+This module contains the Schema class.
+TODO: More info
+"""
+
+from typing import Dict, List
+
+from b_asic.signal_flow_graph import SFG
+from b_asic.graph_component import GraphID
+from b_asic.operation import Operation
+
+
+class Schema:
+    """A class that represents 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: int = None, cyclic: bool = False, resolution: int = 1, scheduling_alg: str = "ASAP"):
+
+        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):
+        """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):
+        raise NotImplementedError
+
+    def backward_slack(self, op_id):
+        raise NotImplementedError
+
+    def print_slacks(self):
+        raise NotImplementedError
+
+    def _schedule_asap(self):
+        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."
+
+                            source_end_time = source_op_time + source_port.latency_offset
+
+                        op_start_time_from_in = source_end_time - inport.latency_offset
+                        op_start_time = max(op_start_time, op_start_time_from_in)
+
+                    self._start_times[op.graph_id] = op_start_time
diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py
index d51f13b4209fd1d5fc87e369b2f23dc8bf69301b..1a0cd8e7cee0f1ae8798233348a7b45e12f7e74f 100644
--- a/b_asic/signal_flow_graph.py
+++ b/b_asic/signal_flow_graph.py
@@ -8,7 +8,7 @@ from numbers import Number
 from collections import defaultdict, deque
 from io import StringIO
 from queue import PriorityQueue
-import itertools
+import itertools as it
 from graphviz import Digraph
 
 from b_asic.port import SignalSourceProvider, OutputPort
@@ -612,7 +612,7 @@ class SFG(AbstractOperation):
     def show_precedence_graph(self) -> None:
         p_list = self.get_precedence_list()
         pg = Digraph()
-        pg.attr(rankdir = 'LR')
+        pg.attr(rankdir='LR')
 
         # Creates nodes for each output port in the precedence list
         for i in range(len(p_list)):
@@ -627,11 +627,11 @@ class SFG(AbstractOperation):
             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.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.node(port.operation.graph_id, shape='square')
 
-        pg.view()  
+        pg.view()
 
     def print_precedence_graph(self) -> None:
         """Prints a representation of the SFG's precedence list to the standard out.
@@ -682,14 +682,18 @@ class SFG(AbstractOperation):
         first_op = no_inputs_queue.popleft()
         visited = set([first_op])
         p_queue = PriorityQueue()
-        p_queue.put((-first_op.output_count, first_op))  # Negative priority as max-heap popping is wanted
+        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()[1]
+
+                op = p_queue.get()[2]
 
                 operations_left -= 1
                 top_order.append(op)
@@ -701,7 +705,7 @@ class SFG(AbstractOperation):
                         remaining_inports = remaining_inports_per_operation[neighbor_op]
 
                         if remaining_inports == 0:
-                            p_queue.put((-neighbor_op.output_count, neighbor_op))
+                            p_queue.put((-neighbor_op.output_count, -next(p_queue_entry_num), neighbor_op))
 
                         elif remaining_inports > 0:
                             if neighbor_op in seen:
@@ -717,15 +721,15 @@ class SFG(AbstractOperation):
                 # 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, new_op))
+                    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 itertools.count(start=1):
+                    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, new_op))
+                            p_queue.put((-new_op.output_count, -next(p_queue_entry_num), new_op))
                             seen_but_not_visited_count -= 1
                             break
                 else:
@@ -734,3 +738,11 @@ class SFG(AbstractOperation):
         self._operations_topological_order = top_order
 
         return self._operations_topological_order
+
+    def set_latency_of_type(self, type_name: TypeName, latency: int):
+        for op in self.get_components_with_type_name(type_name):
+            op.set_latency(latency)
+
+    def set_latency_offsets_of_type(self, type_name: TypeName, latency_offsets: Dict[str, int]):
+        for op in self.get_components_with_type_name(type_name):
+            op.set_latency_offsets(latency_offsets)
diff --git a/b_asic/special_operations.py b/b_asic/special_operations.py
index 0a256bc86c7d5e24582a54d95b7d3afbef1ad941..14440eb098e7100e5399f3ec023b17bcab4ed458 100644
--- a/b_asic/special_operations.py
+++ b/b_asic/special_operations.py
@@ -6,7 +6,7 @@ TODO: More info.
 from numbers import Number
 from typing import Optional, Sequence
 
-from b_asic.operation import AbstractOperation, OutputKey, RegisterMap, MutableOutputMap, MutableRegisterMap
+from b_asic.operation import AbstractOperation, RegisterMap, MutableOutputMap, MutableRegisterMap
 from b_asic.graph_component import Name, TypeName
 from b_asic.port import SignalSourceProvider
 
diff --git a/test/fixtures/signal_flow_graph.py b/test/fixtures/signal_flow_graph.py
index 08d9e8aa2bacd0b1c1a11c17c174179d853e6ed7..6cf434dbfa5fbad4380e4b4d05ef07b9a02e30ec 100644
--- a/test/fixtures/signal_flow_graph.py
+++ b/test/fixtures/signal_flow_graph.py
@@ -21,12 +21,12 @@ def sfg_two_inputs_two_outputs():
     out1 = in1 + in2
         out2 = in1 + 2 * in2
     """
-    in1 = Input()
-    in2 = Input()
-    add1 = in1 + in2
-    add2 = add1 + in2
-    out1 = Output(add1)
-    out2 = Output(add2)
+    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])
 
 
@@ -58,6 +58,31 @@ def sfg_two_inputs_two_outputs_independent():
     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(in1, "OUT1")
+    out2 = Output(add1, "OUT2")
+    return SFG(inputs=[in1, in2], outputs=[out1, out2])
+
+
 @pytest.fixture
 def sfg_nested():
     """Valid SFG with two inputs and one output.
diff --git a/test/test_abstract_operation.py b/test/test_abstract_operation.py
deleted file mode 100644
index 9163fce2a955c7fbc68d5d24de86896d251934da..0000000000000000000000000000000000000000
--- a/test/test_abstract_operation.py
+++ /dev/null
@@ -1,91 +0,0 @@
-"""
-B-ASIC test suite for the AbstractOperation class.
-"""
-
-import pytest
-
-from b_asic import Addition, Subtraction, Multiplication, ConstantMultiplication, Division
-
-
-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, 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():
-    """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():
-    """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():
-    """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
diff --git a/test/test_operation.py b/test/test_operation.py
index 77e9ba3cbd0eaa75886b5a7e5d11f00f6cfeb479..52ae8c53457994642c505f722655b47551862ba5 100644
--- a/test/test_operation.py
+++ b/test/test_operation.py
@@ -1,6 +1,94 @@
+"""
+B-ASIC test suite for the AbstractOperation class.
+"""
+
 import pytest
 
-from b_asic import Constant, Addition, MAD, Butterfly, SquareRoot
+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):
@@ -24,20 +112,21 @@ class TestTraverse:
     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 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 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):
@@ -51,3 +140,47 @@ class TestToSfg:
         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 list(bfly.latency_offsets) == [[0, 0], [5, 5]]
+
+    def test_latency_offsets_constructor(self):
+        bfly = Butterfly(latency_offsets={'in0': 2, 'in1': 3, 'out0': 5, 'out1': 10})
+
+        assert bfly.latency == 8
+        assert list(bfly.latency_offsets) == [[2, 3], [5, 10]]
+
+    def test_latency_and_latency_offsets_constructor(self):
+        bfly = Butterfly(latency=5, latency_offsets={'in1': 2, 'out0': 9})
+
+        assert bfly.latency == 9
+        assert list(bfly.latency_offsets) == [[0, 2], [9, 5]]
+
+    def test_set_latency(self):
+        bfly = Butterfly()
+
+        bfly.set_latency(9)
+
+        assert bfly.latency == 9
+        assert list(bfly.latency_offsets) == [[0, 0], [9, 9]]
+
+    def test_set_latency_offsets(self):
+        bfly = Butterfly()
+
+        bfly.set_latency_offsets({'in0': 3, 'out1': 5})
+
+        assert list(bfly.latency_offsets) == [[3, None], [None, 5]]
+
+
+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 list(bfly_copy.latency_offsets) == [[4, 2], [10, 9]]
diff --git a/test/test_schema.py b/test/test_schema.py
new file mode 100644
index 0000000000000000000000000000000000000000..510a0d997cb6cbdb525d600f992cbbae3468e8dd
--- /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, simple_filter):
+        simple_filter.set_latency_of_type(Addition.type_name(), 5)
+        simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 4)
+
+        schema = Schema(simple_filter)
+
+        assert schema._start_times == {"add1": 4, "cmul1": 0}
+
+    def test_complicated_single_outputs_normal_latency(self, precedence_sfg_registers):
+        precedence_sfg_registers.set_latency_of_type(Addition.type_name(), 4)
+        precedence_sfg_registers.set_latency_of_type(ConstantMultiplication.type_name(), 3)
+
+        schema = Schema(precedence_sfg_registers, 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_registers.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_registers):
+        precedence_sfg_registers.set_latency_offsets_of_type(ConstantMultiplication.type_name(), {'in0': 3, 'out0': 5})
+
+        precedence_sfg_registers.find_by_name("B1")[0].set_latency_offsets({'in0': 4, 'out0': 7})
+        precedence_sfg_registers.find_by_name("B2")[0].set_latency_offsets({'in0': 1, 'out0': 4})
+        precedence_sfg_registers.find_by_name("ADD2")[0].set_latency_offsets({'in0': 4, 'in1': 2, 'out0': 4})
+        precedence_sfg_registers.find_by_name("ADD1")[0].set_latency_offsets({'in0': 1, 'in1': 2, 'out0': 4})
+        precedence_sfg_registers.find_by_name("Q1")[0].set_latency_offsets({'in0': 3, 'out0': 6})
+        precedence_sfg_registers.find_by_name("A0")[0].set_latency_offsets({'in0': 0, 'out0': 2})
+
+        precedence_sfg_registers.find_by_name("A1")[0].set_latency_offsets({'in0': 0, 'out0': 5})
+        precedence_sfg_registers.find_by_name("A2")[0].set_latency_offsets({'in0': 2, 'out0': 3})
+        precedence_sfg_registers.find_by_name("ADD3")[0].set_latency_offsets({'in0': 2, 'in1': 1, 'out0': 4})
+        precedence_sfg_registers.find_by_name("ADD4")[0].set_latency_offsets({'in0': 6, 'in1': 7, 'out0': 9})
+
+        schema = Schema(precedence_sfg_registers, scheduling_alg="ASAP")
+
+        start_times_names = dict()
+        for op_id, start_time in schema._start_times.items():
+            op_name = precedence_sfg_registers.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
index a27b404e8d2ac0eaf6b4146ed497e2f06b5973cf..5a13e151dbb682c60f732cfc8399da4ea1e3a34b 100644
--- a/test/test_sfg.py
+++ b/test/test_sfg.py
@@ -691,12 +691,13 @@ class TestConnectExternalSignalsToComponentsMultipleComp:
         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, simple_filter):
         topological_order = simple_filter.get_operations_topological_order()
@@ -708,6 +709,12 @@ class TestTopologicalOrderOperations:
 
         assert [comp.name for comp in topological_order] == ["IN1", "OUT1", "IN2", "C1", "ADD1", "OUT2"]
 
+    def test_complex_graph(self, precedence_sfg_registers):
+        topological_order = precedence_sfg_registers.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, simple_filter):
@@ -776,3 +783,19 @@ class TestRemove:
     def remove_different_number_inputs_outputs(self, simple_filter):
         with pytest.raises(ValueError):
             simple_filter.remove_operation("add1")
+
+
+class TestGetComponentsOfType:
+    def test_get_no_operations_of_type(self, sfg_two_inputs_two_outputs):
+        assert [op.name for op in sfg_two_inputs_two_outputs.get_components_with_type_name(Multiplication.type_name())] \
+            == []
+
+    def test_get_multple_operations_of_type(self, sfg_two_inputs_two_outputs):
+        assert [op.name for op in sfg_two_inputs_two_outputs.get_components_with_type_name(Addition.type_name())] \
+            == ["ADD1", "ADD2"]
+
+        assert [op.name for op in sfg_two_inputs_two_outputs.get_components_with_type_name(Input.type_name())] \
+            == ["IN1", "IN2"]
+
+        assert [op.name for op in sfg_two_inputs_two_outputs.get_components_with_type_name(Output.type_name())] \
+            == ["OUT1", "OUT2"]