From f42c18bb9ac3c2cdd1393b501fb9417f930b18d1 Mon Sep 17 00:00:00 2001 From: Simon Bjurek <simbj106@student.liu.se> Date: Fri, 21 Feb 2025 18:08:44 +0100 Subject: [PATCH] List scheduler now refactored to sort continously given indecies by subclass. --- b_asic/__init__.py | 2 +- b_asic/core_schedulers.py | 206 ---------- b_asic/list_schedulers.py | 33 ++ b_asic/scheduler.py | 339 ++++++++++++---- b_asic/signal_flow_graph.py | 2 +- .../auto_scheduling_with_custom_io_times.py | 3 +- examples/fivepointwinograddft.py | 2 +- examples/folding_example_with_architecture.py | 2 +- examples/ldlt_matrix_inverse.py | 5 +- examples/lwdfallpass.py | 2 +- examples/memory_constrained_scheduling.py | 3 +- examples/secondorderdirectformiir.py | 2 +- .../secondorderdirectformiir_architecture.py | 2 +- examples/thirdorderblwdf.py | 2 +- examples/threepointwinograddft.py | 2 +- test/fixtures/schedule.py | 2 +- test/integration/test_sfg_to_architecture.py | 3 +- test/unit/test_architecture.py | 2 +- ..._schedulers.py => test_list_schedulers.py} | 371 ++++-------------- test/unit/test_schedule.py | 2 +- test/unit/test_scheduler.py | 266 +++++++++++++ test/unit/test_scheduler_gui.py | 2 +- 22 files changed, 673 insertions(+), 582 deletions(-) delete mode 100644 b_asic/core_schedulers.py create mode 100644 b_asic/list_schedulers.py rename test/unit/{test_core_schedulers.py => test_list_schedulers.py} (75%) create mode 100644 test/unit/test_scheduler.py diff --git a/b_asic/__init__.py b/b_asic/__init__.py index 5b365ed1..1d0af5f6 100644 --- a/b_asic/__init__.py +++ b/b_asic/__init__.py @@ -4,8 +4,8 @@ ASIC toolbox that simplifies circuit design and optimization. # Python modules. from b_asic.core_operations import * -from b_asic.core_schedulers import * from b_asic.graph_component import * +from b_asic.list_schedulers import * from b_asic.operation import * from b_asic.port import * from b_asic.save_load_structure import * diff --git a/b_asic/core_schedulers.py b/b_asic/core_schedulers.py deleted file mode 100644 index cce720e3..00000000 --- a/b_asic/core_schedulers.py +++ /dev/null @@ -1,206 +0,0 @@ -import copy -from typing import TYPE_CHECKING, cast - -from b_asic.scheduler import ListScheduler, Scheduler -from b_asic.special_operations import Delay, Output - -if TYPE_CHECKING: - from b_asic.schedule import Schedule - from b_asic.types import GraphID - - -class ASAPScheduler(Scheduler): - """Scheduler that implements the as-soon-as-possible (ASAP) algorithm.""" - - def apply_scheduling(self, schedule: "Schedule") -> None: - """Applies the scheduling algorithm on the given Schedule. - - Parameters - ---------- - schedule : Schedule - Schedule to apply the scheduling algorithm on. - """ - prec_list = schedule.sfg.get_precedence_list() - if len(prec_list) < 2: - raise ValueError("Empty signal flow graph cannot be scheduled.") - - # handle the first set in precedence graph (input and delays) - non_schedulable_ops = [] - for outport in prec_list[0]: - operation = outport.operation - if operation.type_name() == Delay.type_name(): - non_schedulable_ops.append(operation.graph_id) - else: - schedule.start_times[operation.graph_id] = 0 - - # handle second set in precedence graph (first operations) - for outport in prec_list[1]: - operation = outport.operation - schedule.start_times[operation.graph_id] = 0 - - # handle the remaining sets - for outports in prec_list[2:]: - for outport in outports: - operation = outport.operation - if operation.graph_id not in schedule.start_times: - op_start_time = 0 - for current_input in operation.inputs: - source_port = current_input.signals[0].source - - if source_port.operation.graph_id in non_schedulable_ops: - source_end_time = 0 - else: - source_op_time = schedule.start_times[ - source_port.operation.graph_id - ] - - if source_port.latency_offset is None: - raise ValueError( - f"Output port {source_port.index} of" - " operation" - f" {source_port.operation.graph_id} has no" - " latency-offset." - ) - - source_end_time = ( - source_op_time + source_port.latency_offset - ) - - if current_input.latency_offset is None: - raise ValueError( - f"Input port {current_input.index} of operation" - f" {current_input.operation.graph_id} has no" - " latency-offset." - ) - op_start_time_from_in = ( - source_end_time - current_input.latency_offset - ) - op_start_time = max(op_start_time, op_start_time_from_in) - - schedule.start_times[operation.graph_id] = op_start_time - - self._handle_outputs(schedule, non_schedulable_ops) - schedule.remove_delays() - - -class ALAPScheduler(Scheduler): - """Scheduler that implements the as-late-as-possible (ALAP) algorithm.""" - - def apply_scheduling(self, schedule: "Schedule") -> None: - """Applies the scheduling algorithm on the given Schedule. - - Parameters - ---------- - schedule : Schedule - Schedule to apply the scheduling algorithm on. - """ - ASAPScheduler().apply_scheduling(schedule) - max_end_time = schedule.get_max_end_time() - - if schedule.schedule_time is None: - schedule.set_schedule_time(max_end_time) - elif schedule.schedule_time < max_end_time: - raise ValueError(f"Too short schedule time. Minimum is {max_end_time}.") - - # move all outputs ALAP before operations - for output in schedule.sfg.find_by_type_name(Output.type_name()): - output = cast(Output, output) - schedule.move_operation_alap(output.graph_id) - - # move all operations ALAP - for step in reversed(schedule.sfg.get_precedence_list()): - for outport in step: - if not isinstance(outport.operation, Delay): - schedule.move_operation_alap(outport.operation.graph_id) - - -class EarliestDeadlineScheduler(ListScheduler): - """Scheduler that implements the earliest-deadline-first algorithm.""" - - @staticmethod - def _get_sorted_operations(schedule: "Schedule") -> list["GraphID"]: - schedule_copy = copy.copy(schedule) - ALAPScheduler().apply_scheduling(schedule_copy) - - deadlines = {} - for op_id, start_time in schedule_copy.start_times.items(): - deadlines[op_id] = start_time + schedule.sfg.find_by_id(op_id).latency - - return sorted(deadlines, key=deadlines.get) - - -class LeastSlackTimeScheduler(ListScheduler): - """Scheduler that implements the least slack time first algorithm.""" - - @staticmethod - def _get_sorted_operations(schedule: "Schedule") -> list["GraphID"]: - schedule_copy = copy.copy(schedule) - ALAPScheduler().apply_scheduling(schedule_copy) - - sorted_ops = sorted( - schedule_copy.start_times, key=schedule_copy.start_times.get - ) - return sorted_ops - - -class MaxFanOutScheduler(ListScheduler): - """Scheduler that implements the maximum fan-out algorithm.""" - - @staticmethod - def _get_sorted_operations(schedule: "Schedule") -> list["GraphID"]: - schedule_copy = copy.copy(schedule) - ALAPScheduler().apply_scheduling(schedule_copy) - - fan_outs = {} - for op_id, start_time in schedule_copy.start_times.items(): - fan_outs[op_id] = len(schedule.sfg.find_by_id(op_id).output_signals) - - sorted_ops = sorted(fan_outs, key=fan_outs.get, reverse=True) - return sorted_ops - - -class HybridScheduler(ListScheduler): - """Scheduler that implements a hybrid algorithm. Will receive a new name once finalized.""" - - @staticmethod - def _get_sorted_operations(schedule: "Schedule") -> list["GraphID"]: - # sort least-slack and then resort ties according to max-fan-out - schedule_copy = copy.copy(schedule) - ALAPScheduler().apply_scheduling(schedule_copy) - - sorted_items = sorted( - schedule_copy.start_times.items(), key=lambda item: item[1] - ) - - fan_out_sorted_items = [] - - last_value = sorted_items[0][0] - current_group = [] - for key, value in sorted_items: - - if value != last_value: - # the group is completed, sort it internally - sorted_group = sorted( - current_group, - key=lambda pair: len( - schedule.sfg.find_by_id(pair[0]).output_signals - ), - reverse=True, - ) - fan_out_sorted_items += sorted_group - current_group = [] - - current_group.append((key, value)) - - last_value = value - - sorted_group = sorted( - current_group, - key=lambda pair: len(schedule.sfg.find_by_id(pair[0]).output_signals), - reverse=True, - ) - fan_out_sorted_items += sorted_group - - sorted_op_list = [pair[0] for pair in fan_out_sorted_items] - - return sorted_op_list diff --git a/b_asic/list_schedulers.py b/b_asic/list_schedulers.py new file mode 100644 index 00000000..3f53a3d7 --- /dev/null +++ b/b_asic/list_schedulers.py @@ -0,0 +1,33 @@ +from b_asic.scheduler import ListScheduler + + +class EarliestDeadlineScheduler(ListScheduler): + """Scheduler that implements the earliest-deadline-first algorithm.""" + + @property + def sort_indices(self) -> tuple[tuple[int, bool]]: + return ((1, True),) + + +class LeastSlackTimeScheduler(ListScheduler): + """Scheduler that implements the least slack time first algorithm.""" + + @property + def sort_indices(self) -> tuple[tuple[int, bool]]: + return ((2, True),) + + +class MaxFanOutScheduler(ListScheduler): + """Scheduler that implements the maximum fan-out algorithm.""" + + @property + def sort_indices(self) -> tuple[tuple[int, bool]]: + return ((3, False),) + + +class HybridScheduler(ListScheduler): + """Scheduler that implements a hybrid algorithm. Will receive a new name once finalized.""" + + @property + def sort_indices(self) -> tuple[tuple[int, bool]]: + return ((2, True), (3, False)) diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py index bf9a2474..fc512557 100644 --- a/b_asic/scheduler.py +++ b/b_asic/scheduler.py @@ -1,3 +1,4 @@ +import copy import sys from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Optional, cast @@ -46,6 +47,111 @@ class Scheduler(ABC): ] + cast(int, source_port.latency_offset) +class ASAPScheduler(Scheduler): + """Scheduler that implements the as-soon-as-possible (ASAP) algorithm.""" + + def apply_scheduling(self, schedule: "Schedule") -> None: + """Applies the scheduling algorithm on the given Schedule. + + Parameters + ---------- + schedule : Schedule + Schedule to apply the scheduling algorithm on. + """ + prec_list = schedule.sfg.get_precedence_list() + if len(prec_list) < 2: + raise ValueError("Empty signal flow graph cannot be scheduled.") + + # handle the first set in precedence graph (input and delays) + non_schedulable_ops = [] + for outport in prec_list[0]: + operation = outport.operation + if operation.type_name() == Delay.type_name(): + non_schedulable_ops.append(operation.graph_id) + else: + schedule.start_times[operation.graph_id] = 0 + + # handle second set in precedence graph (first operations) + for outport in prec_list[1]: + operation = outport.operation + schedule.start_times[operation.graph_id] = 0 + + # handle the remaining sets + for outports in prec_list[2:]: + for outport in outports: + operation = outport.operation + if operation.graph_id not in schedule.start_times: + op_start_time = 0 + for current_input in operation.inputs: + source_port = current_input.signals[0].source + + if source_port.operation.graph_id in non_schedulable_ops: + source_end_time = 0 + else: + source_op_time = schedule.start_times[ + source_port.operation.graph_id + ] + + if source_port.latency_offset is None: + raise ValueError( + f"Output port {source_port.index} of" + " operation" + f" {source_port.operation.graph_id} has no" + " latency-offset." + ) + + source_end_time = ( + source_op_time + source_port.latency_offset + ) + + if current_input.latency_offset is None: + raise ValueError( + f"Input port {current_input.index} of operation" + f" {current_input.operation.graph_id} has no" + " latency-offset." + ) + op_start_time_from_in = ( + source_end_time - current_input.latency_offset + ) + op_start_time = max(op_start_time, op_start_time_from_in) + + schedule.start_times[operation.graph_id] = op_start_time + + self._handle_outputs(schedule, non_schedulable_ops) + schedule.remove_delays() + + +class ALAPScheduler(Scheduler): + """Scheduler that implements the as-late-as-possible (ALAP) algorithm.""" + + def apply_scheduling(self, schedule: "Schedule") -> None: + """Applies the scheduling algorithm on the given Schedule. + + Parameters + ---------- + schedule : Schedule + Schedule to apply the scheduling algorithm on. + """ + ASAPScheduler().apply_scheduling(schedule) + max_end_time = schedule.get_max_end_time() + + if schedule.schedule_time is None: + schedule.set_schedule_time(max_end_time) + elif schedule.schedule_time < max_end_time: + raise ValueError(f"Too short schedule time. Minimum is {max_end_time}.") + + # move all outputs ALAP before operations + for output in schedule.sfg.find_by_type_name(Output.type_name()): + output = cast(Output, output) + schedule.move_operation_alap(output.graph_id) + + # move all operations ALAP + for step in reversed(schedule.sfg.get_precedence_list()): + for outport in step: + if not isinstance(outport.operation, Delay): + schedule.move_operation_alap(outport.operation.graph_id) + + class ListScheduler(Scheduler, ABC): def __init__( self, @@ -75,6 +181,11 @@ class ListScheduler(Scheduler, ABC): self._input_times = input_times or {} self._output_delta_times = output_delta_times or {} + @property + @abstractmethod + def sort_indices(self) -> tuple[tuple[int, bool]]: + raise NotImplementedError + def apply_scheduling(self, schedule: "Schedule") -> None: """Applies the scheduling algorithm on the given Schedule. @@ -85,6 +196,11 @@ class ListScheduler(Scheduler, ABC): """ sfg = schedule.sfg + alap_schedule = copy.copy(schedule) + ALAPScheduler().apply_scheduling(alap_schedule) + alap_start_times = alap_schedule.start_times + schedule.start_times = {} + used_resources_ready_times = {} remaining_resources = self._max_resources.copy() if Input.type_name() not in remaining_resources: @@ -92,75 +208,94 @@ class ListScheduler(Scheduler, ABC): if Output.type_name() not in remaining_resources: remaining_resources[Output.type_name()] = 1 - sorted_operations = self._get_sorted_operations(schedule) + remaining_ops = ( + sfg.operations + + sfg.find_by_type_name(Input.type_name()) + + sfg.find_by_type_name(Output.type_name()) + ) + remaining_ops = [op.graph_id for op in remaining_ops] schedule.start_times = {} - remaining_reads = self._max_concurrent_reads # initial input placement if self._input_times: for input_id in self._input_times: schedule.start_times[input_id] = self._input_times[input_id] - sorted_operations = [ - elem for elem in sorted_operations if not elem.startswith("in") + remaining_ops = [ + elem for elem in remaining_ops if not elem.startswith("in") ] - current_time = 0 - timeout_counter = 0 - while sorted_operations: + remaining_ops = [op for op in remaining_ops if not op.startswith("dontcare")] + remaining_ops = [op for op in remaining_ops if not op.startswith("t")] - # generate the best schedulable candidate - candidate = sfg.find_by_id(sorted_operations[0]) - counter = 0 - while not self._candidate_is_schedulable( + current_time = 0 + time_out_counter = 0 + while remaining_ops: + ready_ops_priority_table = self._get_ready_ops_priority_table( + sfg, schedule.start_times, + current_time, + alap_start_times, + remaining_ops, + remaining_resources, + remaining_reads, + ) + while ready_ops_priority_table: + next_op = sfg.find_by_id(self._get_next_op_id(ready_ops_priority_table)) + + if next_op.type_name() in remaining_resources: + remaining_resources[next_op.type_name()] -= 1 + if ( + next_op.type_name() == Input.type_name() + or next_op.type_name() == Output.type_name() + ): + used_resources_ready_times[next_op] = current_time + 1 + else: + used_resources_ready_times[next_op] = ( + current_time + next_op.execution_time + ) + remaining_reads -= next_op.input_count + + remaining_ops = [ + op_id for op_id in remaining_ops if op_id != next_op.graph_id + ] + schedule.start_times[next_op.graph_id] = current_time + + ready_ops_priority_table = self._get_ready_ops_priority_table( + sfg, + schedule.start_times, + current_time, + alap_start_times, + remaining_ops, + remaining_resources, + remaining_reads, + ) + + current_time += 1 + time_out_counter += 1 + if time_out_counter >= 100: + raise TimeoutError( + "Algorithm did not schedule any operation for 10 time steps, " + "try relaxing constraints." + ) + + ready_ops_priority_table = self._get_ready_ops_priority_table( sfg, - candidate, + schedule.start_times, current_time, + alap_start_times, + remaining_ops, remaining_resources, remaining_reads, - self._max_concurrent_writes, - sorted_operations, - ): - if counter == len(sorted_operations): - counter = 0 - current_time += 1 - timeout_counter += 1 - - if timeout_counter > 10: - msg = "Algorithm did not schedule any operation for 10 time steps, try relaxing constraints." - raise TimeoutError(msg) + ) + # update available reads and operators + remaining_reads = self._max_concurrent_reads + for operation, ready_time in used_resources_ready_times.items(): + if ready_time == current_time: + remaining_resources[operation.type_name()] += 1 - remaining_reads = self._max_concurrent_reads - - # update available operators - for operation, ready_time in used_resources_ready_times.items(): - if ready_time == current_time: - remaining_resources[operation.type_name()] += 1 - - else: - candidate = sfg.find_by_id(sorted_operations[counter]) - counter += 1 - - timeout_counter = 0 - # if the resource is constrained, update remaining resources - if candidate.type_name() in remaining_resources: - remaining_resources[candidate.type_name()] -= 1 - if ( - candidate.type_name() == Input.type_name() - or candidate.type_name() == Output.type_name() - ): - used_resources_ready_times[candidate] = current_time + 1 - else: - used_resources_ready_times[candidate] = ( - current_time + candidate.execution_time - ) - remaining_reads -= candidate.input_count - - # schedule the best candidate to the current time - sorted_operations.remove(candidate.graph_id) - schedule.start_times[candidate.graph_id] = current_time + current_time -= 1 self._handle_outputs(schedule) @@ -173,16 +308,92 @@ class ListScheduler(Scheduler, ABC): schedule.remove_delays() - # move all dont cares ALAP - for dc_op in schedule.sfg.find_by_type_name(DontCare.type_name()): + # schedule all dont cares ALAP + for dc_op in sfg.find_by_type_name(DontCare.type_name()): dc_op = cast(DontCare, dc_op) + schedule.start_times[dc_op.graph_id] = 0 schedule.move_operation_alap(dc_op.graph_id) + def _get_next_op_id( + self, ready_ops_priority_table: list[tuple["GraphID", int, ...]] + ) -> "GraphID": + def sort_key(item): + return tuple( + (item[index] * (-1 if not asc else 1),) + for index, asc in self.sort_indices + ) + + sorted_table = sorted(ready_ops_priority_table, key=sort_key) + return sorted_table[0][0] + + def _get_ready_ops_priority_table( + self, + sfg: "SFG", + start_times: dict["GraphID", int], + current_time: int, + alap_start_times: dict["GraphID", int], + remaining_ops: list["GraphID"], + remaining_resources: dict["GraphID", int], + remaining_reads: int, + ) -> list[tuple["GraphID", int, int, int]]: + + ready_ops = [ + op_id + for op_id in remaining_ops + if self._op_is_schedulable( + start_times, + sfg, + sfg.find_by_id(op_id), + current_time, + remaining_resources, + remaining_reads, + self._max_concurrent_writes, + remaining_ops, + ) + ] + + deadlines = self._calculate_deadlines(sfg, alap_start_times) + output_slacks = self._calculate_alap_output_slacks( + current_time, alap_start_times + ) + fan_outs = self._calculate_fan_outs(sfg, alap_start_times) + + ready_ops_priority_table = [] + for op_id in ready_ops: + ready_ops_priority_table.append( + (op_id, deadlines[op_id], output_slacks[op_id], fan_outs[op_id]) + ) + return ready_ops_priority_table + + def _calculate_deadlines( + self, sfg, alap_start_times: dict["GraphID", int] + ) -> dict["GraphID", int]: + return { + op_id: start_time + sfg.find_by_id(op_id).latency + for op_id, start_time in alap_start_times.items() + } + + def _calculate_alap_output_slacks( + self, current_time: int, alap_start_times: dict["GraphID", int] + ) -> dict["GraphID", int]: + return { + op_id: start_time - current_time + for op_id, start_time in alap_start_times.items() + } + + def _calculate_fan_outs( + self, sfg: "SFG", alap_start_times: dict["GraphID", int] + ) -> dict["GraphID", int]: + return { + op_id: len(sfg.find_by_id(op_id).output_signals) + for op_id, start_time in alap_start_times.items() + } + @staticmethod - def _candidate_is_schedulable( + def _op_is_schedulable( start_times: dict["GraphID"], sfg: "SFG", - operation: "Operation", + op: "Operation", current_time: int, remaining_resources: dict["GraphID", int], remaining_reads: int, @@ -190,12 +401,12 @@ class ListScheduler(Scheduler, ABC): remaining_ops: list["GraphID"], ) -> bool: if ( - operation.type_name() in remaining_resources - and remaining_resources[operation.type_name()] == 0 + op.type_name() in remaining_resources + and remaining_resources[op.type_name()] == 0 ): return False - op_finish_time = current_time + operation.latency + op_finish_time = current_time + op.latency future_ops = [ sfg.find_by_id(item[0]) for item in start_times.items() @@ -205,16 +416,16 @@ class ListScheduler(Scheduler, ABC): future_ops_writes = sum([op.input_count for op in future_ops]) if ( - not operation.graph_id.startswith("out") + not op.graph_id.startswith("out") and future_ops_writes >= max_concurrent_writes ): return False read_counter = 0 earliest_start_time = 0 - for op_input in operation.inputs: + for op_input in op.inputs: source_op = op_input.signals[0].source.operation - if isinstance(source_op, Delay): + if isinstance(source_op, Delay) or isinstance(source_op, DontCare): continue source_op_graph_id = source_op.graph_id @@ -235,10 +446,6 @@ class ListScheduler(Scheduler, ABC): return earliest_start_time <= current_time - @abstractmethod - def _get_sorted_operations(schedule: "Schedule") -> list["GraphID"]: - raise NotImplementedError - def _handle_outputs( self, schedule: "Schedule", non_schedulable_ops: Optional[list["GraphID"]] = [] ) -> None: diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py index 74f5698f..903ab2af 100644 --- a/b_asic/signal_flow_graph.py +++ b/b_asic/signal_flow_graph.py @@ -29,7 +29,6 @@ from typing import ( import numpy as np from graphviz import Digraph -from b_asic.core_schedulers import ASAPScheduler from b_asic.graph_component import GraphComponent from b_asic.operation import ( AbstractOperation, @@ -1708,6 +1707,7 @@ class SFG(AbstractOperation): """Return the time of the critical path.""" # Import here needed to avoid circular imports from b_asic.schedule import Schedule + from b_asic.scheduler import ASAPScheduler return Schedule(self, ASAPScheduler()).schedule_time diff --git a/examples/auto_scheduling_with_custom_io_times.py b/examples/auto_scheduling_with_custom_io_times.py index 8913bfd8..c981f467 100644 --- a/examples/auto_scheduling_with_custom_io_times.py +++ b/examples/auto_scheduling_with_custom_io_times.py @@ -6,8 +6,9 @@ Auto Scheduling With Custom IO times """ from b_asic.core_operations import Butterfly, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler, HybridScheduler +from b_asic.list_schedulers import HybridScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.sfg_generators import radix_2_dif_fft sfg = radix_2_dif_fft(points=8) diff --git a/examples/fivepointwinograddft.py b/examples/fivepointwinograddft.py index 856dcb6c..d2156151 100644 --- a/examples/fivepointwinograddft.py +++ b/examples/fivepointwinograddft.py @@ -13,8 +13,8 @@ import networkx as nx from b_asic.architecture import Architecture, Memory, ProcessingElement from b_asic.core_operations import AddSub, Butterfly, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Input, Output diff --git a/examples/folding_example_with_architecture.py b/examples/folding_example_with_architecture.py index 66e40299..43bdc798 100644 --- a/examples/folding_example_with_architecture.py +++ b/examples/folding_example_with_architecture.py @@ -16,8 +16,8 @@ shorter than the scheduling period. from b_asic.architecture import Architecture, Memory, ProcessingElement from b_asic.core_operations import Addition, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Delay, Input, Output diff --git a/examples/ldlt_matrix_inverse.py b/examples/ldlt_matrix_inverse.py index cf5961aa..ffe74c83 100644 --- a/examples/ldlt_matrix_inverse.py +++ b/examples/ldlt_matrix_inverse.py @@ -7,15 +7,14 @@ LDLT Matrix Inversion Algorithm from b_asic.architecture import Architecture, Memory, ProcessingElement from b_asic.core_operations import MADS, DontCare, Reciprocal -from b_asic.core_schedulers import ( - ALAPScheduler, - ASAPScheduler, +from b_asic.list_schedulers import ( EarliestDeadlineScheduler, HybridScheduler, LeastSlackTimeScheduler, MaxFanOutScheduler, ) from b_asic.schedule import Schedule +from b_asic.scheduler import ALAPScheduler, ASAPScheduler from b_asic.sfg_generators import ldlt_matrix_inverse from b_asic.special_operations import Input, Output diff --git a/examples/lwdfallpass.py b/examples/lwdfallpass.py index 7eb78ca7..6bbde629 100644 --- a/examples/lwdfallpass.py +++ b/examples/lwdfallpass.py @@ -8,8 +8,8 @@ This has different latency offsets for the different inputs/outputs. """ from b_asic.core_operations import SymmetricTwoportAdaptor -from b_asic.core_schedulers import ASAPScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Delay, Input, Output diff --git a/examples/memory_constrained_scheduling.py b/examples/memory_constrained_scheduling.py index c1f81fae..a4719cc0 100644 --- a/examples/memory_constrained_scheduling.py +++ b/examples/memory_constrained_scheduling.py @@ -7,8 +7,9 @@ Memory Constrained Scheduling from b_asic.architecture import Architecture, Memory, ProcessingElement from b_asic.core_operations import Butterfly, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler, HybridScheduler +from b_asic.list_schedulers import HybridScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.sfg_generators import radix_2_dif_fft from b_asic.special_operations import Input, Output diff --git a/examples/secondorderdirectformiir.py b/examples/secondorderdirectformiir.py index 772d8fb8..aa84c26b 100644 --- a/examples/secondorderdirectformiir.py +++ b/examples/secondorderdirectformiir.py @@ -6,8 +6,8 @@ Second-order IIR Filter with Schedule """ from b_asic.core_operations import Addition, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Delay, Input, Output diff --git a/examples/secondorderdirectformiir_architecture.py b/examples/secondorderdirectformiir_architecture.py index 2147961f..a7d72fb3 100644 --- a/examples/secondorderdirectformiir_architecture.py +++ b/examples/secondorderdirectformiir_architecture.py @@ -7,8 +7,8 @@ Second-order IIR Filter with Architecture from b_asic.architecture import Architecture, Memory, ProcessingElement from b_asic.core_operations import Addition, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Delay, Input, Output diff --git a/examples/thirdorderblwdf.py b/examples/thirdorderblwdf.py index 0b815731..d29fd215 100644 --- a/examples/thirdorderblwdf.py +++ b/examples/thirdorderblwdf.py @@ -10,8 +10,8 @@ import numpy as np from mplsignal.freq_plots import freqz_fir from b_asic.core_operations import Addition, SymmetricTwoportAdaptor -from b_asic.core_schedulers import ASAPScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.signal_flow_graph import SFG from b_asic.signal_generator import Impulse from b_asic.simulation import Simulation diff --git a/examples/threepointwinograddft.py b/examples/threepointwinograddft.py index 5dc962e6..7e7a58ac 100644 --- a/examples/threepointwinograddft.py +++ b/examples/threepointwinograddft.py @@ -11,8 +11,8 @@ import networkx as nx from b_asic.architecture import Architecture, Memory, ProcessingElement from b_asic.core_operations import AddSub, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Input, Output diff --git a/test/fixtures/schedule.py b/test/fixtures/schedule.py index b5e9d0fb..39b375ed 100644 --- a/test/fixtures/schedule.py +++ b/test/fixtures/schedule.py @@ -1,8 +1,8 @@ import pytest from b_asic.core_operations import Addition, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.signal_flow_graph import SFG diff --git a/test/integration/test_sfg_to_architecture.py b/test/integration/test_sfg_to_architecture.py index c07e9766..3bc47504 100644 --- a/test/integration/test_sfg_to_architecture.py +++ b/test/integration/test_sfg_to_architecture.py @@ -8,8 +8,9 @@ from b_asic.core_operations import ( DontCare, Reciprocal, ) -from b_asic.core_schedulers import ASAPScheduler, HybridScheduler +from b_asic.list_schedulers import HybridScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.sfg_generators import ldlt_matrix_inverse, radix_2_dif_fft from b_asic.special_operations import Input, Output diff --git a/test/unit/test_architecture.py b/test/unit/test_architecture.py index 432f0652..ce2d7bcf 100644 --- a/test/unit/test_architecture.py +++ b/test/unit/test_architecture.py @@ -6,10 +6,10 @@ import pytest from b_asic.architecture import Architecture, Memory, ProcessingElement from b_asic.core_operations import Addition, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler from b_asic.process import PlainMemoryVariable from b_asic.resources import ProcessCollection from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler from b_asic.special_operations import Input, Output diff --git a/test/unit/test_core_schedulers.py b/test/unit/test_list_schedulers.py similarity index 75% rename from test/unit/test_core_schedulers.py rename to test/unit/test_list_schedulers.py index 755f5cc6..136e285a 100644 --- a/test/unit/test_core_schedulers.py +++ b/test/unit/test_list_schedulers.py @@ -9,9 +9,7 @@ from b_asic.core_operations import ( ConstantMultiplication, Reciprocal, ) -from b_asic.core_schedulers import ( - ALAPScheduler, - ASAPScheduler, +from b_asic.list_schedulers import ( EarliestDeadlineScheduler, HybridScheduler, LeastSlackTimeScheduler, @@ -26,266 +24,6 @@ from b_asic.sfg_generators import ( from b_asic.special_operations import Input, Output -class TestASAPScheduler: - def test_empty_sfg(self, sfg_empty): - with pytest.raises( - ValueError, match="Empty signal flow graph cannot be scheduled." - ): - Schedule(sfg_empty, scheduler=ASAPScheduler()) - - def test_direct_form_1_iir(self): - sfg = direct_form_1_iir([1, 2, 3], [1, 2, 3]) - - sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) - sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) - sfg.set_latency_of_type(Addition.type_name(), 3) - sfg.set_execution_time_of_type(Addition.type_name(), 1) - - schedule = Schedule(sfg, scheduler=ASAPScheduler()) - - assert schedule.start_times == { - "in0": 0, - "cmul0": 0, - "cmul1": 0, - "cmul2": 0, - "cmul3": 0, - "cmul4": 0, - "add3": 2, - "add1": 2, - "add0": 5, - "add2": 8, - "out0": 11, - } - assert schedule.schedule_time == 11 - - def test_direct_form_2_iir(self, sfg_direct_form_iir_lp_filter): - sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) - sfg_direct_form_iir_lp_filter.set_latency_of_type( - ConstantMultiplication.type_name(), 4 - ) - - schedule = Schedule(sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler()) - - assert schedule.start_times == { - "in0": 0, - "cmul1": 0, - "cmul4": 0, - "cmul2": 0, - "cmul3": 0, - "add3": 4, - "add1": 4, - "add0": 9, - "cmul0": 14, - "add2": 18, - "out0": 23, - } - assert schedule.schedule_time == 23 - - def test_direct_form_2_iir_with_scheduling_time( - self, sfg_direct_form_iir_lp_filter - ): - sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) - sfg_direct_form_iir_lp_filter.set_latency_of_type( - ConstantMultiplication.type_name(), 4 - ) - - schedule = Schedule( - sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler(), schedule_time=30 - ) - - assert schedule.start_times == { - "in0": 0, - "cmul1": 0, - "cmul4": 0, - "cmul2": 0, - "cmul3": 0, - "add3": 4, - "add1": 4, - "add0": 9, - "cmul0": 14, - "add2": 18, - "out0": 23, - } - assert schedule.schedule_time == 30 - - def test_radix_2_fft_8_points(self): - sfg = radix_2_dif_fft(points=8) - - sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) - sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) - sfg.set_latency_of_type(Butterfly.type_name(), 1) - sfg.set_execution_time_of_type(Butterfly.type_name(), 1) - - schedule = Schedule(sfg, scheduler=ASAPScheduler()) - - assert schedule.start_times == { - "in0": 0, - "in1": 0, - "in2": 0, - "in3": 0, - "in4": 0, - "in5": 0, - "in6": 0, - "in7": 0, - "bfly0": 0, - "bfly6": 0, - "bfly8": 0, - "bfly11": 0, - "cmul3": 1, - "bfly7": 1, - "cmul2": 1, - "bfly1": 1, - "cmul0": 1, - "cmul4": 2, - "bfly9": 2, - "bfly5": 3, - "bfly2": 3, - "out0": 3, - "out4": 3, - "bfly10": 4, - "cmul1": 4, - "bfly3": 4, - "out1": 5, - "out2": 5, - "out5": 5, - "out6": 5, - "bfly4": 6, - "out3": 7, - "out7": 7, - } - assert schedule.schedule_time == 7 - - -class TestALAPScheduler: - def test_empty_sfg(self, sfg_empty): - with pytest.raises( - ValueError, match="Empty signal flow graph cannot be scheduled." - ): - Schedule(sfg_empty, scheduler=ALAPScheduler()) - - def test_direct_form_1_iir(self): - sfg = direct_form_1_iir([1, 2, 3], [1, 2, 3]) - - sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) - sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) - sfg.set_latency_of_type(Addition.type_name(), 3) - sfg.set_execution_time_of_type(Addition.type_name(), 1) - - schedule = Schedule(sfg, scheduler=ALAPScheduler()) - - assert schedule.start_times == { - "cmul3": 0, - "cmul4": 0, - "add1": 2, - "in0": 3, - "cmul0": 3, - "cmul1": 3, - "cmul2": 3, - "add3": 5, - "add0": 5, - "add2": 8, - "out0": 11, - } - assert schedule.schedule_time == 11 - - def test_direct_form_2_iir(self, sfg_direct_form_iir_lp_filter): - sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) - sfg_direct_form_iir_lp_filter.set_latency_of_type( - ConstantMultiplication.type_name(), 4 - ) - - schedule = Schedule(sfg_direct_form_iir_lp_filter, scheduler=ALAPScheduler()) - - assert schedule.start_times == { - "cmul3": 0, - "cmul4": 0, - "add1": 4, - "in0": 9, - "cmul2": 9, - "cmul1": 9, - "add0": 9, - "add3": 13, - "cmul0": 14, - "add2": 18, - "out0": 23, - } - assert schedule.schedule_time == 23 - - def test_direct_form_2_iir_with_scheduling_time( - self, sfg_direct_form_iir_lp_filter - ): - sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) - sfg_direct_form_iir_lp_filter.set_latency_of_type( - ConstantMultiplication.type_name(), 4 - ) - - schedule = Schedule( - sfg_direct_form_iir_lp_filter, scheduler=ALAPScheduler(), schedule_time=30 - ) - - assert schedule.start_times == { - "cmul3": 7, - "cmul4": 7, - "add1": 11, - "in0": 16, - "cmul2": 16, - "cmul1": 16, - "add0": 16, - "add3": 20, - "cmul0": 21, - "add2": 25, - "out0": 30, - } - assert schedule.schedule_time == 30 - - def test_radix_2_fft_8_points(self): - sfg = radix_2_dif_fft(points=8) - - sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) - sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) - sfg.set_latency_of_type(Butterfly.type_name(), 1) - sfg.set_execution_time_of_type(Butterfly.type_name(), 1) - - schedule = Schedule(sfg, scheduler=ALAPScheduler()) - - assert schedule.start_times == { - "in3": 0, - "in7": 0, - "in1": 0, - "in5": 0, - "bfly6": 0, - "bfly8": 0, - "cmul2": 1, - "cmul3": 1, - "in2": 2, - "in6": 2, - "bfly11": 2, - "bfly7": 3, - "cmul0": 3, - "bfly5": 3, - "in0": 4, - "in4": 4, - "cmul4": 4, - "cmul1": 4, - "bfly0": 4, - "bfly1": 5, - "bfly2": 5, - "bfly9": 6, - "bfly10": 6, - "bfly3": 6, - "bfly4": 6, - "out0": 7, - "out1": 7, - "out2": 7, - "out3": 7, - "out4": 7, - "out5": 7, - "out6": 7, - "out7": 7, - } - assert schedule.schedule_time == 7 - - class TestEarliestDeadlineScheduler: def test_empty_sfg(self, sfg_empty): with pytest.raises( @@ -313,14 +51,14 @@ class TestEarliestDeadlineScheduler: assert schedule.start_times == { "in0": 0, - "cmul4": 0, - "cmul3": 1, + "cmul3": 0, + "cmul4": 1, "cmul0": 2, "add1": 3, "cmul1": 3, "cmul2": 4, - "add3": 6, - "add0": 7, + "add0": 6, + "add3": 7, "add2": 10, "out0": 13, } @@ -351,8 +89,8 @@ class TestEarliestDeadlineScheduler: ) assert schedule.start_times == { "in0": 0, - "cmul4": 0, - "cmul3": 1, + "cmul3": 0, + "cmul4": 1, "cmul1": 2, "cmul2": 3, "add1": 4, @@ -487,14 +225,14 @@ class TestLeastSlackTimeScheduler: assert schedule.start_times == { "in0": 0, - "cmul4": 0, - "cmul3": 1, + "cmul3": 0, + "cmul4": 1, "cmul0": 2, "add1": 3, "cmul1": 3, "cmul2": 4, - "add3": 6, - "add0": 7, + "add0": 6, + "add3": 7, "add2": 10, "out0": 13, } @@ -525,8 +263,8 @@ class TestLeastSlackTimeScheduler: ) assert schedule.start_times == { "in0": 0, - "cmul4": 0, - "cmul3": 1, + "cmul3": 0, + "cmul4": 1, "cmul1": 2, "cmul2": 3, "add1": 4, @@ -657,8 +395,8 @@ class TestMaxFanOutScheduler: "cmul0": 0, "cmul1": 1, "cmul2": 2, - "cmul4": 3, - "cmul3": 4, + "cmul3": 3, + "cmul4": 4, "add3": 4, "add1": 6, "add0": 9, @@ -667,6 +405,57 @@ class TestMaxFanOutScheduler: } assert schedule.schedule_time == 15 + def test_ldlt_inverse_3x3(self): + sfg = ldlt_matrix_inverse(N=3) + + sfg.set_latency_of_type(MADS.type_name(), 3) + sfg.set_latency_of_type(Reciprocal.type_name(), 2) + sfg.set_execution_time_of_type(MADS.type_name(), 1) + sfg.set_execution_time_of_type(Reciprocal.type_name(), 1) + + resources = {MADS.type_name(): 1, Reciprocal.type_name(): 1} + schedule = Schedule(sfg, scheduler=MaxFanOutScheduler(resources)) + + assert schedule.start_times == { + "in1": 0, + "in2": 1, + "in0": 2, + "rec0": 2, + "in4": 3, + "in5": 4, + "mads1": 4, + "dontcare4": 4, + "dontcare5": 5, + "in3": 5, + "mads0": 5, + "mads13": 7, + "mads12": 8, + "mads14": 9, + "rec1": 12, + "mads8": 14, + "dontcare2": 14, + "mads10": 17, + "rec2": 20, + "mads9": 22, + "out5": 22, + "dontcare0": 22, + "dontcare1": 23, + "mads11": 23, + "mads7": 25, + "out4": 25, + "mads3": 26, + "mads6": 27, + "dontcare3": 27, + "out3": 28, + "mads2": 29, + "out2": 29, + "mads5": 30, + "mads4": 33, + "out1": 33, + "out0": 36, + } + assert schedule.schedule_time == 36 + class TestHybridScheduler: def test_empty_sfg(self, sfg_empty): @@ -688,14 +477,14 @@ class TestHybridScheduler: assert schedule.start_times == { "in0": 0, - "cmul4": 0, - "cmul3": 1, + "cmul3": 0, + "cmul4": 1, "cmul0": 2, "add1": 3, "cmul1": 3, "cmul2": 4, - "add3": 6, - "add0": 7, + "add0": 6, + "add3": 7, "add2": 10, "out0": 13, } @@ -797,13 +586,13 @@ class TestHybridScheduler: "bfly3": 5, "out0": 5, "bfly4": 6, - "out1": 6, - "out2": 12, - "out3": 11, + "out2": 6, + "out3": 7, + "out7": 8, + "out6": 9, "out4": 10, - "out5": 9, - "out6": 8, - "out7": 7, + "out1": 11, + "out5": 12, } assert schedule.schedule_time == 12 @@ -871,9 +660,9 @@ class TestHybridScheduler: "bfly5": 12, "cmul4": 13, "bfly9": 13, - "bfly10": 15, + "bfly3": 15, "cmul1": 15, - "bfly3": 16, + "bfly10": 16, "bfly4": 17, "out0": 18, "out1": 19, @@ -945,9 +734,9 @@ class TestHybridScheduler: "bfly5": 12, "cmul4": 13, "bfly9": 13, - "bfly10": 15, + "bfly3": 15, "cmul1": 15, - "bfly3": 16, + "bfly10": 16, "bfly4": 17, "out0": 18, "out1": 19, diff --git a/test/unit/test_schedule.py b/test/unit/test_schedule.py index 456d53f4..bbd98c36 100644 --- a/test/unit/test_schedule.py +++ b/test/unit/test_schedule.py @@ -8,9 +8,9 @@ import matplotlib.testing.decorators import pytest from b_asic.core_operations import Addition, Butterfly, ConstantMultiplication -from b_asic.core_schedulers import ALAPScheduler, ASAPScheduler from b_asic.process import OperatorProcess from b_asic.schedule import Schedule +from b_asic.scheduler import ALAPScheduler, ASAPScheduler from b_asic.sfg_generators import direct_form_fir from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Delay, Input, Output diff --git a/test/unit/test_scheduler.py b/test/unit/test_scheduler.py new file mode 100644 index 00000000..9ec682b2 --- /dev/null +++ b/test/unit/test_scheduler.py @@ -0,0 +1,266 @@ +import pytest + +from b_asic.core_operations import Addition, Butterfly, ConstantMultiplication +from b_asic.schedule import Schedule +from b_asic.scheduler import ALAPScheduler, ASAPScheduler +from b_asic.sfg_generators import direct_form_1_iir, radix_2_dif_fft + + +class TestASAPScheduler: + def test_empty_sfg(self, sfg_empty): + with pytest.raises( + ValueError, match="Empty signal flow graph cannot be scheduled." + ): + Schedule(sfg_empty, scheduler=ASAPScheduler()) + + def test_direct_form_1_iir(self): + sfg = direct_form_1_iir([1, 2, 3], [1, 2, 3]) + + sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) + sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) + sfg.set_latency_of_type(Addition.type_name(), 3) + sfg.set_execution_time_of_type(Addition.type_name(), 1) + + schedule = Schedule(sfg, scheduler=ASAPScheduler()) + + assert schedule.start_times == { + "in0": 0, + "cmul0": 0, + "cmul1": 0, + "cmul2": 0, + "cmul3": 0, + "cmul4": 0, + "add3": 2, + "add1": 2, + "add0": 5, + "add2": 8, + "out0": 11, + } + assert schedule.schedule_time == 11 + + def test_direct_form_2_iir(self, sfg_direct_form_iir_lp_filter): + sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) + sfg_direct_form_iir_lp_filter.set_latency_of_type( + ConstantMultiplication.type_name(), 4 + ) + + schedule = Schedule(sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler()) + + assert schedule.start_times == { + "in0": 0, + "cmul1": 0, + "cmul4": 0, + "cmul2": 0, + "cmul3": 0, + "add3": 4, + "add1": 4, + "add0": 9, + "cmul0": 14, + "add2": 18, + "out0": 23, + } + assert schedule.schedule_time == 23 + + def test_direct_form_2_iir_with_scheduling_time( + self, sfg_direct_form_iir_lp_filter + ): + sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) + sfg_direct_form_iir_lp_filter.set_latency_of_type( + ConstantMultiplication.type_name(), 4 + ) + + schedule = Schedule( + sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler(), schedule_time=30 + ) + + assert schedule.start_times == { + "in0": 0, + "cmul1": 0, + "cmul4": 0, + "cmul2": 0, + "cmul3": 0, + "add3": 4, + "add1": 4, + "add0": 9, + "cmul0": 14, + "add2": 18, + "out0": 23, + } + assert schedule.schedule_time == 30 + + def test_radix_2_fft_8_points(self): + sfg = radix_2_dif_fft(points=8) + + sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) + sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) + sfg.set_latency_of_type(Butterfly.type_name(), 1) + sfg.set_execution_time_of_type(Butterfly.type_name(), 1) + + schedule = Schedule(sfg, scheduler=ASAPScheduler()) + + assert schedule.start_times == { + "in0": 0, + "in1": 0, + "in2": 0, + "in3": 0, + "in4": 0, + "in5": 0, + "in6": 0, + "in7": 0, + "bfly0": 0, + "bfly6": 0, + "bfly8": 0, + "bfly11": 0, + "cmul3": 1, + "bfly7": 1, + "cmul2": 1, + "bfly1": 1, + "cmul0": 1, + "cmul4": 2, + "bfly9": 2, + "bfly5": 3, + "bfly2": 3, + "out0": 3, + "out4": 3, + "bfly10": 4, + "cmul1": 4, + "bfly3": 4, + "out1": 5, + "out2": 5, + "out5": 5, + "out6": 5, + "bfly4": 6, + "out3": 7, + "out7": 7, + } + assert schedule.schedule_time == 7 + + +class TestALAPScheduler: + def test_empty_sfg(self, sfg_empty): + with pytest.raises( + ValueError, match="Empty signal flow graph cannot be scheduled." + ): + Schedule(sfg_empty, scheduler=ALAPScheduler()) + + def test_direct_form_1_iir(self): + sfg = direct_form_1_iir([1, 2, 3], [1, 2, 3]) + + sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) + sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) + sfg.set_latency_of_type(Addition.type_name(), 3) + sfg.set_execution_time_of_type(Addition.type_name(), 1) + + schedule = Schedule(sfg, scheduler=ALAPScheduler()) + + assert schedule.start_times == { + "cmul3": 0, + "cmul4": 0, + "add1": 2, + "in0": 3, + "cmul0": 3, + "cmul1": 3, + "cmul2": 3, + "add3": 5, + "add0": 5, + "add2": 8, + "out0": 11, + } + assert schedule.schedule_time == 11 + + def test_direct_form_2_iir(self, sfg_direct_form_iir_lp_filter): + sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) + sfg_direct_form_iir_lp_filter.set_latency_of_type( + ConstantMultiplication.type_name(), 4 + ) + + schedule = Schedule(sfg_direct_form_iir_lp_filter, scheduler=ALAPScheduler()) + + assert schedule.start_times == { + "cmul3": 0, + "cmul4": 0, + "add1": 4, + "in0": 9, + "cmul2": 9, + "cmul1": 9, + "add0": 9, + "add3": 13, + "cmul0": 14, + "add2": 18, + "out0": 23, + } + assert schedule.schedule_time == 23 + + def test_direct_form_2_iir_with_scheduling_time( + self, sfg_direct_form_iir_lp_filter + ): + sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) + sfg_direct_form_iir_lp_filter.set_latency_of_type( + ConstantMultiplication.type_name(), 4 + ) + + schedule = Schedule( + sfg_direct_form_iir_lp_filter, scheduler=ALAPScheduler(), schedule_time=30 + ) + + assert schedule.start_times == { + "cmul3": 7, + "cmul4": 7, + "add1": 11, + "in0": 16, + "cmul2": 16, + "cmul1": 16, + "add0": 16, + "add3": 20, + "cmul0": 21, + "add2": 25, + "out0": 30, + } + assert schedule.schedule_time == 30 + + def test_radix_2_fft_8_points(self): + sfg = radix_2_dif_fft(points=8) + + sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) + sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) + sfg.set_latency_of_type(Butterfly.type_name(), 1) + sfg.set_execution_time_of_type(Butterfly.type_name(), 1) + + schedule = Schedule(sfg, scheduler=ALAPScheduler()) + + assert schedule.start_times == { + "in3": 0, + "in7": 0, + "in1": 0, + "in5": 0, + "bfly6": 0, + "bfly8": 0, + "cmul2": 1, + "cmul3": 1, + "in2": 2, + "in6": 2, + "bfly11": 2, + "bfly7": 3, + "cmul0": 3, + "bfly5": 3, + "in0": 4, + "in4": 4, + "cmul4": 4, + "cmul1": 4, + "bfly0": 4, + "bfly1": 5, + "bfly2": 5, + "bfly9": 6, + "bfly10": 6, + "bfly3": 6, + "bfly4": 6, + "out0": 7, + "out1": 7, + "out2": 7, + "out3": 7, + "out4": 7, + "out5": 7, + "out6": 7, + "out7": 7, + } + assert schedule.schedule_time == 7 diff --git a/test/unit/test_scheduler_gui.py b/test/unit/test_scheduler_gui.py index 99e84850..02e9d36e 100644 --- a/test/unit/test_scheduler_gui.py +++ b/test/unit/test_scheduler_gui.py @@ -1,8 +1,8 @@ import pytest from b_asic.core_operations import Addition, ConstantMultiplication -from b_asic.core_schedulers import ASAPScheduler from b_asic.schedule import Schedule +from b_asic.scheduler import ASAPScheduler try: from b_asic.scheduler_gui.main_window import ScheduleMainWindow -- GitLab