diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8c3439a7392b11b6f1a37b58e0f9ffdee5fe5091..4a822db9af4dcb4c6ef6615ad8caf6e22d2e4728 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -23,7 +23,7 @@ before_script:
 .run-test:
   stage: test
   script:
-    - pytest --cov=b_asic --cov-report=xml:cov.xml --cov-report=term --cov-branch --color=yes test --timeout=20 --durations=10
+    - pytest --cov=b_asic --cov-report=xml:cov.xml --cov-report=term --cov-branch --color=yes test --timeout=30 --durations=10
     # - lcov --capture --directory . --output-file coverage.info
     # - lcov --output-file coverage.info --extract coverage.info $PWD/src/'*' $PWD/b_asic/'*'
     # - lcov --list coverage.info
diff --git a/b_asic/architecture.py b/b_asic/architecture.py
index f55f7d8b08ec2931cc2327c121bdc1302aaf0c9e..6e10d0f660f7f24be9ef17528b10e514d78e3b84 100644
--- a/b_asic/architecture.py
+++ b/b_asic/architecture.py
@@ -396,6 +396,13 @@ class ProcessingElement(Resource):
         assign: bool = True,
     ):
         super().__init__(process_collection=process_collection, entity_name=entity_name)
+
+        if not isinstance(process_collection, ProcessCollection):
+            raise TypeError(
+                "Argument process_collection must be ProcessCollection, "
+                f"not {type(process_collection)}"
+            )
+
         if not all(
             isinstance(operator, OperatorProcess)
             for operator in process_collection.collection
diff --git a/b_asic/core_schedulers.py b/b_asic/core_schedulers.py
index 32cb23db9244ba15bcc51f5a3592e5ec3468795d..cce720e39e83b6c3ca74fd82af71cd815e81a049 100644
--- a/b_asic/core_schedulers.py
+++ b/b_asic/core_schedulers.py
@@ -124,8 +124,7 @@ class EarliestDeadlineScheduler(ListScheduler):
 
         deadlines = {}
         for op_id, start_time in schedule_copy.start_times.items():
-            if not op_id.startswith("in"):
-                deadlines[op_id] = start_time + schedule.sfg.find_by_id(op_id).latency
+            deadlines[op_id] = start_time + schedule.sfg.find_by_id(op_id).latency
 
         return sorted(deadlines, key=deadlines.get)
 
@@ -141,7 +140,7 @@ class LeastSlackTimeScheduler(ListScheduler):
         sorted_ops = sorted(
             schedule_copy.start_times, key=schedule_copy.start_times.get
         )
-        return [op for op in sorted_ops if not op.startswith("in")]
+        return sorted_ops
 
 
 class MaxFanOutScheduler(ListScheduler):
@@ -157,7 +156,7 @@ class MaxFanOutScheduler(ListScheduler):
             fan_outs[op_id] = len(schedule.sfg.find_by_id(op_id).output_signals)
 
         sorted_ops = sorted(fan_outs, key=fan_outs.get, reverse=True)
-        return [op for op in sorted_ops if not op.startswith("in")]
+        return sorted_ops
 
 
 class HybridScheduler(ListScheduler):
@@ -204,4 +203,4 @@ class HybridScheduler(ListScheduler):
 
         sorted_op_list = [pair[0] for pair in fan_out_sorted_items]
 
-        return [op for op in sorted_op_list if not op.startswith("in")]
+        return sorted_op_list
diff --git a/b_asic/resources.py b/b_asic/resources.py
index 97dffb6802daef8d1306f646add9dd090a43a7cc..597e7f2983271bfe865f03e521d69bb4f691a141 100644
--- a/b_asic/resources.py
+++ b/b_asic/resources.py
@@ -877,7 +877,7 @@ class ProcessCollection:
 
         Parameters
         ----------
-        heuristic : {'graph_color', 'left_edge'}, default: 'graph_color'
+        heuristic : {'graph_color', 'left_edge'}, default: 'left_edge'
             The heuristic used when splitting based on execution times.
 
         coloring_strategy : str, default: 'saturation_largest_first'
@@ -919,7 +919,7 @@ class ProcessCollection:
 
         Parameters
         ----------
-        heuristic : str, default: "graph_color"
+        heuristic : str, default: "left_edge"
             The heuristic used when splitting this :class:`ProcessCollection`.
             Valid options are:
 
diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index 1ecfbb2d0f53dbeeb63cf754ad8fae4eaf2f5731..bf9a2474c9e72d1c0f57da0a33a98f9127a25dc5 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,3 +1,4 @@
+import sys
 from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, Optional, cast
 
@@ -9,6 +10,7 @@ from b_asic.types import TypeName
 if TYPE_CHECKING:
     from b_asic.operation import Operation
     from b_asic.schedule import Schedule
+    from b_asic.signal_flow_graph import SFG
     from b_asic.types import GraphID
 
 
@@ -48,6 +50,8 @@ class ListScheduler(Scheduler, ABC):
     def __init__(
         self,
         max_resources: Optional[dict[TypeName, int]] = None,
+        max_concurrent_reads: Optional[int] = None,
+        max_concurrent_writes: Optional[int] = None,
         input_times: Optional[dict["GraphID", int]] = None,
         output_delta_times: Optional[dict["GraphID", int]] = None,
         cyclic: Optional[bool] = False,
@@ -65,8 +69,11 @@ class ListScheduler(Scheduler, ABC):
         else:
             self._max_resources = {}
 
-        self._input_times = input_times if input_times else {}
-        self._output_delta_times = output_delta_times if output_delta_times else {}
+        self._max_concurrent_reads = max_concurrent_reads or sys.maxsize
+        self._max_concurrent_writes = max_concurrent_writes or sys.maxsize
+
+        self._input_times = input_times or {}
+        self._output_delta_times = output_delta_times or {}
 
     def apply_scheduling(self, schedule: "Schedule") -> None:
         """Applies the scheduling algorithm on the given Schedule.
@@ -77,60 +84,83 @@ class ListScheduler(Scheduler, ABC):
             Schedule to apply the scheduling algorithm on.
         """
         sfg = schedule.sfg
-        start_times = schedule.start_times
 
         used_resources_ready_times = {}
         remaining_resources = self._max_resources.copy()
+        if Input.type_name() not in remaining_resources:
+            remaining_resources[Input.type_name()] = 1
+        if Output.type_name() not in remaining_resources:
+            remaining_resources[Output.type_name()] = 1
+
         sorted_operations = self._get_sorted_operations(schedule)
 
+        schedule.start_times = {}
+
+        remaining_reads = self._max_concurrent_reads
+
         # initial input placement
         if self._input_times:
             for input_id in self._input_times:
-                start_times[input_id] = self._input_times[input_id]
-
-        for input_op in sfg.find_by_type_name(Input.type_name()):
-            if input_op.graph_id not in self._input_times:
-                start_times[input_op.graph_id] = 0
+                schedule.start_times[input_id] = self._input_times[input_id]
+            sorted_operations = [
+                elem for elem in sorted_operations if not elem.startswith("in")
+            ]
 
         current_time = 0
+        timeout_counter = 0
         while sorted_operations:
 
             # generate the best schedulable candidate
             candidate = sfg.find_by_id(sorted_operations[0])
             counter = 0
             while not self._candidate_is_schedulable(
-                start_times,
+                schedule.start_times,
+                sfg,
                 candidate,
                 current_time,
                 remaining_resources,
+                remaining_reads,
+                self._max_concurrent_writes,
                 sorted_operations,
             ):
                 if counter == len(sorted_operations):
                     counter = 0
                     current_time += 1
+                    timeout_counter += 1
+
+                    if timeout_counter > 10:
+                        msg = "Algorithm did not schedule any operation for 10 time steps, try relaxing constraints."
+                        raise TimeoutError(msg)
+
+                    remaining_reads = self._max_concurrent_reads
+
                     # update available operators
                     for operation, ready_time in used_resources_ready_times.items():
                         if ready_time == current_time:
                             remaining_resources[operation.type_name()] += 1
+
                 else:
                     candidate = sfg.find_by_id(sorted_operations[counter])
                     counter += 1
 
+            timeout_counter = 0
             # if the resource is constrained, update remaining resources
             if candidate.type_name() in remaining_resources:
                 remaining_resources[candidate.type_name()] -= 1
-                if candidate.execution_time:
-                    used_resources_ready_times[candidate] = (
-                        current_time + candidate.execution_time
-                    )
+                if (
+                    candidate.type_name() == Input.type_name()
+                    or candidate.type_name() == Output.type_name()
+                ):
+                    used_resources_ready_times[candidate] = current_time + 1
                 else:
                     used_resources_ready_times[candidate] = (
-                        current_time + candidate.latency
+                        current_time + candidate.execution_time
                     )
+            remaining_reads -= candidate.input_count
 
             # schedule the best candidate to the current time
             sorted_operations.remove(candidate.graph_id)
-            start_times[candidate.graph_id] = current_time
+            schedule.start_times[candidate.graph_id] = current_time
 
         self._handle_outputs(schedule)
 
@@ -138,12 +168,11 @@ class ListScheduler(Scheduler, ABC):
             max_start_time = max(schedule.start_times.values())
             if current_time < max_start_time:
                 current_time = max_start_time
+            current_time = max(current_time, schedule.get_max_end_time())
             schedule.set_schedule_time(current_time)
 
         schedule.remove_delays()
 
-        self._handle_inputs(schedule)
-
         # move all dont cares ALAP
         for dc_op in schedule.sfg.find_by_type_name(DontCare.type_name()):
             dc_op = cast(DontCare, dc_op)
@@ -152,9 +181,12 @@ class ListScheduler(Scheduler, ABC):
     @staticmethod
     def _candidate_is_schedulable(
         start_times: dict["GraphID"],
+        sfg: "SFG",
         operation: "Operation",
         current_time: int,
         remaining_resources: dict["GraphID", int],
+        remaining_reads: int,
+        max_concurrent_writes: int,
         remaining_ops: list["GraphID"],
     ) -> bool:
         if (
@@ -163,20 +195,43 @@ class ListScheduler(Scheduler, ABC):
         ):
             return False
 
+        op_finish_time = current_time + operation.latency
+        future_ops = [
+            sfg.find_by_id(item[0])
+            for item in start_times.items()
+            if item[1] + sfg.find_by_id(item[0]).latency == op_finish_time
+        ]
+
+        future_ops_writes = sum([op.input_count for op in future_ops])
+
+        if (
+            not operation.graph_id.startswith("out")
+            and future_ops_writes >= max_concurrent_writes
+        ):
+            return False
+
+        read_counter = 0
         earliest_start_time = 0
         for op_input in operation.inputs:
             source_op = op_input.signals[0].source.operation
+            if isinstance(source_op, Delay):
+                continue
+
             source_op_graph_id = source_op.graph_id
 
             if source_op_graph_id in remaining_ops:
                 return False
 
-            proceeding_op_start_time = start_times.get(source_op_graph_id)
+            if start_times[source_op_graph_id] != current_time - 1:
+                # not a direct connection -> memory read required
+                read_counter += 1
+
+            if read_counter > remaining_reads:
+                return False
 
-            if not isinstance(source_op, Delay):
-                earliest_start_time = max(
-                    earliest_start_time, proceeding_op_start_time + source_op.latency
-                )
+            proceeding_op_start_time = start_times.get(source_op_graph_id)
+            proceeding_op_finish_time = proceeding_op_start_time + source_op.latency
+            earliest_start_time = max(earliest_start_time, proceeding_op_finish_time)
 
         return earliest_start_time <= current_time
 
@@ -184,17 +239,9 @@ class ListScheduler(Scheduler, ABC):
     def _get_sorted_operations(schedule: "Schedule") -> list["GraphID"]:
         raise NotImplementedError
 
-    def _handle_inputs(self, schedule: "Schedule") -> None:
-        for input_op in schedule.sfg.find_by_type_name(Input.type_name()):
-            input_op = cast(Input, input_op)
-            if input_op.graph_id not in self._input_times:
-                schedule.move_operation_alap(input_op.graph_id)
-
     def _handle_outputs(
         self, schedule: "Schedule", non_schedulable_ops: Optional[list["GraphID"]] = []
     ) -> None:
-        super()._handle_outputs(schedule, non_schedulable_ops)
-
         schedule.set_schedule_time(schedule.get_max_end_time())
 
         for output in schedule.sfg.find_by_type_name(Output.type_name()):
diff --git a/examples/auto_scheduling_with_custom_io_times.py b/examples/auto_scheduling_with_custom_io_times.py
index 6b6a90b614ac5adb8fe1ff00490b0f7b3f61f270..8913bfd853bfe650ba1546aa83488097618a435b 100644
--- a/examples/auto_scheduling_with_custom_io_times.py
+++ b/examples/auto_scheduling_with_custom_io_times.py
@@ -52,7 +52,12 @@ output_delta_times = {
     "out7": 5,
 }
 schedule = Schedule(
-    sfg, scheduler=HybridScheduler(resources, input_times, output_delta_times)
+    sfg,
+    scheduler=HybridScheduler(
+        resources,
+        input_times=input_times,
+        output_delta_times=output_delta_times,
+    ),
 )
 schedule.show()
 
@@ -70,7 +75,11 @@ output_delta_times = {
 }
 schedule = Schedule(
     sfg,
-    scheduler=HybridScheduler(resources, input_times, output_delta_times),
+    scheduler=HybridScheduler(
+        resources,
+        input_times=input_times,
+        output_delta_times=output_delta_times,
+    ),
     cyclic=True,
 )
 schedule.show()
diff --git a/examples/ldlt_matrix_inverse.py b/examples/ldlt_matrix_inverse.py
index 4432e41764f96508d1e5c2638849bef67d2c2e4a..cf5961aaa3b8f1641e44233df0bfe86eeb96fa7d 100644
--- a/examples/ldlt_matrix_inverse.py
+++ b/examples/ldlt_matrix_inverse.py
@@ -84,7 +84,9 @@ output_delta_times = {
 }
 schedule = Schedule(
     sfg,
-    scheduler=HybridScheduler(resources, input_times, output_delta_times),
+    scheduler=HybridScheduler(
+        resources, input_times=input_times, output_delta_times=output_delta_times
+    ),
     cyclic=True,
 )
 print("Scheduling time:", schedule.schedule_time)
@@ -137,4 +139,3 @@ arch = Architecture(
 
 # %%
 arch
-# schedule.edit()
diff --git a/examples/memory_constrained_scheduling.py b/examples/memory_constrained_scheduling.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1f81fae59329520d0bd19868db64db3ca43c7af
--- /dev/null
+++ b/examples/memory_constrained_scheduling.py
@@ -0,0 +1,143 @@
+"""
+=========================================
+Memory Constrained Scheduling
+=========================================
+
+"""
+
+from b_asic.architecture import Architecture, Memory, ProcessingElement
+from b_asic.core_operations import Butterfly, ConstantMultiplication
+from b_asic.core_schedulers import ASAPScheduler, HybridScheduler
+from b_asic.schedule import Schedule
+from b_asic.sfg_generators import radix_2_dif_fft
+from b_asic.special_operations import Input, Output
+
+sfg = radix_2_dif_fft(points=16)
+
+# %%
+# The SFG is
+sfg
+
+# %%
+# Set latencies and execution times.
+sfg.set_latency_of_type(Butterfly.type_name(), 3)
+sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2)
+sfg.set_execution_time_of_type(Butterfly.type_name(), 1)
+sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1)
+
+# # %%
+# Generate an ASAP schedule for reference
+schedule = Schedule(sfg, scheduler=ASAPScheduler())
+schedule.show()
+
+# %%
+# Generate a PE constrained HybridSchedule
+resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1}
+schedule = Schedule(sfg, scheduler=HybridScheduler(resources))
+schedule.show()
+
+# %% Print the max number of read and write port accesses to non-direct memories
+direct, mem_vars = schedule.get_memory_variables().split_on_length()
+print("Max read ports:", mem_vars.read_ports_bound())
+print("Max write ports:", mem_vars.write_ports_bound())
+
+# %%
+operations = schedule.get_operations()
+bfs = operations.get_by_type_name(Butterfly.type_name())
+bfs.show(title="Butterfly executions")
+const_muls = operations.get_by_type_name(ConstantMultiplication.type_name())
+const_muls.show(title="ConstMul executions")
+inputs = operations.get_by_type_name(Input.type_name())
+inputs.show(title="Input executions")
+outputs = operations.get_by_type_name(Output.type_name())
+outputs.show(title="Output executions")
+
+bf_pe = ProcessingElement(bfs, entity_name="bf")
+mul_pe = ProcessingElement(const_muls, entity_name="mul")
+
+pe_in = ProcessingElement(inputs, entity_name='input')
+pe_out = ProcessingElement(outputs, entity_name='output')
+
+mem_vars = schedule.get_memory_variables()
+mem_vars.show(title="All memory variables")
+direct, mem_vars = mem_vars.split_on_length()
+mem_vars.show(title="Non-zero time memory variables")
+mem_vars_set = mem_vars.split_on_ports(
+    read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color"
+)
+
+# %%
+memories = []
+for i, mem in enumerate(mem_vars_set):
+    memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}")
+    memories.append(memory)
+    mem.show(title=f"{memory.entity_name}")
+    memory.assign("graph_color")
+    memory.show_content(title=f"Assigned {memory.entity_name}")
+
+direct.show(title="Direct interconnects")
+
+# %%
+arch = Architecture(
+    {bf_pe, mul_pe, pe_in, pe_out},
+    memories,
+    direct_interconnects=direct,
+)
+arch
+
+# %%
+# Generate another HybridSchedule but this time constrain the amount of reads and writes to reduce the amount of memories
+resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1}
+schedule = Schedule(
+    sfg,
+    scheduler=HybridScheduler(
+        resources, max_concurrent_reads=2, max_concurrent_writes=2
+    ),
+)
+schedule.show()
+
+# %% Print the max number of read and write port accesses to non-direct memories
+direct, mem_vars = schedule.get_memory_variables().split_on_length()
+print("Max read ports:", mem_vars.read_ports_bound())
+print("Max write ports:", mem_vars.write_ports_bound())
+
+# %% Proceed to construct PEs and plot executions and non-direct memory variables
+operations = schedule.get_operations()
+bfs = operations.get_by_type_name(Butterfly.type_name())
+bfs.show(title="Butterfly executions")
+const_muls = operations.get_by_type_name(ConstantMultiplication.type_name())
+const_muls.show(title="ConstMul executions")
+inputs = operations.get_by_type_name(Input.type_name())
+inputs.show(title="Input executions")
+outputs = operations.get_by_type_name(Output.type_name())
+outputs.show(title="Output executions")
+
+bf_pe = ProcessingElement(bfs, entity_name="bf")
+mul_pe = ProcessingElement(const_muls, entity_name="mul")
+
+pe_in = ProcessingElement(inputs, entity_name='input')
+pe_out = ProcessingElement(outputs, entity_name='output')
+
+mem_vars.show(title="Non-zero time memory variables")
+mem_vars_set = mem_vars.split_on_ports(
+    read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color"
+)
+
+# %% Allocate memories by graph coloring
+memories = []
+for i, mem in enumerate(mem_vars_set):
+    memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}")
+    memories.append(memory)
+    mem.show(title=f"{memory.entity_name}")
+    memory.assign("graph_color")
+    memory.show_content(title=f"Assigned {memory.entity_name}")
+
+direct.show(title="Direct interconnects")
+
+# %% Synthesize the new architecture, now only using two memories but with data rate
+arch = Architecture(
+    {bf_pe, mul_pe, pe_in, pe_out},
+    memories,
+    direct_interconnects=direct,
+)
+arch
diff --git a/test/integration/test_sfg_to_architecture.py b/test/integration/test_sfg_to_architecture.py
new file mode 100644
index 0000000000000000000000000000000000000000..c07e9766d1a3e52e18f55afdfc4ba4f8f55e1e4e
--- /dev/null
+++ b/test/integration/test_sfg_to_architecture.py
@@ -0,0 +1,157 @@
+import pytest
+
+from b_asic.architecture import Architecture, Memory, ProcessingElement
+from b_asic.core_operations import (
+    MADS,
+    Butterfly,
+    ConstantMultiplication,
+    DontCare,
+    Reciprocal,
+)
+from b_asic.core_schedulers import ASAPScheduler, HybridScheduler
+from b_asic.schedule import Schedule
+from b_asic.sfg_generators import ldlt_matrix_inverse, radix_2_dif_fft
+from b_asic.special_operations import Input, Output
+
+
+def test_pe_constrained_schedule():
+    sfg = ldlt_matrix_inverse(N=5)
+
+    sfg.set_latency_of_type(MADS.type_name(), 3)
+    sfg.set_latency_of_type(Reciprocal.type_name(), 2)
+    sfg.set_execution_time_of_type(MADS.type_name(), 1)
+    sfg.set_execution_time_of_type(Reciprocal.type_name(), 1)
+
+    resources = {MADS.type_name(): 2, Reciprocal.type_name(): 1}
+
+    # Generate a schedule to ensure that schedule can be overwritten without bugs
+    schedule = Schedule(sfg, scheduler=ASAPScheduler())
+
+    schedule = Schedule(sfg, scheduler=HybridScheduler(resources))
+
+    direct, mem_vars = schedule.get_memory_variables().split_on_length()
+    assert mem_vars.read_ports_bound() <= 7
+    assert mem_vars.write_ports_bound() <= 4
+
+    operations = schedule.get_operations()
+
+    with pytest.raises(
+        TypeError, match="Different Operation types in ProcessCollection"
+    ):
+        ProcessingElement(operations)
+
+    mads = operations.get_by_type_name(MADS.type_name())
+    with pytest.raises(
+        ValueError, match="Cannot map ProcessCollection to single ProcessingElement"
+    ):
+        ProcessingElement(mads, entity_name="mad")
+    mads = mads.split_on_execution_time()
+    with pytest.raises(
+        TypeError,
+        match="Argument process_collection must be ProcessCollection, not <class 'list'>",
+    ):
+        ProcessingElement(mads, entity_name="mad")
+
+    assert len(mads) == 2
+
+    reciprocals = operations.get_by_type_name(Reciprocal.type_name())
+    dont_cares = operations.get_by_type_name(DontCare.type_name())
+    inputs = operations.get_by_type_name(Input.type_name())
+    outputs = operations.get_by_type_name(Output.type_name())
+
+    mads0 = ProcessingElement(mads[0], entity_name="mads0")
+    mads1 = ProcessingElement(mads[1], entity_name="mads1")
+    reciprocal_pe = ProcessingElement(reciprocals, entity_name="rec")
+
+    dont_care_pe = ProcessingElement(dont_cares, entity_name="dc")
+
+    pe_in = ProcessingElement(inputs, entity_name='input')
+    pe_out = ProcessingElement(outputs, entity_name='output')
+
+    mem_vars_set = mem_vars.split_on_ports(read_ports=1, write_ports=1, total_ports=2)
+    memories = []
+    for i, mem in enumerate(mem_vars_set):
+        memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}")
+        memories.append(memory)
+        memory.assign("graph_color")
+
+    arch = Architecture(
+        {mads0, mads1, reciprocal_pe, dont_care_pe, pe_in, pe_out},
+        memories,
+        direct_interconnects=direct,
+    )
+
+    assert len(arch.memories) == len(memories)
+    for i in range(len(memories)):
+        assert arch.memories[i] == memories[i]
+
+    assert len(arch.processing_elements) == 6
+
+    assert arch.direct_interconnects == direct
+
+    assert arch.schedule_time == schedule.schedule_time
+
+
+def test_pe_and_memory_constrained_chedule():
+    sfg = radix_2_dif_fft(points=16)
+
+    sfg.set_latency_of_type(Butterfly.type_name(), 3)
+    sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2)
+    sfg.set_execution_time_of_type(Butterfly.type_name(), 1)
+    sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1)
+
+    # generate a schedule to ensure that schedule can be overwritten without bugs
+    schedule = Schedule(sfg, scheduler=ASAPScheduler())
+
+    # generate the real constrained schedule
+    resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1}
+    schedule = Schedule(
+        sfg,
+        scheduler=HybridScheduler(
+            resources, max_concurrent_reads=2, max_concurrent_writes=2
+        ),
+    )
+
+    operations = schedule.get_operations()
+    bfs = operations.get_by_type_name(Butterfly.type_name())
+    const_muls = operations.get_by_type_name(ConstantMultiplication.type_name())
+    inputs = operations.get_by_type_name(Input.type_name())
+    outputs = operations.get_by_type_name(Output.type_name())
+
+    bf_pe = ProcessingElement(bfs, entity_name="bf")
+    mul_pe = ProcessingElement(const_muls, entity_name="mul")
+
+    pe_in = ProcessingElement(inputs, entity_name='input')
+    pe_out = ProcessingElement(outputs, entity_name='output')
+
+    mem_vars = schedule.get_memory_variables()
+    direct, mem_vars = mem_vars.split_on_length()
+    mem_vars_set = mem_vars.split_on_ports(
+        read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color"
+    )
+
+    mem_vars_set = mem_vars.split_on_ports(
+        read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color"
+    )
+
+    memories = []
+    for i, mem in enumerate(mem_vars_set):
+        memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}")
+        memories.append(memory)
+        memory.assign("graph_color")
+
+    arch = Architecture(
+        {bf_pe, mul_pe, pe_in, pe_out},
+        memories,
+        direct_interconnects=direct,
+    )
+
+    assert len(arch.memories) == 2
+    assert arch.memories[0] == memories[0]
+    assert arch.memories[1] == memories[1]
+
+    assert len(arch.processing_elements) == 4
+
+    assert arch.direct_interconnects == direct
+
+    assert arch.schedule_time == schedule.schedule_time
diff --git a/test/baseline_images/test_resources/test_draw_matrix_transposer_4.png b/test/unit/baseline_images/test_resources/test_draw_matrix_transposer_4.png
similarity index 100%
rename from test/baseline_images/test_resources/test_draw_matrix_transposer_4.png
rename to test/unit/baseline_images/test_resources/test_draw_matrix_transposer_4.png
diff --git a/test/baseline_images/test_resources/test_draw_process_collection.png b/test/unit/baseline_images/test_resources/test_draw_process_collection.png
similarity index 100%
rename from test/baseline_images/test_resources/test_draw_process_collection.png
rename to test/unit/baseline_images/test_resources/test_draw_process_collection.png
diff --git a/test/baseline_images/test_resources/test_left_edge_cell_assignment.png b/test/unit/baseline_images/test_resources/test_left_edge_cell_assignment.png
similarity index 100%
rename from test/baseline_images/test_resources/test_left_edge_cell_assignment.png
rename to test/unit/baseline_images/test_resources/test_left_edge_cell_assignment.png
diff --git a/test/baseline_images/test_resources/test_max_min_lifetime_bar_plot.png b/test/unit/baseline_images/test_resources/test_max_min_lifetime_bar_plot.png
similarity index 100%
rename from test/baseline_images/test_resources/test_max_min_lifetime_bar_plot.png
rename to test/unit/baseline_images/test_resources/test_max_min_lifetime_bar_plot.png
diff --git a/test/baseline_images/test_schedule/test__get_figure_no_execution_times.png b/test/unit/baseline_images/test_schedule/test__get_figure_no_execution_times.png
similarity index 100%
rename from test/baseline_images/test_schedule/test__get_figure_no_execution_times.png
rename to test/unit/baseline_images/test_schedule/test__get_figure_no_execution_times.png
diff --git a/test/test_architecture.py b/test/unit/test_architecture.py
similarity index 99%
rename from test/test_architecture.py
rename to test/unit/test_architecture.py
index 6b72bbdcbe952a5cbe6a93e1dded67ac029349fd..432f06524a50c8326c56e713ea2176c43f285171 100644
--- a/test/test_architecture.py
+++ b/test/unit/test_architecture.py
@@ -187,7 +187,7 @@ def test_move_process(schedule_direct_form_iir_lp_filter: Schedule):
     mvs = schedule_direct_form_iir_lp_filter.get_memory_variables()
     operations = schedule_direct_form_iir_lp_filter.get_operations()
     adders1, adders2 = operations.get_by_type_name(Addition.type_name()).split_on_ports(
-        total_ports=1
+        heuristic="left_edge", total_ports=1
     )
     adders1 = [adders1]  # Fake two PEs needed for the adders
     adders2 = [adders2]  # Fake two PEs needed for the adders
