From 2a7a7305c79f9eb587cf1e4e68e2b013f9bdc945 Mon Sep 17 00:00:00 2001 From: Simon Bjurek <simbj106@student.liu.se> Date: Mon, 27 Jan 2025 16:00:09 +0100 Subject: [PATCH] fixes from mr comments --- b_asic/schedule.py | 179 +----------------------------- b_asic/scheduler.py | 193 +++++++++++++++++++++++++++++++++ b_asic/scheduling_algorithm.py | 9 -- pyproject.toml | 7 +- 4 files changed, 199 insertions(+), 189 deletions(-) create mode 100644 b_asic/scheduler.py delete mode 100644 b_asic/scheduling_algorithm.py diff --git a/b_asic/schedule.py b/b_asic/schedule.py index 58fa572a..a708fe1a 100644 --- a/b_asic/schedule.py +++ b/b_asic/schedule.py @@ -33,7 +33,7 @@ from b_asic.operation import Operation from b_asic.port import InputPort, OutputPort from b_asic.process import MemoryVariable, OperatorProcess from b_asic.resources import ProcessCollection -from b_asic.scheduling_algorithm import SchedAlg +from b_asic.scheduler import Scheduler, SchedulingAlgorithm from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Delay, Input, Output from b_asic.types import TypeName @@ -96,7 +96,7 @@ class Schedule: sfg: SFG, schedule_time: Optional[int] = None, cyclic: bool = False, - algorithm: SchedAlg = "ASAP", + algorithm: SchedulingAlgorithm = "ASAP", start_times: Optional[Dict[GraphID, int]] = None, laps: Optional[Dict[GraphID, int]] = None, max_resources: Optional[Dict[TypeName, int]] = None, @@ -1227,178 +1227,3 @@ 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: - else: - 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 - - self._handle_outputs_and_delays(non_schedulable_ops) - - 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, process_elements: list[Operation]) -> None: - """Schedule the operations using earliest deadline scheduling.""" - - # ACT BASED ON THE NUMBER OF PEs! - - 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 - - # latencies = [outport.operation.latency for outport in prec_list[1]] - current_time = 0 - sorted_outports = sorted( - prec_list[1], key=lambda outport: outport.operation.latency - ) - for outport in sorted_outports: - op = outport.operation - sched._start_times[op.graph_id] = current_time - current_time += 1 - - for outports in prec_list[2:]: - # try all remaining operations for one time step - candidates = [] - current_time -= 1 - while len(candidates) == 0: - current_time += 1 - for outport in outports: - remaining_op = outport.operation - op_is_ready_to_be_scheduled = True - for op_input in remaining_op.inputs: - source_op = op_input.signals[0].source.operation - source_op_time = sched.start_times[source_op.graph_id] - source_end_time = source_op_time + source_op.latency - if source_end_time > current_time: - op_is_ready_to_be_scheduled = False - if op_is_ready_to_be_scheduled: - candidates.append(remaining_op) - # sched._start_times[remaining_op.graph_id] = current_time - sorted_candidates = sorted( - candidates, key=lambda candidate: candidate.latency - ) - # schedule the best candidate to current time - sched._start_times[sorted_candidates[0].graph_id] = current_time - - self._handle_outputs_and_delays(non_schedulable_ops) - - def _handle_outputs_and_delays(self, non_schedulable_ops) -> None: - sched = self.schedule - 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() diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py new file mode 100644 index 00000000..845859c9 --- /dev/null +++ b/b_asic/scheduler.py @@ -0,0 +1,193 @@ +from enum import Enum +from typing import TYPE_CHECKING, cast + +from b_asic.operation import Operation +from b_asic.port import OutputPort +from b_asic.special_operations import Delay, Output + +if TYPE_CHECKING: + from b_asic.schedule import Schedule + + +class SchedulingAlgorithm(Enum): + ASAP = "ASAP" + ALAP = "ALAP" + EARLIEST_DEADLINE = "earliest_deadline" + # LEAST_SLACK = "least_slack" # to be implemented + PROVIDED = "provided" + + +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: + else: + 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 + + self._handle_outputs_and_delays(non_schedulable_ops) + + 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, process_elements: dict[Operation, int] + ) -> None: + """Schedule the operations using earliest deadline scheduling.""" + + # ACT BASED ON THE NUMBER OF PEs! + + 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 + + current_time = 0 + sorted_outports = sorted( + prec_list[1], key=lambda outport: outport.operation.latency + ) + for outport in sorted_outports: + op = outport.operation + sched._start_times[op.graph_id] = current_time + current_time += 1 + + for outports in prec_list[2:]: + # try all remaining operations for one time step + candidates = [] + current_time -= 1 + while len(candidates) == 0: + current_time += 1 + for outport in outports: + remaining_op = outport.operation + op_is_ready_to_be_scheduled = True + for op_input in remaining_op.inputs: + source_op = op_input.signals[0].source.operation + source_op_time = sched.start_times[source_op.graph_id] + source_end_time = source_op_time + source_op.latency + if source_end_time > current_time: + op_is_ready_to_be_scheduled = False + if op_is_ready_to_be_scheduled: + candidates.append(remaining_op) + # sched._start_times[remaining_op.graph_id] = current_time + sorted_candidates = sorted( + candidates, key=lambda candidate: candidate.latency + ) + # schedule the best candidate to current time + sched._start_times[sorted_candidates[0].graph_id] = current_time + + self._handle_outputs_and_delays(non_schedulable_ops) + + def _handle_outputs_and_delays(self, non_schedulable_ops) -> None: + sched = self.schedule + 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() diff --git a/b_asic/scheduling_algorithm.py b/b_asic/scheduling_algorithm.py deleted file mode 100644 index c6787a73..00000000 --- a/b_asic/scheduling_algorithm.py +++ /dev/null @@ -1,9 +0,0 @@ -from enum import Enum - - -class SchedAlg(Enum): - ASAP = "ASAP" - ALAP = "ALAP" - EARLIEST_DEADLINE = "earliest_deadline" - LEAST_SLACK = "least_slack" - PROVIDED = "provided" diff --git a/pyproject.toml b/pyproject.toml index ff1ea7b2..21ece32f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,16 +12,17 @@ dependencies = [ "matplotlib>=3.7", "setuptools_scm[toml]>=6.2", "networkx>=3", - "qtawesome" + "qtawesome", + "pyqt6", ] classifiers = [ "Intended Audience :: Education", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: C++", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", -- GitLab