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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⪙&6^{ib^mSqilt~rO_}0C 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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