Skip to content
Snippets Groups Projects
Commit 8f2a3b2d authored by Oscar Gustafsson's avatar Oscar Gustafsson :bicyclist:
Browse files

Qt6 fixes and scheduler class started

parent d08a9e8c
No related branches found
No related tags found
1 merge request!459Qt6 fixes and scheduler class started
Pipeline #155346 failed
......@@ -6,6 +6,7 @@ before_script:
- apt-get update --yes
# - apt-get install --yes build-essential cmake graphviz python3-pyqt5 xvfb xdg-utils lcov
- apt-get install --yes graphviz python3-pyqt5 xvfb xdg-utils
- apt-get install -y libxcb-cursor-dev
- python -m pip install --upgrade pip
- python --version
- pip install -r requirements.txt
......@@ -35,53 +36,33 @@ before_script:
path: cov.xml
coverage: /(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/
run-test-3.8-pyside2:
variables:
QT_API: pyside2
image: python:3.8
extends: ".run-test"
run-test-3.8-pyqt5:
variables:
QT_API: pyqt5
image: python:3.8
extends: ".run-test"
run-test-3.9-pyside2:
run-test-3.10-pyside2:
variables:
QT_API: pyside2
image: python:3.9
image: python:3.10
extends: ".run-test"
run-test-3.9-pyqt5:
run-test-3.10-pyqt5:
variables:
QT_API: pyqt5
image: python:3.9
image: python:3.10
extends: ".run-test"
run-test-3.10-pyside2:
run-test-3.10-pyqt6:
variables:
QT_API: pyside2
QT_API: pyqt6
image: python:3.10
extends: ".run-test"
run-test-3.10-pyqt5:
run-test-3.11-pyqt5:
variables:
QT_API: pyqt5
image: python:3.10
image: python:3.11
extends: ".run-test"
# PySide2 does not seem to have support for 3.11, "almost works" though
#run-test-3.11-pyside2:
# variables:
# QT_API: pyside2
# image: python:3.11
# extends: ".run-test"
# allow_failure: true
run-test-3.11-pyqt5:
run-test-3.11-pyqt6:
variables:
QT_API: pyqt5
QT_API: pyqt6
image: python:3.11
extends: ".run-test"
......@@ -91,20 +72,11 @@ run-test-3.12-pyqt5:
image: python:3.12
extends: ".run-test"
# Seemingly works with Qt6, but tests stall on closing scheduler GUI due to modal dialog(?)
#run-test-3.10-pyside6:
# variables:
# QT_API: pyside6
# image: python:3.10
# extends: ".run-test"
# allow_failure: true
#
#run-test-3.10-pyqt6:
# variables:
# QT_API: pyqt6
# image: python:3.10
# extends: ".run-test"
# allow_failure: true
run-test-3.12-pyqt6:
variables:
QT_API: pyqt6
image: python:3.12
extends: ".run-test"
run-vhdl-tests:
variables:
......
......@@ -7,7 +7,7 @@ Contains the schedule class for scheduling operations in an SFG.
import io
import sys
from collections import defaultdict
from typing import Dict, List, Literal, Optional, Sequence, Tuple, cast
from typing import Dict, List, Optional, Sequence, Tuple, cast
import matplotlib.pyplot as plt
import numpy as np
......@@ -33,6 +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.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
......@@ -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: SchedulingAlgorithm = "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] = []
......
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()
......@@ -31,7 +31,7 @@ from qtpy.QtCore import (
Qt,
Slot,
)
from qtpy.QtGui import QCloseEvent, QColor, QFont, QIcon, QIntValidator
from qtpy.QtGui import QCloseEvent, QColor, QFont, QIcon, QIntValidator, QPalette
from qtpy.QtWidgets import (
QAbstractButton,
QAction,
......@@ -1688,8 +1688,16 @@ def start_scheduler(schedule: Optional[Schedule] = None) -> Optional[Schedule]:
The edited schedule.
"""
if not QApplication.instance():
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(sys.argv)
# Enforce a light palette regardless of laptop theme
palette = QPalette()
palette.setColor(QPalette.ColorRole.Window, QtCore.Qt.white)
palette.setColor(QPalette.ColorRole.WindowText, QtCore.Qt.black)
palette.setColor(QPalette.ColorRole.ButtonText, QtCore.Qt.black)
palette.setColor(QPalette.ColorRole.Base, QtCore.Qt.white)
palette.setColor(QPalette.ColorRole.AlternateBase, QtCore.Qt.lightGray)
palette.setColor(QPalette.ColorRole.Text, QtCore.Qt.black)
app.setPalette(palette)
else:
app = QApplication.instance()
window = ScheduleMainWindow()
......
This diff is collapsed.
......@@ -4,7 +4,7 @@ description = "Better ASIC Toolbox"
readme = "README.md"
maintainers = [{ name = "Oscar Gustafsson", email = "oscar.gustafsson@liu.se" }]
license = { file = "LICENSE" }
requires-python = ">=3.8"
requires-python = ">=3.10"
dependencies = [
"numpy",
"qtpy",
......@@ -12,16 +12,16 @@ dependencies = [
"matplotlib>=3.7",
"setuptools_scm[toml]>=6.2",
"networkx>=3",
"qtawesome"
"qtawesome",
]
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",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment