Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • da/B-ASIC
  • lukja239/B-ASIC
  • robal695/B-ASIC
3 results
Show changes
Showing
with 2321 additions and 641 deletions
b_asic/GUI/operation_icons/sqrt_grey.png

2.87 KiB

b_asic/GUI/operation_icons/sub.png

1.13 KiB

b_asic/GUI/operation_icons/sub_grey.png

1.18 KiB

import sys
from PySide2.QtWidgets import QPushButton, QMenu
from PySide2.QtCore import Qt, Signal
class PortButton(QPushButton):
connectionRequested = Signal(QPushButton)
moved = Signal()
def __init__(self, name, operation, port, window, parent=None):
super(PortButton, self).__init__(name, operation, parent)
self.pressed = False
self._window = window
self.port = port
self.operation = operation
self.clicked = 0
self._m_drag = False
self._m_press = False
self.setStyleSheet("background-color: white")
self.connectionRequested.connect(self._window._connect_button)
def contextMenuEvent(self, event):
menu = QMenu()
menu.addAction("Connect", lambda: self.connectionRequested.emit(self))
menu.exec_(self.cursor().pos())
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.select_port(event.modifiers())
super(PortButton, self).mousePressEvent(event)
def mouseReleaseEvent(self, event):
super(PortButton, self).mouseReleaseEvent(event)
def _toggle_port(self, pressed=False):
self.pressed = not pressed
self.setStyleSheet(f"background-color: {'white' if not self.pressed else 'grey'}")
def select_port(self, modifiers=None):
if modifiers != Qt.ControlModifier:
for port in self._window.pressed_ports:
port._toggle_port(port.pressed)
self._toggle_port(self.pressed)
self._window.pressed_ports = [self]
else:
self._toggle_port(self.pressed)
if self in self._window.pressed_ports:
self._window.pressed_ports.remove(self)
else:
self._window.pressed_ports.append(self)
for signal in self._window.signalList:
signal.update()
from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\
QLabel, QCheckBox, QGridLayout
from PySide2.QtCore import Qt
from PySide2.QtGui import QIntValidator
class PropertiesWindow(QDialog):
def __init__(self, operation, main_window):
super(PropertiesWindow, self).__init__()
self.operation = operation
self._window = main_window
self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
self.setWindowTitle("Properties")
self.name_layout = QHBoxLayout()
self.name_layout.setSpacing(50)
self.name_label = QLabel("Name:")
self.edit_name = QLineEdit(self.operation.operation_path_name)
self.name_layout.addWidget(self.name_label)
self.name_layout.addWidget(self.edit_name)
self.latency_fields = dict()
self.vertical_layout = QVBoxLayout()
self.vertical_layout.addLayout(self.name_layout)
if self.operation.operation_path_name == "c":
self.constant_layout = QHBoxLayout()
self.constant_layout.setSpacing(50)
self.constant_value = QLabel("Constant:")
self.edit_constant = QLineEdit(str(self.operation.operation.value))
self.only_accept_int = QIntValidator()
self.edit_constant.setValidator(self.only_accept_int)
self.constant_layout.addWidget(self.constant_value)
self.constant_layout.addWidget(self.edit_constant)
self.vertical_layout.addLayout(self.constant_layout)
self.show_name_layout = QHBoxLayout()
self.check_show_name = QCheckBox("Show name?")
if self.operation.is_show_name:
self.check_show_name.setChecked(1)
else:
self.check_show_name.setChecked(0)
self.check_show_name.setLayoutDirection(Qt.RightToLeft)
self.check_show_name.setStyleSheet("spacing: 170px")
self.show_name_layout.addWidget(self.check_show_name)
self.vertical_layout.addLayout(self.show_name_layout)
if self.operation.operation.input_count > 0:
self.latency_layout = QHBoxLayout()
self.latency_label = QLabel("Set Latency For Input Ports (-1 for None):")
self.latency_layout.addWidget(self.latency_label)
self.vertical_layout.addLayout(self.latency_layout)
input_grid = QGridLayout()
x, y = 0, 0
for i in range(self.operation.operation.input_count):
input_layout = QHBoxLayout()
input_layout.addStretch()
if i % 2 == 0 and i > 0:
x += 1
y = 0
input_label = QLabel("in" + str(i))
input_layout.addWidget(input_label)
input_value = QLineEdit()
try:
input_value.setPlaceholderText(str(self.operation.operation.latency))
except ValueError:
input_value.setPlaceholderText("-1")
int_valid = QIntValidator()
int_valid.setBottom(-1)
input_value.setValidator(int_valid)
input_value.setFixedWidth(50)
self.latency_fields["in" + str(i)] = input_value
input_layout.addWidget(input_value)
input_layout.addStretch()
input_layout.setSpacing(10)
input_grid.addLayout(input_layout, x, y)
y += 1
self.vertical_layout.addLayout(input_grid)
if self.operation.operation.output_count > 0:
self.latency_layout = QHBoxLayout()
self.latency_label = QLabel("Set Latency For Output Ports (-1 for None):")
self.latency_layout.addWidget(self.latency_label)
self.vertical_layout.addLayout(self.latency_layout)
input_grid = QGridLayout()
x, y = 0, 0
for i in range(self.operation.operation.output_count):
input_layout = QHBoxLayout()
input_layout.addStretch()
if i % 2 == 0 and i > 0:
x += 1
y = 0
input_label = QLabel("out" + str(i))
input_layout.addWidget(input_label)
input_value = QLineEdit()
try:
input_value.setPlaceholderText(str(self.operation.operation.latency))
except ValueError:
input_value.setPlaceholderText("-1")
int_valid = QIntValidator()
int_valid.setBottom(-1)
input_value.setValidator(int_valid)
input_value.setFixedWidth(50)
self.latency_fields["out" + str(i)] = input_value
input_layout.addWidget(input_value)
input_layout.addStretch()
input_layout.setSpacing(10)
input_grid.addLayout(input_layout, x, y)
y += 1
self.vertical_layout.addLayout(input_grid)
self.ok = QPushButton("OK")
self.ok.clicked.connect(self.save_properties)
self.vertical_layout.addWidget(self.ok)
self.setLayout(self.vertical_layout)
def save_properties(self):
self._window.logger.info(f"Saving properties of operation: {self.operation.name}.")
self.operation.name = self.edit_name.text()
self.operation.label.setPlainText(self.operation.name)
if self.operation.operation_path_name == "c":
self.operation.operation.value = int(self.edit_constant.text())
if self.check_show_name.isChecked():
self.operation.label.setOpacity(1)
self.operation.is_show_name = True
else:
self.operation.label.setOpacity(0)
self.operation.is_show_name = False
self.operation.operation.set_latency_offsets({port: int(self.latency_fields[port].text()) if self.latency_fields[port].text() and int(self.latency_fields[port].text()) > 0 else None for port in self.latency_fields})
self.reject()
\ No newline at end of file
from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\
QLabel, QCheckBox, QSpinBox, QGroupBox, QFrame, QFormLayout, QGridLayout, QSizePolicy, QFileDialog, QShortcut, QComboBox
from PySide2.QtCore import Qt, Signal
from PySide2.QtGui import QIntValidator, QKeySequence
from matplotlib.backends import qt_compat
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class SelectSFGWindow(QDialog):
ok = Signal()
def __init__(self, window):
super(SelectSFGWindow, self).__init__()
self._window = window
self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
self.setWindowTitle("Select SFG")
self.dialog_layout = QVBoxLayout()
self.ok_btn = QPushButton("Ok")
self.ok_btn.clicked.connect(self.save_properties)
self.dialog_layout.addWidget(self.ok_btn)
self.combo_box = QComboBox()
self.sfg = None
self.setLayout(self.dialog_layout)
self.add_sfgs_to_layout()
def add_sfgs_to_layout(self):
for sfg in self._window.sfg_dict:
self.combo_box.addItem(sfg)
self.dialog_layout.addWidget(self.combo_box)
def save_properties(self):
self.sfg = self._window.sfg_dict[self.combo_box.currentText()]
self.accept()
self.ok.emit()
from PySide2.QtWidgets import QDialog, QPushButton, QVBoxLayout, QCheckBox,\
QFrame, QFormLayout
from PySide2.QtCore import Qt, Signal
from b_asic import SFG
class ShowPCWindow(QDialog):
pc = Signal()
def __init__(self, window):
super(ShowPCWindow, self).__init__()
self._window = window
self.check_box_dict = dict()
self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
self.setWindowTitle("Show PC")
self.dialog_layout = QVBoxLayout()
self.pc_btn = QPushButton("Show PC")
self.pc_btn.clicked.connect(self.show_precedence_graph)
self.dialog_layout.addWidget(self.pc_btn)
self.setLayout(self.dialog_layout)
def add_sfg_to_dialog(self):
self.sfg_layout = QVBoxLayout()
self.options_layout = QFormLayout()
for sfg in self._window.sfg_dict:
check_box = QCheckBox()
self.options_layout.addRow(sfg, check_box)
self.check_box_dict[check_box] = sfg
self.sfg_layout.addLayout(self.options_layout)
frame = QFrame()
frame.setFrameShape(QFrame.HLine)
frame.setFrameShadow(QFrame.Sunken)
self.dialog_layout.addWidget(frame)
self.dialog_layout.addLayout(self.sfg_layout)
def show_precedence_graph(self):
for check_box, sfg in self.check_box_dict.items():
if check_box.isChecked():
self._window.logger.info(f"Creating a precedence chart from sfg with name: {sfg}.")
self._window.sfg_dict[sfg].show_precedence_graph()
self.accept()
self.pc.emit()
\ No newline at end of file
from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\
QLabel, QCheckBox, QSpinBox, QGroupBox, QFrame, QFormLayout, QGridLayout, QSizePolicy, QFileDialog, QShortcut
from PySide2.QtCore import Qt, Signal
from PySide2.QtGui import QIntValidator, QKeySequence
from matplotlib.backends import qt_compat
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class SimulateSFGWindow(QDialog):
simulate = Signal()
def __init__(self, window):
super(SimulateSFGWindow, self).__init__()
self._window = window
self.properties = dict()
self.sfg_to_layout = dict()
self.input_fields = dict()
self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
self.setWindowTitle("Simulate SFG")
self.dialog_layout = QVBoxLayout()
self.simulate_btn = QPushButton("Simulate")
self.simulate_btn.clicked.connect(self.save_properties)
self.dialog_layout.addWidget(self.simulate_btn)
self.setLayout(self.dialog_layout)
def add_sfg_to_dialog(self, sfg):
sfg_layout = QVBoxLayout()
options_layout = QFormLayout()
name_label = QLabel(f"{sfg.name}")
sfg_layout.addWidget(name_label)
spin_box = QSpinBox()
spin_box.setRange(0, 2147483647)
options_layout.addRow("Iteration Count: ", spin_box)
check_box_plot = QCheckBox()
options_layout.addRow("Plot Results: ", check_box_plot)
check_box_all = QCheckBox()
options_layout.addRow("Get All Results: ", check_box_all)
sfg_layout.addLayout(options_layout)
self.input_fields[sfg] = {
"iteration_count": spin_box,
"show_plot": check_box_plot,
"all_results": check_box_all,
"input_values": []
}
if sfg.input_count > 0:
input_label = QHBoxLayout()
input_label = QLabel("Input Values:")
options_layout.addRow(input_label)
input_grid = QGridLayout()
x, y = 0, 0
for i in range(sfg.input_count):
input_layout = QHBoxLayout()
input_layout.addStretch()
if i % 2 == 0 and i > 0:
x += 1
y = 0
input_label = QLabel("in" + str(i))
input_layout.addWidget(input_label)
input_value = QLineEdit()
input_value.setPlaceholderText("0")
input_value.setValidator(QIntValidator())
input_value.setFixedWidth(50)
input_layout.addWidget(input_value)
input_layout.addStretch()
input_layout.setSpacing(10)
input_grid.addLayout(input_layout, x, y)
self.input_fields[sfg]["input_values"].append(input_value)
y += 1
sfg_layout.addLayout(input_grid)
frame = QFrame()
frame.setFrameShape(QFrame.HLine)
frame.setFrameShadow(QFrame.Sunken)
self.dialog_layout.addWidget(frame)
self.sfg_to_layout[sfg] = sfg_layout
self.dialog_layout.addLayout(sfg_layout)
def save_properties(self):
for sfg, properties in self.input_fields.items():
self.properties[sfg] = {
"iteration_count": self.input_fields[sfg]["iteration_count"].value(),
"show_plot": self.input_fields[sfg]["show_plot"].isChecked(),
"all_results": self.input_fields[sfg]["all_results"].isChecked(),
"input_values": [int(widget.text()) if widget.text() else 0 for widget in self.input_fields[sfg]["input_values"]]
}
# If we plot we should also print the entire data, since you can't really interact with the graph.
if self.properties[sfg]["show_plot"]:
self.properties[sfg]["all_results"] = True
self.accept()
self.simulate.emit()
class Plot(FigureCanvas):
def __init__(self, simulation, sfg, window, parent=None, width=5, height=4, dpi=100):
self.simulation = simulation
self.sfg = sfg
self.dpi = dpi
self._window = window
fig = Figure(figsize=(width, height), dpi=dpi)
fig.suptitle(sfg.name, fontsize=20)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.save_figure = QShortcut(QKeySequence("Ctrl+S"), self)
self.save_figure.activated.connect(self._save_plot_figure)
self._plot_values_sfg()
def _save_plot_figure(self):
self._window.logger.info(f"Saving plot of figure: {self.sfg.name}.")
file_choices = "PNG (*.png)|*.png"
path, ext = QFileDialog.getSaveFileName(self, "Save file", "", file_choices)
path = path.encode("utf-8")
if not path[-4:] == file_choices[-4:].encode("utf-8"):
path += file_choices[-4:].encode("utf-8")
if path:
self.print_figure(path.decode(), dpi=self.dpi)
self._window.logger.info(f"Saved plot: {self.sfg.name} to path: {path}.")
def _plot_values_sfg(self):
x_axis = list(range(len(self.simulation.results["0"])))
for _output in range(self.sfg.output_count):
y_axis = self.simulation.results[str(_output)]
self.axes.plot(x_axis, y_axis)
from PySide2.QtWidgets import QErrorMessage
from traceback import format_exc
def handle_error(fn):
def wrapper(self, *args, **kwargs):
try:
return fn(self, *args, **kwargs)
except Exception as e:
self._window.logger.error(f"Unexpected error: {format_exc()}")
QErrorMessage(self._window).showMessage(f"Unexpected error: {format_exc()}")
return wrapper
def decorate_class(decorator):
def decorate(cls):
for attr in cls.__dict__:
if callable(getattr(cls, attr)):
setattr(cls, attr, decorator(getattr(cls, attr)))
return cls
return decorate
\ No newline at end of file
......@@ -2,13 +2,14 @@
Better ASIC Toolbox.
TODO: More info.
"""
from _b_asic import *
from b_asic.core_operations import *
from b_asic.graph_component import *
from b_asic.graph_id import *
from b_asic.operation import *
from b_asic.precedence_chart import *
from b_asic.port import *
from b_asic.schema import *
from b_asic.signal_flow_graph import *
from b_asic.signal import *
from b_asic.simulation import *
from b_asic.special_operations import *
from b_asic.save_load_structure import *
from b_asic.schema import *
......@@ -4,43 +4,39 @@ TODO: More info.
"""
from numbers import Number
from typing import Any
from typing import Optional, Dict
from numpy import conjugate, sqrt, abs as np_abs
from b_asic.port import InputPort, OutputPort
from b_asic.graph_id import GraphIDType
from b_asic.port import SignalSourceProvider, InputPort, OutputPort
from b_asic.operation import AbstractOperation
from b_asic.graph_component import Name, TypeName
class Input(AbstractOperation):
"""Input operation.
TODO: More info.
"""
# TODO: Implement all functions.
@property
def type_name(self) -> TypeName:
return "in"
class Constant(AbstractOperation):
"""Constant value operation.
TODO: More info.
"""
def __init__(self, value: Number = 0, name: Name = ""):
super().__init__(name)
super().__init__(input_count=0, output_count=1, name=name)
self.set_param("value", value)
self._output_ports = [OutputPort(0, self)]
self._parameters["value"] = value
@classmethod
def type_name(cls) -> TypeName:
return "c"
def evaluate(self):
return self.param("value")
@property
def type_name(self) -> TypeName:
return "c"
def value(self) -> Number:
"""Get the constant value of this operation."""
return self.param("value")
@value.setter
def value(self, value: Number) -> None:
"""Set the constant value of this operation."""
return self.set_param("value", value)
class Addition(AbstractOperation):
......@@ -48,134 +44,86 @@ class Addition(AbstractOperation):
TODO: More info.
"""
def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self), InputPort(1, self)]
self._output_ports = [OutputPort(0, self)]
def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
if source2 is not None:
self._input_ports[1].connect(source2)
@classmethod
def type_name(cls) -> TypeName:
return "add"
def evaluate(self, a, b):
return a + b
@property
def type_name(self) -> TypeName:
return "add"
class Subtraction(AbstractOperation):
"""Binary subtraction operation.
TODO: More info.
"""
def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self), InputPort(1, self)]
self._output_ports = [OutputPort(0, self)]
def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
if source2 is not None:
self._input_ports[1].connect(source2)
@classmethod
def type_name(cls) -> TypeName:
return "sub"
def evaluate(self, a, b):
return a - b
@property
def type_name(self) -> TypeName:
return "sub"
class Multiplication(AbstractOperation):
"""Binary multiplication operation.
TODO: More info.
"""
def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self), InputPort(1, self)]
self._output_ports = [OutputPort(0, self)]
def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
if source2 is not None:
self._input_ports[1].connect(source2)
@classmethod
def type_name(cls) -> TypeName:
return "mul"
def evaluate(self, a, b):
return a * b
@property
def type_name(self) -> TypeName:
return "mul"
class Division(AbstractOperation):
"""Binary division operation.
TODO: More info.
"""
def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self), InputPort(1, self)]
self._output_ports = [OutputPort(0, self)]
def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
if source2 is not None:
self._input_ports[1].connect(source2)
@classmethod
def type_name(cls) -> TypeName:
return "div"
def evaluate(self, a, b):
return a / b
@property
def type_name(self) -> TypeName:
return "div"
class SquareRoot(AbstractOperation):
"""Unary square root operation.
TODO: More info.
"""
def __init__(self, source1: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self)]
self._output_ports = [OutputPort(0, self)]
if source1 is not None:
self._input_ports[0].connect(source1)
def evaluate(self, a):
return sqrt((complex)(a))
@property
def type_name(self) -> TypeName:
return "sqrt"
class ComplexConjugate(AbstractOperation):
"""Unary complex conjugate operation.
class Min(AbstractOperation):
"""Binary min operation.
TODO: More info.
"""
def __init__(self, source1: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self)]
self._output_ports = [OutputPort(0, self)]
def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
def evaluate(self, a):
return conjugate(a)
@classmethod
def type_name(cls) -> TypeName:
return "min"
@property
def type_name(self) -> TypeName:
return "conj"
def evaluate(self, a, b):
assert not isinstance(a, complex) and not isinstance(b, complex), \
("core_operations.Min does not support complex numbers.")
return a if a < b else b
class Max(AbstractOperation):
......@@ -183,155 +131,129 @@ class Max(AbstractOperation):
TODO: More info.
"""
def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self), InputPort(1, self)]
self._output_ports = [OutputPort(0, self)]
def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=2, output_count=1, name=name, input_sources=[src0, src1],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
if source2 is not None:
self._input_ports[1].connect(source2)
@classmethod
def type_name(cls) -> TypeName:
return "max"
def evaluate(self, a, b):
assert not isinstance(a, complex) and not isinstance(b, complex), \
("core_operations.Max does not support complex numbers.")
return a if a > b else b
@property
def type_name(self) -> TypeName:
return "max"
class Min(AbstractOperation):
"""Binary min operation.
class SquareRoot(AbstractOperation):
"""Unary square root operation.
TODO: More info.
"""
def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self), InputPort(1, self)]
self._output_ports = [OutputPort(0, self)]
if source1 is not None:
self._input_ports[0].connect(source1)
if source2 is not None:
self._input_ports[1].connect(source2)
def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
latency=latency, latency_offsets=latency_offsets)
def evaluate(self, a, b):
assert not isinstance(a, complex) and not isinstance(b, complex), \
("core_operations.Min does not support complex numbers.")
return a if a < b else b
@classmethod
def type_name(cls) -> TypeName:
return "sqrt"
@property
def type_name(self) -> TypeName:
return "min"
def evaluate(self, a):
return sqrt(complex(a))
class Absolute(AbstractOperation):
"""Unary absolute value operation.
class ComplexConjugate(AbstractOperation):
"""Unary complex conjugate operation.
TODO: More info.
"""
def __init__(self, source1: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self)]
self._output_ports = [OutputPort(0, self)]
def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
@classmethod
def type_name(cls) -> TypeName:
return "conj"
def evaluate(self, a):
return np_abs(a)
@property
def type_name(self) -> TypeName:
return "abs"
return conjugate(a)
class ConstantMultiplication(AbstractOperation):
"""Unary constant multiplication operation.
class Absolute(AbstractOperation):
"""Unary absolute value operation.
TODO: More info.
"""
def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self)]
self._output_ports = [OutputPort(0, self)]
self._parameters["coefficient"] = coefficient
def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
@classmethod
def type_name(cls) -> TypeName:
return "abs"
def evaluate(self, a):
return a * self.param("coefficient")
@property
def type_name(self) -> TypeName:
return "cmul"
return np_abs(a)
class ConstantAddition(AbstractOperation):
"""Unary constant addition operation.
class ConstantMultiplication(AbstractOperation):
"""Unary constant multiplication operation.
TODO: More info.
"""
def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self)]
self._output_ports = [OutputPort(0, self)]
self._parameters["coefficient"] = coefficient
def __init__(self, value: Number = 0, src0: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=1, output_count=1, name=name, input_sources=[src0],
latency=latency, latency_offsets=latency_offsets)
self.set_param("value", value)
if source1 is not None:
self._input_ports[0].connect(source1)
@classmethod
def type_name(cls) -> TypeName:
return "cmul"
def evaluate(self, a):
return a + self.param("coefficient")
return a * self.param("value")
@property
def type_name(self) -> TypeName:
return "cadd"
def value(self) -> Number:
"""Get the constant value of this operation."""
return self.param("value")
@value.setter
def value(self, value: Number) -> None:
"""Set the constant value of this operation."""
return self.set_param("value", value)
class ConstantSubtraction(AbstractOperation):
"""Unary constant subtraction operation.
class Butterfly(AbstractOperation):
"""Butterfly operation that returns two outputs.
The first output is a + b and the second output is a - b.
TODO: More info.
"""
def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self)]
self._output_ports = [OutputPort(0, self)]
self._parameters["coefficient"] = coefficient
def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=2, output_count=2, name=name, input_sources=[src0, src1],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
@classmethod
def type_name(cls) -> TypeName:
return "bfly"
def evaluate(self, a):
return a - self.param("coefficient")
@property
def type_name(self) -> TypeName:
return "csub"
def evaluate(self, a, b):
return a + b, a - b
class ConstantDivision(AbstractOperation):
"""Unary constant division operation.
class MAD(AbstractOperation):
"""Multiply-and-add operation.
TODO: More info.
"""
def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""):
super().__init__(name)
self._input_ports = [InputPort(0, self)]
self._output_ports = [OutputPort(0, self)]
self._parameters["coefficient"] = coefficient
def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, src2: Optional[SignalSourceProvider] = None, name: Name = "", latency: int = None, latency_offsets: Dict[str, int] = None):
super().__init__(input_count=3, output_count=1, name=name, input_sources=[src0, src1, src2],
latency=latency, latency_offsets=latency_offsets)
if source1 is not None:
self._input_ports[0].connect(source1)
@classmethod
def type_name(cls) -> TypeName:
return "mad"
def evaluate(self, a):
return a / self.param("coefficient")
@property
def type_name(self) -> TypeName:
return "cdiv"
def evaluate(self, a, b, c):
return a * b + c
......@@ -4,10 +4,15 @@ TODO: More info.
"""
from abc import ABC, abstractmethod
from typing import NewType
from collections import deque
from copy import copy, deepcopy
from typing import NewType, Any, Dict, Mapping, Iterable, Generator
Name = NewType("Name", str)
TypeName = NewType("TypeName", str)
GraphID = NewType("GraphID", str)
GraphIDNumber = NewType("GraphIDNumber", int)
class GraphComponent(ABC):
......@@ -15,35 +20,94 @@ class GraphComponent(ABC):
TODO: More info.
"""
@property
@classmethod
@abstractmethod
def type_name(self) -> TypeName:
"""Return the type name of the graph component"""
def type_name(cls) -> TypeName:
"""Get the type name of this graph component"""
raise NotImplementedError
@property
@abstractmethod
def name(self) -> Name:
"""Return the name of the graph component."""
"""Get the name of this graph component."""
raise NotImplementedError
@name.setter
@abstractmethod
def name(self, name: Name) -> None:
"""Set the name of the graph component to the entered name."""
"""Set the name of this graph component to the given name."""
raise NotImplementedError
@property
@abstractmethod
def graph_id(self) -> GraphID:
"""Get the graph id of this graph component."""
raise NotImplementedError
@graph_id.setter
@abstractmethod
def graph_id(self, graph_id: GraphID) -> None:
"""Set the graph id of this graph component to the given id.
Note that this id will be ignored if this component is used to create a new graph,
and that a new local id will be generated for it instead."""
raise NotImplementedError
@property
@abstractmethod
def params(self) -> Mapping[str, Any]:
"""Get a dictionary of all parameter values."""
raise NotImplementedError
@abstractmethod
def param(self, name: str) -> Any:
"""Get the value of a parameter.
Returns None if the parameter is not defined.
"""
raise NotImplementedError
@abstractmethod
def set_param(self, name: str, value: Any) -> None:
"""Set the value of a parameter.
Adds the parameter if it is not already defined.
"""
raise NotImplementedError
@abstractmethod
def copy_component(self, *args, **kwargs) -> "GraphComponent":
"""Get a new instance of this graph component type with the same name, id and parameters."""
raise NotImplementedError
@property
@abstractmethod
def neighbors(self) -> Iterable["GraphComponent"]:
"""Get all components that are directly connected to this operation."""
raise NotImplementedError
@abstractmethod
def traverse(self) -> Generator["GraphComponent", None, None]:
"""Get a generator that recursively iterates through all components that are connected to this operation,
as well as the ones that they are connected to.
"""
raise NotImplementedError
class AbstractGraphComponent(GraphComponent):
"""Abstract Graph Component class which is a component of a signal flow graph.
TODO: More info.
"""
_name: Name
_graph_id: GraphID
_parameters: Dict[str, Any]
def __init__(self, name: Name = ""):
self._name = name
self._graph_id = ""
self._parameters = {}
def __str__(self):
return f"id: {self.graph_id if self.graph_id else 'no_id'}, \tname: {self.name if self.name else 'no_name'}" + \
"".join((f", \t{key}: {str(param)}" for key, param in self._parameters.items()))
@property
def name(self) -> Name:
......@@ -52,3 +116,41 @@ class AbstractGraphComponent(GraphComponent):
@name.setter
def name(self, name: Name) -> None:
self._name = name
@property
def graph_id(self) -> GraphID:
return self._graph_id
@graph_id.setter
def graph_id(self, graph_id: GraphID) -> None:
self._graph_id = graph_id
@property
def params(self) -> Mapping[str, Any]:
return self._parameters.copy()
def param(self, name: str) -> Any:
return self._parameters.get(name)
def set_param(self, name: str, value: Any) -> None:
self._parameters[name] = value
def copy_component(self, *args, **kwargs) -> GraphComponent:
new_component = self.__class__(*args, **kwargs)
new_component.name = copy(self.name)
new_component.graph_id = copy(self.graph_id)
for name, value in self.params.items():
new_component.set_param(copy(name), deepcopy(value)) # pylint: disable=no-member
return new_component
def traverse(self) -> Generator[GraphComponent, None, None]:
# Breadth first search.
visited = {self}
fontier = deque([self])
while fontier:
component = fontier.popleft()
yield component
for neighbor in component.neighbors:
if neighbor not in visited:
visited.add(neighbor)
fontier.append(neighbor)
"""@package docstring
B-ASIC Graph ID module for handling IDs of different objects in a graph.
TODO: More info
"""
from collections import defaultdict
from typing import NewType, DefaultDict
GraphID = NewType("GraphID", str)
GraphIDType = NewType("GraphIDType", str)
GraphIDNumber = NewType("GraphIDNumber", int)
class GraphIDGenerator:
"""A class that generates Graph IDs for objects."""
_next_id_number: DefaultDict[GraphIDType, GraphIDNumber]
def __init__(self):
self._next_id_number = defaultdict(lambda: 1) # Initalises every key element to 1
def get_next_id(self, graph_id_type: GraphIDType) -> GraphID:
"""Return the next graph id for a certain graph id type."""
graph_id = graph_id_type + str(self._next_id_number[graph_id_type])
self._next_id_number[graph_id_type] += 1 # Increase the current id number
return graph_id
This diff is collapsed.
......@@ -4,12 +4,15 @@ TODO: More info.
"""
from abc import ABC, abstractmethod
from typing import NewType, Optional, List
from copy import copy
from typing import Optional, List, Iterable, TYPE_CHECKING
from b_asic.operation import Operation
from b_asic.signal import Signal
from b_asic.graph_component import Name
if TYPE_CHECKING:
from b_asic.operation import Operation
PortIndex = NewType("PortIndex", int)
class Port(ABC):
"""Port Interface.
......@@ -19,59 +22,45 @@ class Port(ABC):
@property
@abstractmethod
def operation(self) -> Operation:
def operation(self) -> "Operation":
"""Return the connected operation."""
raise NotImplementedError
@property
@abstractmethod
def index(self) -> PortIndex:
"""Return the unique PortIndex."""
def index(self) -> int:
"""Return the index of the port."""
raise NotImplementedError
@property
@abstractmethod
def signals(self) -> List[Signal]:
"""Return a list of all connected signals."""
def latency_offset(self) -> int:
"""Get the latency_offset of the port."""
raise NotImplementedError
@latency_offset.setter
@abstractmethod
def signal(self, i: int = 0) -> Signal:
"""Return the connected signal at index i.
Keyword argumens:
i: integer index of the signal requsted.
"""
def latency_offset(self, latency_offset: int) -> None:
"""Set the latency_offset of the port to the integer specified value."""
raise NotImplementedError
@property
@abstractmethod
def connected_ports(self) -> List["Port"]:
"""Return a list of all connected Ports."""
raise NotImplementedError
@abstractmethod
def signal_count(self) -> int:
"""Return the number of connected signals."""
raise NotImplementedError
@property
@abstractmethod
def connect(self, port: "Port") -> Signal:
"""Create and return a signal that is connected to this port and the entered
port and connect this port to the signal and the entered port to the signal."""
def signals(self) -> Iterable[Signal]:
"""Return all connected signals."""
raise NotImplementedError
@abstractmethod
def add_signal(self, signal: Signal) -> None:
"""Connect this port to the entered signal. If the entered signal isn't connected to
this port then connect the entered signal to the port aswell."""
raise NotImplementedError
@abstractmethod
def disconnect(self, port: "Port") -> None:
"""Disconnect the entered port from the port by removing it from the ports signal.
If the entered port is still connected to this ports signal then disconnect the entered
port from the signal aswell."""
this port then connect the entered signal to the port aswell.
"""
raise NotImplementedError
@abstractmethod
......@@ -97,21 +86,43 @@ class AbstractPort(Port):
Handles functionality for port id and saves the connection to the parent operation.
"""
_operation: "Operation"
_index: int
_operation: Operation
_latency_offset: Optional[int]
def __init__(self, index: int, operation: Operation):
self._index = index
def __init__(self, operation: "Operation", index: int, latency_offset: int = None):
self._operation = operation
self._index = index
self._latency_offset = latency_offset
@property
def operation(self) -> Operation:
def operation(self) -> "Operation":
return self._operation
@property
def index(self) -> PortIndex:
def index(self) -> int:
return self._index
@property
def latency_offset(self) -> int:
return self._latency_offset
@latency_offset.setter
def latency_offset(self, latency_offset: int):
self._latency_offset = latency_offset
class SignalSourceProvider(ABC):
"""Signal source provider interface.
TODO: More info.
"""
@property
@abstractmethod
def source(self) -> "OutputPort":
"""Get the main source port provided by this object."""
raise NotImplementedError
class InputPort(AbstractPort):
"""Input port.
......@@ -120,104 +131,88 @@ class InputPort(AbstractPort):
_source_signal: Optional[Signal]
def __init__(self, port_id: PortIndex, operation: Operation):
super().__init__(port_id, operation)
def __init__(self, operation: "Operation", index: int):
super().__init__(operation, index)
self._source_signal = None
@property
def signals(self) -> List[Signal]:
return [] if self._source_signal is None else [self._source_signal]
def signal(self, i: int = 0) -> Signal:
assert 0 <= i < self.signal_count(), "Signal index out of bound."
assert self._source_signal is not None, "No Signal connect to InputPort."
return self._source_signal
@property
def connected_ports(self) -> List[Port]:
return [] if self._source_signal is None or self._source_signal.source is None \
else [self._source_signal.source]
def signal_count(self) -> int:
return 0 if self._source_signal is None else 1
def connect(self, port: "OutputPort") -> Signal:
assert self._source_signal is None, "Connecting new port to already connected input port."
return Signal(port, self) # self._source_signal is set by the signal constructor
@property
def signals(self) -> Iterable[Signal]:
return [] if self._source_signal is None else [self._source_signal]
def add_signal(self, signal: Signal) -> None:
assert self._source_signal is None, "Connecting new port to already connected input port."
self._source_signal: Signal = signal
if self is not signal.destination:
# Connect this inputport as destination for this signal if it isn't already.
signal.set_destination(self)
def disconnect(self, port: "OutputPort") -> None:
assert self._source_signal.source is port, "The entered port is not connected to this port."
self._source_signal.remove_source()
assert self._source_signal is None, "Input port may have only one signal added."
assert signal is not self._source_signal, "Attempted to add already connected signal."
self._source_signal = signal
signal.set_destination(self)
def remove_signal(self, signal: Signal) -> None:
old_signal: Signal = self._source_signal
assert signal is self._source_signal, "Attempted to remove signal that is not connected."
self._source_signal = None
if self is old_signal.destination:
# Disconnect the dest of the signal if this inputport currently is the dest
old_signal.remove_destination()
signal.remove_destination()
def clear(self) -> None:
self.remove_signal(self._source_signal)
if self._source_signal is not None:
self.remove_signal(self._source_signal)
@property
def connected_source(self) -> Optional["OutputPort"]:
"""Get the output port that is currently connected to this input port,
or None if it is unconnected.
"""
return None if self._source_signal is None else self._source_signal.source
class OutputPort(AbstractPort):
def connect(self, src: SignalSourceProvider, name: Name = "") -> Signal:
"""Connect the provided signal source to this input port by creating a new signal.
Returns the new signal.
"""
assert self._source_signal is None, "Attempted to connect already connected input port."
# self._source_signal is set by the signal constructor.
return Signal(source=src.source, destination=self, name=name)
def __lshift__(self, src: SignalSourceProvider) -> Signal:
"""Overloads the left shift operator to make it connect the provided signal source to this input port.
Returns the new signal.
"""
return self.connect(src)
class OutputPort(AbstractPort, SignalSourceProvider):
"""Output port.
TODO: More info.
"""
_destination_signals: List[Signal]
def __init__(self, port_id: PortIndex, operation: Operation):
super().__init__(port_id, operation)
def __init__(self, operation: "Operation", index: int):
super().__init__(operation, index)
self._destination_signals = []
@property
def signals(self) -> List[Signal]:
return self._destination_signals.copy()
def signal(self, i: int = 0) -> Signal:
assert 0 <= i < self.signal_count(), "Signal index out of bounds."
return self._destination_signals[i]
@property
def connected_ports(self) -> List[Port]:
return [signal.destination for signal in self._destination_signals \
if signal.destination is not None]
def signal_count(self) -> int:
return len(self._destination_signals)
def connect(self, port: InputPort) -> Signal:
return Signal(self, port) # Signal is added to self._destination_signals in signal constructor
@property
def signals(self) -> Iterable[Signal]:
return self._destination_signals
def add_signal(self, signal: Signal) -> None:
assert signal not in self.signals, \
"Attempting to connect to Signal already connected."
assert signal not in self._destination_signals, "Attempted to add already connected signal."
self._destination_signals.append(signal)
if self is not signal.source:
# Connect this outputport to the signal if it isn't already
signal.set_source(self)
def disconnect(self, port: InputPort) -> None:
assert port in self.connected_ports, "Attempting to disconnect port that isn't connected."
for sig in self._destination_signals:
if sig.destination is port:
sig.remove_destination()
break
signal.set_source(self)
def remove_signal(self, signal: Signal) -> None:
i: int = self._destination_signals.index(signal)
old_signal: Signal = self._destination_signals[i]
del self._destination_signals[i]
if self is old_signal.source:
old_signal.remove_source()
assert signal in self._destination_signals, "Attempted to remove signal that is not connected."
self._destination_signals.remove(signal)
signal.remove_source()
def clear(self) -> None:
for signal in self._destination_signals:
for signal in copy(self._destination_signals):
self.remove_signal(signal)
@property
def source(self) -> "OutputPort":
return self
"""@package docstring
B-ASIC Precedence Chart Module.
TODO: More info.
"""
from b_asic.signal_flow_graph import SFG
class PrecedenceChart:
"""Precedence chart constructed from a signal flow graph.
TODO: More info.
"""
sfg: SFG
# TODO: More members.
def __init__(self, sfg: SFG):
self.sfg = sfg
# TODO: Implement.
# TODO: More stuff.
"""@package docstring
B-ASIC Save/Load SFG Module.
Given a structure try to serialize it and save it to a file.
Given a serialized file try to deserialize it and load it to the library.
"""
from b_asic import SFG, GraphComponent
from datetime import datetime
from inspect import signature
from os import path
def sfg_to_python(sfg: SFG, counter: int = 0, suffix: str = None) -> str:
result = (
"\n\"\"\"\nB-ASIC automatically generated SFG file.\n" +
"Name: " + f"{sfg.name}" + "\n" +
"Last saved: " + f"{datetime.now()}" + ".\n" +
"\"\"\""
)
result += "\nfrom b_asic import SFG, Signal, Input, Output"
for op in {type(op) for op in sfg.operations}:
result += f", {op.__name__}"
def kwarg_unpacker(comp: GraphComponent, params=None) -> str:
if params is None:
params_filtered = {attr: getattr(op, attr) for attr in signature(op.__init__).parameters if attr != "latency" and hasattr(op, attr)}
params = {attr: getattr(op, attr) if not isinstance(getattr(op, attr), str) else f'"{getattr(op, attr)}"' for attr in params_filtered}
return ", ".join([f"{param[0]}={param[1]}" for param in params.items()])
result += "\n# Inputs:\n"
for op in sfg._input_operations:
result += f"{op.graph_id} = Input({kwarg_unpacker(op)})\n"
result += "\n# Outputs:\n"
for op in sfg._output_operations:
result += f"{op.graph_id} = Output({kwarg_unpacker(op)})\n"
result += "\n# Operations:\n"
for op in sfg.split():
if isinstance(op, SFG):
counter += 1
result = sfg_to_python(op, counter) + result
continue
result += f"{op.graph_id} = {op.__class__.__name__}({kwarg_unpacker(op)})\n"
result += "\n# Signals:\n"
connections = list() # Keep track of already existing connections to avoid adding duplicates
for op in sfg.split():
for out in op.outputs:
for signal in out.signals:
dest_op = signal.destination.operation
connection = f"\nSignal(source={op.graph_id}.output({op.outputs.index(signal.source)}), destination={dest_op.graph_id}.input({dest_op.inputs.index(signal.destination)}))"
if connection in connections:
continue
result += connection
connections.append(connection)
inputs = "[" + ", ".join(op.graph_id for op in sfg.input_operations) + "]"
outputs = "[" + ", ".join(op.graph_id for op in sfg.output_operations) + "]"
sfg_name = sfg.name if sfg.name else "sfg" + str(counter) if counter > 0 else 'sfg'
result += f"\n{sfg_name} = SFG(inputs={inputs}, outputs={outputs}, name='{sfg_name}')\n"
result += "\n# SFG Properties:\n" + "prop = {'name':" + f"{sfg_name}" + "}"
if suffix is not None:
result += "\n" + suffix + "\n"
return result
def python_to_sfg(path: str) -> SFG:
with open(path) as f:
code = compile(f.read(), path, 'exec')
exec(code, globals(), locals())
return locals()["prop"]["name"], locals()["positions"] if "positions" in locals() else None
\ No newline at end of file
"""@package docstring
B-ASIC Schema Module.
TODO: More info.
This module contains the Schema class.
TODO: More info
"""
from b_asic.precedence_chart import PrecedenceChart
from typing import Dict, List
from b_asic.signal_flow_graph import SFG
from b_asic.graph_component import GraphID
from b_asic.operation import Operation
from b_asic.special_operations import *
class Schema:
"""Schema constructed from a precedence chart.
TODO: More info.
"""
"""A class that represents an SFG with scheduled Operations."""
_sfg: SFG
_start_times: Dict[GraphID, int]
_laps: Dict[GraphID, List[int]]
_schedule_time: int
_cyclic: bool
_resolution: int
def __init__(self, sfg: SFG, schedule_time: int = None, cyclic: bool = False, resolution: int = 1, scheduling_alg: str = "ASAP"):
self._sfg = sfg
self._start_times = dict()
self._laps = dict()
self._cyclic = cyclic
self._resolution = resolution
self._memory_elements = dict()
if scheduling_alg == "ASAP":
self._schedule_asap()
else:
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)
if not self._cyclic:
if schedule_time is None:
self._schedule_time = max_end_time
elif schedule_time < max_end_time:
raise ValueError("Too short schedule time for non-cyclic Schedule entered.")
else:
self._schedule_time = schedule_time
def start_time_of_operation(self, op_id: GraphID):
"""Get the start time of the operation with the specified by the op_id."""
assert op_id in self._start_times, "No operation with the specified op_id in this schema."
return self._start_times[op_id]
def forward_slack(self, op_id):
raise NotImplementedError
def backward_slack(self, op_id):
raise NotImplementedError
def print_slacks(self):
raise NotImplementedError
def _schedule_asap(self):
pl = self._sfg.get_precedence_list()
if len(pl) < 2:
print("Empty signal flow graph cannot be scheduled.")
return
non_schedulable_ops = set((outp.operation.graph_id for outp in pl[0]))
for outport in pl[1]:
op = outport.operation
if op not in self._start_times:
# Set start time of all operations in the first iter to 0
self._start_times[op.graph_id] = 0
for outports in pl[2:]:
for outport in outports:
op = outport.operation
if op.graph_id not in self._start_times:
# Schedule the operation if it doesn't have a start time yet.
op_start_time = 0
for inport in op.inputs:
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."
source_port = inport.signals[0].source
source_end_time = None
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]
assert source_port.latency_offset is not None, f"Output port: {source_port.index} of operation: \
{source_port.operation.graph_id} has no latency-offset."
assert inport.latency_offset is not None, f"Input port: {inport.index} of operation: \
{inport.operation.graph_id} has no latency-offset."
source_end_time = source_op_time + source_port.latency_offset
op_start_time_from_in = source_end_time - inport.latency_offset
op_start_time = max(op_start_time, op_start_time_from_in)
self._start_times[op.graph_id] = op_start_time
def get_memory_elements(self):
operation_orderd = self._sfg.get_operations_topological_order()
for op in operation_orderd:
if isinstance(op, Input) or isinstance(op, Output):
pass
for key in self._start_times:
if op.graph_id == key:
for i in range(len(op.outputs)):
time_list = []
start_time = self._start_times.get(op.graph_id)+op.outputs[i].latency_offset
time_list.append(start_time)
for j in range(len(op.outputs[i].signals)):
new_op = self.get_op_after_delay(op.outputs[i].signals[j].destination.operation, op.outputs[i].signals[j].destination)
end_start_time = self._start_times.get(new_op[0].graph_id)
end_start_time_latency_offset = new_op[1].latency_offset
if end_start_time_latency_offset is None:
end_start_time_latency_offset = 0
if end_start_time is None:
end_time = self._schedule_time
else:
end_time = end_start_time + end_start_time_latency_offset
time_list.append(end_time)
read_name = op.name
write_name = new_op[0].name
key_name = read_name + "->" + write_name
self._memory_elements[key_name] = time_list
def get_op_after_delay(self, op, destination):
if isinstance(op, Delay):
for i in range(len(op.outputs[0].signals)):
connected_op = op.outputs[0].signals[i].destination.operation
dest = op.outputs[0].signals[i].destination
return self.get_op_after_delay(connected_op, dest)
return [op, destination]
pc: PrecedenceChart
# TODO: More members.
def print_memory_elements(self):
self.get_memory_elements()
output_string = ""
for key in self._memory_elements:
output_string += key
output_string += ": start time: "
output_string += str(self._memory_elements[key][0])
output_string += " end time: "
output_string += str(self._memory_elements[key][1])
output_string += '\n'
print(output_string)
def __init__(self, pc: PrecedenceChart):
self.pc = pc
# TODO: Implement.
# TODO: More stuff.
"""@package docstring
B-ASIC Signal Module.
"""
from typing import Optional, TYPE_CHECKING
from typing import Optional, Iterable, TYPE_CHECKING
from b_asic.graph_component import AbstractGraphComponent, TypeName, Name
from b_asic.graph_component import GraphComponent, AbstractGraphComponent, TypeName, Name
if TYPE_CHECKING:
from b_asic.port import InputPort, OutputPort
......@@ -12,30 +12,34 @@ if TYPE_CHECKING:
class Signal(AbstractGraphComponent):
"""A connection between two ports."""
_source: "OutputPort"
_destination: "InputPort"
def __init__(self, source: Optional["OutputPort"] = None, \
destination: Optional["InputPort"] = None, name: Name = ""):
_source: Optional["OutputPort"]
_destination: Optional["InputPort"]
def __init__(self, source: Optional["OutputPort"] = None, destination: Optional["InputPort"] = None, bits: Optional[int] = None, name: Name = ""):
super().__init__(name)
self._source = source
self._destination = destination
self._source = None
self._destination = None
if source is not None:
self.set_source(source)
if destination is not None:
self.set_destination(destination)
self.set_param("bits", bits)
@classmethod
def type_name(cls) -> TypeName:
return "s"
@property
def source(self) -> "OutputPort":
def neighbors(self) -> Iterable[GraphComponent]:
return [p.operation for p in [self.source, self.destination] if p is not None]
@property
def source(self) -> Optional["OutputPort"]:
"""Return the source OutputPort of the signal."""
return self._source
@property
def destination(self) -> "InputPort":
def destination(self) -> Optional["InputPort"]:
"""Return the destination "InputPort" of the signal."""
return self._destination
......@@ -47,11 +51,11 @@ class Signal(AbstractGraphComponent):
Keyword arguments:
- src: OutputPort to connect as source to the signal.
"""
self.remove_source()
self._source = src
if self not in src.signals:
# If the new source isn't connected to this signal then connect it.
src.add_signal(self)
if src is not self._source:
self.remove_source()
self._source = src
if self not in src.signals:
src.add_signal(self)
def set_destination(self, dest: "InputPort") -> None:
"""Disconnect the previous destination InputPort of the signal and
......@@ -61,36 +65,44 @@ class Signal(AbstractGraphComponent):
Keywords argments:
- dest: InputPort to connect as destination to the signal.
"""
self.remove_destination()
self._destination = dest
if self not in dest.signals:
# If the new destination isn't connected to tis signal then connect it.
dest.add_signal(self)
@property
def type_name(self) -> TypeName:
return "s"
if dest is not self._destination:
self.remove_destination()
self._destination = dest
if self not in dest.signals:
dest.add_signal(self)
def remove_source(self) -> None:
"""Disconnect the source OutputPort of the signal. If the source port
still is connected to this signal then also disconnect the source port."""
if self._source is not None:
old_source: "OutputPort" = self._source
src = self._source
if src is not None:
self._source = None
if self in old_source.signals:
# If the old destination port still is connected to this signal, then disconnect it.
old_source.remove_signal(self)
if self in src.signals:
src.remove_signal(self)
def remove_destination(self) -> None:
"""Disconnect the destination InputPort of the signal."""
if self._destination is not None:
old_destination: "InputPort" = self._destination
dest = self._destination
if dest is not None:
self._destination = None
if self in old_destination.signals:
# If the old destination port still is connected to this signal, then disconnect it.
old_destination.remove_signal(self)
if self in dest.signals:
dest.remove_signal(self)
def is_connected(self) -> bool:
"""Returns true if the signal is connected to both a source and a destination,
def dangling(self) -> bool:
"""Returns true if the signal is missing either a source or a destination,
else false."""
return self._source is not None and self._destination is not None
return self._source is None or self._destination is None
@property
def bits(self) -> Optional[int]:
"""Get the number of bits that this operations using this signal as an input should truncate received values to.
None = unlimited."""
return self.param("bits")
@bits.setter
def bits(self, bits: Optional[int]) -> None:
"""Set the number of bits that operations using this signal as an input should truncate received values to.
None = unlimited."""
assert bits is None or (isinstance(bits, int)
and bits >= 0), "Bits must be non-negative."
self.set_param("bits", bits)
This diff is collapsed.