diff --git a/b_asic/schedule.py b/b_asic/schedule.py index 4b34d5802d13a8662c3bf2a20640bfaaea48d35f..3f8a0592680e1ec9230a84af3ada73fbe4ba966b 100644 --- a/b_asic/schedule.py +++ b/b_asic/schedule.py @@ -36,6 +36,7 @@ from b_asic.resources import ProcessCollection from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Delay, Input, Output from b_asic.types import TypeName +from b_asic.scheduling_algorithm import SchedAlg # Need RGB from 0 to 1 _EXECUTION_TIME_COLOR: Tuple[float, ...] = tuple( @@ -68,13 +69,8 @@ class Schedule: algorithm. cyclic : bool, default: False If the schedule is cyclic. - algorithm : {'ASAP', 'ALAP', 'provided'}, default: 'ASAP' - The scheduling algorithm to use. The following algorithm are available: - - * ``'ASAP'``: As-soon-as-possible scheduling. - * ``'ALAP'``: As-late-as-possible scheduling. - - If 'provided', use provided *start_times* and *laps* dictionaries. + algorithm : SchedulingAlgorithm, default: 'ASAP' + The scheduling algorithm to use. start_times : dict, optional Dictionary with GraphIDs as keys and start times as values. Used when *algorithm* is 'provided'. @@ -100,7 +96,7 @@ class Schedule: sfg: SFG, schedule_time: Optional[int] = None, cyclic: bool = False, - algorithm: Literal["ASAP", "ALAP", "provided"] = "ASAP", + algorithm: SchedAlg = "ASAP", start_times: Optional[Dict[GraphID, int]] = None, laps: Optional[Dict[GraphID, int]] = None, max_resources: Optional[Dict[TypeName, int]] = None, @@ -115,10 +111,14 @@ class Schedule: self._cyclic = cyclic self._y_locations = defaultdict(_y_locations_default) self._schedule_time = schedule_time + + self.scheduler = Scheduler(self) if algorithm == "ASAP": - self._schedule_asap() + self.scheduler.schedule_asap() elif algorithm == "ALAP": - self._schedule_alap() + self.scheduler.schedule_alap() + elif algorithm == "earliest_deadline": + self.scheduler.schedule_earliest_deadline() elif algorithm == "provided": if start_times is None: raise ValueError("Must provide start_times when using 'provided'") @@ -797,116 +797,116 @@ 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 _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] = [] @@ -1227,3 +1227,149 @@ class Schedule: # SVG is valid HTML. This is useful for e.g. sphinx-gallery _repr_html_ = _repr_svg_ + + +class Scheduler(): + def __init__(self, schedule: Schedule) -> None: + self.schedule = schedule + + def schedule_asap(self) -> None: + """Schedule the operations using as-soon-as-possible scheduling.""" + sched = self.schedule + prec_list = sched.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) + elif operation.graph_id not in sched._start_times: + sched._start_times[operation.graph_id] = 0 + + # handle second set in precedence graph (first operations) + for outport in prec_list[1]: + operation = outport.operation + if operation.graph_id not in sched._start_times: + sched._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 sched._start_times: + 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 = sched._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) + + sched._start_times[operation.graph_id] = op_start_time + + # handle output and remove delays + for output in sched._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: + sched._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." + ) + sched._start_times[output.graph_id] = sched._start_times[ + source_port.operation.graph_id + ] + cast(int, source_port.latency_offset) + sched._remove_delays() + + def schedule_alap(self) -> None: + """Schedule the operations using as-late-as-possible scheduling.""" + self.schedule_asap() + sched = self.schedule + max_end_time = sched.get_max_end_time() + + if sched.schedule_time is None: + sched.set_schedule_time(max_end_time) + elif sched.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 sched.sfg.find_by_type_name(Output.type_name()): + output = cast(Output, output) + sched.move_operation_alap(output.graph_id) + + # move all operations ALAP + for step in reversed(sched.sfg.get_precedence_list()): + for outport in step: + if not isinstance(outport.operation, Delay): + sched.move_operation_alap(outport.operation.graph_id) + + def schedule_earliest_deadline(self) -> None: + """Schedule the operations using earliest deadline scheduling.""" + pass + # sched = self.schedule + # prec_list = sched.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) + # elif operation.graph_id not in sched._start_times: + # sched._start_times[operation.graph_id] = 0 + + # # handle second set in precedence graph (first operations) + # for outport in prec_list[1]: + # operation = outport.operation + # if operation.graph_id not in sched._start_times: + # sched._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 sched._start_times: + # pass \ No newline at end of file diff --git a/b_asic/scheduling_algorithm.py b/b_asic/scheduling_algorithm.py new file mode 100644 index 0000000000000000000000000000000000000000..8a1dbcd31dbc0f064cd9bfb97ea5bd4dbf61453e --- /dev/null +++ b/b_asic/scheduling_algorithm.py @@ -0,0 +1,8 @@ +from enum import Enum + +class SchedAlg(Enum): + ASAP = "ASAP" + ALAP = "ALAP" + EARLIEST_DEADLINE = "earliest_deadline" + LEAST_SLACK = "least_slack" + PROVIDED = "provided"