From 276856f0f9d3b9a2522a8a90612e7e3b22bd917e Mon Sep 17 00:00:00 2001
From: Oscar Gustafsson <oscar.gustafsson@gmail.com>
Date: Sat, 15 Mar 2025 13:09:36 +0100
Subject: [PATCH] Better drawing and ordering of cyclic schedules with latency
 offsets

---
 b_asic/schedule.py         | 55 +++++++++++++++++++++++++++++---------
 test/unit/test_schedule.py |  4 +--
 2 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 013925cd..a20410d4 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -18,6 +18,7 @@ from matplotlib.lines import Line2D
 from matplotlib.patches import PathPatch, Polygon
 from matplotlib.path import Path
 from matplotlib.ticker import MaxNLocator
+from matplotlib.transforms import Bbox, TransformedBbox
 
 from b_asic import Signal
 from b_asic._preferences import (
@@ -358,9 +359,7 @@ class Schedule:
         usage_time = start_time + cast(int, input_port.latency_offset)
         for signal in input_port.signals:
             source = cast(OutputPort, signal.source)
-            if isinstance(source.operation, DontCare):
-                available_time = 0
-            elif isinstance(source.operation, Delay):
+            if isinstance(source.operation, (DontCare, Delay)):
                 available_time = 0
             else:
                 if self._schedule_time is not None:
@@ -770,9 +769,7 @@ class Schedule:
                 source_port = source_op = input_port.signals[0].source
                 source_op = source_port.operation
 
-                if not isinstance(source_op, Delay) and not isinstance(
-                    source_op, DontCare
-                ):
+                if not isinstance(source_op, (Delay, DontCare)):
                     if op_laps[source_op.graph_id] < current_lap:
                         laps += current_lap - op_laps[source_op.graph_id]
                     source_available_time = (
@@ -802,7 +799,7 @@ class Schedule:
             and isinstance(op, DontCare)
             and self._laps[op.output(0).signals[0].graph_id] == 0
         ):
-            start = time
+            start = time % self._schedule_time
 
         self._start_times[op.graph_id] = start
 
@@ -1074,6 +1071,20 @@ class Schedule:
             sorted(self._start_times, key=self._start_times.get)
         ):
             self.set_y_location(graph_id, i)
+        for graph_id in self._start_times:
+            op = cast(Operation, self._sfg.find_by_id(graph_id))
+            if isinstance(op, Output):
+                self.move_y_location(
+                    graph_id,
+                    self.get_y_location(op.preceding_operations[0].graph_id) + 1,
+                    True,
+                )
+            if isinstance(op, DontCare):
+                self.move_y_location(
+                    graph_id,
+                    self.get_y_location(op.subsequent_operations[0].graph_id),
+                    True,
+                )
 
     def _plot_schedule(self, ax: Axes, operation_gap: float = OPERATION_GAP) -> None:
         """Draw the schedule."""
@@ -1083,6 +1094,10 @@ class Schedule:
             start: Sequence[float], end: Sequence[float], name: str = "", laps: int = 0
         ) -> None:
             """Draw an arrow from *start* to *end*."""
+            if end[0] > self.schedule_time:
+                end[0] %= self.schedule_time
+            if start[0] > self.schedule_time:
+                start[0] %= self.schedule_time
             if end[0] < start[0] or laps > 0:  # Wrap around
                 if start not in line_cache:
                     line = Line2D(
@@ -1175,10 +1190,15 @@ class Schedule:
             _x, _y = zip(*latency_coordinates)
             x = np.array(_x)
             y = np.array(_y)
-            xy = np.stack((x + op_start_time, y + y_pos))
-            ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR))
-
-            if 'in' in str(graph_id):
+            xvalues = x + op_start_time
+            xy = np.stack((xvalues, y + y_pos))
+            p = ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR))
+            p.set_clip_box(TransformedBbox(Bbox([[0, 0], [1, 1]]), ax.transAxes))
+            if any(xvalues > self.schedule_time) and not isinstance(operation, Output):
+                xy = np.stack((xvalues - self.schedule_time, y + y_pos))
+                p = ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR))
+                p.set_clip_box(TransformedBbox(Bbox([[0, 0], [1, 1]]), ax.transAxes))
+            if isinstance(operation, Input):
                 ax.annotate(
                     graph_id,
                     xy=(op_start_time - 0.48, y_pos + 0.7),
@@ -1196,12 +1216,23 @@ class Schedule:
                 _x, _y = zip(*execution_time_coordinates)
                 x = np.array(_x)
                 y = np.array(_y)
+                xvalues = x + op_start_time
                 ax.plot(
-                    x + op_start_time,
+                    xvalues,
                     y + y_pos,
                     color=_EXECUTION_TIME_COLOR,
                     linewidth=3,
                 )
+                if any(xvalues > self.schedule_time) and not isinstance(
+                    operation, Output
+                ):
+                    ax.plot(
+                        xvalues - self.schedule_time,
+                        y + y_pos,
+                        color=_EXECUTION_TIME_COLOR,
+                        linewidth=3,
+                    )
+
             ytickpositions.append(y_pos + 0.5)
             yticklabels.append(cast(Operation, self._sfg.find_by_id(graph_id)).name)
 
diff --git a/test/unit/test_schedule.py b/test/unit/test_schedule.py
index 357e3cb4..6b499bd7 100644
--- a/test/unit/test_schedule.py
+++ b/test/unit/test_schedule.py
@@ -843,7 +843,7 @@ class TestYLocations:
         sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2)
         schedule = Schedule(sfg_simple_filter, ASAPScheduler())
 
-        assert schedule._y_locations == {"in0": 0, "cmul0": 1, "add0": 3, "out0": 2}
+        assert schedule._y_locations == {"in0": 0, "cmul0": 1, "add0": 2, "out0": 3}
         schedule.move_y_location("add0", 1, insert=True)
         assert schedule._y_locations == {"in0": 0, "cmul0": 2, "add0": 1, "out0": 3}
         schedule.move_y_location("out0", 1)
@@ -854,7 +854,7 @@ class TestYLocations:
         sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2)
         schedule = Schedule(sfg_simple_filter, ASAPScheduler())
 
-        assert schedule._y_locations == {"in0": 0, "cmul0": 1, "add0": 3, "out0": 2}
+        assert schedule._y_locations == {"in0": 0, "cmul0": 1, "add0": 2, "out0": 3}
         schedule.reset_y_locations()
         assert schedule._y_locations["in0"] is None
         assert schedule._y_locations["cmul0"] is None
-- 
GitLab