diff --git a/test/test_codegen.py b/test/unit/test_codegen.py
similarity index 100%
rename from test/test_codegen.py
rename to test/unit/test_codegen.py
diff --git a/test/test_core_operations.py b/test/unit/test_core_operations.py
similarity index 100%
rename from test/test_core_operations.py
rename to test/unit/test_core_operations.py
diff --git a/test/test_core_schedulers.py b/test/unit/test_core_schedulers.py
similarity index 79%
rename from test/test_core_schedulers.py
rename to test/unit/test_core_schedulers.py
index 296974866613df2e207446e4feb9db038c203a2d..755f5cc675d7ae8ac84dfc93b1e20207ec18dd06 100644
--- a/test/test_core_schedulers.py
+++ b/test/unit/test_core_schedulers.py
@@ -1,3 +1,5 @@
+import sys
+
 import pytest
 
 from b_asic.core_operations import (
@@ -21,6 +23,7 @@ from b_asic.sfg_generators import (
     ldlt_matrix_inverse,
     radix_2_dif_fft,
 )
+from b_asic.special_operations import Input, Output
 
 
 class TestASAPScheduler:
@@ -298,15 +301,20 @@ class TestEarliestDeadlineScheduler:
         sfg.set_latency_of_type(Addition.type_name(), 3)
         sfg.set_execution_time_of_type(Addition.type_name(), 1)
 
-        resources = {Addition.type_name(): 1, ConstantMultiplication.type_name(): 1}
+        resources = {
+            Addition.type_name(): 1,
+            ConstantMultiplication.type_name(): 1,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
         schedule = Schedule(
             sfg, scheduler=EarliestDeadlineScheduler(max_resources=resources)
         )
 
         assert schedule.start_times == {
+            "in0": 0,
             "cmul4": 0,
             "cmul3": 1,
-            "in0": 2,
             "cmul0": 2,
             "add1": 3,
             "cmul1": 3,
@@ -318,67 +326,7 @@ class TestEarliestDeadlineScheduler:
         }
         assert schedule.schedule_time == 13
 
-    def test_direct_form_2_iir_inf_resources_no_exec_time(
-        self, sfg_direct_form_iir_lp_filter
-    ):
-        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
-        sfg_direct_form_iir_lp_filter.set_latency_of_type(
-            ConstantMultiplication.type_name(), 4
-        )
-
-        schedule = Schedule(
-            sfg_direct_form_iir_lp_filter, scheduler=EarliestDeadlineScheduler()
-        )
-
-        # should be the same as for ASAP due to infinite resources, except for input
-        assert schedule.start_times == {
-            "in0": 9,
-            "cmul1": 0,
-            "cmul4": 0,
-            "cmul2": 0,
-            "cmul3": 0,
-            "add3": 4,
-            "add1": 4,
-            "add0": 9,
-            "cmul0": 14,
-            "add2": 18,
-            "out0": 23,
-        }
-        assert schedule.schedule_time == 23
-
-    def test_direct_form_2_iir_1_add_1_mul_no_exec_time(
-        self, sfg_direct_form_iir_lp_filter
-    ):
-        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
-        sfg_direct_form_iir_lp_filter.set_latency_of_type(
-            ConstantMultiplication.type_name(), 4
-        )
-
-        max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1}
-
-        schedule = Schedule(
-            sfg_direct_form_iir_lp_filter,
-            scheduler=EarliestDeadlineScheduler(max_resources),
-        )
-        assert schedule.start_times == {
-            "cmul4": 0,
-            "cmul3": 4,
-            "cmul1": 8,
-            "add1": 8,
-            "cmul2": 12,
-            "in0": 13,
-            "add0": 13,
-            "add3": 18,
-            "cmul0": 18,
-            "add2": 23,
-            "out0": 28,
-        }
-
-        assert schedule.schedule_time == 28
-
-    def test_direct_form_2_iir_1_add_1_mul_exec_time_1(
-        self, sfg_direct_form_iir_lp_filter
-    ):
+    def test_direct_form_2_iir_1_add_1_mul(self, sfg_direct_form_iir_lp_filter):
         sfg_direct_form_iir_lp_filter.set_latency_of_type(
             ConstantMultiplication.type_name(), 3
         )
@@ -390,19 +338,24 @@ class TestEarliestDeadlineScheduler:
             Addition.type_name(), 1
         )
 
