From 87268b645b76b1a7aa7af3d67444987853eac04e Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 29 Jan 2025 15:38:11 +0100
Subject: [PATCH] seems like its working for execution time 1

---
 b_asic/scheduler.py | 122 +++++++++++++++++++++++++++-----------------
 1 file changed, 76 insertions(+), 46 deletions(-)

diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index 554ce97e..a7d1b85a 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,8 +1,9 @@
 from abc import ABC, abstractmethod
+from collections import defaultdict
 from typing import TYPE_CHECKING, Optional, cast
 
 from b_asic.port import OutputPort
-from b_asic.special_operations import Delay, Output
+from b_asic.special_operations import Delay, Input, Output
 from b_asic.types import TypeName
 
 if TYPE_CHECKING:
@@ -21,7 +22,7 @@ class Scheduler(ABC):
         """
         pass
 
-    def _handle_outputs(self, schedule, non_schedulable_ops) -> None:
+    def _handle_outputs(self, schedule, non_schedulable_ops=set()) -> None:
         for output in schedule.sfg.find_by_type_name(Output.type_name()):
             output = cast(Output, output)
             source_port = cast(OutputPort, output.inputs[0].signals[0].source)
@@ -165,8 +166,11 @@ class EarliestDeadlineScheduler(Scheduler):
         resource is used.
     """
 
-    def __init__(self, max_resources: Optional[dict[TypeName, int]]) -> None:
-        self._max_resources = max_resources
+    def __init__(self, max_resources: Optional[dict[TypeName, int]] = None) -> None:
+        if max_resources:
+            self._max_resources = max_resources
+        else:
+            self._max_resources = {}
 
     def apply_scheduling(self, schedule: "Schedule") -> None:
         """Applies the scheduling algorithm on the given Schedule.
@@ -176,50 +180,76 @@ class EarliestDeadlineScheduler(Scheduler):
         schedule : Schedule
             Schedule to apply the scheduling algorithm on.
         """
-        # ACT BASED ON THE NUMBER OF PEs!
-        prec_list = schedule.sfg.get_precedence_list()
-        if len(prec_list) < 2:
-            raise ValueError("Empty signal flow graph cannot be scheduled.")
 
-        # handle the first set in precedence graph (input and delays)
-        non_schedulable_ops = set()
-        for outport in prec_list[0]:
-            operation = outport.operation
-            if operation.type_name() == Delay.type_name():
-                non_schedulable_ops.add(operation.graph_id)
-            elif operation.graph_id not in schedule.start_times:
-                schedule.start_times[operation.graph_id] = 0
+        # TODO: Take the execution time into consideration!
 
+        ALAPScheduler().apply_scheduling(schedule)
+
+        # move all inputs ASAP to ensure correct operation
+        for input_op in schedule.sfg.find_by_type_name(Input.type_name()):
+            input_op = cast(Input, input_op)
+            schedule.move_operation_asap(input_op.graph_id)
+
+        remaining_ops = set(schedule.start_times.keys())
+        remaining_ops = {elem for elem in remaining_ops if not elem.startswith("in")}
+
+        remaining_resources = self._max_resources.copy()
         current_time = 0
-        sorted_outports = sorted(
-            prec_list[1], key=lambda outport: outport.operation.latency
-        )
-        for outport in sorted_outports:
-            op = outport.operation
-            schedule.start_times[op.graph_id] = current_time
-            current_time += 1
+        while remaining_ops:
+            best_candidate = self._find_best_candidate(
+                schedule, remaining_ops, remaining_resources, current_time
+            )
+
+            if not best_candidate:
+                current_time += 1
+                remaining_resources = self._max_resources.copy()
+                continue
+
+            # update remaining resources
+            if best_candidate.type_name() in remaining_resources:
+                remaining_resources[best_candidate.type_name()] -= 1
+
+            remaining_ops.remove(best_candidate.graph_id)
+            schedule.start_times[best_candidate.graph_id] = current_time
+
+        # move all inputs and outputs ALAP now that operations have moved
+        for input_op in schedule.sfg.find_by_type_name(Input.type_name()):
+            input_op = cast(Input, input_op)
+            schedule.move_operation_alap(input_op.graph_id)
+        self._handle_outputs(schedule)
+
+    @staticmethod
+    def _find_best_candidate(
+        schedule, remaining_ops, remaining_resources, current_time
+    ):
+        # precompute source end times for faster checks
+        sfg = schedule.sfg
+        source_end_times = defaultdict(float)
+        for op_id in remaining_ops:
+            operation = sfg.find_by_id(op_id)
+            for op_input in operation.inputs:
+                source_op = op_input.signals[0].source.operation
+                if not isinstance(source_op, Delay):
+                    source_end_times[op_id] = max(
+                        source_end_times[op_id],
+                        schedule.start_times[source_op.graph_id] + source_op.latency,
+                    )
 
-        for outports in prec_list[2:]:
-            current_time -= 1
-            for outport in outports:
-                op = outport.operation
-                candidates = []
-                while not candidates:
-                    current_time += 1
-                    op_is_ready_to_be_scheduled = True
-                    for op_input in op.inputs:
-                        source_op = op_input.signals[0].source.operation
-                        source_op_time = schedule.start_times[source_op.graph_id]
-                        source_end_time = source_op_time + source_op.latency
-                        if source_end_time > current_time:
-                            op_is_ready_to_be_scheduled = False
-                    if op_is_ready_to_be_scheduled:
-                        candidates.append(op)
-                sorted_candidates = sorted(
-                    candidates, key=lambda candidate: candidate.latency
-                )
-                # schedule the best candidate to current time
-                schedule.start_times[sorted_candidates[0].graph_id] = current_time
+        best_candidate = None
+        best_deadline = float('inf')
+        for op_id in remaining_ops:
+            operation = sfg.find_by_id(op_id)
 
-        self._handle_outputs(schedule, non_schedulable_ops)
-        schedule.remove_delays()
+            # check resource constraints
+            if operation.type_name() in remaining_resources:
+                if remaining_resources[operation.type_name()] == 0:
+                    continue
+
+            # check if all inputs are available
+            if source_end_times[op_id] <= current_time:
+                operation_deadline = schedule.start_times[op_id] + operation.latency
+                if operation_deadline < best_deadline:
+                    best_candidate = operation
+                    best_deadline = operation_deadline
+
+        return best_candidate
-- 
GitLab