From ae508261459f359b86e929962638b1f5143aa2ce Mon Sep 17 00:00:00 2001 From: Mikael Henriksson <mike.zx@hotmail.com> Date: Thu, 11 May 2023 14:29:05 +0200 Subject: [PATCH] resources.py: fix maximum life-time left-edge algorithm bug (closes #250) --- b_asic/resources.py | 93 +++++++++++++++++++++++++++--------------- test/test_resources.py | 22 ++++++++++ 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/b_asic/resources.py b/b_asic/resources.py index 945ccba8..b33de2eb 100644 --- a/b_asic/resources.py +++ b/b_asic/resources.py @@ -876,11 +876,7 @@ class ProcessCollection: A list of new ProcessCollection objects with the process splitting. """ if heuristic == "graph_color": - exclusion_graph = self.create_exclusion_graph_from_execution_time() - coloring = nx.coloring.greedy_color( - exclusion_graph, strategy=coloring_strategy - ) - return self._split_from_graph_coloring(coloring) + return self._graph_color_assignment(coloring_strategy) elif heuristic == "left_edge": return self._left_edge_assignment() else: @@ -1046,6 +1042,13 @@ class ProcessCollection: List[ProcessCollection] """ + for process in self: + if process.execution_time > self.schedule_time: + # Can not assign process to any cell + raise ValueError( + f"{process} has execution time greater than the schedule time" + ) + cell_assignment: Dict[int, ProcessCollection] = dict() exclusion_graph = self.create_exclusion_graph_from_execution_time() if coloring is None: @@ -1066,40 +1069,64 @@ class ProcessCollection: Two or more processes can share a single resource if, and only if, they have no overlaping execution time. + Raises :class:`ValueError` if any process in this collection has an execution + time which is greater than the collection schedule time. + Returns ------- List[ProcessCollection] """ - next_empty_cell = 0 - cell_assignment: Dict[int, ProcessCollection] = dict() + assignment: List[ProcessCollection] = [] 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.start_time - > 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=[], schedule_time=self._schedule_time + if next_process.execution_time > self.schedule_time: + # Can not assign process to any cell + raise ValueError( + f"{next_process} has execution time greater than the schedule time" + ) + elif next_process.execution_time == self.schedule_time: + # Always assign maximum lifetime process to new cell + assignment.append( + ProcessCollection( + (next_process,), + schedule_time=self.schedule_time, + cyclic=self._cyclic, + ) ) - cell_assignment[next_empty_cell].add_process(next_process) - next_empty_cell += 1 - return [pc for pc in cell_assignment.values()] + continue # Continue assigning next process + else: + next_process_stop_time = ( + next_process.start_time + next_process.execution_time + ) % self._schedule_time + insert_to_new_cell = True + for cell_assignment in assignment: + insert_to_this_cell = True + for process in cell_assignment: + # The next_process start_time is always greater than or equal to + # the start time of all other assigned processes + process_end_time = process.start_time + process.execution_time + if next_process.start_time < process_end_time: + insert_to_this_cell = False + break + if ( + next_process.start_time + > next_process_stop_time + > process.start_time + ): + insert_to_this_cell = False + break + if insert_to_this_cell: + cell_assignment.add_process(next_process) + insert_to_new_cell = False + break + if insert_to_new_cell: + assignment.append( + ProcessCollection( + (next_process,), + schedule_time=self.schedule_time, + cyclic=self._cyclic, + ) + ) + return assignment def generate_memory_based_storage_vhdl( self, diff --git a/test/test_resources.py b/test/test_resources.py index b5f5b13d..7b05a7bd 100644 --- a/test/test_resources.py +++ b/test/test_resources.py @@ -210,3 +210,25 @@ class TestProcessCollectionPlainMemoryVariable: assert exclusion_graph.degree(p1) == 3 assert exclusion_graph.degree(p2) == 1 assert exclusion_graph.degree(p3) == 3 + + def test_left_edge_maximum_lifetime(self): + a = PlainMemoryVariable(2, 0, {0: 1}, "cmul1.0") + b = PlainMemoryVariable(4, 0, {0: 7}, "cmul4.0") + c = PlainMemoryVariable(5, 0, {0: 4}, "cmul5.0") + collection = ProcessCollection([a, b, c], schedule_time=7, cyclic=True) + for heuristic in ("graph_color", "left_edge"): + assignment = collection.split_on_execution_time(heuristic) + assert len(assignment) == 2 + a_idx = 0 if a in assignment[0] else 1 + assert b not in assignment[a_idx] + assert c in assignment[a_idx] + + def test_split_on_execution_lifetime_assert(self): + a = PlainMemoryVariable(3, 0, {0: 10}, "MV0") + collection = ProcessCollection([a], schedule_time=9, cyclic=True) + for heuristic in ("graph_color", "left_edge"): + with pytest.raises( + ValueError, + match="MV0 has execution time greater than the schedule time", + ): + collection.split_on_execution_time(heuristic) -- GitLab