-        max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1}
+        resources = {
+            Addition.type_name(): 1,
+            ConstantMultiplication.type_name(): 1,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
 
         schedule = Schedule(
             sfg_direct_form_iir_lp_filter,
-            scheduler=EarliestDeadlineScheduler(max_resources),
+            scheduler=EarliestDeadlineScheduler(resources),
         )
         assert schedule.start_times == {
+            "in0": 0,
             "cmul4": 0,
             "cmul3": 1,
             "cmul1": 2,
             "cmul2": 3,
             "add1": 4,
-            "in0": 6,
             "add0": 6,
             "add3": 7,
             "cmul0": 8,
@@ -412,9 +365,7 @@ class TestEarliestDeadlineScheduler:
 
         assert schedule.schedule_time == 13
 
-    def test_direct_form_2_iir_2_add_3_mul_exec_time_1(
-        self, sfg_direct_form_iir_lp_filter
-    ):
+    def test_direct_form_2_iir_2_add_3_mul(self, sfg_direct_form_iir_lp_filter):
         sfg_direct_form_iir_lp_filter.set_latency_of_type(
             ConstantMultiplication.type_name(), 3
         )
@@ -426,20 +377,25 @@ class TestEarliestDeadlineScheduler:
             Addition.type_name(), 1
         )
 
-        max_resources = {ConstantMultiplication.type_name(): 3, Addition.type_name(): 2}
+        resources = {
+            Addition.type_name(): 2,
+            ConstantMultiplication.type_name(): 3,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
 
         schedule = Schedule(
             sfg_direct_form_iir_lp_filter,
-            scheduler=EarliestDeadlineScheduler(max_resources),
+            scheduler=EarliestDeadlineScheduler(resources),
         )
         assert schedule.start_times == {
+            "in0": 0,
             "cmul1": 0,
             "cmul4": 0,
             "cmul3": 0,
             "cmul2": 1,
             "add1": 3,
             "add3": 4,
-            "in0": 5,
             "add0": 5,
             "cmul0": 7,
             "add2": 10,
@@ -456,26 +412,31 @@ class TestEarliestDeadlineScheduler:
         sfg.set_latency_of_type(Butterfly.type_name(), 1)
         sfg.set_execution_time_of_type(Butterfly.type_name(), 1)
 
-        resources = {Butterfly.type_name(): 2, ConstantMultiplication.type_name(): 2}
+        resources = {
+            Butterfly.type_name(): 2,
+            ConstantMultiplication.type_name(): 2,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
         schedule = Schedule(
             sfg, scheduler=EarliestDeadlineScheduler(max_resources=resources)
         )
 
         assert schedule.start_times == {
+            "in0": 0,
             "in1": 0,
+            "in2": 0,
             "in3": 0,
+            "in4": 0,
             "in5": 0,
+            "in6": 0,
             "in7": 0,
             "bfly6": 0,
             "bfly8": 0,
-            "in2": 1,
-            "in6": 1,
             "cmul2": 1,
             "cmul3": 1,
             "bfly11": 1,
             "bfly7": 1,
-            "in0": 2,
-            "in4": 2,
             "cmul0": 2,
             "bfly0": 2,
             "cmul4": 2,
@@ -514,15 +475,20 @@ class TestLeastSlackTimeScheduler:
         sfg.set_latency_of_type(Addition.type_name(), 3)
         sfg.set_execution_time_of_type(Addition.type_name(), 1)
 
-        resources = {Addition.type_name(): 1, ConstantMultiplication.type_name(): 1}
+        resources = {
+            Addition.type_name(): 1,
+            ConstantMultiplication.type_name(): 1,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
         schedule = Schedule(
             sfg, scheduler=LeastSlackTimeScheduler(max_resources=resources)
         )
 
         assert schedule.start_times == {
+            "in0": 0,
             "cmul4": 0,
             "cmul3": 1,
-            "in0": 2,
             "cmul0": 2,
             "add1": 3,
             "cmul1": 3,
@@ -534,67 +500,7 @@ class TestLeastSlackTimeScheduler:
         }
         assert schedule.schedule_time == 13
 
-    def test_direct_form_2_iir_inf_resources_no_exec_time(
-        self, sfg_direct_form_iir_lp_filter
-    ):
-        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
-        sfg_direct_form_iir_lp_filter.set_latency_of_type(
-            ConstantMultiplication.type_name(), 4
-        )
-
-        schedule = Schedule(
-            sfg_direct_form_iir_lp_filter, scheduler=LeastSlackTimeScheduler()
-        )
-
-        # should be the same as for ASAP due to infinite resources, except for input
-        assert schedule.start_times == {
-            "in0": 9,
-            "cmul1": 0,
-            "cmul4": 0,
-            "cmul2": 0,
-            "cmul3": 0,
-            "add3": 4,
-            "add1": 4,
-            "add0": 9,
-            "cmul0": 14,
-            "add2": 18,
-            "out0": 23,
-        }
-        assert schedule.schedule_time == 23
-
-    def test_direct_form_2_iir_1_add_1_mul_no_exec_time(
-        self, sfg_direct_form_iir_lp_filter
-    ):
-        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
-        sfg_direct_form_iir_lp_filter.set_latency_of_type(
-            ConstantMultiplication.type_name(), 4
-        )
-
-        max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1}
-
-        schedule = Schedule(
-            sfg_direct_form_iir_lp_filter,
-            scheduler=LeastSlackTimeScheduler(max_resources),
-        )
-        assert schedule.start_times == {
-            "cmul4": 0,
-            "cmul3": 4,
-            "cmul1": 8,
-            "add1": 8,
-            "cmul2": 12,
-            "in0": 13,
-            "add0": 13,
-            "add3": 18,
-            "cmul0": 18,
-            "add2": 23,
-            "out0": 28,
-        }
-
-        assert schedule.schedule_time == 28
-
-    def test_direct_form_2_iir_1_add_1_mul_exec_time_1(
-        self, sfg_direct_form_iir_lp_filter
-    ):
+    def test_direct_form_2_iir_1_add_1_mul(self, sfg_direct_form_iir_lp_filter):
         sfg_direct_form_iir_lp_filter.set_latency_of_type(
             ConstantMultiplication.type_name(), 3
         )
@@ -606,19 +512,24 @@ class TestLeastSlackTimeScheduler:
             Addition.type_name(), 1
         )
 
-        max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1}
+        resources = {
+            Addition.type_name(): 1,
+            ConstantMultiplication.type_name(): 1,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
 
         schedule = Schedule(
             sfg_direct_form_iir_lp_filter,
-            scheduler=LeastSlackTimeScheduler(max_resources),
+            scheduler=LeastSlackTimeScheduler(resources),
         )
         assert schedule.start_times == {
+            "in0": 0,
             "cmul4": 0,
             "cmul3": 1,
             "cmul1": 2,
             "cmul2": 3,
             "add1": 4,
-            "in0": 6,
             "add0": 6,
             "add3": 7,
             "cmul0": 8,
@@ -628,9 +539,7 @@ class TestLeastSlackTimeScheduler:
 
         assert schedule.schedule_time == 13
 
-    def test_direct_form_2_iir_2_add_3_mul_exec_time_1(
-        self, sfg_direct_form_iir_lp_filter
-    ):
+    def test_direct_form_2_iir_2_add_3_mul(self, sfg_direct_form_iir_lp_filter):
         sfg_direct_form_iir_lp_filter.set_latency_of_type(
             ConstantMultiplication.type_name(), 3
         )
@@ -642,20 +551,25 @@ class TestLeastSlackTimeScheduler:
             Addition.type_name(), 1
         )
 
-        max_resources = {ConstantMultiplication.type_name(): 3, Addition.type_name(): 2}
+        resources = {
+            Addition.type_name(): 2,
+            ConstantMultiplication.type_name(): 3,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
 
         schedule = Schedule(
             sfg_direct_form_iir_lp_filter,
-            scheduler=LeastSlackTimeScheduler(max_resources),
+            scheduler=LeastSlackTimeScheduler(resources),
         )
         assert schedule.start_times == {
+            "in0": 0,
             "cmul1": 0,
             "cmul4": 0,
             "cmul3": 0,
             "cmul2": 1,
             "add1": 3,
             "add3": 4,
-            "in0": 5,
             "add0": 5,
             "cmul0": 7,
             "add2": 10,
@@ -672,26 +586,31 @@ class TestLeastSlackTimeScheduler:
         sfg.set_latency_of_type(Butterfly.type_name(), 1)
         sfg.set_execution_time_of_type(Butterfly.type_name(), 1)
 
-        resources = {Butterfly.type_name(): 2, ConstantMultiplication.type_name(): 2}
+        resources = {
+            Butterfly.type_name(): 2,
+            ConstantMultiplication.type_name(): 2,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
         schedule = Schedule(
             sfg, scheduler=LeastSlackTimeScheduler(max_resources=resources)
         )
 
         assert schedule.start_times == {
+            "in0": 0,
             "in1": 0,
+            "in2": 0,
             "in3": 0,
+            "in4": 0,
             "in5": 0,
+            "in6": 0,
             "in7": 0,
             "bfly6": 0,
             "bfly8": 0,
-            "in2": 1,
-            "in6": 1,
             "cmul2": 1,
             "cmul3": 1,
             "bfly11": 1,
             "bfly7": 1,
-            "in0": 2,
-            "in4": 2,
             "cmul0": 2,
             "bfly0": 2,
             "cmul4": 2,
@@ -768,9 +687,9 @@ class TestHybridScheduler:
         schedule = Schedule(sfg, scheduler=HybridScheduler(max_resources=resources))
 
         assert schedule.start_times == {
+            "in0": 0,
             "cmul4": 0,
             "cmul3": 1,
-            "in0": 2,
             "cmul0": 2,
             "add1": 3,
             "cmul1": 3,
@@ -790,24 +709,29 @@ class TestHybridScheduler:
         sfg.set_latency_of_type(Butterfly.type_name(), 1)
         sfg.set_execution_time_of_type(Butterfly.type_name(), 1)
 
-        resources = {Butterfly.type_name(): 2, ConstantMultiplication.type_name(): 2}
+        resources = {
+            Butterfly.type_name(): 2,
+            ConstantMultiplication.type_name(): 2,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
         schedule = Schedule(sfg, scheduler=HybridScheduler(max_resources=resources))
 
         assert schedule.start_times == {
+            "in0": 0,
             "in1": 0,
+            "in2": 0,
             "in3": 0,
+            "in4": 0,
             "in5": 0,
+            "in6": 0,
             "in7": 0,
             "bfly6": 0,
             "bfly8": 0,
-            "in2": 1,
-            "in6": 1,
             "cmul2": 1,
             "cmul3": 1,
             "bfly11": 1,
             "bfly7": 1,
-            "in0": 2,
-            "in4": 2,
             "cmul0": 2,
             "bfly0": 2,
             "cmul4": 2,
@@ -830,6 +754,59 @@ class TestHybridScheduler:
         }
         assert schedule.schedule_time == 7
 
+    def test_radix_2_fft_8_points_one_output(self):
+        sfg = radix_2_dif_fft(points=8)
+
+        sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2)
+        sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1)
+        sfg.set_latency_of_type(Butterfly.type_name(), 1)
+        sfg.set_execution_time_of_type(Butterfly.type_name(), 1)
+
+        resources = {
+            Butterfly.type_name(): 2,
+            ConstantMultiplication.type_name(): 2,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): 1,
+        }
+        schedule = Schedule(sfg, scheduler=HybridScheduler(max_resources=resources))
+
+        assert schedule.start_times == {
+            "in0": 0,
+            "in1": 0,
+            "in2": 0,
+            "in3": 0,
+            "in4": 0,
+            "in5": 0,
+            "in6": 0,
+            "in7": 0,
+            "bfly6": 0,
+            "bfly8": 0,
+            "cmul2": 1,
+            "cmul3": 1,
+            "bfly11": 1,
+            "bfly7": 1,
+            "cmul0": 2,
+            "bfly0": 2,
+            "cmul4": 2,
+            "bfly5": 3,
+            "bfly1": 3,
+            "cmul1": 4,
+            "bfly2": 4,
+            "bfly9": 4,
+            "bfly10": 5,
+            "bfly3": 5,
+            "out0": 5,
+            "bfly4": 6,
+            "out1": 6,
+            "out2": 12,
+            "out3": 11,
+            "out4": 10,
+            "out5": 9,
+            "out6": 8,
+            "out7": 7,
+        }
+        assert schedule.schedule_time == 12
+
     def test_radix_2_fft_8_points_specified_IO_times_cyclic(self):
         sfg = radix_2_dif_fft(points=8)
 
@@ -838,7 +815,12 @@ class TestHybridScheduler:
         sfg.set_execution_time_of_type(Butterfly.type_name(), 1)
         sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1)
 
-        resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1}
+        resources = {
+            Butterfly.type_name(): 1,
+            ConstantMultiplication.type_name(): 1,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
         input_times = {
             "in0": 0,
             "in1": 1,
@@ -861,7 +843,9 @@ class TestHybridScheduler:
         }
         schedule = Schedule(
             sfg,
-            scheduler=HybridScheduler(resources, input_times, output_times),
+            scheduler=HybridScheduler(
+                resources, input_times=input_times, output_delta_times=output_times
+            ),
             cyclic=True,
         )
 
@@ -933,7 +917,9 @@ class TestHybridScheduler:
         }
         schedule = Schedule(
             sfg,
-            scheduler=HybridScheduler(resources, input_times, output_times),
+            scheduler=HybridScheduler(
+                resources, input_times=input_times, output_delta_times=output_times
+            ),
             cyclic=False,
         )
 
@@ -982,7 +968,12 @@ class TestHybridScheduler:
         sfg.set_execution_time_of_type(MADS.type_name(), 1)
         sfg.set_execution_time_of_type(Reciprocal.type_name(), 1)
 
-        resources = {MADS.type_name(): 1, Reciprocal.type_name(): 1}
+        resources = {
+            MADS.type_name(): 1,
+            Reciprocal.type_name(): 1,
+            Input.type_name(): sys.maxsize,
+            Output.type_name(): sys.maxsize,
+        }
         schedule = Schedule(
             sfg,
             scheduler=HybridScheduler(resources),
@@ -990,11 +981,11 @@ class TestHybridScheduler:
 
         assert schedule.start_times == {
             "in0": 0,
+            "in1": 0,
+            "in2": 0,
             "rec0": 0,
-            "in1": 2,
             "dontcare1": 2,
             "mads0": 2,
-            "in2": 5,
             "mads3": 5,
             "rec1": 8,
             "dontcare0": 10,
@@ -1027,7 +1018,9 @@ class TestHybridScheduler:
         }
         schedule = Schedule(
             sfg,
-            scheduler=HybridScheduler(resources, input_times, output_times),
+            scheduler=HybridScheduler(
+                resources, input_times=input_times, output_delta_times=output_times
+            ),
             cyclic=True,
         )
 
@@ -1078,3 +1071,47 @@ class TestHybridScheduler:
         resources = {MADS.type_name(): "test"}
         with pytest.raises(ValueError, match="max_resources value must be an integer."):
             Schedule(sfg, scheduler=HybridScheduler(resources))
+
+    def test_ldlt_inverse_3x3_read_and_write_constrained(self):
+        sfg = ldlt_matrix_inverse(N=3)
+
+        sfg.set_latency_of_type(MADS.type_name(), 3)
+        sfg.set_latency_of_type(Reciprocal.type_name(), 2)
+        sfg.set_execution_time_of_type(MADS.type_name(), 1)
+        sfg.set_execution_time_of_type(Reciprocal.type_name(), 1)
+
+        resources = {MADS.type_name(): 1, Reciprocal.type_name(): 1}
+
+        schedule = Schedule(
+            sfg,
+            scheduler=HybridScheduler(
+                max_resources=resources,
+                max_concurrent_reads=3,
+                max_concurrent_writes=1,
+            ),
+        )
+
+        direct, mem_vars = schedule.get_memory_variables().split_on_length()
+        assert mem_vars.read_ports_bound() == 3
+        assert mem_vars.write_ports_bound() == 1
+
+    def test_read_constrained_too_tight(self):
+        sfg = ldlt_matrix_inverse(N=2)
+
+        sfg.set_latency_of_type(MADS.type_name(), 3)
+        sfg.set_latency_of_type(Reciprocal.type_name(), 2)
+        sfg.set_execution_time_of_type(MADS.type_name(), 1)
+        sfg.set_execution_time_of_type(Reciprocal.type_name(), 1)
+
+        resources = {MADS.type_name(): 1, Reciprocal.type_name(): 1}
+        with pytest.raises(
+            TimeoutError,
+            match="Algorithm did not schedule any operation for 10 time steps, try relaxing constraints.",
+        ):
+            Schedule(
+                sfg,
+                scheduler=HybridScheduler(
+                    max_resources=resources,
+                    max_concurrent_reads=2,
+                ),
+            )
diff --git a/test/test_graph_id_generator.py b/test/unit/test_graph_id_generator.py
similarity index 100%
rename from test/test_graph_id_generator.py
rename to test/unit/test_graph_id_generator.py
diff --git a/test/test_gui.py b/test/unit/test_gui.py
similarity index 100%
rename from test/test_gui.py
rename to test/unit/test_gui.py
diff --git a/test/test_gui/twotapfir.py b/test/unit/test_gui/twotapfir.py
similarity index 100%
rename from test/test_gui/twotapfir.py
rename to test/unit/test_gui/twotapfir.py
diff --git a/test/test_inputport.py b/test/unit/test_inputport.py
similarity index 100%
rename from test/test_inputport.py
rename to test/unit/test_inputport.py
diff --git a/test/test_operation.py b/test/unit/test_operation.py
similarity index 100%
rename from test/test_operation.py
rename to test/unit/test_operation.py
diff --git a/test/test_outputport.py b/test/unit/test_outputport.py
similarity index 100%
rename from test/test_outputport.py
rename to test/unit/test_outputport.py
diff --git a/test/test_process.py b/test/unit/test_process.py
similarity index 100%
rename from test/test_process.py
rename to test/unit/test_process.py
diff --git a/test/test_quantization.py b/test/unit/test_quantization.py
similarity index 100%
rename from test/test_quantization.py
rename to test/unit/test_quantization.py
diff --git a/test/test_resources.py b/test/unit/test_resources.py
similarity index 100%
rename from test/test_resources.py
rename to test/unit/test_resources.py
diff --git a/test/test_schedule.py b/test/unit/test_schedule.py
similarity index 100%
rename from test/test_schedule.py
rename to test/unit/test_schedule.py
diff --git a/test/test_scheduler_gui.py b/test/unit/test_scheduler_gui.py
similarity index 100%
rename from test/test_scheduler_gui.py
rename to test/unit/test_scheduler_gui.py
diff --git a/test/test_sfg.py b/test/unit/test_sfg.py
similarity index 100%
rename from test/test_sfg.py
rename to test/unit/test_sfg.py
diff --git a/test/test_sfg_generators.py b/test/unit/test_sfg_generators.py
similarity index 100%
rename from test/test_sfg_generators.py
rename to test/unit/test_sfg_generators.py
diff --git a/test/test_signal.py b/test/unit/test_signal.py
similarity index 100%
rename from test/test_signal.py
rename to test/unit/test_signal.py
diff --git a/test/test_signal_generator.py b/test/unit/test_signal_generator.py
similarity index 100%
rename from test/test_signal_generator.py
rename to test/unit/test_signal_generator.py
diff --git a/test/test_signal_generator/bad.csv b/test/unit/test_signal_generator/bad.csv
similarity index 100%
rename from test/test_signal_generator/bad.csv
rename to test/unit/test_signal_generator/bad.csv
diff --git a/test/test_signal_generator/input.csv b/test/unit/test_signal_generator/input.csv
similarity index 66%
rename from test/test_signal_generator/input.csv
rename to test/unit/test_signal_generator/input.csv
index b0917c8e2523298b91f0612287acd0150219ca53..64737476b070a120c18c4373b54a59a11d5afc8f 100644
--- a/test/test_signal_generator/input.csv
+++ b/test/unit/test_signal_generator/input.csv
@@ -1,5 +1,5 @@
-0
-1
-0
-0
-0
+0
+1
+0
+0
+0
diff --git a/test/test_simulation.py b/test/unit/test_simulation.py
similarity index 100%
rename from test/test_simulation.py
rename to test/unit/test_simulation.py
diff --git a/test/test_simulation_gui.py b/test/unit/test_simulation_gui.py
similarity index 100%
rename from test/test_simulation_gui.py
rename to test/unit/test_simulation_gui.py
diff --git a/test/test_utils.py b/test/unit/test_utils.py
similarity index 100%
rename from test/test_utils.py
rename to test/unit/test_utils.py