From 9a73281b554fbdc166601d480970726d36be846c Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 14 Apr 2025 15:03:53 +0000
Subject: [PATCH] Fix scheduler execution-time bug, update README and split
 long tests

---
 README.md                                    |  2 +
 b_asic/scheduler.py                          | 37 +++++------
 test/integration/test_sfg_to_architecture.py | 69 ++++++++++++++++----
 3 files changed, 75 insertions(+), 33 deletions(-)

diff --git a/README.md b/README.md
index b2b827ee..55e764be 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@ The following packages are required in order to build the library:
   - [setuptools_scm](https://github.com/pypa/setuptools_scm/)
   - [NetworkX](https://networkx.org/)
   - [QtAwesome](https://github.com/spyder-ide/qtawesome/)
+  - [PuLP](https://github.com/coin-or/pulp)
 - Qt 6, with Python bindings, one of: (install with `pip install .[$BINDING_NAME]`)
   - pyqt6
   - pyside6
@@ -42,6 +43,7 @@ To run the test suite, the following additional packages are required:
   - [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) (for testing with coverage)
   - [pytest-xvfb](https://github.com/The-Compiler/pytest-xvfb) (for testing without showing windows on Linux, you will also need to install [xvfb](https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml))
   - [pytest-xdist](https://pytest-xdist.readthedocs.io/) (for parallel testing)
+  - [SciPy](https://scipy.org/)
 
 To generate the documentation, the following additional packages are required:
 
diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index cbd5160b..17523302 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -432,17 +432,10 @@ class ListScheduler(Scheduler):
             if self._schedule._schedule_time is not None
             else 0
         )
-        time_slot = (
-            self._current_time
-            if self._schedule._schedule_time is None
-            else self._current_time % self._schedule._schedule_time
-        )
         ready_ops = [
             op_id
             for op_id in candidate_ids
-            if self._op_is_schedulable(
-                self._sfg.find_by_id(op_id), schedule_time, time_slot
-            )
+            if self._op_is_schedulable(self._sfg.find_by_id(op_id), schedule_time)
         ]
         memory_reads = self._calculate_memory_reads(ready_ops)
 
@@ -514,14 +507,18 @@ class ListScheduler(Scheduler):
                 count += 1
         return count
 
-    def _op_satisfies_resource_constraints(
-        self, op: "Operation", time_slot: int
-    ) -> bool:
+    def _op_satisfies_resource_constraints(self, op: "Operation") -> bool:
         op_type = type(op)
-        return (
-            self._cached_execution_times_in_time[op_type][time_slot]
-            < self._remaining_resources[op_type]
-        )
+        for i in range(max(1, op.execution_time)):
+            time_slot = (
+                self._current_time + i
+                if self._schedule._schedule_time is None
+                else (self._current_time + i) % self._schedule._schedule_time
+            )
+            count = self._cached_execution_times_in_time[op_type][time_slot]
+            if count >= self._remaining_resources[op_type]:
+                return False
+        return True
 
     def _op_satisfies_concurrent_writes(self, op: "Operation") -> bool:
         if self._max_concurrent_writes:
@@ -607,11 +604,9 @@ class ListScheduler(Scheduler):
                 return False
         return True
 
-    def _op_is_schedulable(
-        self, op: "Operation", schedule_time: int, time_slot: int
-    ) -> bool:
+    def _op_is_schedulable(self, op: "Operation", schedule_time: int) -> bool:
         return (
-            self._op_satisfies_resource_constraints(op, time_slot)
+            self._op_satisfies_resource_constraints(op)
             and self._op_satisfies_data_dependencies(op, schedule_time)
             and self._op_satisfies_concurrent_writes(op)
             and self._op_satisfies_concurrent_reads(op)
@@ -771,7 +766,7 @@ class ListScheduler(Scheduler):
                     time_slot = (
                         (self._current_time + i) % self._schedule._schedule_time
                         if self._schedule._schedule_time
-                        else self._current_time
+                        else self._current_time + i
                     )
                     self._cached_execution_times_in_time[type(next_op)][time_slot] += 1
 
@@ -1008,7 +1003,7 @@ class RecursiveListScheduler(ListScheduler):
                 time_slot = (
                     (self._current_time + i) % self._schedule._schedule_time
                     if self._schedule._schedule_time
-                    else self._current_time
+                    else self._current_time + i
                 )
                 self._cached_execution_times_in_time[op_type][time_slot] += 1
 
diff --git a/test/integration/test_sfg_to_architecture.py b/test/integration/test_sfg_to_architecture.py
index 11c4763a..10a5ce0d 100644
--- a/test/integration/test_sfg_to_architecture.py
+++ b/test/integration/test_sfg_to_architecture.py
@@ -156,7 +156,7 @@ def test_pe_and_memory_constrained_schedule():
     assert arch.schedule_time == schedule.schedule_time
 
 
-def test_different_resource_algorithms():
+def test_heuristic_resource_algorithms():
     POINTS = 32
     sfg = radix_2_dif_fft(POINTS)
     sfg.set_latency_of_type(Butterfly, 1)
@@ -170,14 +170,14 @@ def test_different_resource_algorithms():
         Input.type_name(): 1,
         Output.type_name(): 1,
     }
-    schedule_1 = Schedule(
+    schedule = Schedule(
         sfg,
         scheduler=HybridScheduler(
             resources, max_concurrent_reads=4, max_concurrent_writes=4
         ),
     )
 
-    operations = schedule_1.get_operations()
+    operations = schedule.get_operations()
     bfs = operations.get_by_type_name(Butterfly.type_name())
     bfs = bfs.split_on_execution_time()
     const_muls = operations.get_by_type_name(ConstantMultiplication.type_name())
@@ -196,7 +196,7 @@ def test_different_resource_algorithms():
 
     processing_elements = [bf_pe_1, bf_pe_2, mul_pe_1, mul_pe_2, pe_in, pe_out]
 
-    mem_vars = schedule_1.get_memory_variables()
+    mem_vars = schedule.get_memory_variables()
     direct, mem_vars = mem_vars.split_on_length()
 
     # LEFT-EDGE
@@ -314,7 +314,8 @@ def test_different_resource_algorithms():
     assert len(arch.processing_elements) == 6
     assert len(arch.memories) == 7
 
-    # FOR ILP points is reduced due to time complexity
+
+def test_ilp_resource_algorithms():
     POINTS = 16
     sfg = radix_2_dif_fft(POINTS)
     sfg.set_latency_of_type(Butterfly, 1)
@@ -322,14 +323,20 @@ def test_different_resource_algorithms():
     sfg.set_execution_time_of_type(Butterfly, 1)
     sfg.set_execution_time_of_type(ConstantMultiplication, 1)
 
-    schedule_2 = Schedule(
+    resources = {
+        Butterfly.type_name(): 2,
+        ConstantMultiplication.type_name(): 2,
+        Input.type_name(): 1,
+        Output.type_name(): 1,
+    }
+    schedule = Schedule(
         sfg,
         scheduler=HybridScheduler(
             resources, max_concurrent_reads=4, max_concurrent_writes=4
         ),
     )
 
-    operations = schedule_2.get_operations()
+    operations = schedule.get_operations()
     bfs = operations.get_by_type_name(Butterfly.type_name())
     bfs = bfs.split_on_execution_time()
     const_muls = operations.get_by_type_name(ConstantMultiplication.type_name())
@@ -346,7 +353,7 @@ def test_different_resource_algorithms():
 
     processing_elements = [bf_pe_1, bf_pe_2, mul_pe_1, pe_in, pe_out]
 
-    mem_vars = schedule_2.get_memory_variables()
+    mem_vars = schedule.get_memory_variables()
     direct, mem_vars = mem_vars.split_on_length()
 
     # ILP COLOR
@@ -468,7 +475,45 @@ def test_different_resource_algorithms():
     assert len(arch.processing_elements) == 5
     assert len(arch.memories) == 4
 
-    # ILP COLOR MIN TOTAL MUX (custom solver)
+
+def test_ilp_resource_algorithm_custom_solver():
+    POINTS = 16
+    sfg = radix_2_dif_fft(POINTS)
+    sfg.set_latency_of_type(Butterfly, 3)
+    sfg.set_latency_of_type(ConstantMultiplication, 8)
+    sfg.set_execution_time_of_type(Butterfly, 2)
+    sfg.set_execution_time_of_type(ConstantMultiplication, 8)
+
+    resources = {
+        Butterfly.type_name(): 1,
+        ConstantMultiplication.type_name(): 1,
+        Input.type_name(): 1,
+        Output.type_name(): 1,
+    }
+    schedule = Schedule(
+        sfg,
+        scheduler=HybridScheduler(
+            resources, max_concurrent_reads=3, max_concurrent_writes=3
+        ),
+    )
+
+    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="bf1")
+    mul_pe = ProcessingElement(const_muls, entity_name="mul1")
+
+    pe_in = ProcessingElement(inputs, entity_name="input")
+    pe_out = ProcessingElement(outputs, entity_name="output")
+
+    processing_elements = [bf_pe, mul_pe, pe_in, pe_out]
+
+    mem_vars = schedule.get_memory_variables()
+    direct, mem_vars = mem_vars.split_on_length()
+
     from pulp import PULP_CBC_CMD
 
     mem_vars_set = mem_vars.split_on_ports(
@@ -477,7 +522,7 @@ def test_different_resource_algorithms():
         total_ports=2,
         strategy="ilp_min_total_mux",
         processing_elements=processing_elements,
-        max_colors=4,
+        max_colors=3,
         solver=PULP_CBC_CMD(),
     )
 
@@ -492,5 +537,5 @@ def test_different_resource_algorithms():
         memories,
         direct_interconnects=direct,
     )
-    assert len(arch.processing_elements) == 5
-    assert len(arch.memories) == 4
+    assert len(arch.processing_elements) == 4
+    assert len(arch.memories) == 3
-- 
GitLab