diff --git a/b_asic/operation.py b/b_asic/operation.py index 629da3690e9684af1406c91da6895e14aecbf66f..c0985f9f6db1bd0c02ee143ad56070f1831310b1 100644 --- a/b_asic/operation.py +++ b/b_asic/operation.py @@ -248,13 +248,6 @@ class Operation(GraphComponent, SignalSourceProvider): """ raise NotImplementedError - @abstractmethod - def set_execution_time(self, latency: int) -> None: - """Sets the execution time of the operation to the specified integer - value. The execution time cannot be a negative integer. - """ - raise NotImplementedError - @property @abstractmethod def execution_time(self) -> int: @@ -263,6 +256,14 @@ class Operation(GraphComponent, SignalSourceProvider): """ raise NotImplementedError + @execution_time.setter + @abstractmethod + def execution_time(self, latency: int) -> None: + """Sets the execution time of the operation to the specified integer + value. The execution time cannot be a negative integer. + """ + raise NotImplementedError + @abstractmethod def get_plot_coordinates(self) -> Tuple[List[List[Number]], List[List[Number]]]: """Get a tuple constaining coordinates for the two polygons outlining @@ -556,6 +557,7 @@ class AbstractOperation(Operation, AbstractGraphComponent): new_component.input(i).latency_offset = inp.latency_offset for i, outp in enumerate(self.outputs): new_component.output(i).latency_offset = outp.latency_offset + new_component.execution_time = self._execution_time return new_component def inputs_required_for_output(self, output_index: int) -> Iterable[int]: @@ -654,8 +656,9 @@ class AbstractOperation(Operation, AbstractGraphComponent): raise ValueError("No execution time specified.") return self._execution_time - def set_execution_time(self, execution_time: int) -> None: - assert execution_time >= 0, "Negative execution time entered." + @execution_time.setter + def execution_time(self, execution_time: int) -> None: + assert execution_time is None or execution_time >= 0 , "Negative execution time entered." self._execution_time = execution_time def get_plot_coordinates(self) -> Tuple[List[List[Number]], List[List[Number]]]: diff --git a/b_asic/schema.py b/b_asic/schema.py index a01825490f97942959e320724266d20b23fc75f3..3fd66f58f7afb8ab50675c60f6f3a2d9f6a602d0 100644 --- a/b_asic/schema.py +++ b/b_asic/schema.py @@ -3,10 +3,12 @@ Contains the schema class for scheduling operations in an SFG. """ +from collections import defaultdict from typing import Dict, List, Optional import matplotlib.pyplot as plt import numpy as np from scipy import interpolate +import sys from b_asic.signal_flow_graph import SFG from b_asic.graph_component import GraphID @@ -28,7 +30,7 @@ class Schema: """Construct a Schema from an SFG.""" self._sfg = sfg self._start_times = dict() - self._laps = dict() + self._laps = defaultdict(lambda: 0) self._cyclic = cyclic self._resolution = resolution @@ -38,12 +40,7 @@ class Schema: raise NotImplementedError( f"No algorithm with name: {scheduling_alg} defined.") - max_end_time = 0 - for op_id, op_start_time in self._start_times.items(): - op = self._sfg.find_by_id(op_id) - for outport in op.outputs: - max_end_time = max( - max_end_time, op_start_time + outport.latency_offset) + max_end_time = self._get_max_end_time() if not self._cyclic: if schedule_time is None: @@ -59,15 +56,68 @@ class Schema: assert op_id in self._start_times, "No operation with the specified op_id in this schema." return self._start_times[op_id] + def _get_max_end_time(self) -> int: + max_end_time = 0 + for op_id, op_start_time in self._start_times.items(): + op = self._sfg.find_by_id(op_id) + for outport in op.outputs: + max_end_time = max( + max_end_time, op_start_time + outport.latency_offset) + return max_end_time + def forward_slack(self, op_id: GraphID) -> int: - raise NotImplementedError + assert op_id in self._start_times, "No operation with the specified op_id in this schema." + start_time = self._start_times[op_id] + op = self._sfg.find_by_id(op_id) + slack = sys.maxsize + for output_port in op.outputs: + available_time = start_time + output_port.latency_offset + + for signal in output_port.signals: + usage_time = (signal.destination.latency_offset + + self._start_times[signal.destination.operation.graph_id] + + self._schedule_time*self._laps[signal.graph_id]) + slack = min(slack, usage_time - available_time) + return slack def backward_slack(self, op_id: GraphID) -> int: - raise NotImplementedError + assert op_id in self._start_times, "No operation with the specified op_id in this schema." + start_time = self._start_times[op_id] + op = self._sfg.find_by_id(op_id) + slack = sys.maxsize + for input_port in op.inputs: + usage_time = start_time + input_port.latency_offset + + for signal in input_port.signals: + available_time = (signal.source.latency_offset + + self._start_times[signal.source.operation.graph_id] - + self._schedule_time*self._laps[signal.graph_id]) + slack = min(slack, usage_time - available_time) + return slack def print_slacks(self) -> None: raise NotImplementedError + def set_schedule_time(self, time: int) -> None: + assert self._get_max_end_time() < time, "New schedule time to short." + self._schedule_time = time + + @property + def schedule_time(self) -> int: + return self._schedule_time + + def _remove_delays(self) -> None: + delay_list = self._sfg.find_by_type_name(Delay.type_name()) + while delay_list: + delay_op = delay_list[0] + delay_input_id = delay_op.input(0).signals[0].graph_id + delay_output_ids = [sig.graph_id for sig in delay_op.output(0).signals] + self._sfg = self._sfg.remove_operation(delay_op.graph_id) + for output_id in delay_output_ids: + self._laps[output_id] += 1 + self._laps[delay_input_id] + del self._laps[delay_input_id] + delay_list = self._sfg.find_by_type_name(Delay.type_name()) + def _schedule_asap(self) -> None: pl = self._sfg.get_precedence_list() @@ -90,7 +140,6 @@ class Schema: # Schedule the operation if it doesn't have a start time yet. op_start_time = 0 for inport in op.inputs: - print(inport.operation.graph_id) assert len( inport.signals) == 1, "Error in scheduling, dangling input port detected." assert inport.signals[0].source is not None, "Error in scheduling, signal with no source detected." @@ -114,6 +163,8 @@ class Schema: op_start_time, op_start_time_from_in) self._start_times[op.graph_id] = op_start_time + self._remove_delays() + print(self._laps) def plot_schedule(self) -> None: def _draw_arrow2(start, end): @@ -137,10 +188,13 @@ class Schema: out = interpolate.splev(u3, tck) plt.plot(out[0], out[1], color='black') - def _draw_arrow(start, end): + def _draw_arrow(start, end, name="", laps=0): if end[0] < start[0]: # Wrap around - plt.plot([start[0], self._schedule_time], [start[1], start[1]], color='black') - plt.plot([0, end[0]], [end[1], end[1]], color='black') + plt.plot([start[0], self._schedule_time + 0.2], [start[1], start[1]], color='black') + plt.plot([-0.2, end[0]], [end[1], end[1]], color='black') + plt.text(self._schedule_time + 0.2, start[1], name, verticalalignment='center') + plt.text(-0.2, end[1], "{}: {}".format(name, laps), verticalalignment='center', horizontalalignment='right') + elif end[0] == start[0]: _draw_spline([start[0], start[0] + 0.2, start[0] + 0.2, start[0] - 0.2, start[0] - 0.2, start[0]], [start[1], start[1], (start[1] + end[1])/2, (start[1] + end[1])/2, end[1], end[1]]) @@ -148,15 +202,15 @@ class Schema: _draw_spline([start[0], (start[0] + end[0])/2, (start[0] + end[0])/2, end[0]], [start[1], start[1], end[1], end[1]]) - def _draw_offset_arrow(start, end, start_offset, end_offset): + def _draw_offset_arrow(start, end, start_offset, end_offset, name="", laps=0): _draw_arrow([start[0] + start_offset[0], start[1] + start_offset[1]], - [end[0] + end_offset[0], end[1] + end_offset[1]]) + [end[0] + end_offset[0], end[1] + end_offset[1]], name=name, laps=laps) ypos = 0.5 ytickpositions = [] yticklabels = [] plt.figure() - plt.grid() + plt.grid(zorder=0.5) ypositions = dict() for op_id, op_start_time in self._start_times.items(): op = self._sfg.find_by_id(op_id) @@ -169,7 +223,7 @@ class Schema: x = np.array(_x) y = np.array(_y) plt.plot(x + op_start_time, y + ypos, color='black', linewidth=3, alpha=0.5) - ytickpositions.append(ypos + 0.75) + ytickpositions.append(ypos + 0.5) yticklabels.append(self._sfg.find_by_id(op_id).name) ypositions[op_id] = ypos ypos += 1.5 @@ -181,15 +235,18 @@ class Schema: for output_port in op.outputs: for output_signal in output_port.signals: dest_op = output_signal.destination.operation - if dest_op.type_name() != Delay.type_name() and dest_op.type_name() != Output.type_name(): + if dest_op.type_name() and dest_op.type_name() != Output.type_name(): dest_start_time = self._start_times[dest_op.graph_id] dest_ypos = ypositions[dest_op.graph_id] dest_in_coords, _ = output_signal.destination.operation.get_io_coordinates() _draw_offset_arrow(out_coords[output_port.index], dest_in_coords[output_signal.destination.index], [op_start_time, source_ypos], - [dest_start_time, dest_ypos]) + [dest_start_time, dest_ypos], name=op_id, + laps=self._laps[output_signal.graph_id]) plt.yticks(ytickpositions, yticklabels) - plt.axis([0, self._schedule_time, 0, ypos]) + plt.axis([-1, self._schedule_time+1, 0, ypos]) + plt.plot([0, 0], [0, ypos], linestyle='--', color='black') + plt.plot([self._schedule_time, self._schedule_time], [0, ypos], linestyle='--', color='black') plt.show() diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py index 4436ca6f43abc539b1492427f2120cff22968bad..3b1d530b866a35a198ea374400e8ac8c7f1257cb 100644 --- a/b_asic/signal_flow_graph.py +++ b/b_asic/signal_flow_graph.py @@ -10,6 +10,7 @@ from io import StringIO from queue import PriorityQueue import itertools as it from graphviz import Digraph +import re from b_asic.port import SignalSourceProvider, OutputPort from b_asic.operation import Operation, AbstractOperation, ResultKey, DelayMap, MutableResultMap, MutableDelayMap @@ -345,14 +346,9 @@ class SFG(AbstractOperation): Keyword arguments: type_name: The type_name of the desired components. """ - i = self.id_number_offset + 1 - components = [] - found_comp = self.find_by_id(type_name + str(i)) - while found_comp is not None: - components.append(found_comp) - i += 1 - found_comp = self.find_by_id(type_name + str(i)) - + reg = "{}[0-9]+".format(type_name) + p = re.compile(reg) + components = [val for key, val in self._components_by_id.items() if p.match(key)] return components def find_by_id(self, graph_id: GraphID) -> Optional[GraphComponent]: @@ -652,7 +648,7 @@ class SFG(AbstractOperation): def set_execution_time_of_type(self, type_name: TypeName, execution_time: int) -> None: """Set the execution time of all components with the given type name.""" for op in self.find_by_type_name(type_name): - op.set_execution_time(execution_time) + op.execution_time = execution_time def set_latency_offsets_of_type(self, type_name: TypeName, latency_offsets: Dict[str, int]) -> None: """Set the latency offset of all components with the given type name.""" @@ -692,9 +688,10 @@ class SFG(AbstractOperation): assert original_component not in self._original_components_to_new, "Tried to add duplicate SFG component" new_component = original_component.copy_component() self._original_components_to_new[original_component] = new_component - new_id = self._graph_id_generator.next_id(new_component.type_name()) - new_component.graph_id = new_id - self._components_by_id[new_id] = new_component + if not new_component.graph_id or new_component.graph_id in self._components_by_id: + new_id = self._graph_id_generator.next_id(new_component.type_name()) + new_component.graph_id = new_id + self._components_by_id[new_component.graph_id] = new_component self._components_by_name[new_component.name].append(new_component) return new_component