diff --git a/.gitignore b/.gitignore index 52db8cb9aeab4fa7229202d8e66a8080e81fa27d..c5b2148a43663314ad0317ed1d16a12c0627085e 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,4 @@ result_images/ .coverage Digraph.gv Digraph.gv.pdf +*.pstat diff --git a/b_asic/__init__.py b/b_asic/__init__.py index 423e4676d9eda02aed6ec6accff2900743a0a04f..5b365ed17b584f6be96d7ec90d216fa6709306a5 100644 --- a/b_asic/__init__.py +++ b/b_asic/__init__.py @@ -4,6 +4,7 @@ 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.operation import * from b_asic.port import * diff --git a/b_asic/core_schedulers.py b/b_asic/core_schedulers.py new file mode 100644 index 0000000000000000000000000000000000000000..05bcc3f454dc59ab48b776baa3e46281c7da3451 --- /dev/null +++ b/b_asic/core_schedulers.py @@ -0,0 +1,124 @@ +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.deepcopy(schedule) + ALAPScheduler().apply_scheduling(schedule_copy) + return sorted(schedule_copy.start_times, key=schedule_copy.start_times.get) diff --git a/b_asic/schedule.py b/b_asic/schedule.py index 4c7f01b6dea0be070b775e84605c0b0b84363ab8..922f3dbc8d1d21010865124db3eaf228734a6dae 100644 --- a/b_asic/schedule.py +++ b/b_asic/schedule.py @@ -793,117 +793,6 @@ class Schedule: new_sfg = new_sfg.insert_operation_before(op, Delay(), port) return new_sfg() - # def _schedule_alap(self) -> None: - # """Schedule the operations using as-late-as-possible scheduling.""" - # precedence_list = self._sfg.get_precedence_list() - # self._schedule_asap() - # max_end_time = self.get_max_end_time() - - # if self.schedule_time is None: - # self._schedule_time = max_end_time - # elif self.schedule_time < max_end_time: - # raise ValueError(f"Too short schedule time. Minimum is {max_end_time}.") - - # for output in self._sfg.find_by_type_name(Output.type_name()): - # output = cast(Output, output) - # self.move_operation_alap(output.graph_id) - # for step in reversed(precedence_list): - # graph_ids = { - # outport.operation.graph_id - # for outport in step - # if not isinstance(outport.operation, Delay) - # } - # for graph_id in graph_ids: - # self.move_operation_alap(graph_id) - - # def _schedule_asap(self) -> None: - # """Schedule the operations using as-soon-as-possible scheduling.""" - # precedence_list = self._sfg.get_precedence_list() - - # if len(precedence_list) < 2: - # raise ValueError("Empty signal flow graph cannot be scheduled.") - - # non_schedulable_ops = set() - # for outport in precedence_list[0]: - # operation = outport.operation - # if operation.type_name() not in [Delay.type_name()]: - # if operation.graph_id not in self._start_times: - # # Set start time of all operations in the first iter to 0 - # self._start_times[operation.graph_id] = 0 - # else: - # non_schedulable_ops.add(operation.graph_id) - - # for outport in precedence_list[1]: - # operation = outport.operation - # if operation.graph_id not in self._start_times: - # # Set start time of all operations in the first iter to 0 - # self._start_times[operation.graph_id] = 0 - - # for outports in precedence_list[2:]: - # for outport in outports: - # operation = outport.operation - # if operation.graph_id not in self._start_times: - # # Schedule the operation if it does not have a start time yet. - # op_start_time = 0 - # for current_input in operation.inputs: - # if len(current_input.signals) != 1: - # raise ValueError( - # "Error in scheduling, dangling input port detected." - # ) - # if current_input.signals[0].source is None: - # raise ValueError( - # "Error in scheduling, signal with no source detected." - # ) - # 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 = self._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) - - # self._start_times[operation.graph_id] = op_start_time - # for output in self._sfg.find_by_type_name(Output.type_name()): - # output = cast(Output, output) - # source_port = cast(OutputPort, output.inputs[0].signals[0].source) - # if source_port.operation.graph_id in non_schedulable_ops: - # self._start_times[output.graph_id] = 0 - # else: - # 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." - # ) - # self._start_times[output.graph_id] = self._start_times[ - # source_port.operation.graph_id - # ] + cast(int, source_port.latency_offset) - # self._remove_delays() - def _get_memory_variables_list(self) -> List[MemoryVariable]: ret: List[MemoryVariable] = [] for graph_id, start_time in self._start_times.items(): diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py index e109d01d9de77ed74b7f685ead2982d085def749..cc47294527a31eff720fe2dfb11594436ce19dc8 100644 --- a/b_asic/scheduler.py +++ b/b_asic/scheduler.py @@ -1,6 +1,4 @@ -import sys from abc import ABC, abstractmethod -from collections import defaultdict from typing import TYPE_CHECKING, Optional, cast from b_asic.port import OutputPort @@ -8,7 +6,9 @@ from b_asic.special_operations import Delay, Input, Output from b_asic.types import TypeName if TYPE_CHECKING: + from b_asic.operation import Operation from b_asic.schedule import Schedule + from b_asic.types import GraphID class Scheduler(ABC): @@ -23,7 +23,9 @@ class Scheduler(ABC): """ raise NotImplementedError - def _handle_outputs(self, schedule, non_schedulable_ops=set()) -> None: + def _handle_outputs( + self, schedule: "Schedule", non_schedulable_ops: Optional[list["GraphID"]] = [] + ) -> None: for output in schedule.sfg.find_by_type_name(Output.type_name()): output = cast(Output, output) source_port = cast(OutputPort, output.inputs[0].signals[0].source) @@ -41,124 +43,7 @@ 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 = set() - for outport in prec_list[0]: - operation = outport.operation - if operation.type_name() == Delay.type_name(): - non_schedulable_ops.add(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(Scheduler): - """ - Scheduler that implements the earliest-deadline-first algorithm. - - Parameters - ---------- - max_resources : dict, optional - Dictionary like ``{Addition.type_name(): 2}`` denoting the maximum number of - resources for a given operation type if the scheduling algorithm considers - that. If not provided, or an operation type is not provided, at most one - resource is used. - """ - +class ListScheduler(Scheduler, ABC): def __init__(self, max_resources: Optional[dict[TypeName, int]] = None) -> None: if max_resources: self._max_resources = max_resources @@ -173,97 +58,98 @@ class EarliestDeadlineScheduler(Scheduler): schedule : Schedule Schedule to apply the scheduling algorithm on. """ + sfg = schedule.sfg + start_times = schedule.start_times - ALAPScheduler().apply_scheduling(schedule) - - # move all inputs ASAP to ensure correct operation - for input_op in schedule.sfg.find_by_type_name(Input.type_name()): - input_op = cast(Input, input_op) - schedule.move_operation_asap(input_op.graph_id) - - # construct the set of remaining operations, excluding inputs - remaining_ops = list(schedule.start_times.keys()) - remaining_ops = [elem for elem in remaining_ops if not elem.startswith("in")] - - # construct a dictionarry for storing how many times until a resource is available again used_resources_ready_times = {} - - # iterate through all remaining operations and schedule them - # while not exceeding the available resources remaining_resources = self._max_resources.copy() - current_time = 0 - while remaining_ops: - best_candidate = self._find_best_candidate( - schedule, remaining_ops, remaining_resources, current_time - ) + sorted_operations = self._get_sorted_operations(schedule) - if not best_candidate: - current_time += 1 + # place all inputs at time 0 + for input_op in sfg.find_by_type_name(Input.type_name()): + start_times[input_op.graph_id] = 0 - # update available operators - for operation, ready_time in used_resources_ready_times.items(): - if ready_time == current_time: - remaining_resources[operation.type_name()] += 1 - # remaining_resources = self._max_resources.copy() - continue + current_time = 0 + while sorted_operations: + + # generate the best schedulable candidate + candidate = sfg.find_by_id(sorted_operations[0]) + counter = 0 + while not self._candidate_is_schedulable( + start_times, + candidate, + current_time, + remaining_resources, + sorted_operations, + ): + if counter == len(sorted_operations): + counter = 0 + current_time += 1 + # 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 # if the resource is constrained, update remaining resources - if best_candidate.type_name() in remaining_resources: - remaining_resources[best_candidate.type_name()] -= 1 - if best_candidate.execution_time: - used_resources_ready_times[best_candidate] = ( - current_time + best_candidate.execution_time + if candidate.type_name() in remaining_resources: + remaining_resources[candidate.type_name()] -= 1 + if candidate.execution_time: + used_resources_ready_times[candidate] = ( + current_time + candidate.execution_time ) else: - used_resources_ready_times[best_candidate] = ( - current_time + best_candidate.latency + used_resources_ready_times[candidate] = ( + current_time + candidate.latency ) # schedule the best candidate to the current time - remaining_ops.remove(best_candidate.graph_id) - schedule.start_times[best_candidate.graph_id] = current_time + sorted_operations.remove(candidate.graph_id) + start_times[candidate.graph_id] = current_time + + schedule.set_schedule_time(current_time) + + self._handle_outputs(schedule) + schedule.remove_delays() # move all inputs and outputs ALAP now that operations have moved for input_op in schedule.sfg.find_by_type_name(Input.type_name()): input_op = cast(Input, input_op) schedule.move_operation_alap(input_op.graph_id) - self._handle_outputs(schedule) @staticmethod - def _find_best_candidate( - schedule, remaining_ops, remaining_resources, current_time - ): - sfg = schedule.sfg - source_end_times = defaultdict(float) + def _candidate_is_schedulable( + start_times: dict["GraphID"], + operation: "Operation", + current_time: int, + remaining_resources: dict["GraphID", int], + remaining_ops: list["GraphID"], + ) -> bool: + if ( + operation.type_name() in remaining_resources + and remaining_resources[operation.type_name()] == 0 + ): + return False + + earliest_start_time = 0 + for op_input in operation.inputs: + source_op = op_input.signals[0].source.operation + source_op_graph_id = source_op.graph_id + + if source_op_graph_id in remaining_ops: + return False + + proceeding_op_start_time = start_times.get(source_op_graph_id) + + if not isinstance(source_op, Delay): + earliest_start_time = max( + earliest_start_time, proceeding_op_start_time + source_op.latency + ) + + return earliest_start_time <= current_time - # find the best candidate - best_candidate = None - best_deadline = float('inf') - for op_id in remaining_ops: - operation = sfg.find_by_id(op_id) - - # compute maximum end times of preceding operations - for op_input in operation.inputs: - source_op = op_input.signals[0].source.operation - if not isinstance(source_op, Delay): - source_end_times[op_id] = max( - source_end_times[op_id], - schedule.start_times[source_op.graph_id] + source_op.latency, - ) - # ensure that the source is already scheduled - if source_op.graph_id in remaining_ops: - source_end_times[op_id] = sys.maxsize - - # check resource constraints - if operation.type_name() in remaining_resources: - if remaining_resources[operation.type_name()] == 0: - continue - - # check if all inputs are available - if source_end_times[op_id] <= current_time: - operation_deadline = schedule.start_times[op_id] + operation.latency - if operation_deadline < best_deadline: - best_candidate = operation - best_deadline = operation_deadline - - return best_candidate + @abstractmethod + def _get_sorted_operations(schedule: "Schedule") -> list["GraphID"]: + raise NotImplementedError diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py index db482e57c93b482ed3990a8b61a219441a676e0c..74f5698fc4bd9ca1720afa65a74a1752cd7a9f6e 100644 --- a/b_asic/signal_flow_graph.py +++ b/b_asic/signal_flow_graph.py @@ -29,6 +29,7 @@ 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, @@ -38,7 +39,6 @@ from b_asic.operation import ( ResultKey, ) from b_asic.port import InputPort, OutputPort, SignalSourceProvider -from b_asic.scheduler import ASAPScheduler from b_asic.signal import Signal from b_asic.special_operations import Delay, Input, Output from b_asic.types import GraphID, GraphIDNumber, Name, Num, TypeName diff --git a/examples/fivepointwinograddft.py b/examples/fivepointwinograddft.py index d21561511d5fafa2c9249adf4ffac5e939e91cd4..856dcb6c453ae9ba008fe1ecfdfb65ece1b47a6c 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 43bdc798e0a24fcd0ce8a10e66cc27c412b0acea..66e40299ef02c31830f14907cf133f09ade5dd16 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/lwdfallpass.py b/examples/lwdfallpass.py index 6bbde6291eca6404efb3a5df66e28633b3ea0a91..7eb78ca7e3024e69848d762025475345e904a113 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/secondorderdirectformiir.py b/examples/secondorderdirectformiir.py index aa84c26b532b0304fd012805bb88b1719723e9a7..772d8fb826850c83b75863c442ca8cc3cbdb4179 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 a7d72fb36fa631c7a39395597351a9b4c1674930..2147961ffe10ec52e4dcdaad3a9e6d322e3d0c1b 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 d29fd21566982464ffae9107ba64e79eccf79fce..0b81573104a1a35c14828dda4f45cbf3cb161be8 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 7e7a58acd5465969142aa85f95883998b0a74689..5dc962e645967e40f77d8c63aecec137d2ee9d39 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 39b375ed12eaae2a350ad9f38d1d8dd0273093d3..b5e9d0fbf910cdb153552dc9bfbcf613b6266201 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/test_architecture.py b/test/test_architecture.py index 2ab9c539c6e3985e50829a0a4068eb89cfeec459..6b72bbdcbe952a5cbe6a93e1dded67ac029349fd 100644 --- a/test/test_architecture.py +++ b/test/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/test_scheduler.py b/test/test_core_schedulers.py similarity index 51% rename from test/test_scheduler.py rename to test/test_core_schedulers.py index 0959b2cb3fd81422d0de97531c3c004828e6f66e..147d230d2da5bb840e4262315544a94f4b4b5c3c 100644 --- a/test/test_scheduler.py +++ b/test/test_core_schedulers.py @@ -1,8 +1,13 @@ import pytest -from b_asic.core_operations import Addition, ConstantMultiplication +from b_asic.core_operations import Addition, Butterfly, ConstantMultiplication +from b_asic.core_schedulers import ( + ALAPScheduler, + ASAPScheduler, + EarliestDeadlineScheduler, +) from b_asic.schedule import Schedule -from b_asic.scheduler import ALAPScheduler, ASAPScheduler, EarliestDeadlineScheduler +from b_asic.sfg_generators import direct_form_1_iir, radix_2_dif_fft class TestASAPScheduler: @@ -12,6 +17,31 @@ class TestASAPScheduler: ): Schedule(sfg_empty, scheduler=ASAPScheduler()) + def test_direct_form_1_iir(self): + sfg = direct_form_1_iir([1, 2, 3], [4, 5, 6]) + + 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( @@ -20,7 +50,7 @@ class TestASAPScheduler: schedule = Schedule(sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler()) - assert schedule._start_times == { + assert schedule.start_times == { "in0": 0, "cmul1": 0, "cmul4": 0, @@ -47,7 +77,7 @@ class TestASAPScheduler: sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler(), schedule_time=30 ) - assert schedule._start_times == { + assert schedule.start_times == { "in0": 0, "cmul1": 0, "cmul4": 0, @@ -62,6 +92,53 @@ class TestASAPScheduler: } 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): @@ -70,6 +147,31 @@ class TestALAPScheduler: ): Schedule(sfg_empty, scheduler=ALAPScheduler()) + def test_direct_form_1_iir(self): + sfg = direct_form_1_iir([1, 2, 3], [4, 5, 6]) + + 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( @@ -78,7 +180,7 @@ class TestALAPScheduler: schedule = Schedule(sfg_direct_form_iir_lp_filter, scheduler=ALAPScheduler()) - assert schedule._start_times == { + assert schedule.start_times == { "cmul3": 0, "cmul4": 0, "add1": 4, @@ -105,7 +207,7 @@ class TestALAPScheduler: sfg_direct_form_iir_lp_filter, scheduler=ALAPScheduler(), schedule_time=30 ) - assert schedule._start_times == { + assert schedule.start_times == { "cmul3": 7, "cmul4": 7, "add1": 11, @@ -120,6 +222,53 @@ class TestALAPScheduler: } 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): @@ -128,6 +277,34 @@ class TestEarliestDeadlineScheduler: ): Schedule(sfg_empty, scheduler=EarliestDeadlineScheduler()) + def test_direct_form_1_iir(self): + sfg = direct_form_1_iir([1, 2, 3], [4, 5, 6]) + + 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) + + resources = {Addition.type_name(): 1, ConstantMultiplication.type_name(): 1} + schedule = Schedule( + sfg, scheduler=EarliestDeadlineScheduler(max_resources=resources) + ) + + assert schedule.start_times == { + "cmul4": 0, + "cmul3": 1, + "in0": 2, + "cmul0": 2, + "add1": 3, + "cmul1": 3, + "cmul2": 4, + "add3": 6, + "add0": 7, + "add2": 10, + "out0": 13, + } + assert schedule.schedule_time == 13 + def test_direct_form_2_iir_inf_resources_no_exec_time( self, sfg_direct_form_iir_lp_filter ): @@ -141,7 +318,7 @@ class TestEarliestDeadlineScheduler: ) # should be the same as for ASAP due to infinite resources, except for input - assert schedule._start_times == { + assert schedule.start_times == { "in0": 9, "cmul1": 0, "cmul4": 0, @@ -170,7 +347,7 @@ class TestEarliestDeadlineScheduler: sfg_direct_form_iir_lp_filter, scheduler=EarliestDeadlineScheduler(max_resources), ) - assert schedule._start_times == { + assert schedule.start_times == { "cmul4": 0, "cmul3": 4, "cmul1": 8, @@ -206,7 +383,7 @@ class TestEarliestDeadlineScheduler: sfg_direct_form_iir_lp_filter, scheduler=EarliestDeadlineScheduler(max_resources), ) - assert schedule._start_times == { + assert schedule.start_times == { "cmul4": 0, "cmul3": 1, "cmul1": 2, @@ -242,7 +419,7 @@ class TestEarliestDeadlineScheduler: sfg_direct_form_iir_lp_filter, scheduler=EarliestDeadlineScheduler(max_resources), ) - assert schedule._start_times == { + assert schedule.start_times == { "cmul1": 0, "cmul4": 0, "cmul3": 0, @@ -257,3 +434,53 @@ class TestEarliestDeadlineScheduler: } assert schedule.schedule_time == 12 + + 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) + + resources = {Butterfly.type_name(): 2, ConstantMultiplication.type_name(): 2} + schedule = Schedule( + sfg, scheduler=EarliestDeadlineScheduler(max_resources=resources) + ) + + assert schedule.start_times == { + "in1": 0, + "in3": 0, + "in5": 0, + "in7": 0, + "bfly6": 0, + "bfly8": 0, + "in2": 1, + "in6": 1, + "cmul2": 1, + "cmul3": 1, + "bfly11": 1, + "bfly7": 1, + "in0": 2, + "in4": 2, + "cmul0": 2, + "bfly0": 2, + "cmul4": 2, + "bfly5": 3, + "bfly1": 3, + "cmul1": 4, + "bfly2": 4, + "bfly9": 4, + "bfly10": 5, + "bfly3": 5, + "out0": 5, + "out4": 5, + "bfly4": 6, + "out1": 6, + "out2": 6, + "out5": 6, + "out6": 6, + "out7": 7, + "out3": 7, + } + assert schedule.schedule_time == 7 diff --git a/test/test_schedule.py b/test/test_schedule.py index bbd98c3622e99526a59663dd08394997de2d57d1..456d53f470bf87296aae5f50731f673cfe4882e0 100644 --- a/test/test_schedule.py +++ b/test/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/test_scheduler_gui.py b/test/test_scheduler_gui.py index 02e9d36e475d32c2db5d8a37a22c9e828c2b8ddf..99e8485002139c2aa2fd0a09f9121e88078fb220 100644 --- a/test/test_scheduler_gui.py +++ b/test/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