From 078ac59c6049d42848d2bdf21e3ff7a589825673 Mon Sep 17 00:00:00 2001
From: Mikael Henriksson <mike.zx@hotmail.com>
Date: Mon, 27 Feb 2023 17:15:07 +0100
Subject: [PATCH] Add left_edge_cell_assignment method to ProcessCollection

---
 b_asic/resources.py                           | 130 +++++++++++++++---
 .../test_left_edge_cell_assignment.png        | Bin 0 -> 23058 bytes
 test/test_resources.py                        |   9 ++
 3 files changed, 117 insertions(+), 22 deletions(-)
 create mode 100644 test/baseline/test_left_edge_cell_assignment.png

diff --git a/b_asic/resources.py b/b_asic/resources.py
index 12451389..8977b3b3 100644
--- a/b_asic/resources.py
+++ b/b_asic/resources.py
@@ -68,7 +68,6 @@ def draw_exclusion_graph_coloring(
     Returns
     -------
     None
-
     """
     COLOR_LIST = [
         '#aa0000',
@@ -151,6 +150,7 @@ class ProcessCollection:
         marker_read: str = "X",
         marker_write: str = "o",
         show_markers: bool = True,
+        row: Optional[int] = None,
     ):
         """
         Plot a process variable lifetime chart.
@@ -173,6 +173,10 @@ class ProcessCollection:
             Marker at read time in the lifetime chart.
         show_markers : bool, default True
             Show markers at read and write times.
+        row : int, optional
+            Render all processes in this collection on a specified row in the matplotlib axes object.
+            Defaults to None, which renders all processes on separate rows. This option is useful when
+            drawing cell assignments.
 
         Returns
         -------
@@ -198,6 +202,7 @@ class ProcessCollection:
 
         # Generate the life-time chart
         for i, process in enumerate(_sorted_nicely(self._collection)):
+            bar_row = i if row == None else row
             bar_start = process.start_time % self._schedule_time
             bar_end = process.start_time + process.execution_time
             bar_end = (
@@ -206,52 +211,55 @@ class ProcessCollection:
                 else bar_end % self._schedule_time
             )
             if show_markers:
-                _ax.scatter(
+                _ax.scatter(  # type: ignore
                     x=bar_start,
-                    y=i + 1,
+                    y=bar_row + 1,
                     marker=marker_write,
                     color=marker_color,
                     zorder=10,
                 )
-                _ax.scatter(
+                _ax.scatter(  # type: ignore
                     x=bar_end,
-                    y=i + 1,
+                    y=bar_row + 1,
                     marker=marker_read,
                     color=marker_color,
                     zorder=10,
                 )
             if bar_end >= bar_start:
-                _ax.broken_barh(
+                _ax.broken_barh(  # type: ignore
                     [(PAD_L + bar_start, bar_end - bar_start - PAD_L - PAD_R)],
-                    (i + 0.55, 0.9),
+                    (bar_row + 0.55, 0.9),
                     color=bar_color,
                 )
             else:  # bar_end < bar_start
-                _ax.broken_barh(
+                _ax.broken_barh(  # type: ignore
                     [
                         (
                             PAD_L + bar_start,
                             self._schedule_time - bar_start - PAD_L,
                         )
                     ],
-                    (i + 0.55, 0.9),
+                    (bar_row + 0.55, 0.9),
                     color=bar_color,
                 )
-                _ax.broken_barh(
-                    [(0, bar_end - PAD_R)], (i + 0.55, 0.9), color=bar_color
+                _ax.broken_barh(  # type: ignore
+                    [(0, bar_end - PAD_R)], (bar_row + 0.55, 0.9), color=bar_color
                 )
             if show_name:
-                _ax.annotate(
+                _ax.annotate(  # type: ignore
                     str(process),
-                    (bar_start + PAD_L + 0.025, i + 1.00),
+                    (bar_start + PAD_L + 0.025, bar_row + 1.00),
                     va="center",
                 )
-        _ax.grid(True)
+        _ax.grid(True)  # type: ignore
 
-        _ax.xaxis.set_major_locator(MaxNLocator(integer=True))
-        _ax.yaxis.set_major_locator(MaxNLocator(integer=True))
-        _ax.set_xlim(0, self._schedule_time)
-        _ax.set_ylim(0.25, len(self._collection) + 0.75)
+        _ax.xaxis.set_major_locator(MaxNLocator(integer=True))  # type: ignore
+        _ax.yaxis.set_major_locator(MaxNLocator(integer=True))  # type: ignore
+        _ax.set_xlim(0, self._schedule_time)  # type: ignore
+        if row == None:
+            _ax.set_ylim(0.25, len(self._collection) + 0.75)  # type: ignore
+        else:
+            pass
         return _ax
 
     def create_exclusion_graph_from_ports(
@@ -413,7 +421,7 @@ class ProcessCollection:
         total_ports: Optional[int] = None,
     ) -> Set["ProcessCollection"]:
         """
-        Split this process storage based on some heuristic.
+        Split this process storage based on concurrent read/write times according to some heuristic.
 
         Parameters
         ----------
@@ -528,10 +536,9 @@ class ProcessCollection:
         e.g. Jupyter Qt console.
         """
         fig, ax = plt.subplots()
-        self.plot(ax, show_markers=False)
+        self.plot(ax=ax, show_markers=False)
         f = io.StringIO()
-        fig.savefig(f, format="svg")
-
+        fig.savefig(f, format="svg")  # type: ignore
         return f.getvalue()
 
     def __repr__(self):
@@ -539,3 +546,82 @@ class ProcessCollection:
             f"ProcessCollection({self._collection}, {self._schedule_time},"
             f" {self._cyclic})"
         )
+
+    def __iter__(self):
+        return iter(self._collection)
+
+    def graph_color_cell_assignment(
+        self,
+        coloring_strategy: str = "saturation_largest_first",
+    ) -> Dict[int, "ProcessCollection"]:
+        """graph_color_cell_assignment.
+
+        Parameters
+        ----------
+
+
+        Returns
+        -------
+        Dict[int, "ProcessCollection"]
+
+        """
+        cell_assignment: Dict[int, ProcessCollection] = dict()
+        exclusion_graph = self.create_exclusion_graph_from_execution_time()
+        coloring: Dict[Process, int] = nx.coloring.greedy_color(
+            exclusion_graph, strategy=coloring_strategy
+        )
+        return cell_assignment
+
+    def left_edge_cell_assignment(self) -> Dict[int, "ProcessCollection"]:
+        """
+        Perform left edge cell assignment of this process collection.
+
+        Returns
+        -------
+        Dict[Process, int]
+        """
+        next_empty_cell = 0
+        cell_assignment: Dict[int, ProcessCollection] = dict()
+        for next_process in sorted(self):
+            insert_to_new_cell = True
+            for cell in cell_assignment:
+                insert_to_this_cell = True
+                for process in cell_assignment[cell]:
+                    next_process_stop_time = (
+                        next_process.start_time + next_process.execution_time
+                    ) % self._schedule_time
+                    if (
+                        next_process.start_time
+                        < process.start_time + process.execution_time
+                        or next_process_stop_time < next_process.start_time
+                        and next_process_stop_time > process.start_time
+                    ):
+                        insert_to_this_cell = False
+                        break
+                if insert_to_this_cell:
+                    cell_assignment[cell].add_process(next_process)
+                    insert_to_new_cell = False
+                    break
+            if insert_to_new_cell:
+                cell_assignment[next_empty_cell] = ProcessCollection(
+                    collection=set(), schedule_time=self._schedule_time
+                )
+                cell_assignment[next_empty_cell].add_process(next_process)
+                next_empty_cell += 1
+        return cell_assignment
+
+    def generate_memory_based_storage_vhdl(
+        self,
+        filename: str,
+    ):
+        """
+        Generate VHDL code for memory based storage of processes (MemoryVariables).
+
+        Parameters
+        ----------
+        filename : str
+            Filename of output file.
+        """
+
+        # Check that hardware can be generated for the ProcessCollection...
+        raise NotImplementedError("Not implemented yet!")
diff --git a/test/baseline/test_left_edge_cell_assignment.png b/test/baseline/test_left_edge_cell_assignment.png
new file mode 100644
index 0000000000000000000000000000000000000000..45420a64ac885227c686564337c2be329830e71e
GIT binary patch
literal 23058
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

literal 0
HcmV?d00001

diff --git a/test/test_resources.py b/test/test_resources.py
index 5d00ccf2..48a55898 100644
--- a/test/test_resources.py
+++ b/test/test_resources.py
@@ -29,6 +29,15 @@ class TestProcessCollectionPlainMemoryVariable:
         )
         assert len(collection_split) == 3
 
+    @pytest.mark.mpl_image_compare(style='mpl20')
+    def test_left_edge_cell_assignment(self, simple_collection: ProcessCollection):
+        fig, ax = plt.subplots(1, 2)
+        assignment = simple_collection.left_edge_cell_assignment()
+        for cell in assignment.keys():
+            assignment[cell].plot(ax=ax[1], row=cell)
+        simple_collection.plot(ax[0])
+        return fig
+
     # Issue: #175
     def test_interleaver_issue175(self):
         with open('test/fixtures/interleaver-two-port-issue175.p', 'rb') as f:
-- 
GitLab