diff --git a/CMakeLists.txt b/CMakeLists.txt index e0d087c07656292751f53b55abd2bdea0feeaeaa..485cd69235641fdd10b8aebacdebcee91bc65a26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project( ) # Find dependencies. -find_package(fmt 5.2.1 REQUIRED) +find_package(fmt REQUIRED) find_package(pybind11 CONFIG REQUIRED) set(LIBRARY_NAME "b_asic") # Name of the python library directory. diff --git a/GUI/main_window.py b/GUI/main_window.py deleted file mode 100644 index 53dacce61b5acbfe9de9607a2499c57e0a386eae..0000000000000000000000000000000000000000 --- a/GUI/main_window.py +++ /dev/null @@ -1,200 +0,0 @@ -"""@package docstring -B-ASIC GUI Module. -This python file is an example of how a GUI can be implemented -using buttons and textboxes. -""" - -import sys - -from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QAction,\ -QStatusBar, QMenuBar, QLineEdit, QPushButton -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QIcon, QFont, QPainter, QPen - - -class DragButton(QPushButton): - """How to create a dragbutton""" - def mousePressEvent(self, event): - self._mouse_press_pos = None - self._mouse_move_pos = None - if event.button() == Qt.LeftButton: - self._mouse_press_pos = event.globalPos() - self._mouse_move_pos = event.globalPos() - - super(DragButton, self).mousePressEvent(event) - - def mouseMoveEvent(self, event): - if event.buttons() == Qt.LeftButton: - cur_pos = self.mapToGlobal(self.pos()) - global_pos = event.globalPos() - diff = global_pos - self._mouse_move_pos - new_pos = self.mapFromGlobal(cur_pos + diff) - self.move(new_pos) - - self._mouse_move_pos = global_pos - - super(DragButton, self).mouseMoveEvent(event) - - def mouseReleaseEvent(self, event): - if self._mouse_press_pos is not None: - moved = event.globalPos() - self._mouse_press_pos - if moved.manhattanLength() > 3: - event.ignore() - return - - super(DragButton, self).mouseReleaseEvent(event) - -class SubWindow(QWidget): - """Creates a sub window """ - def create_window(self, window_width, window_height): - """Creates a window - """ - parent = None - super(SubWindow, self).__init__(parent) - self.setWindowFlags(Qt.WindowStaysOnTopHint) - self.resize(window_width, window_height) - -class MainWindow(QMainWindow): - """Main window for the program""" - def __init__(self, *args, **kwargs): - super(MainWindow, self).__init__(*args, **kwargs) - - self.setWindowTitle(" ") - self.setWindowIcon(QIcon('small_logo.png')) - - # Menu buttons - test_button = QAction("Test", self) - - exit_button = QAction("Exit", self) - exit_button.setShortcut("Ctrl+Q") - exit_button.triggered.connect(self.exit_app) - - edit_button = QAction("Edit", self) - edit_button.setStatusTip("Open edit menu") - edit_button.triggered.connect(self.on_edit_button_click) - - view_button = QAction("View", self) - view_button.setStatusTip("Open view menu") - view_button.triggered.connect(self.on_view_button_click) - - menu_bar = QMenuBar() - menu_bar.setStyleSheet("background-color:rgb(222, 222, 222)") - self.setMenuBar(menu_bar) - - file_menu = menu_bar.addMenu("&File") - file_menu.addAction(exit_button) - file_menu.addSeparator() - file_menu.addAction(test_button) - - edit_menu = menu_bar.addMenu("&Edit") - edit_menu.addAction(edit_button) - - edit_menu.addSeparator() - - view_menu = menu_bar.addMenu("&View") - view_menu.addAction(view_button) - - self.setStatusBar(QStatusBar(self)) - - def on_file_button_click(self): - print("File") - - def on_edit_button_click(self): - print("Edit") - - def on_view_button_click(self): - print("View") - - def exit_app(self, checked): - QApplication.quit() - - def clicked(self): - print("Drag button clicked") - - def add_drag_buttons(self): - """Adds draggable buttons""" - addition_button = DragButton("Addition", self) - addition_button.move(10, 130) - addition_button.setFixedSize(70, 20) - addition_button.clicked.connect(self.create_sub_window) - - addition_button2 = DragButton("Addition", self) - addition_button2.move(10, 130) - addition_button2.setFixedSize(70, 20) - addition_button2.clicked.connect(self.create_sub_window) - - subtraction_button = DragButton("Subtraction", self) - subtraction_button.move(10, 170) - subtraction_button.setFixedSize(70, 20) - subtraction_button.clicked.connect(self.create_sub_window) - - subtraction_button2 = DragButton("Subtraction", self) - subtraction_button2.move(10, 170) - subtraction_button2.setFixedSize(70, 20) - subtraction_button2.clicked.connect(self.create_sub_window) - - multiplication_button = DragButton("Multiplication", self) - multiplication_button.move(10, 210) - multiplication_button.setFixedSize(70, 20) - multiplication_button.clicked.connect(self.create_sub_window) - - multiplication_button2 = DragButton("Multiplication", self) - multiplication_button2.move(10, 210) - multiplication_button2.setFixedSize(70, 20) - multiplication_button2.clicked.connect(self.create_sub_window) - - def paintEvent(self, e): - # Temporary black box for operations - painter = QPainter(self) - painter.setPen(QPen(Qt.black, 5, Qt.SolidLine)) - painter.drawRect(0, 110, 100, 400) - - # Temporary arrow resembling a signal - painter.setRenderHint(QPainter.Antialiasing) - painter.setPen(Qt.black) - painter.setBrush(Qt.white) - painter.drawLine(300, 200, 400, 200) - painter.drawLine(400, 200, 395, 195) - painter.drawLine(400, 200, 395, 205) - - def create_sub_window(self): - """ Example of how to create a sub window - """ - self.sub_window = SubWindow() - self.sub_window.create_window(400, 300) - self.sub_window.setWindowTitle("Properties") - - self.sub_window.properties_label = QLabel(self.sub_window) - self.sub_window.properties_label.setText('Properties') - self.sub_window.properties_label.setFixedWidth(400) - self.sub_window.properties_label.setFont(QFont('SansSerif', 14, QFont.Bold)) - self.sub_window.properties_label.setAlignment(Qt.AlignCenter) - - self.sub_window.name_label = QLabel(self.sub_window) - self.sub_window.name_label.setText('Name:') - self.sub_window.name_label.move(20, 40) - - self.sub_window.name_line = QLineEdit(self.sub_window) - self.sub_window.name_line.setPlaceholderText("Write a name here") - self.sub_window.name_line.move(70, 40) - self.sub_window.name_line.resize(100, 20) - - self.sub_window.id_label = QLabel(self.sub_window) - self.sub_window.id_label.setText('Id:') - self.sub_window.id_label.move(20, 70) - - self.sub_window.id_line = QLineEdit(self.sub_window) - self.sub_window.id_line.setPlaceholderText("Write an id here") - self.sub_window.id_line.move(70, 70) - self.sub_window.id_line.resize(100, 20) - - self.sub_window.show() - - -if __name__ == "__main__": - app = QApplication(sys.argv) - window = MainWindow() - window.add_drag_buttons() - window.resize(960, 720) - window.show() - app.exec_() diff --git a/README.md b/README.md index 20f28bee3cfd71fc1b03100b9f9f3632f8a8c284..b2972f30828f19038e5efe612831f989b8f5ffd5 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The following packages are required in order to build the library: * setuptools * pybind11 * numpy - * pyside2/pyqt5 + * pyside2 To build a binary distribution, the following additional packages are required: * Python: diff --git a/b_asic/GUI/__init__.py b/b_asic/GUI/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..16dd0253b1690cef57efa5a25f5adf28f53646f7 --- /dev/null +++ b/b_asic/GUI/__init__.py @@ -0,0 +1,4 @@ +"""TODO""" +from drag_button import * +from improved_main_window import * +from gui_interface import * diff --git a/b_asic/GUI/about_window.py b/b_asic/GUI/about_window.py new file mode 100644 index 0000000000000000000000000000000000000000..0cb5fa7e3ee73eac015995592db86f4f8c3972c4 --- /dev/null +++ b/b_asic/GUI/about_window.py @@ -0,0 +1,131 @@ +from PySide2.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QDialog, QLabel, QFrame, QScrollArea +from PySide2.QtCore import Qt + + +QUESTIONS = { + "Adding operations": "Select an operation under 'Special operations' or 'Core operations' to add it to the workspace.", + "Moving operations": "To drag an operation, select the operation on the workspace and drag it around.", + "Selecting operations": "To select one operation just press it once, it will then turn grey.", + "Selecting multiple operations using dragging": "To select multiple operations using your mouse, \ndrag the mouse while pressing left mouse button, any operation under the selection box will then be selected.", + "Selecting multiple operations using without dragging": "To select mutliple operations using without dragging, \npress 'Ctrl+LMouseButton' on any operation. Alternatively press 'Ctrl+A' to select all operations.", + "Remove operations": "To remove an operation, select the operation to be deleted, \nfinally press RMouseButton to bring up the context menu, then press 'Delete'.", + "Remove multiple operations": "To remove multiple operations, \nselect all operations to be deleted and press 'Delete' on your keyboard.", + "Connecting operations": "To connect operations, select the ports on the operation to connect from, \nthen select the next port by pressing 'Ctrl+LMouseButton' on the destination port. Tip: You can chain connection by selecting the ports in the order they should be connected.", + "Creating a signal-flow-graph": "To create a signal-flow-graph (SFG), \ncouple together the operations you wish to create a sfg from, then select all operations you wish to include in the sfg, \nfinally press 'Create SFG' in the upper left corner and enter the name of the sfg.", + "Simulating a signal-flow-graph": "To simulate a signal-flow-graph (SFG), press the run button in the toolbar, \nthen press 'Simulate SFG' and enter the properties of the simulation.", + "Properties of simulation": "The properties of the simulation are, 'Iteration Count': The number of iterations to run the simulation for, \n'Plot Results': Open a plot over the output in matplotlib, \n'Get All Results': Print the detailed output from simulating the sfg in the terminal, \n'Input Values': The input values to the SFG by index of the port." +} + + +class KeybindsWindow(QDialog): + def __init__(self, window): + super(KeybindsWindow, self).__init__() + self._window = window + self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint) + self.setWindowTitle("B-ASIC Keybinds") + + self.dialog_layout = QVBoxLayout() + self.setLayout(self.dialog_layout) + + self.add_information_to_layout() + + def add_information_to_layout(self): + information_layout = QVBoxLayout() + + title_label = QLabel("B-ASIC / Better ASIC Toolbox") + subtitle_label = QLabel("Keybinds in the GUI.") + + frame = QFrame() + frame.setFrameShape(QFrame.HLine) + frame.setFrameShadow(QFrame.Sunken) + self.dialog_layout.addWidget(frame) + + keybinds_label = QLabel( + "'Ctrl+A' - Select all operations on the workspace.\n" + "'Ctrl+R' - Reload the operation list to add any new operations created.\n" + "'Ctrl+Q' - Quit the application.\n" + "'Ctrl+LMouseButton' - On a operation will select the operation, without deselecting the other operations.\n" + "'Ctrl+S' (Plot) - Save the plot if a plot is visible.\n" + "'Ctrl+?' - Open the FAQ section." + ) + + information_layout.addWidget(title_label) + information_layout.addWidget(subtitle_label) + + self.dialog_layout.addLayout(information_layout) + self.dialog_layout.addWidget(frame) + + self.dialog_layout.addWidget(keybinds_label) + + +class AboutWindow(QDialog): + def __init__(self, window): + super(AboutWindow, self).__init__() + self._window = window + self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint) + self.setWindowTitle("About B-ASIC") + + self.dialog_layout = QVBoxLayout() + self.setLayout(self.dialog_layout) + + self.add_information_to_layout() + + def add_information_to_layout(self): + information_layout = QVBoxLayout() + + title_label = QLabel("B-ASIC / Better ASIC Toolbox") + subtitle_label = QLabel("Construct, simulate and analyze components of an ASIC.") + + frame = QFrame() + frame.setFrameShape(QFrame.HLine) + frame.setFrameShadow(QFrame.Sunken) + self.dialog_layout.addWidget(frame) + + about_label = QLabel( + "B-ASIC is a open source tool using the B-ASIC library to construct, simulate and analyze ASICs.\n" + "B-ASIC is developed under the MIT-license and any extension to the program should follow that same license.\n" + "To read more about how the GUI works please refer to the FAQ under 'Help'." + ) + + information_layout.addWidget(title_label) + information_layout.addWidget(subtitle_label) + + self.dialog_layout.addLayout(information_layout) + self.dialog_layout.addWidget(frame) + + self.dialog_layout.addWidget(about_label) + + +class FaqWindow(QDialog): + def __init__(self, window): + super(FaqWindow, self).__init__() + self._window = window + self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint) + self.setWindowTitle("Frequently Asked Questions") + + self.dialog_layout = QVBoxLayout() + self.scroll_area = QScrollArea() + self.setLayout(self.dialog_layout) + for question, answer in QUESTIONS.items(): + self.add_question_to_layout(question, answer) + + self.scroll_area.setWidget(self) + self.scroll_area.setWidgetResizable(True) + + def add_question_to_layout(self, question, answer): + question_layout = QVBoxLayout() + answer_layout = QHBoxLayout() + + question_label = QLabel(question) + question_layout.addWidget(question_label) + + answer_label = QLabel(answer) + answer_layout.addWidget(answer_label) + + frame = QFrame() + frame.setFrameShape(QFrame.HLine) + frame.setFrameShadow(QFrame.Sunken) + self.dialog_layout.addWidget(frame) + + question_layout.addLayout(answer_layout) + self.dialog_layout.addLayout(question_layout) diff --git a/b_asic/GUI/arrow.py b/b_asic/GUI/arrow.py new file mode 100644 index 0000000000000000000000000000000000000000..3c931150f3af95f6f2f2d17552c91072e41c4008 --- /dev/null +++ b/b_asic/GUI/arrow.py @@ -0,0 +1,38 @@ +from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QAction,\ +QStatusBar, QMenuBar, QLineEdit, QPushButton, QSlider, QScrollArea, QVBoxLayout,\ +QHBoxLayout, QDockWidget, QToolBar, QMenu, QLayout, QSizePolicy, QListWidget, QListWidgetItem,\ +QGraphicsLineItem, QGraphicsWidget +from PySide2.QtCore import Qt, QSize, QLineF, QPoint, QRectF +from PySide2.QtGui import QIcon, QFont, QPainter, QPen + +from b_asic import Signal + +class Arrow(QGraphicsLineItem): + + def __init__(self, source, destination, window, parent=None): + super(Arrow, self).__init__(parent) + self.source = source + self.signal = Signal(source.port, destination.port) + self.destination = destination + self._window = window + self.moveLine() + self.source.moved.connect(self.moveLine) + self.destination.moved.connect(self.moveLine) + + def contextMenuEvent(self, event): + menu = QMenu() + menu.addAction("Delete", self.remove) + menu.exec_(self.cursor().pos()) + + def remove(self): + self.signal.remove_destination() + self.signal.remove_source() + self._window.scene.removeItem(self) + self._window.signalList.remove(self) + + def moveLine(self): + self.setPen(QPen(Qt.black, 3)) + self.setLine(QLineF(self.source.operation.x()+self.source.x()+14,\ + self.source.operation.y()+self.source.y()+7.5,\ + self.destination.operation.x()+self.destination.x(),\ + self.destination.operation.y()+self.destination.y()+7.5)) diff --git a/b_asic/GUI/drag_button.py b/b_asic/GUI/drag_button.py new file mode 100644 index 0000000000000000000000000000000000000000..cd54115cfe0dd4b3c9365b21aaeb45124fa45944 --- /dev/null +++ b/b_asic/GUI/drag_button.py @@ -0,0 +1,139 @@ +"""@package docstring +Drag button class. +This class creates a dragbutton which can be clicked, dragged and dropped. +""" + +import os.path + +from properties_window import PropertiesWindow + +from PySide2.QtWidgets import QPushButton, QMenu, QAction +from PySide2.QtCore import Qt, QSize, Signal +from PySide2.QtGui import QIcon + +from utils import decorate_class, handle_error + + +@decorate_class(handle_error) +class DragButton(QPushButton): + connectionRequested = Signal(QPushButton) + moved = Signal() + def __init__(self, name, operation, operation_path_name, is_show_name, window, parent = None): + self.name = name + self.ports = [] + self.is_show_name = is_show_name + self._window = window + self.operation = operation + self.operation_path_name = operation_path_name + self.clicked = 0 + self.pressed = False + self._m_press = False + self._m_drag = False + self._mouse_press_pos = None + self._mouse_move_pos = None + super(DragButton, self).__init__(parent) + + def contextMenuEvent(self, event): + menu = QMenu() + properties = QAction("Properties") + menu.addAction(properties) + properties.triggered.connect(self.show_properties_window) + + delete = QAction("Delete") + menu.addAction(delete) + delete.triggered.connect(self.remove) + menu.exec_(self.cursor().pos()) + + def show_properties_window(self): + self.properties_window = PropertiesWindow(self, self._window) + self.properties_window.show() + + def add_label(self, label): + self.label = label + + def mousePressEvent(self, event): + if event.button() == Qt.LeftButton: + self._m_press = True + self._mouse_press_pos = event.pos() + self._mouse_move_pos = event.pos() + + super(DragButton, self).mousePressEvent(event) + + def mouseMoveEvent(self, event): + if event.buttons() == Qt.LeftButton and self._m_press: + self._m_drag = True + self.move(self.mapToParent(event.pos() - self._mouse_press_pos)) + if self in self._window.pressed_operations: + for button in self._window.pressed_operations: + if button is self: + continue + + button.move(button.mapToParent(event.pos() - self._mouse_press_pos)) + + self._window.update() + super(DragButton, self).mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + self._m_press = False + if self._m_drag: + if self._mouse_press_pos is not None: + moved = event.pos() - self._mouse_press_pos + if moved.manhattanLength() > 3: + event.ignore() + + self._m_drag = False + + else: + self.select_button(event.modifiers()) + + super(DragButton, self).mouseReleaseEvent(event) + + def _toggle_button(self, pressed=False): + self.pressed = not pressed + self.setStyleSheet(f"background-color: {'white' if not self.pressed else 'grey'}; border-style: solid;\ + border-color: black; border-width: 2px") + path_to_image = os.path.join('operation_icons', f"{self.operation_path_name}{'_grey.png' if self.pressed else '.png'}") + self.setIcon(QIcon(path_to_image)) + self.setIconSize(QSize(55, 55)) + + def select_button(self, modifiers=None): + if modifiers != Qt.ControlModifier: + for button in self._window.pressed_operations: + button._toggle_button(button.pressed) + + self._toggle_button(self.pressed) + self._window.pressed_operations = [self] + + else: + self._toggle_button(self.pressed) + if self in self._window.pressed_operations: + self._window.pressed_operations.remove(self) + else: + self._window.pressed_operations.append(self) + + for signal in self._window.signalList: + signal.update() + + def remove(self): + self._window.logger.info(f"Removing operation with name {self.operation.name}.") + self._window.scene.removeItem(self._window.operationDict[self]) + + _signals = [] + for signal, ports in self._window.signalPortDict.items(): + if any([port in self._window.portDict[self] for port in ports]): + self._window.logger.info(f"Removed signal with name: {signal.signal.name} to/from operation: {self.operation.name}.") + signal.remove() + _signals.append(signal) + + for signal in _signals: + del self._window.signalPortDict[signal] + + for port in self._window.portDict[self]: + if port in self._window.pressed_ports: + self._window.pressed_ports.remove(port) + + if self in self._window.pressed_operations: + self._window.pressed_operations.remove(self) + + if self in self._window.operationDict.keys(): + del self._window.operationDict[self] \ No newline at end of file diff --git a/b_asic/GUI/gui_interface.py b/b_asic/GUI/gui_interface.py new file mode 100644 index 0000000000000000000000000000000000000000..50c6c1dd419744b9366057a357af8e81f4a4c64a --- /dev/null +++ b/b_asic/GUI/gui_interface.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'gui_interface.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PySide2 import QtCore, QtGui, QtWidgets + + +class Ui_main_window(object): + def setupUi(self, main_window): + main_window.setObjectName("main_window") + main_window.setEnabled(True) + main_window.resize(897, 633) + self.centralwidget = QtWidgets.QWidget(main_window) + self.centralwidget.setObjectName("centralwidget") + self.operation_box = QtWidgets.QGroupBox(self.centralwidget) + self.operation_box.setGeometry(QtCore.QRect(10, 10, 201, 531)) + self.operation_box.setLayoutDirection(QtCore.Qt.LeftToRight) + self.operation_box.setAutoFillBackground(False) + self.operation_box.setStyleSheet("QGroupBox { \n" +" border: 2px solid gray; \n" +" border-radius: 3px;\n" +" margin-top: 0.5em; \n" +" } \n" +"\n" +"QGroupBox::title {\n" +" subcontrol-origin: margin;\n" +" left: 10px;\n" +" padding: 0 3px 0 3px;\n" +"}") + self.operation_box.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.operation_box.setFlat(False) + self.operation_box.setCheckable(False) + self.operation_box.setObjectName("operation_box") + self.operation_list = QtWidgets.QToolBox(self.operation_box) + self.operation_list.setGeometry(QtCore.QRect(10, 20, 171, 271)) + self.operation_list.setAutoFillBackground(False) + self.operation_list.setObjectName("operation_list") + self.core_operations_page = QtWidgets.QWidget() + self.core_operations_page.setGeometry(QtCore.QRect(0, 0, 171, 217)) + self.core_operations_page.setObjectName("core_operations_page") + self.core_operations_list = QtWidgets.QListWidget(self.core_operations_page) + self.core_operations_list.setGeometry(QtCore.QRect(10, 0, 141, 211)) + self.core_operations_list.setMinimumSize(QtCore.QSize(141, 0)) + self.core_operations_list.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed) + self.core_operations_list.setDragEnabled(False) + self.core_operations_list.setDragDropMode(QtWidgets.QAbstractItemView.NoDragDrop) + self.core_operations_list.setMovement(QtWidgets.QListView.Static) + self.core_operations_list.setFlow(QtWidgets.QListView.TopToBottom) + self.core_operations_list.setProperty("isWrapping", False) + self.core_operations_list.setResizeMode(QtWidgets.QListView.Adjust) + self.core_operations_list.setLayoutMode(QtWidgets.QListView.SinglePass) + self.core_operations_list.setViewMode(QtWidgets.QListView.ListMode) + self.core_operations_list.setUniformItemSizes(False) + self.core_operations_list.setWordWrap(False) + self.core_operations_list.setSelectionRectVisible(False) + self.core_operations_list.setObjectName("core_operations_list") + self.operation_list.addItem(self.core_operations_page, "") + self.special_operations_page = QtWidgets.QWidget() + self.special_operations_page.setGeometry(QtCore.QRect(0, 0, 171, 217)) + self.special_operations_page.setObjectName("special_operations_page") + self.special_operations_list = QtWidgets.QListWidget(self.special_operations_page) + self.special_operations_list.setGeometry(QtCore.QRect(10, 0, 141, 81)) + self.special_operations_list.setObjectName("special_operations_list") + self.operation_list.addItem(self.special_operations_page, "") + main_window.setCentralWidget(self.centralwidget) + self.menu_bar = QtWidgets.QMenuBar(main_window) + self.menu_bar.setGeometry(QtCore.QRect(0, 0, 897, 21)) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 255, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(127, 127, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(170, 170, 170)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 128)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.PlaceholderText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 255, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(127, 127, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(170, 170, 170)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 128)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.PlaceholderText, brush) + brush = QtGui.QBrush(QtGui.QColor(127, 127, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 255, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(127, 127, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(170, 170, 170)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(127, 127, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(127, 127, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 128)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.PlaceholderText, brush) + self.menu_bar.setPalette(palette) + self.menu_bar.setObjectName("menu_bar") + self.file_menu = QtWidgets.QMenu(self.menu_bar) + self.file_menu.setObjectName("file_menu") + self.edit_menu = QtWidgets.QMenu(self.menu_bar) + self.edit_menu.setObjectName("edit_menu") + self.view_menu = QtWidgets.QMenu(self.menu_bar) + self.view_menu.setObjectName("view_menu") + self.run_menu = QtWidgets.QMenu(self.menu_bar) + self.run_menu.setObjectName("run_menu") + self.help_menu = QtWidgets.QMenu(self.menu_bar) + self.help_menu.setObjectName("help_menu") + main_window.setMenuBar(self.menu_bar) + self.status_bar = QtWidgets.QStatusBar(main_window) + self.status_bar.setObjectName("status_bar") + main_window.setStatusBar(self.status_bar) + self.save_menu = QtWidgets.QAction(main_window) + self.save_menu.setObjectName("save_menu") + self.exit_menu = QtWidgets.QAction(main_window) + self.exit_menu.setObjectName("exit_menu") + self.actionUndo = QtWidgets.QAction(main_window) + self.actionUndo.setObjectName("actionUndo") + self.actionRedo = QtWidgets.QAction(main_window) + self.actionRedo.setObjectName("actionRedo") + self.actionSimulateSFG = QtWidgets.QAction(main_window) + self.actionSimulateSFG.setObjectName("actionSimulateSFG") + self.actionShowPC = QtWidgets.QAction(main_window) + self.actionShowPC.setObjectName("actionShowPC") + self.aboutBASIC = QtWidgets.QAction(main_window) + self.aboutBASIC.setObjectName("aboutBASIC") + self.faqBASIC = QtWidgets.QAction(main_window) + self.faqBASIC.setObjectName("faqBASIC") + self.keybindsBASIC = QtWidgets.QAction(main_window) + self.keybindsBASIC.setObjectName("keybindsBASIC") + self.actionToolbar = QtWidgets.QAction(main_window) + self.actionToolbar.setCheckable(True) + self.actionToolbar.setObjectName("actionToolbar") + self.file_menu.addAction(self.save_menu) + self.file_menu.addSeparator() + self.file_menu.addAction(self.exit_menu) + self.edit_menu.addAction(self.actionUndo) + self.edit_menu.addAction(self.actionRedo) + self.view_menu.addAction(self.actionToolbar) + self.run_menu.addAction(self.actionShowPC) + self.run_menu.addAction(self.actionSimulateSFG) + self.help_menu.addAction(self.aboutBASIC) + self.help_menu.addAction(self.faqBASIC) + self.help_menu.addAction(self.keybindsBASIC) + self.menu_bar.addAction(self.file_menu.menuAction()) + self.menu_bar.addAction(self.edit_menu.menuAction()) + self.menu_bar.addAction(self.view_menu.menuAction()) + self.menu_bar.addAction(self.run_menu.menuAction()) + self.menu_bar.addAction(self.help_menu.menuAction()) + + self.retranslateUi(main_window) + self.operation_list.setCurrentIndex(1) + self.core_operations_list.setCurrentRow(-1) + QtCore.QMetaObject.connectSlotsByName(main_window) + + def retranslateUi(self, main_window): + _translate = QtCore.QCoreApplication.translate + main_window.setWindowTitle(_translate("main_window", "B-ASIC")) + self.operation_box.setTitle(_translate("main_window", "Operations")) + self.core_operations_list.setSortingEnabled(False) + __sortingEnabled = self.core_operations_list.isSortingEnabled() + self.core_operations_list.setSortingEnabled(False) + self.core_operations_list.setSortingEnabled(__sortingEnabled) + self.operation_list.setItemText(self.operation_list.indexOf(self.core_operations_page), _translate("main_window", "Core operations")) + __sortingEnabled = self.special_operations_list.isSortingEnabled() + self.special_operations_list.setSortingEnabled(False) + self.special_operations_list.setSortingEnabled(__sortingEnabled) + self.operation_list.setItemText(self.operation_list.indexOf(self.special_operations_page), _translate("main_window", "Special operations")) + self.file_menu.setTitle(_translate("main_window", "File")) + self.edit_menu.setTitle(_translate("main_window", "Edit")) + self.view_menu.setTitle(_translate("main_window", "View")) + self.run_menu.setTitle(_translate("main_window", "Run")) + self.actionShowPC.setText(_translate("main_window", "Show PC")) + self.help_menu.setTitle(_translate("main_window", "Help")) + self.actionSimulateSFG.setText(_translate("main_window", "Simulate SFG")) + self.aboutBASIC.setText(_translate("main_window", "About B-ASIC")) + self.faqBASIC.setText(_translate("main_window", "FAQ")) + self.keybindsBASIC.setText(_translate("main_window", "Keybinds")) + self.save_menu.setText(_translate("main_window", "Save")) + self.exit_menu.setText(_translate("main_window", "Exit")) + self.exit_menu.setShortcut(_translate("main_window", "Ctrl+Q")) + self.actionUndo.setText(_translate("main_window", "Undo")) + self.actionRedo.setText(_translate("main_window", "Redo")) + self.actionToolbar.setText(_translate("main_window", "Toolbar")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + main_window = QtWidgets.QMainWindow() + ui = Ui_main_window() + ui.setupUi(main_window) + main_window.show() + sys.exit(app.exec_()) diff --git a/b_asic/GUI/gui_interface.ui b/b_asic/GUI/gui_interface.ui new file mode 100644 index 0000000000000000000000000000000000000000..382747818a3286373b825e4115c9b283799156bc --- /dev/null +++ b/b_asic/GUI/gui_interface.ui @@ -0,0 +1,763 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>main_window</class> + <widget class="QMainWindow" name="main_window"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>897</width> + <height>633</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <widget class="QGroupBox" name="operation_box"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>201</width> + <height>531</height> + </rect> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="styleSheet"> + <string notr="true">QGroupBox { + border: 2px solid gray; + border-radius: 3px; + margin-top: 0.5em; + } + +QGroupBox::title { + subcontrol-origin: margin; + left: 10px; + padding: 0 3px 0 3px; +}</string> + </property> + <property name="title"> + <string>Operations</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <widget class="QToolBox" name="operation_list"> + <property name="geometry"> + <rect> + <x>10</x> + <y>20</y> + <width>171</width> + <height>271</height> + </rect> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="currentIndex"> + <number>1</number> + </property> + <widget class="QWidget" name="core_operations_page"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>171</width> + <height>217</height> + </rect> + </property> + <attribute name="label"> + <string>Core operations</string> + </attribute> + <widget class="QListWidget" name="core_operations_list"> + <property name="geometry"> + <rect> + <x>10</x> + <y>0</y> + <width>141</width> + <height>211</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>141</width> + <height>0</height> + </size> + </property> + <property name="editTriggers"> + <set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set> + </property> + <property name="dragEnabled"> + <bool>false</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::NoDragDrop</enum> + </property> + <property name="movement"> + <enum>QListView::Static</enum> + </property> + <property name="flow"> + <enum>QListView::TopToBottom</enum> + </property> + <property name="isWrapping" stdset="0"> + <bool>false</bool> + </property> + <property name="resizeMode"> + <enum>QListView::Adjust</enum> + </property> + <property name="layoutMode"> + <enum>QListView::SinglePass</enum> + </property> + <property name="viewMode"> + <enum>QListView::ListMode</enum> + </property> + <property name="uniformItemSizes"> + <bool>false</bool> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + <property name="selectionRectVisible"> + <bool>false</bool> + </property> + <property name="currentRow"> + <number>-1</number> + </property> + <property name="sortingEnabled"> + <bool>false</bool> + </property> + <item> + <property name="text"> + <string>Addition</string> + </property> + </item> + <item> + <property name="text"> + <string>Subtraction</string> + </property> + </item> + <item> + <property name="text"> + <string>Multiplication</string> + </property> + </item> + <item> + <property name="text"> + <string>Division</string> + </property> + </item> + <item> + <property name="text"> + <string>Constant</string> + </property> + </item> + <item> + <property name="text"> + <string>Constant multiplication</string> + </property> + </item> + <item> + <property name="text"> + <string>Square root</string> + </property> + </item> + <item> + <property name="text"> + <string>Complex conjugate</string> + </property> + </item> + <item> + <property name="text"> + <string>Absolute</string> + </property> + </item> + <item> + <property name="text"> + <string>Max</string> + </property> + </item> + <item> + <property name="text"> + <string>Min</string> + </property> + </item> + <item> + <property name="text"> + <string>Butterfly</string> + </property> + </item> + </widget> + </widget> + <widget class="QWidget" name="special_operations_page"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>171</width> + <height>217</height> + </rect> + </property> + <attribute name="label"> + <string>Special operations</string> + </attribute> + <widget class="QListWidget" name="special_operations_list"> + <property name="geometry"> + <rect> + <x>10</x> + <y>0</y> + <width>141</width> + <height>81</height> + </rect> + </property> + <item> + <property name="text"> + <string>Input</string> + </property> + </item> + <item> + <property name="text"> + <string>Output</string> + </property> + </item> + <item> + <property name="text"> + <string>Delay</string> + </property> + </item> + <item> + <property name="text"> + <string>Custom</string> + </property> + </item> + </widget> + </widget> + </widget> + </widget> + </widget> + <widget class="QMenuBar" name="menu_bar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>897</width> + <height>21</height> + </rect> + </property> + <property name="palette"> + <palette> + <active> + <colorrole role="WindowText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="Button"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>255</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Midlight"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Dark"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>127</red> + <green>127</green> + <blue>127</blue> + </color> + </brush> + </colorrole> + <colorrole role="Mid"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>170</red> + <green>170</green> + <blue>170</blue> + </color> + </brush> + </colorrole> + <colorrole role="Text"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="BrightText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="ButtonText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="Base"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Window"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Shadow"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="AlternateBase"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="ToolTipBase"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>220</blue> + </color> + </brush> + </colorrole> + <colorrole role="ToolTipText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="PlaceholderText"> + <brush brushstyle="SolidPattern"> + <color alpha="128"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </active> + <inactive> + <colorrole role="WindowText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="Button"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>255</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Midlight"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Dark"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>127</red> + <green>127</green> + <blue>127</blue> + </color> + </brush> + </colorrole> + <colorrole role="Mid"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>170</red> + <green>170</green> + <blue>170</blue> + </color> + </brush> + </colorrole> + <colorrole role="Text"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="BrightText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="ButtonText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="Base"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Window"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Shadow"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="AlternateBase"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="ToolTipBase"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>220</blue> + </color> + </brush> + </colorrole> + <colorrole role="ToolTipText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="PlaceholderText"> + <brush brushstyle="SolidPattern"> + <color alpha="128"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </inactive> + <disabled> + <colorrole role="WindowText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>127</red> + <green>127</green> + <blue>127</blue> + </color> + </brush> + </colorrole> + <colorrole role="Button"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>255</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Midlight"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Dark"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>127</red> + <green>127</green> + <blue>127</blue> + </color> + </brush> + </colorrole> + <colorrole role="Mid"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>170</red> + <green>170</green> + <blue>170</blue> + </color> + </brush> + </colorrole> + <colorrole role="Text"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>127</red> + <green>127</green> + <blue>127</blue> + </color> + </brush> + </colorrole> + <colorrole role="BrightText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="ButtonText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>127</red> + <green>127</green> + <blue>127</blue> + </color> + </brush> + </colorrole> + <colorrole role="Base"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Window"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="Shadow"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="AlternateBase"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </brush> + </colorrole> + <colorrole role="ToolTipBase"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>255</green> + <blue>220</blue> + </color> + </brush> + </colorrole> + <colorrole role="ToolTipText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + <colorrole role="PlaceholderText"> + <brush brushstyle="SolidPattern"> + <color alpha="128"> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </disabled> + </palette> + </property> + <widget class="QMenu" name="file_menu"> + <property name="title"> + <string>File</string> + </property> + <addaction name="save_menu"/> + <addaction name="separator"/> + <addaction name="exit_menu"/> + </widget> + <widget class="QMenu" name="edit_menu"> + <property name="title"> + <string>Edit</string> + </property> + <addaction name="actionUndo"/> + <addaction name="actionRedo"/> + </widget> + <widget class="QMenu" name="view_menu"> + <property name="title"> + <string>View</string> + </property> + <addaction name="actionToolbar"/> + </widget> + <addaction name="file_menu"/> + <addaction name="edit_menu"/> + <addaction name="view_menu"/> + </widget> + <widget class="QStatusBar" name="status_bar"/> + <action name="save_menu"> + <property name="text"> + <string>Save</string> + </property> + </action> + <action name="exit_menu"> + <property name="text"> + <string>Exit</string> + </property> + <property name="shortcut"> + <string>Ctrl+Q</string> + </property> + </action> + <action name="actionUndo"> + <property name="text"> + <string>Undo</string> + </property> + </action> + <action name="actionRedo"> + <property name="text"> + <string>Redo</string> + </property> + </action> + <action name="actionToolbar"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Toolbar</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui> diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py new file mode 100644 index 0000000000000000000000000000000000000000..178eeefaf7053ebfffc83f18ac7acda509977788 --- /dev/null +++ b/b_asic/GUI/main_window.py @@ -0,0 +1,341 @@ +"""@package docstring +B-ASIC GUI Module. +This python file is the main window of the GUI for B-ASIC. +""" + +from pprint import pprint +from os import getcwd, path +import logging +logging.basicConfig(level=logging.INFO) +import sys + +from about_window import AboutWindow, FaqWindow, KeybindsWindow +from drag_button import DragButton +from gui_interface import Ui_main_window +from arrow import Arrow +from port_button import PortButton +from show_pc_window import ShowPCWindow + +from b_asic import Operation, SFG, InputPort, OutputPort +from b_asic.simulation import Simulation +import b_asic.core_operations as c_oper +import b_asic.special_operations as s_oper +from utils import decorate_class, handle_error +from simulate_sfg_window import SimulateSFGWindow, Plot + +from numpy import linspace + +from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QAction,\ +QStatusBar, QMenuBar, QLineEdit, QPushButton, QSlider, QScrollArea, QVBoxLayout,\ +QHBoxLayout, QDockWidget, QToolBar, QMenu, QLayout, QSizePolicy, QListWidget,\ +QListWidgetItem, QGraphicsView, QGraphicsScene, QShortcut, QGraphicsTextItem,\ +QGraphicsProxyWidget, QInputDialog, QTextEdit +from PySide2.QtCore import Qt, QSize +from PySide2.QtGui import QIcon, QFont, QPainter, QPen, QBrush, QKeySequence + +MIN_WIDTH_SCENE = 600 +MIN_HEIGHT_SCENE = 520 + +@decorate_class(handle_error) +class MainWindow(QMainWindow): + def __init__(self): + super(MainWindow, self).__init__() + self.ui = Ui_main_window() + self.ui.setupUi(self) + self.setWindowIcon(QIcon('small_logo.png')) + self.scene = None + self._operations_from_name = dict() + self.zoom = 1 + self.sfg_name_i = 0 + self.operationDict = dict() + self.operationItemSceneList = [] + self.signalList = [] + self.pressed_operations = [] + self.portDict = dict() + self.signalPortDict = dict() + self.pressed_ports = [] + self.sfg_list = [] + self._window = self + self.logger = logging.getLogger(__name__) + self.init_ui() + self.add_operations_from_namespace(c_oper, self.ui.core_operations_list) + self.add_operations_from_namespace(s_oper, self.ui.special_operations_list) + + self.shortcut_core = QShortcut(QKeySequence("Ctrl+R"), self.ui.operation_box) + self.shortcut_core.activated.connect(self._refresh_operations_list_from_namespace) + self.shortcut_select_all = QShortcut(QKeySequence("Ctrl+A"), self.graphic_view) + self.shortcut_select_all.activated.connect(self._select_all_operations) + self.scene.selectionChanged.connect(self._select_operations) + + self.move_button_index = 0 + self.is_show_names = True + + self.check_show_names = QAction("Show operation names") + self.check_show_names.triggered.connect(self.view_operation_names) + self.check_show_names.setCheckable(True) + self.check_show_names.setChecked(1) + self.ui.view_menu.addAction(self.check_show_names) + + self.ui.actionShowPC.triggered.connect(self.show_precedence_chart) + self.ui.actionSimulateSFG.triggered.connect(self.simulate_sfg) + self.ui.faqBASIC.triggered.connect(self.display_faq_page) + self.ui.aboutBASIC.triggered.connect(self.display_about_page) + self.ui.keybindsBASIC.triggered.connect(self.display_keybinds_page) + self.shortcut_help = QShortcut(QKeySequence("Ctrl+?"), self) + self.shortcut_help.activated.connect(self.display_faq_page) + + self.logger.info("Finished setting up GUI") + self.logger.info("For questions please refer to 'Ctrl+?', or visit the 'Help' section on the toolbar.") + + def init_ui(self): + self.ui.core_operations_list.itemClicked.connect(self.on_list_widget_item_clicked) + self.ui.special_operations_list.itemClicked.connect(self.on_list_widget_item_clicked) + self.ui.exit_menu.triggered.connect(self.exit_app) + self.create_toolbar_view() + self.create_graphics_view() + + def create_graphics_view(self): + self.scene = QGraphicsScene(self) + self.graphic_view = QGraphicsView(self.scene, self) + self.graphic_view.setRenderHint(QPainter.Antialiasing) + self.graphic_view.setGeometry(self.ui.operation_box.width(), 20, self.width(), self.height()) + self.graphic_view.setDragMode(QGraphicsView.RubberBandDrag) + + def create_toolbar_view(self): + self.toolbar = self.addToolBar("Toolbar") + self.toolbar.addAction("Create SFG", self.create_SFG_from_toolbar) + + def resizeEvent(self, event): + self.ui.operation_box.setGeometry(10, 10, self.ui.operation_box.width(), self.height()) + self.graphic_view.setGeometry(self.ui.operation_box.width() + 20, 30, self.width() - self.ui.operation_box.width() - 20, self.height()-30) + super(MainWindow, self).resizeEvent(event) + + def wheelEvent(self, event): + if event.modifiers() == Qt.ControlModifier: + old_zoom = self.zoom + self.zoom += event.angleDelta().y()/2500 + self.graphic_view.scale(self.zoom, self.zoom) + self.zoom = old_zoom + + def view_operation_names(self): + if self.check_show_names.isChecked(): + self.is_show_names = True + else: + self.is_show_names = False + + for operation in self.operationDict.keys(): + operation.label.setOpacity(self.is_show_names) + operation.is_show_name = self.is_show_names + + def exit_app(self): + self.logger.info("Exiting the application.") + QApplication.quit() + + def create_SFG_from_toolbar(self): + inputs = [] + outputs = [] + for op in self.pressed_operations: + if isinstance(op.operation, s_oper.Input): + inputs.append(op.operation) + elif isinstance(op.operation, s_oper.Output): + outputs.append(op.operation) + + name = QInputDialog.getText(self, "Create SFG", "Name: ", QLineEdit.Normal) + self.logger.info(f"Creating SFG with name: {name[0]} from selected operations.") + + sfg = SFG(inputs=inputs, outputs=outputs, name=name[0]) + self.logger.info(f"Created SFG with name: {name[0]} from selected operations.") + + for op in self.pressed_operations: + op.setToolTip(sfg.name) + self.sfg_list.append(sfg) + + def show_precedence_chart(self): + self.dialog = ShowPCWindow(self) + self.dialog.add_sfg_to_dialog() + self.dialog.show() + + def _determine_port_distance(self, length, ports): + """Determine the distance between each port on the side of an operation. + The method returns the distance that each port should have from 0. + """ + return [length / 2] if ports == 1 else linspace(0, length, ports) + + def add_ports(self, operation): + _output_ports_dist = self._determine_port_distance(55 - 17, operation.operation.output_count) + _input_ports_dist = self._determine_port_distance(55 - 17, operation.operation.input_count) + self.portDict[operation] = list() + + for i, dist in enumerate(_input_ports_dist): + port = PortButton(">", operation, operation.operation.input(i), self) + self.portDict[operation].append(port) + operation.ports.append(port) + port.move(0, dist) + port.show() + + for i, dist in enumerate(_output_ports_dist): + port = PortButton(">", operation, operation.operation.output(i), self) + self.portDict[operation].append(port) + operation.ports.append(port) + port.move(55 - 12, dist) + port.show() + + def get_operations_from_namespace(self, namespace): + self.logger.info(f"Fetching operations from namespace: {namespace.__name__}.") + return [comp for comp in dir(namespace) if hasattr(getattr(namespace, comp), "type_name")] + + def add_operations_from_namespace(self, namespace, _list): + for attr_name in self.get_operations_from_namespace(namespace): + attr = getattr(namespace, attr_name) + try: + attr.type_name() + item = QListWidgetItem(attr_name) + _list.addItem(item) + self._operations_from_name[attr_name] = attr + except NotImplementedError: + pass + + self.logger.info(f"Added operations from namespace: {namespace.__name__}.") + + def _create_operation(self, item): + self.logger.info(f"Creating operation of type: {item.text()}.") + try: + attr_oper = self._operations_from_name[item.text()]() + attr_button = DragButton(attr_oper.graph_id, attr_oper, attr_oper.type_name().lower(), True, self) + attr_button.move(250, 100) + attr_button.setFixedSize(55, 55) + attr_button.setStyleSheet("background-color: white; border-style: solid;\ + border-color: black; border-width: 2px") + self.add_ports(attr_button) + + icon_path = path.join("operation_icons", f"{attr_oper.type_name().lower()}.png") + if not path.exists(icon_path): + icon_path = path.join("operation_icons", f"custom_operation.png") + attr_button.setIcon(QIcon(icon_path)) + attr_button.setIconSize(QSize(55, 55)) + attr_button.setToolTip("No sfg") + attr_button.setStyleSheet(""" QToolTip { background-color: white; + color: black }""") + attr_button.setParent(None) + attr_button_scene = self.scene.addWidget(attr_button) + attr_button_scene.moveBy(self.move_button_index * 100, 0) + attr_button_scene.setFlag(attr_button_scene.ItemIsSelectable, True) + self.move_button_index += 1 + operation_label = QGraphicsTextItem(attr_oper.type_name(), attr_button_scene) + if not self.is_show_names: + operation_label.setOpacity(0) + operation_label.setTransformOriginPoint(operation_label.boundingRect().center()) + operation_label.moveBy(10, -20) + attr_button.add_label(operation_label) + self.operationDict[attr_button] = attr_button_scene + except Exception as e: + self.logger.error(f"Unexpected error occured while creating operation: {e}.") + + def _refresh_operations_list_from_namespace(self): + self.logger.info("Refreshing operation list.") + self.ui.core_operations_list.clear() + self.ui.special_operations_list.clear() + + self.add_operations_from_namespace(c_oper, self.ui.core_operations_list) + self.add_operations_from_namespace(s_oper, self.ui.special_operations_list) + self.logger.info("Finished refreshing operation list.") + + def on_list_widget_item_clicked(self, item): + self._create_operation(item) + + def keyPressEvent(self, event): + if event.key() == Qt.Key_Delete: + for pressed_op in self.pressed_operations: + pressed_op.remove() + self.move_button_index -= 1 + self.pressed_operations.clear() + super().keyPressEvent(event) + + def connectButton(self, button): + if len(self.pressed_ports) < 2: + self.logger.warn("Can't connect less than two ports. Please select more.") + return + + for i in range(len(self.pressed_ports) - 1): + if isinstance(self.pressed_ports[i].port, OutputPort) and \ + isinstance(self.pressed_ports[i+1].port, InputPort): + self.logger.info(f"Connecting: {self.pressed_ports[i].operation.operation.type_name()} -> {self.pressed_ports[i + 1].operation.operation.type_name()}") + line = Arrow(self.pressed_ports[i], self.pressed_ports[i + 1], self) + self.signalPortDict[line] = [self.pressed_ports[i], self.pressed_ports[i + 1]] + + self.scene.addItem(line) + self.signalList.append(line) + + for port in self.pressed_ports: + port.select_port() + + self.update() + + def paintEvent(self, event): + for signal in self.signalPortDict.keys(): + signal.moveLine() + + def _select_all_operations(self): + self.logger.info("Selecting all operations in the workspace.") + self.pressed_operations.clear() + for button in self.operationDict.keys(): + button._toggle_button(pressed=False) + self.pressed_operations.append(button) + + def _select_operations(self): + selected = [button.widget() for button in self.scene.selectedItems()] + for button in selected: + button._toggle_button(pressed=False) + + for button in self.pressed_operations: + if button not in selected: + button._toggle_button(pressed=True) + + self.pressed_operations = selected + + def _simulate_sfg(self): + for sfg, properties in self.dialog.properties.items(): + self.logger.info(f"Simulating sfg with name: {sfg.name}.") + simulation = Simulation(sfg, input_providers=properties["input_values"], save_results=properties["all_results"]) + l_result = simulation.run_for(properties["iteration_count"]) + + if properties["all_results"]: + print(f"{'=' * 10} {sfg.name} {'=' * 10}") + pprint(simulation.results) + print(f"{'=' * 10} /{sfg.name} {'=' * 10}") + + if properties["show_plot"]: + self.logger.info(f"Opening plot for sfg with name: {sfg.name}.") + self.logger.info("To save the plot press 'Ctrl+S' when the plot is focused.") + self.plot = Plot(simulation, sfg, self) + self.plot.show() + + def simulate_sfg(self): + self.dialog = SimulateSFGWindow(self) + + for sfg in self.sfg_list: + self.dialog.add_sfg_to_dialog(sfg) + + self.dialog.show() + + # Wait for input to dialog. Kinda buggy because of the separate window in the same thread. + self.dialog.simulate.connect(self._simulate_sfg) + + def display_faq_page(self): + self.faq_page = FaqWindow(self) + self.faq_page.scroll_area.show() + + def display_about_page(self): + self.about_page = AboutWindow(self) + self.about_page.show() + + def display_keybinds_page(self): + self.keybinds_page = KeybindsWindow(self) + self.keybinds_page.show() + + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec_()) diff --git a/b_asic/GUI/operation_icons/abs.png b/b_asic/GUI/operation_icons/abs.png new file mode 100644 index 0000000000000000000000000000000000000000..6573d4d96928d32a3a59641e877108ab60d38c19 Binary files /dev/null and b/b_asic/GUI/operation_icons/abs.png differ diff --git a/b_asic/GUI/operation_icons/abs_grey.png b/b_asic/GUI/operation_icons/abs_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..4e16da3110d3497c7dab55b9cd9edf22aef4c097 Binary files /dev/null and b/b_asic/GUI/operation_icons/abs_grey.png differ diff --git a/b_asic/GUI/operation_icons/add.png b/b_asic/GUI/operation_icons/add.png new file mode 100644 index 0000000000000000000000000000000000000000..504e641e4642d9c03deeea9911927bbe714f053e Binary files /dev/null and b/b_asic/GUI/operation_icons/add.png differ diff --git a/b_asic/GUI/operation_icons/add_grey.png b/b_asic/GUI/operation_icons/add_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..a7620d2b56c8a5d06b2c04ff994eb334777008f4 Binary files /dev/null and b/b_asic/GUI/operation_icons/add_grey.png differ diff --git a/b_asic/GUI/operation_icons/bfly.png b/b_asic/GUI/operation_icons/bfly.png new file mode 100644 index 0000000000000000000000000000000000000000..9948a964d353e7325c696ae7f12e1ded09cdc13f Binary files /dev/null and b/b_asic/GUI/operation_icons/bfly.png differ diff --git a/b_asic/GUI/operation_icons/bfly_grey.png b/b_asic/GUI/operation_icons/bfly_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..cc282efe67637dda8b5e76b0f4c35015b19ddd93 Binary files /dev/null and b/b_asic/GUI/operation_icons/bfly_grey.png differ diff --git a/b_asic/GUI/operation_icons/c.png b/b_asic/GUI/operation_icons/c.png new file mode 100644 index 0000000000000000000000000000000000000000..0068adae8130f7b384f7fd70277fb8f87d9dbf94 Binary files /dev/null and b/b_asic/GUI/operation_icons/c.png differ diff --git a/b_asic/GUI/operation_icons/c_grey.png b/b_asic/GUI/operation_icons/c_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..d7d3e585e21e70aa8b89b85008e5306184ccff8c Binary files /dev/null and b/b_asic/GUI/operation_icons/c_grey.png differ diff --git a/b_asic/GUI/operation_icons/cmul.png b/b_asic/GUI/operation_icons/cmul.png new file mode 100644 index 0000000000000000000000000000000000000000..7e7ff82b3aa577886da6686f62df936e8aa3572e Binary files /dev/null and b/b_asic/GUI/operation_icons/cmul.png differ diff --git a/b_asic/GUI/operation_icons/cmul_grey.png b/b_asic/GUI/operation_icons/cmul_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..8fe92d2606b8bc1c2bce98ed45cccb6236e36476 Binary files /dev/null and b/b_asic/GUI/operation_icons/cmul_grey.png differ diff --git a/b_asic/GUI/operation_icons/conj.png b/b_asic/GUI/operation_icons/conj.png new file mode 100644 index 0000000000000000000000000000000000000000..c74c9de7f45c16b972ddc072e5e0203dcb902f1b Binary files /dev/null and b/b_asic/GUI/operation_icons/conj.png differ diff --git a/b_asic/GUI/operation_icons/conj_grey.png b/b_asic/GUI/operation_icons/conj_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..33f1e60e686cb711644a875341c34a12b8dfe4c9 Binary files /dev/null and b/b_asic/GUI/operation_icons/conj_grey.png differ diff --git a/b_asic/GUI/operation_icons/custom_operation.png b/b_asic/GUI/operation_icons/custom_operation.png new file mode 100644 index 0000000000000000000000000000000000000000..a598abaaf05934222c457fc8b141431ca04f91be Binary files /dev/null and b/b_asic/GUI/operation_icons/custom_operation.png differ diff --git a/b_asic/GUI/operation_icons/custom_operation_grey.png b/b_asic/GUI/operation_icons/custom_operation_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..a1d72e3b7f67fb78acccf8f5fb417be62cbc2b2a Binary files /dev/null and b/b_asic/GUI/operation_icons/custom_operation_grey.png differ diff --git a/b_asic/GUI/operation_icons/div.png b/b_asic/GUI/operation_icons/div.png new file mode 100644 index 0000000000000000000000000000000000000000..d7bf8908ed0acae344dc04adf6a96ae8448bb9d4 Binary files /dev/null and b/b_asic/GUI/operation_icons/div.png differ diff --git a/b_asic/GUI/operation_icons/div_grey.png b/b_asic/GUI/operation_icons/div_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..de3a82c369dde1b6a28ea93e5362e6eb8879fe68 Binary files /dev/null and b/b_asic/GUI/operation_icons/div_grey.png differ diff --git a/b_asic/GUI/operation_icons/in.png b/b_asic/GUI/operation_icons/in.png new file mode 100644 index 0000000000000000000000000000000000000000..ebfd1a23e5723496e69f02bebc959d68e3c2f986 Binary files /dev/null and b/b_asic/GUI/operation_icons/in.png differ diff --git a/b_asic/GUI/operation_icons/in_grey.png b/b_asic/GUI/operation_icons/in_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..07d3362f93039ca65966bc04d0ea481840072559 Binary files /dev/null and b/b_asic/GUI/operation_icons/in_grey.png differ diff --git a/b_asic/GUI/operation_icons/max.png b/b_asic/GUI/operation_icons/max.png new file mode 100644 index 0000000000000000000000000000000000000000..1f759155f632e090a5dea49644f8dbc5dff7b4f4 Binary files /dev/null and b/b_asic/GUI/operation_icons/max.png differ diff --git a/b_asic/GUI/operation_icons/max_grey.png b/b_asic/GUI/operation_icons/max_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..8179fb2e812cc617a012a1fb3d338e1c99e07aa7 Binary files /dev/null and b/b_asic/GUI/operation_icons/max_grey.png differ diff --git a/b_asic/GUI/operation_icons/min.png b/b_asic/GUI/operation_icons/min.png new file mode 100644 index 0000000000000000000000000000000000000000..5365002a74b91d91e40c95091ab8c052cfe3f3c5 Binary files /dev/null and b/b_asic/GUI/operation_icons/min.png differ diff --git a/b_asic/GUI/operation_icons/min_grey.png b/b_asic/GUI/operation_icons/min_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..6978cd91288a9d5705903efb017e2dc3b42b1af1 Binary files /dev/null and b/b_asic/GUI/operation_icons/min_grey.png differ diff --git a/b_asic/GUI/operation_icons/mul.png b/b_asic/GUI/operation_icons/mul.png new file mode 100644 index 0000000000000000000000000000000000000000..2042dd16781e64ea97ed5323b79f4f310f760009 Binary files /dev/null and b/b_asic/GUI/operation_icons/mul.png differ diff --git a/b_asic/GUI/operation_icons/mul_grey.png b/b_asic/GUI/operation_icons/mul_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..00e2304b634e02810d6a17aa2850c9afe4922eb9 Binary files /dev/null and b/b_asic/GUI/operation_icons/mul_grey.png differ diff --git a/b_asic/GUI/operation_icons/out.png b/b_asic/GUI/operation_icons/out.png new file mode 100644 index 0000000000000000000000000000000000000000..e7da51bbe3640b03b9f7d89f9dd90543c3022273 Binary files /dev/null and b/b_asic/GUI/operation_icons/out.png differ diff --git a/b_asic/GUI/operation_icons/out_grey.png b/b_asic/GUI/operation_icons/out_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..2cde317beecf019db5c4eac2a35baa8ef8e99f5e Binary files /dev/null and b/b_asic/GUI/operation_icons/out_grey.png differ diff --git a/b_asic/GUI/operation_icons/reg.png b/b_asic/GUI/operation_icons/reg.png new file mode 100644 index 0000000000000000000000000000000000000000..f294072fc6d3b3567d8252aee881d035aa4913eb Binary files /dev/null and b/b_asic/GUI/operation_icons/reg.png differ diff --git a/b_asic/GUI/operation_icons/reg_grey.png b/b_asic/GUI/operation_icons/reg_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..88af5760169b161fdd7aa5dce7c61cdb13b69231 Binary files /dev/null and b/b_asic/GUI/operation_icons/reg_grey.png differ diff --git a/b_asic/GUI/operation_icons/sqrt.png b/b_asic/GUI/operation_icons/sqrt.png new file mode 100644 index 0000000000000000000000000000000000000000..8160862b675680bb5fc2f31a0a5f870b73352bbb Binary files /dev/null and b/b_asic/GUI/operation_icons/sqrt.png differ diff --git a/b_asic/GUI/operation_icons/sqrt_grey.png b/b_asic/GUI/operation_icons/sqrt_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..4353217de9c38d665f1364a7f7c501c444232abc Binary files /dev/null and b/b_asic/GUI/operation_icons/sqrt_grey.png differ diff --git a/b_asic/GUI/operation_icons/sub.png b/b_asic/GUI/operation_icons/sub.png new file mode 100644 index 0000000000000000000000000000000000000000..73db57daf9984fd1c1eb0775a1e0535b786396a8 Binary files /dev/null and b/b_asic/GUI/operation_icons/sub.png differ diff --git a/b_asic/GUI/operation_icons/sub_grey.png b/b_asic/GUI/operation_icons/sub_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..8b32557a56f08e27adcd028f080d517f6ce5bdf2 Binary files /dev/null and b/b_asic/GUI/operation_icons/sub_grey.png differ diff --git a/b_asic/GUI/port_button.py b/b_asic/GUI/port_button.py new file mode 100644 index 0000000000000000000000000000000000000000..11c36ef5206ae4935a5bcf675a10eee22239d8dc --- /dev/null +++ b/b_asic/GUI/port_button.py @@ -0,0 +1,58 @@ + +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.connectButton) + + 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() + diff --git a/b_asic/GUI/properties_window.py b/b_asic/GUI/properties_window.py new file mode 100644 index 0000000000000000000000000000000000000000..22fdf7d2e9329571e98e38e4aad242b6fd50733e --- /dev/null +++ b/b_asic/GUI/properties_window.py @@ -0,0 +1,63 @@ +from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\ +QLabel, QCheckBox +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.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) + + 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 = 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.reject() \ No newline at end of file diff --git a/b_asic/GUI/show_pc_window.py b/b_asic/GUI/show_pc_window.py new file mode 100644 index 0000000000000000000000000000000000000000..3946ba9f821caed80893c52cb27213cbe3143edf --- /dev/null +++ b/b_asic/GUI/show_pc_window.py @@ -0,0 +1,49 @@ +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_list = [] + 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): + sfg_layout = QVBoxLayout() + options_layout = QFormLayout() + + for sfg in self._window.sfg_list: + check_box = QCheckBox() + options_layout.addRow(sfg.name, check_box) + self.check_box_list.append(check_box) + + sfg_layout.addLayout(options_layout) + + frame = QFrame() + frame.setFrameShape(QFrame.HLine) + frame.setFrameShadow(QFrame.Sunken) + self.dialog_layout.addWidget(frame) + + self.dialog_layout.addLayout(sfg_layout) + + def show_precedence_graph(self): + for i, check_box in enumerate(self.check_box_list): + if check_box.isChecked(): + self._window.logger.info("Creating a precedence chart from " + self._window.sfg_list[i].name) + self._window.sfg_list[i].show_precedence_graph() + break + + self.accept() + self.pc.emit() \ No newline at end of file diff --git a/b_asic/GUI/simulate_sfg_window.py b/b_asic/GUI/simulate_sfg_window.py new file mode 100644 index 0000000000000000000000000000000000000000..48e584dc87e045322f5d92946b431d32d7e180b5 --- /dev/null +++ b/b_asic/GUI/simulate_sfg_window.py @@ -0,0 +1,135 @@ +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.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 = QCheckBox() + options_layout.addRow("Plot Results: ", check_box) + + check_box = QCheckBox() + options_layout.addRow("Get All Results: ", check_box) + + input_layout = QVBoxLayout() + input_label = QLabel("Input Values: ") + input_layout.addWidget(input_label) + input_grid = QGridLayout() + x, y = 0, 0 + for i in range(sfg.input_count): + if i % 2 == 0 and i > 0: + x += 1 + y = 0 + + input_value = QLineEdit() + input_value.setPlaceholderText(str(i)) + input_value.setValidator(QIntValidator()) + input_value.setFixedWidth(50) + input_grid.addWidget(input_value, x, y) + y += 1 + + input_layout.addLayout(input_grid) + sfg_layout.addLayout(options_layout) + sfg_layout.addLayout(input_layout) + + 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, sfg_row in self.sfg_to_layout.items(): + _option_values = sfg_row.children()[0] + _input_values = sfg_row.children()[1].children()[0] + _, spin_box, _, check_box_plot, _, check_box_all = map(lambda x: _option_values.itemAt(x).widget(), range(_option_values.count())) + input_values = map(lambda x: _input_values.itemAt(x).widget(), range(_input_values.count())) + input_values = [int(widget.text()) if widget.text() else 0 for widget in input_values] + self.properties[sfg] = { + "iteration_count": spin_box.value(), + "show_plot": check_box_plot.isChecked(), + "all_results": check_box_all.isChecked(), + "input_values": input_values + } + + # If we plot we should also print the entire data, since you can't really interact with the graph. + if check_box_plot.isChecked(): + 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.keys()))) + for _output in range(self.sfg.output_count): + y_axis = list() + for _iter in range(len(self.simulation.results.keys())): + y_axis.append(self.simulation.results[_iter][str(_output)]) + + self.axes.plot(x_axis, y_axis) diff --git a/b_asic/GUI/utils.py b/b_asic/GUI/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..5234c6548bbbc66eb224fb9d3d3835d67a099213 --- /dev/null +++ b/b_asic/GUI/utils.py @@ -0,0 +1,20 @@ +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 diff --git a/b_asic/__init__.py b/b_asic/__init__.py index 254aa19b7403ad0c2b3dbb7a2b3c582cc5602e9f..537b799102f54559d15bc151e281f4f84a507120 100644 --- a/b_asic/__init__.py +++ b/b_asic/__init__.py @@ -6,9 +6,7 @@ from _b_asic import * from b_asic.core_operations import * from b_asic.graph_component 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 * diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py index 296803e3e55b7b92d85f52b91b30533ecdfbc0b6..a70c86b7d966197850604ad7a269242d364eca36 100644 --- a/b_asic/core_operations.py +++ b/b_asic/core_operations.py @@ -18,16 +18,16 @@ class Constant(AbstractOperation): """ def __init__(self, value: Number = 0, name: Name = ""): - super().__init__(input_count = 0, output_count = 1, name = name) + super().__init__(input_count=0, output_count=1, name=name) self.set_param("value", value) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "c" def evaluate(self): return self.param("value") - + @property def value(self) -> Number: """Get the constant value of this operation.""" @@ -45,10 +45,11 @@ class Addition(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 2, output_count = 1, name = name, input_sources = [src0, src1]) + super().__init__(input_count=2, output_count=1, + name=name, input_sources=[src0, src1]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "add" def evaluate(self, a, b): @@ -61,10 +62,11 @@ class Subtraction(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 2, output_count = 1, name = name, input_sources = [src0, src1]) + super().__init__(input_count=2, output_count=1, + name=name, input_sources=[src0, src1]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "sub" def evaluate(self, a, b): @@ -77,10 +79,11 @@ class Multiplication(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 2, output_count = 1, name = name, input_sources = [src0, src1]) + super().__init__(input_count=2, output_count=1, + name=name, input_sources=[src0, src1]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "mul" def evaluate(self, a, b): @@ -93,10 +96,11 @@ class Division(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 2, output_count = 1, name = name, input_sources = [src0, src1]) + super().__init__(input_count=2, output_count=1, + name=name, input_sources=[src0, src1]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "div" def evaluate(self, a, b): @@ -109,10 +113,11 @@ class Min(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 2, output_count = 1, name = name, input_sources = [src0, src1]) + super().__init__(input_count=2, output_count=1, + name=name, input_sources=[src0, src1]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "min" def evaluate(self, a, b): @@ -127,10 +132,11 @@ class Max(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 2, output_count = 1, name = name, input_sources = [src0, src1]) + super().__init__(input_count=2, output_count=1, + name=name, input_sources=[src0, src1]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "max" def evaluate(self, a, b): @@ -145,10 +151,11 @@ class SquareRoot(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 1, output_count = 1, name = name, input_sources = [src0]) + super().__init__(input_count=1, output_count=1, + name=name, input_sources=[src0]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "sqrt" def evaluate(self, a): @@ -161,10 +168,11 @@ class ComplexConjugate(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 1, output_count = 1, name = name, input_sources = [src0]) + super().__init__(input_count=1, output_count=1, + name=name, input_sources=[src0]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "conj" def evaluate(self, a): @@ -177,10 +185,11 @@ class Absolute(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 1, output_count = 1, name = name, input_sources = [src0]) + super().__init__(input_count=1, output_count=1, + name=name, input_sources=[src0]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "abs" def evaluate(self, a): @@ -193,11 +202,12 @@ class ConstantMultiplication(AbstractOperation): """ def __init__(self, value: Number = 0, src0: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 1, output_count = 1, name = name, input_sources = [src0]) + super().__init__(input_count=1, output_count=1, + name=name, input_sources=[src0]) self.set_param("value", value) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "cmul" def evaluate(self, a): @@ -221,11 +231,27 @@ class Butterfly(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 2, output_count = 2, name = name, input_sources = [src0, src1]) + super().__init__(input_count=2, output_count=2, + name=name, input_sources=[src0, src1]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "bfly" def evaluate(self, a, b): return a + b, a - b + +class MAD(AbstractOperation): + """Multiply-and-add operation. + TODO: More info. + """ + + def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, src2: Optional[SignalSourceProvider] = None, name: Name = ""): + super().__init__(input_count = 3, output_count = 1, name = name, input_sources = [src0, src1, src2]) + + @classmethod + def type_name(cls) -> TypeName: + return "mad" + + def evaluate(self, a, b, c): + return a * b + c diff --git a/b_asic/graph_component.py b/b_asic/graph_component.py index e37997016a3276f4dbde0394c44bf5c54ecbd51c..e08422a842a84d08dcab58ab03d7f581cb1bc664 100644 --- a/b_asic/graph_component.py +++ b/b_asic/graph_component.py @@ -20,9 +20,9 @@ class GraphComponent(ABC): TODO: More info. """ - @property + @classmethod @abstractmethod - def type_name(self) -> TypeName: + def type_name(cls) -> TypeName: """Get the type name of this graph component""" raise NotImplementedError @@ -105,6 +105,10 @@ class AbstractGraphComponent(GraphComponent): 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: return self._name @@ -112,7 +116,7 @@ class AbstractGraphComponent(GraphComponent): @name.setter def name(self, name: Name) -> None: self._name = name - + @property def graph_id(self) -> GraphID: return self._graph_id @@ -136,7 +140,7 @@ class AbstractGraphComponent(GraphComponent): 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 + new_component.set_param(copy(name), deepcopy(value)) # pylint: disable=no-member return new_component def traverse(self) -> Generator[GraphComponent, None, None]: @@ -149,4 +153,4 @@ class AbstractGraphComponent(GraphComponent): for neighbor in component.neighbors: if neighbor not in visited: visited.add(neighbor) - fontier.append(neighbor) \ No newline at end of file + fontier.append(neighbor) diff --git a/b_asic/operation.py b/b_asic/operation.py index 156c580f50e5bbc014c83257e1887810ebc37ed9..5866d8b29cd8cd26c34e5b68c5c6a83c30c6b086 100644 --- a/b_asic/operation.py +++ b/b_asic/operation.py @@ -143,8 +143,8 @@ class Operation(GraphComponent, SignalSourceProvider): @abstractmethod def key(self, index: int, prefix: str = "") -> ResultKey: - """Get the key used to access the result of a certain output of this operation - from the results parameter passed to current_output(s) or evaluate_output(s). + """Get the key used to access the output of a certain output of this operation + from the output parameter passed to current_output(s) or evaluate_output(s). """ raise NotImplementedError @@ -200,6 +200,13 @@ class Operation(GraphComponent, SignalSourceProvider): """Truncate the value to be used as input at the given index to a certain bit length.""" raise NotImplementedError + @abstractmethod + def to_sfg(self) -> "SFG": + """Convert the operation into its corresponding SFG. + If the operation is composed by multiple operations, the operation will be split. + """ + raise NotImplementedError + class AbstractOperation(Operation, AbstractGraphComponent): """Generic abstract operation class which most implementations will derive from. @@ -211,53 +218,63 @@ class AbstractOperation(Operation, AbstractGraphComponent): def __init__(self, input_count: int, output_count: int, name: Name = "", input_sources: Optional[Sequence[Optional[SignalSourceProvider]]] = None): super().__init__(name) - self._input_ports = [InputPort(self, i) for i in range(input_count)] # Allocate input ports. - self._output_ports = [OutputPort(self, i) for i in range(output_count)] # Allocate output ports. + + self._input_ports = [InputPort(self, i) for i in range(input_count)] + self._output_ports = [OutputPort(self, i) for i in range(output_count)] # Connect given input sources, if any. if input_sources is not None: source_count = len(input_sources) if source_count != input_count: - raise ValueError(f"Wrong number of input sources supplied to Operation (expected {input_count}, got {source_count})") + raise ValueError( + f"Wrong number of input sources supplied to Operation (expected {input_count}, got {source_count})") for i, src in enumerate(input_sources): if src is not None: self._input_ports[i].connect(src.source) @abstractmethod - def evaluate(self, *inputs) -> Any: # pylint: disable=arguments-differ + def evaluate(self, *inputs) -> Any: # pylint: disable=arguments-differ """Evaluate the operation and generate a list of output values given a list of input values.""" raise NotImplementedError def __add__(self, src: Union[SignalSourceProvider, Number]) -> "Addition": - from b_asic.core_operations import Constant, Addition # Import here to avoid circular imports. + # Import here to avoid circular imports. + from b_asic.core_operations import Constant, Addition return Addition(self, Constant(src) if isinstance(src, Number) else src) - + def __radd__(self, src: Union[SignalSourceProvider, Number]) -> "Addition": - from b_asic.core_operations import Constant, Addition # Import here to avoid circular imports. + # Import here to avoid circular imports. + from b_asic.core_operations import Constant, Addition return Addition(Constant(src) if isinstance(src, Number) else src, self) def __sub__(self, src: Union[SignalSourceProvider, Number]) -> "Subtraction": - from b_asic.core_operations import Constant, Subtraction # Import here to avoid circular imports. + # Import here to avoid circular imports. + from b_asic.core_operations import Constant, Subtraction return Subtraction(self, Constant(src) if isinstance(src, Number) else src) - + def __rsub__(self, src: Union[SignalSourceProvider, Number]) -> "Subtraction": - from b_asic.core_operations import Constant, Subtraction # Import here to avoid circular imports. + # Import here to avoid circular imports. + from b_asic.core_operations import Constant, Subtraction return Subtraction(Constant(src) if isinstance(src, Number) else src, self) def __mul__(self, src: Union[SignalSourceProvider, Number]) -> "Union[Multiplication, ConstantMultiplication]": - from b_asic.core_operations import Multiplication, ConstantMultiplication # Import here to avoid circular imports. + # Import here to avoid circular imports. + from b_asic.core_operations import Multiplication, ConstantMultiplication return ConstantMultiplication(src, self) if isinstance(src, Number) else Multiplication(self, src) - + def __rmul__(self, src: Union[SignalSourceProvider, Number]) -> "Union[Multiplication, ConstantMultiplication]": - from b_asic.core_operations import Multiplication, ConstantMultiplication # Import here to avoid circular imports. + # Import here to avoid circular imports. + from b_asic.core_operations import Multiplication, ConstantMultiplication return ConstantMultiplication(src, self) if isinstance(src, Number) else Multiplication(src, self) def __truediv__(self, src: Union[SignalSourceProvider, Number]) -> "Division": - from b_asic.core_operations import Constant, Division # Import here to avoid circular imports. + # Import here to avoid circular imports. + from b_asic.core_operations import Constant, Division return Division(self, Constant(src) if isinstance(src, Number) else src) - + def __rtruediv__(self, src: Union[SignalSourceProvider, Number]) -> "Division": - from b_asic.core_operations import Constant, Division # Import here to avoid circular imports. + # Import here to avoid circular imports. + from b_asic.core_operations import Constant, Division return Division(Constant(src) if isinstance(src, Number) else src, self) def __lshift__(self, src: SignalSourceProvider) -> Signal: @@ -267,6 +284,47 @@ class AbstractOperation(Operation, AbstractGraphComponent): f"{self.__class__.__name__} cannot be used as a destination because it has {diff} than 1 input") return self.input(0).connect(src) + def __str__(self): + inputs_dict = dict() + for i, port in enumerate(self.inputs): + if port.signal_count == 0: + inputs_dict[i] = '-' + break + dict_ele = [] + for signal in port.signals: + if signal.source: + if signal.source.operation.graph_id: + dict_ele.append(signal.source.operation.graph_id) + else: + dict_ele.append("no_id") + else: + if signal.graph_id: + dict_ele.append(signal.graph_id) + else: + dict_ele.append("no_id") + inputs_dict[i] = dict_ele + + outputs_dict = dict() + for i, port in enumerate(self.outputs): + if port.signal_count == 0: + outputs_dict[i] = '-' + break + dict_ele = [] + for signal in port.signals: + if signal.destination: + if signal.destination.operation.graph_id: + dict_ele.append(signal.destination.operation.graph_id) + else: + dict_ele.append("no_id") + else: + if signal.graph_id: + dict_ele.append(signal.graph_id) + else: + dict_ele.append("no_id") + outputs_dict[i] = dict_ele + + return super().__str__() + f", \tinputs: {str(inputs_dict)}, \toutputs: {str(outputs_dict)}" + @property def input_count(self) -> int: return len(self._input_ports) @@ -304,7 +362,7 @@ class AbstractOperation(Operation, AbstractGraphComponent): for s in p.signals: result.append(s) return result - + def key(self, index: int, prefix: str = "") -> ResultKey: key = prefix if self.output_count != 1: @@ -320,20 +378,24 @@ class AbstractOperation(Operation, AbstractGraphComponent): def evaluate_output(self, index: int, input_values: Sequence[Number], results: Optional[MutableResultMap] = None, delays: Optional[MutableDelayMap] = None, prefix: str = "", bits_override: Optional[int] = None, truncate: bool = True) -> Number: if index < 0 or index >= self.output_count: - raise IndexError(f"Output index out of range (expected 0-{self.output_count - 1}, got {index})") + raise IndexError( + f"Output index out of range (expected 0-{self.output_count - 1}, got {index})") if len(input_values) != self.input_count: raise ValueError(f"Wrong number of input values supplied to operation (expected {self.input_count}, got {len(input_values)})") values = self.evaluate(*(self.truncate_inputs(input_values, bits_override) if truncate else input_values)) if isinstance(values, collections.abc.Sequence): if len(values) != self.output_count: - raise RuntimeError(f"Operation evaluated to incorrect number of outputs (expected {self.output_count}, got {len(values)})") + raise RuntimeError( + f"Operation evaluated to incorrect number of outputs (expected {self.output_count}, got {len(values)})") elif isinstance(values, Number): if self.output_count != 1: - raise RuntimeError(f"Operation evaluated to incorrect number of outputs (expected {self.output_count}, got 1)") + raise RuntimeError( + f"Operation evaluated to incorrect number of outputs (expected {self.output_count}, got 1)") values = (values,) else: - raise RuntimeError(f"Operation evaluated to invalid type (expected Sequence/Number, got {values.__class__.__name__})") + raise RuntimeError( + f"Operation evaluated to invalid type (expected Sequence/Number, got {values.__class__.__name__})") if results is not None: for i in range(self.output_count): @@ -350,7 +412,7 @@ class AbstractOperation(Operation, AbstractGraphComponent): # Import here to avoid circular imports. from b_asic.special_operations import Input try: - result = self.evaluate([Input()] * self.input_count) + result = self.evaluate(*([Input()] * self.input_count)) if isinstance(result, collections.Sequence) and all(isinstance(e, Operation) for e in result): return result if isinstance(result, Operation): @@ -361,15 +423,49 @@ class AbstractOperation(Operation, AbstractGraphComponent): pass return [self] + def to_sfg(self) -> "SFG": + # Import here to avoid circular imports. + from b_asic.special_operations import Input, Output + from b_asic.signal_flow_graph import SFG + + inputs = [Input() for i in range(self.input_count)] + + try: + last_operations = self.evaluate(*inputs) + if isinstance(last_operations, Operation): + last_operations = [last_operations] + outputs = [Output(o) for o in last_operations] + except TypeError: + operation_copy = self.copy_component() + inputs = [] + for i in range(self.input_count): + _input = Input() + operation_copy.input(i).connect(_input) + inputs.append(_input) + + outputs = [Output(operation_copy)] + + return SFG(inputs=inputs, outputs=outputs) + def inputs_required_for_output(self, output_index: int) -> Iterable[int]: if output_index < 0 or output_index >= self.output_count: raise IndexError(f"Output index out of range (expected 0-{self.output_count - 1}, got {output_index})") - return [i for i in range(self.input_count)] # By default, assume each output depends on all inputs. + return [i for i in range(self.input_count)] # By default, assume each output depends on all inputs. @property def neighbors(self) -> Iterable[GraphComponent]: return list(self.input_signals) + list(self.output_signals) + @property + def preceding_operations(self) -> Iterable[Operation]: + """Returns an Iterable of all Operations that are connected to this Operations input ports.""" + return [signal.source.operation for signal in self.input_signals if signal.source] + + @property + def subsequent_operations(self) -> Iterable[Operation]: + """Returns an Iterable of all Operations that are connected to this Operations output ports.""" + return [signal.destination.operation for signal in self.output_signals if signal.destination] + @property def source(self) -> OutputPort: if self.output_count != 1: @@ -377,7 +473,7 @@ class AbstractOperation(Operation, AbstractGraphComponent): raise TypeError( f"{self.__class__.__name__} cannot be used as an input source because it has {diff} than 1 output") return self.output(0) - + def truncate_input(self, index: int, value: Number, bits: int) -> Number: return int(value) & ((2 ** bits) - 1) diff --git a/b_asic/port.py b/b_asic/port.py index 0b77bc7cbebbd235207c8fafb6ed1a8dd382389f..24f59b8b9433450fb669a225af1019f03635a469 100644 --- a/b_asic/port.py +++ b/b_asic/port.py @@ -5,7 +5,7 @@ TODO: More info. from abc import ABC, abstractmethod from copy import copy -from typing import NewType, Optional, List, Iterable, TYPE_CHECKING +from typing import Optional, List, Iterable, TYPE_CHECKING from b_asic.signal import Signal from b_asic.graph_component import Name @@ -128,7 +128,7 @@ class InputPort(AbstractPort): signal.set_destination(self) def remove_signal(self, signal: Signal) -> None: - assert signal is self._source_signal, "Attempted to remove already removed signal." + assert signal is self._source_signal, "Attempted to remove signal that is not connected." self._source_signal = None signal.remove_destination() @@ -183,7 +183,7 @@ class OutputPort(AbstractPort, SignalSourceProvider): signal.set_source(self) def remove_signal(self, signal: Signal) -> None: - assert signal in self._destination_signals, "Attempted to remove already removed signal." + assert signal in self._destination_signals, "Attempted to remove signal that is not connected." self._destination_signals.remove(signal) signal.remove_source() diff --git a/b_asic/precedence_chart.py b/b_asic/precedence_chart.py deleted file mode 100644 index be55a123e0ab4330057c0bb62581e45195f5e5ba..0000000000000000000000000000000000000000 --- a/b_asic/precedence_chart.py +++ /dev/null @@ -1,21 +0,0 @@ -"""@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. diff --git a/b_asic/schema.py b/b_asic/schema.py deleted file mode 100644 index e5068cdc080c5c5004c44c885ac48f52ba44c1f3..0000000000000000000000000000000000000000 --- a/b_asic/schema.py +++ /dev/null @@ -1,21 +0,0 @@ -"""@package docstring -B-ASIC Schema Module. -TODO: More info. -""" - -from b_asic.precedence_chart import PrecedenceChart - - -class Schema: - """Schema constructed from a precedence chart. - TODO: More info. - """ - - pc: PrecedenceChart - # TODO: More members. - - def __init__(self, pc: PrecedenceChart): - self.pc = pc - # TODO: Implement. - - # TODO: More stuff. diff --git a/b_asic/signal.py b/b_asic/signal.py index d322f161b1d9c1195c2f8e5beb40f0ea7244a25b..24e8cc81566b7ba5dfb961dadb137f74afe2e111 100644 --- a/b_asic/signal.py +++ b/b_asic/signal.py @@ -25,14 +25,14 @@ class Signal(AbstractGraphComponent): self.set_destination(destination) self.set_param("bits", bits) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "s" @property 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.""" @@ -103,5 +103,6 @@ class Signal(AbstractGraphComponent): 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) \ No newline at end of file + assert bits is None or (isinstance(bits, int) + and bits >= 0), "Bits must be non-negative." + self.set_param("bits", bits) diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py index 30dbba97c10e069b2216f44c2dd3c5771207094d..acabedf2e407caeb06a508219bc7a2a39ef446b3 100644 --- a/b_asic/signal_flow_graph.py +++ b/b_asic/signal_flow_graph.py @@ -6,12 +6,16 @@ TODO: More info. from typing import List, Iterable, Sequence, Dict, Optional, DefaultDict, MutableSet, Tuple from numbers import Number from collections import defaultdict, deque +from io import StringIO +from queue import PriorityQueue +import itertools +from graphviz import Digraph from b_asic.port import SignalSourceProvider, OutputPort -from b_asic.operation import Operation, AbstractOperation, ResultKey, DelayMap, MutableResultMap, MutableDelayMap, ResultKey +from b_asic.operation import Operation, AbstractOperation, ResultKey, DelayMap, MutableResultMap, MutableDelayMap from b_asic.signal import Signal from b_asic.graph_component import GraphID, GraphIDNumber, GraphComponent, Name, TypeName -from b_asic.special_operations import Input, Output +from b_asic.special_operations import Input, Output, Delay DelayQueue = List[Tuple[str, ResultKey, OutputPort]] @@ -33,7 +37,7 @@ class GraphIDGenerator: @property def id_number_offset(self) -> GraphIDNumber: """Get the graph id number offset of this generator.""" - return self._next_id_number.default_factory() # pylint: disable=not-callable + return self._next_id_number.default_factory() # pylint: disable=not-callable class SFG(AbstractOperation): @@ -43,37 +47,43 @@ class SFG(AbstractOperation): _components_by_id: Dict[GraphID, GraphComponent] _components_by_name: DefaultDict[Name, List[GraphComponent]] - _components_ordered: List[GraphComponent] - _operations_ordered: List[Operation] + _components_dfs_order: List[GraphComponent] + _operations_dfs_order: List[Operation] + _operations_topological_order: List[Operation] _graph_id_generator: GraphIDGenerator _input_operations: List[Input] _output_operations: List[Output] _original_components_to_new: MutableSet[GraphComponent] _original_input_signals_to_indices: Dict[Signal, int] _original_output_signals_to_indices: Dict[Signal, int] + _precedence_list: Optional[List[List[OutputPort]]] - def __init__(self, input_signals: Optional[Sequence[Signal]] = None, output_signals: Optional[Sequence[Signal]] = None, \ - inputs: Optional[Sequence[Input]] = None, outputs: Optional[Sequence[Output]] = None, \ - id_number_offset: GraphIDNumber = 0, name: Name = "", \ + def __init__(self, input_signals: Optional[Sequence[Signal]] = None, output_signals: Optional[Sequence[Signal]] = None, + inputs: Optional[Sequence[Input]] = None, outputs: Optional[Sequence[Output]] = None, + id_number_offset: GraphIDNumber = 0, name: Name = "", input_sources: Optional[Sequence[Optional[SignalSourceProvider]]] = None): + input_signal_count = 0 if input_signals is None else len(input_signals) input_operation_count = 0 if inputs is None else len(inputs) - output_signal_count = 0 if output_signals is None else len(output_signals) + output_signal_count = 0 if output_signals is None else len( + output_signals) output_operation_count = 0 if outputs is None else len(outputs) - super().__init__(input_count = input_signal_count + input_operation_count, - output_count = output_signal_count + output_operation_count, - name = name, input_sources = input_sources) + super().__init__(input_count=input_signal_count + input_operation_count, + output_count=output_signal_count + output_operation_count, + name=name, input_sources=input_sources) self._components_by_id = dict() self._components_by_name = defaultdict(list) - self._components_ordered = [] - self._operations_ordered = [] + self._components_dfs_order = [] + self._operations_dfs_order = [] + self._operations_topological_order = [] self._graph_id_generator = GraphIDGenerator(id_number_offset) self._input_operations = [] self._output_operations = [] self._original_components_to_new = {} self._original_input_signals_to_indices = {} self._original_output_signals_to_indices = {} + self._precedence_list = None # Setup input signals. if input_signals is not None: @@ -126,7 +136,8 @@ class SFG(AbstractOperation): new_signal = self._original_components_to_new[signal] else: # New signal has to be created. - new_signal = self._add_component_unconnected_copy(signal) + new_signal = self._add_component_unconnected_copy( + signal) new_signal.set_destination(new_output_op.input(0)) self._original_output_signals_to_indices[signal] = output_index @@ -141,13 +152,17 @@ class SFG(AbstractOperation): new_signal = self._original_components_to_new[signal] if new_signal.destination is None: if signal.destination is None: - raise ValueError(f"Input signal #{input_index} is missing destination in SFG") + raise ValueError( + f"Input signal #{input_index} is missing destination in SFG") if signal.destination.operation not in self._original_components_to_new: - self._add_operation_connected_tree_copy(signal.destination.operation) + self._add_operation_connected_tree_copy( + signal.destination.operation) elif new_signal.destination.operation in output_operations_set: # Add directly connected input to output to ordered list. - self._components_ordered.extend([new_signal.source.operation, new_signal, new_signal.destination.operation]) - self._operations_ordered.extend([new_signal.source.operation, new_signal.destination.operation]) + self._components_dfs_order.extend( + [new_signal.source.operation, new_signal, new_signal.destination.operation]) + self._operations_dfs_order.extend( + [new_signal.source.operation, new_signal.destination.operation]) # Search the graph inwards from each output signal. for signal, output_index in self._original_output_signals_to_indices.items(): @@ -155,63 +170,34 @@ class SFG(AbstractOperation): new_signal = self._original_components_to_new[signal] if new_signal.source is None: if signal.source is None: - raise ValueError(f"Output signal #{output_index} is missing source in SFG") + raise ValueError( + f"Output signal #{output_index} is missing source in SFG") if signal.source.operation not in self._original_components_to_new: - self._add_operation_connected_tree_copy(signal.source.operation) - - # Find dependencies. + self._add_operation_connected_tree_copy( + signal.source.operation) - def __str__(self) -> str: """Get a string representation of this SFG.""" - output_string = "" - for component in self._components_ordered: - if isinstance(component, Operation): - for key, value in self._components_by_id.items(): - if value is component: - output_string += "id: " + key + ", name: " - - if component.name != None: - output_string += component.name + ", " - else: - output_string += "-, " - - if component.type_name == "c": - output_string += "value: " + str(component.value) + ", input: [" - else: - output_string += "input: [" - - counter_input = 0 - for input in component.inputs: - counter_input += 1 - for signal in input.signals: - for key, value in self._components_by_id.items(): - if value is signal: - output_string += key + ", " - - if counter_input > 0: - output_string = output_string[:-2] - output_string += "], output: [" - counter_output = 0 - for output in component.outputs: - counter_output += 1 - for signal in output.signals: - for key, value in self._components_by_id.items(): - if value is signal: - output_string += key + ", " - if counter_output > 0: - output_string = output_string[:-2] - output_string += "]\n" - - return output_string + string_io = StringIO() + string_io.write(super().__str__() + "\n") + string_io.write("Internal Operations:\n") + line = "-" * 100 + "\n" + string_io.write(line) + + for operation in self.get_operations_topological_order(): + string_io.write(str(operation) + "\n") + + string_io.write(line) + + return string_io.getvalue() def __call__(self, *src: Optional[SignalSourceProvider], name: Name = "") -> "SFG": """Get a new independent SFG instance that is identical to this SFG except without any of its external connections.""" - return SFG(inputs = self._input_operations, outputs = self._output_operations, - id_number_offset = self.id_number_offset, name = name, input_sources = src if src else None) + return SFG(inputs=self._input_operations, outputs=self._output_operations, + id_number_offset=self.id_number_offset, name=name, input_sources=src if src else None) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "sfg" def evaluate(self, *args): @@ -221,9 +207,11 @@ class SFG(AbstractOperation): def evaluate_output(self, index: int, input_values: Sequence[Number], results: Optional[MutableResultMap] = None, delays: Optional[MutableDelayMap] = None, prefix: str = "", bits_override: Optional[int] = None, truncate: bool = True) -> Number: if index < 0 or index >= self.output_count: - raise IndexError(f"Output index out of range (expected 0-{self.output_count - 1}, got {index})") + raise IndexError( + f"Output index out of range (expected 0-{self.output_count - 1}, got {index})") if len(input_values) != self.input_count: - raise ValueError(f"Wrong number of inputs supplied to SFG for evaluation (expected {self.input_count}, got {len(input_values)})") + raise ValueError( + f"Wrong number of inputs supplied to SFG for evaluation (expected {self.input_count}, got {len(input_values)})") if results is None: results = {} if delays is None: @@ -243,17 +231,78 @@ class SFG(AbstractOperation): results[self.key(index, prefix)] = value return value + def connect_external_signals_to_components(self) -> bool: + """ Connects any external signals to this SFG's internal operations. This SFG becomes unconnected to the SFG + it is a component off, causing it to become invalid afterwards. Returns True if succesful, False otherwise. """ + if len(self.inputs) != len(self.input_operations): + raise IndexError(f"Number of inputs does not match the number of input_operations in SFG.") + if len(self.outputs) != len(self.output_operations): + raise IndexError(f"Number of outputs does not match the number of output_operations SFG.") + if len(self.input_signals) == 0: + return False + if len(self.output_signals) == 0: + return False + + # For each input_signal, connect it to the corresponding operation + for port, input_operation in zip(self.inputs, self.input_operations): + dest = input_operation.output(0).signals[0].destination + dest.clear() + port.signals[0].set_destination(dest) + # For each output_signal, connect it to the corresponding operation + for port, output_operation in zip(self.outputs, self.output_operations): + src = output_operation.input(0).signals[0].source + src.clear() + port.signals[0].set_source(src) + return True + + @property + def input_operations(self) -> Sequence[Operation]: + """Get the internal input operations in the same order as their respective input ports.""" + return self._input_operations + + @property + def output_operations(self) -> Sequence[Operation]: + """Get the internal output operations in the same order as their respective output ports.""" + return self._output_operations + def split(self) -> Iterable[Operation]: return self.operations + def to_sfg(self) -> 'SFG': + return self + def inputs_required_for_output(self, output_index: int) -> Iterable[int]: if output_index < 0 or output_index >= self.output_count: - raise IndexError(f"Output index out of range (expected 0-{self.output_count - 1}, got {output_index})") - return self._inputs_required_for_source(self._output_operations[output_index].input(0).signals[0].source, set()) - + raise IndexError( + f"Output index out of range (expected 0-{self.output_count - 1}, got {output_index})") + + input_indexes_required = [] + sfg_input_operations_to_indexes = { + input_op: index for index, input_op in enumerate(self._input_operations)} + output_op = self._output_operations[output_index] + queue = deque([output_op]) + visited = set([output_op]) + while queue: + op = queue.popleft() + if isinstance(op, Input): + if op in sfg_input_operations_to_indexes: + input_indexes_required.append( + sfg_input_operations_to_indexes[op]) + del sfg_input_operations_to_indexes[op] + + for input_port in op.inputs: + for signal in input_port.signals: + if signal.source is not None: + new_op = signal.source.operation + if new_op not in visited: + queue.append(new_op) + visited.add(new_op) + + return input_indexes_required + def copy_component(self, *args, **kwargs) -> GraphComponent: - return super().copy_component(*args, **kwargs, inputs = self._input_operations, outputs = self._output_operations, - id_number_offset = self.id_number_offset, name = self.name) + return super().copy_component(*args, **kwargs, inputs=self._input_operations, outputs=self._output_operations, + id_number_offset=self.id_number_offset, name=self.name) @property def id_number_offset(self) -> GraphIDNumber: @@ -263,28 +312,35 @@ class SFG(AbstractOperation): @property def components(self) -> Iterable[GraphComponent]: """Get all components of this graph in depth-first order.""" - return self._components_ordered + return self._components_dfs_order @property def operations(self) -> Iterable[Operation]: """Get all operations of this graph in depth-first order.""" - return self._operations_ordered - - @property - def input_operations(self) -> Sequence[Operation]: - """Get the internal input operations in the same order as their respective input ports.""" - return self._input_operations - @property - def output_operations(self) -> Sequence[Operation]: - """Get the internal output operations in the same order as their respective output ports.""" - return self._output_operations + return self._operations_dfs_order + + def get_components_with_type_name(self, type_name: TypeName) -> List[GraphComponent]: + """Get a list with all components in this graph with the specified type_name. + + 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)) + + return components def find_by_id(self, graph_id: GraphID) -> Optional[GraphComponent]: """Find the graph component with the specified ID. Returns None if the component was not found. Keyword arguments: - graph_id: Graph ID of the desired component(s) + graph_id: Graph ID of the desired component. """ return self._components_by_id.get(graph_id, None) @@ -297,11 +353,276 @@ class SFG(AbstractOperation): """ return self._components_by_name.get(name, []) + def replace_component(self, component: Operation, _id: GraphID): + """Find and replace all components matching either on GraphID, Type or both. + Then return a new deepcopy of the sfg with the replaced component. + + Arguments: + component: The new component(s), e.g Multiplication + _id: The GraphID to match the component to replace. + """ + + _sfg_copy = self() + _component = _sfg_copy.find_by_id(_id) + + assert _component is not None and isinstance(_component, Operation), \ + "No operation matching the criteria found" + assert _component.output_count == component.output_count, \ + "The output count may not differ between the operations" + assert _component.input_count == component.input_count, \ + "The input count may not differ between the operations" + + for index_in, _inp in enumerate(_component.inputs): + for _signal in _inp.signals: + _signal.remove_destination() + _signal.set_destination(component.input(index_in)) + + for index_out, _out in enumerate(_component.outputs): + for _signal in _out.signals: + _signal.remove_source() + _signal.set_source(component.output(index_out)) + + # The old SFG will be deleted by Python GC + return _sfg_copy() + + def insert_operation(self, component: Operation, output_comp_id: GraphID) -> Optional["SFG"]: + """Insert an operation in the SFG after a given source operation. + The source operation output count must match the input count of the operation as well as the output + Then return a new deepcopy of the sfg with the inserted component. + + Arguments: + component: The new component, e.g Multiplication. + output_comp_id: The source operation GraphID to connect from. + """ + + # Preserve the original SFG by creating a copy. + sfg_copy = self() + output_comp = sfg_copy.find_by_id(output_comp_id) + if output_comp is None: + return None + + assert not isinstance(output_comp, Output), \ + "Source operation can not be an output operation." + assert len(output_comp.output_signals) == component.input_count, \ + "Source operation output count does not match input count for component." + assert len(output_comp.output_signals) == component.output_count, \ + "Destination operation input count does not match output for component." + + for index, signal_in in enumerate(output_comp.output_signals): + destination = signal_in.destination + signal_in.set_destination(component.input(index)) + destination.connect(component.output(index)) + + # Recreate the newly coupled SFG so that all attributes are correct. + return sfg_copy() + + def remove_operation(self, operation_id: GraphID) -> "SFG": + """Returns a version of the SFG where the operation with the specified GraphID removed. + The operation has to have the same amount of input- and output ports or a ValueError will + be raised. If no operation with the entered operation_id is found then returns None and does nothing.""" + sfg_copy = self() + operation = sfg_copy.find_by_id(operation_id) + if operation is None: + return None + + if operation.input_count != operation.output_count: + raise ValueError("Different number of input and output ports of operation with the specified id") + + for i, outport in enumerate(operation.outputs): + if outport.signal_count > 0: + if operation.input(i).signal_count > 0 and operation.input(i).signals[0].source is not None: + in_sig = operation.input(i).signals[0] + source_port = in_sig.source + source_port.remove_signal(in_sig) + operation.input(i).remove_signal(in_sig) + for out_sig in outport.signals.copy(): + out_sig.set_source(source_port) + else: + for out_sig in outport.signals.copy(): + out_sig.remove_source() + else: + if operation.input(i).signal_count > 0: + in_sig = operation.input(i).signals[0] + operation.input(i).remove_signal(in_sig) + + return sfg_copy() + + def get_precedence_list(self) -> List[List[OutputPort]]: + """Returns a Precedence list of the SFG where each element in n:th the list consists + of elements that are executed in the n:th step. If the precedence list already has been + calculated for the current SFG then returns the cached version.""" + if self._precedence_list: + return self._precedence_list + + # Find all operations with only outputs and no inputs. + no_input_ops = list(filter(lambda op: op.input_count == 0, self.operations)) + delay_ops = self.get_components_with_type_name(Delay.type_name()) + + # Find all first iter output ports for precedence + first_iter_ports = [op.output(i) for op in (no_input_ops + delay_ops) for i in range(op.output_count)] + + self._precedence_list = self._traverse_for_precedence_list(first_iter_ports) + + return self._precedence_list + + def _traverse_for_precedence_list(self, first_iter_ports: List[OutputPort]) -> List[List[OutputPort]]: + # Find dependencies of output ports and input ports. + remaining_inports_per_operation = {op: op.input_count for op in self.operations} + + # Traverse output ports for precedence + curr_iter_ports = first_iter_ports + precedence_list = [] + + while curr_iter_ports: + # Add the found ports to the current iter + precedence_list.append(curr_iter_ports) + + next_iter_ports = [] + + for outport in curr_iter_ports: + for signal in outport.signals: + new_inport = signal.destination + # Don't traverse over delays. + if new_inport is not None and not isinstance(new_inport.operation, Delay): + new_op = new_inport.operation + remaining_inports_per_operation[new_op] -= 1 + if remaining_inports_per_operation[new_op] == 0: + next_iter_ports.extend(new_op.outputs) + + curr_iter_ports = next_iter_ports + + return precedence_list + + def show_precedence_graph(self) -> None: + p_list = self.get_precedence_list() + pg = Digraph() + pg.attr(rankdir = 'LR') + + # Creates nodes for each output port in the precedence list + for i in range(len(p_list)): + ports = p_list[i] + with pg.subgraph(name='cluster_' + str(i)) as sub: + sub.attr(label='N' + str(i + 1)) + for port in ports: + sub.node(port.operation.graph_id + '.' + str(port.index)) + # Creates edges for each output port and creates nodes for each operation and edges for them as well + for i in range(len(p_list)): + ports = p_list[i] + for port in ports: + for signal in port.signals: + pg.edge(port.operation.graph_id + '.' + str(port.index), signal.destination.operation.graph_id) + pg.node(signal.destination.operation.graph_id, shape = 'square') + pg.edge(port.operation.graph_id, port.operation.graph_id + '.' + str(port.index)) + pg.node(port.operation.graph_id, shape = 'square') + + pg.view() + + def print_precedence_graph(self) -> None: + """Prints a representation of the SFG's precedence list to the standard out. + If the precedence list already has been calculated then it uses the cached version, + otherwise it calculates the precedence list and then prints it.""" + precedence_list = self.get_precedence_list() + + line = "-" * 120 + out_str = StringIO() + out_str.write(line) + + printed_ops = set() + + for iter_num, iter in enumerate(precedence_list, start=1): + for outport_num, outport in enumerate(iter, start=1): + if outport not in printed_ops: + # Only print once per operation, even if it has multiple outports + out_str.write("\n") + out_str.write(str(iter_num)) + out_str.write(".") + out_str.write(str(outport_num)) + out_str.write(" \t") + out_str.write(str(outport.operation)) + printed_ops.add(outport) + + out_str.write("\n") + out_str.write(line) + + print(out_str.getvalue()) + + def get_operations_topological_order(self) -> Iterable[Operation]: + """Returns an Iterable of the Operations in the SFG in Topological Order. + Feedback loops makes an absolutely correct Topological order impossible, so an + approximative Topological Order is returned in such cases in this implementation.""" + if self._operations_topological_order: + return self._operations_topological_order + + no_inputs_queue = deque(list(filter(lambda op: op.input_count == 0, self.operations))) + remaining_inports_per_operation = {op: op.input_count for op in self.operations} + + # Maps number of input counts to a queue of seen objects with such a size. + seen_with_inputs_dict = defaultdict(deque) + seen = set() + top_order = [] + + assert len(no_inputs_queue) > 0, "Illegal SFG state, dangling signals in SFG." + + first_op = no_inputs_queue.popleft() + visited = set([first_op]) + p_queue = PriorityQueue() + p_queue.put((-first_op.output_count, first_op)) # Negative priority as max-heap popping is wanted + operations_left = len(self.operations) - 1 + + seen_but_not_visited_count = 0 + + while operations_left > 0: + while not p_queue.empty(): + op = p_queue.get()[1] + + operations_left -= 1 + top_order.append(op) + visited.add(op) + + for neighbor_op in op.subsequent_operations: + if neighbor_op not in visited: + remaining_inports_per_operation[neighbor_op] -= 1 + remaining_inports = remaining_inports_per_operation[neighbor_op] + + if remaining_inports == 0: + p_queue.put((-neighbor_op.output_count, neighbor_op)) + + elif remaining_inports > 0: + if neighbor_op in seen: + seen_with_inputs_dict[remaining_inports + 1].remove(neighbor_op) + else: + seen.add(neighbor_op) + seen_but_not_visited_count += 1 + + seen_with_inputs_dict[remaining_inports].append(neighbor_op) + + # Check if have to fetch Operations from somewhere else since p_queue is empty + if operations_left > 0: + # First check if can fetch from Operations with no input ports + if no_inputs_queue: + new_op = no_inputs_queue.popleft() + p_queue.put((new_op.output_count, new_op)) + + # Else fetch operation with lowest input count that is not zero + elif seen_but_not_visited_count > 0: + for i in itertools.count(start=1): + seen_inputs_queue = seen_with_inputs_dict[i] + if seen_inputs_queue: + new_op = seen_inputs_queue.popleft() + p_queue.put((-new_op.output_count, new_op)) + seen_but_not_visited_count -= 1 + break + else: + raise RuntimeError("Unallowed structure in SFG detected") + + self._operations_topological_order = top_order + return self._operations_topological_order + def _add_component_unconnected_copy(self, original_component: GraphComponent) -> GraphComponent: 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_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 self._components_by_name[new_component.name].append(new_component) @@ -315,8 +636,8 @@ class SFG(AbstractOperation): new_op = None if original_op not in self._original_components_to_new: new_op = self._add_component_unconnected_copy(original_op) - self._components_ordered.append(new_op) - self._operations_ordered.append(new_op) + self._components_dfs_order.append(new_op) + self._operations_dfs_order.append(new_op) else: new_op = self._original_components_to_new[original_op] @@ -331,29 +652,33 @@ class SFG(AbstractOperation): # New signal already created during first step of constructor. new_signal = self._original_components_to_new[original_signal] new_signal.set_destination(new_op.input(original_input_port.index)) - self._components_ordered.extend([new_signal, new_signal.source.operation]) - self._operations_ordered.append(new_signal.source.operation) + + self._components_dfs_order.extend([new_signal, new_signal.source.operation]) + self._operations_dfs_order.append(new_signal.source.operation) # Check if the signal has not been added before. elif original_signal not in self._original_components_to_new: if original_signal.source is None: raise ValueError("Dangling signal without source in SFG") - + new_signal = self._add_component_unconnected_copy(original_signal) new_signal.set_destination(new_op.input(original_input_port.index)) - self._components_ordered.append(new_signal) + + self._components_dfs_order.append(new_signal) original_connected_op = original_signal.source.operation # Check if connected Operation has been added before. if original_connected_op in self._original_components_to_new: # Set source to the already added operations port. - new_signal.set_source(self._original_components_to_new[original_connected_op].output(original_signal.source.index)) + new_signal.set_source(self._original_components_to_new[original_connected_op].output( + original_signal.source.index)) else: # Create new operation, set signal source to it. new_connected_op = self._add_component_unconnected_copy(original_connected_op) new_signal.set_source(new_connected_op.output(original_signal.source.index)) - self._components_ordered.append(new_connected_op) - self._operations_ordered.append(new_connected_op) + + self._components_dfs_order.append(new_connected_op) + self._operations_dfs_order.append(new_connected_op) # Add connected operation to queue of operations to visit. op_stack.append(original_connected_op) @@ -366,8 +691,9 @@ class SFG(AbstractOperation): # New signal already created during first step of constructor. new_signal = self._original_components_to_new[original_signal] new_signal.set_source(new_op.output(original_output_port.index)) - self._components_ordered.extend([new_signal, new_signal.destination.operation]) - self._operations_ordered.append(new_signal.destination.operation) + + self._components_dfs_order.extend([new_signal, new_signal.destination.operation]) + self._operations_dfs_order.append(new_signal.destination.operation) # Check if signal has not been added before. elif original_signal not in self._original_components_to_new: @@ -376,61 +702,26 @@ class SFG(AbstractOperation): new_signal = self._add_component_unconnected_copy(original_signal) new_signal.set_source(new_op.output(original_output_port.index)) - self._components_ordered.append(new_signal) + + self._components_dfs_order.append(new_signal) original_connected_op = original_signal.destination.operation # Check if connected operation has been added. if original_connected_op in self._original_components_to_new: # Set destination to the already connected operations port. - new_signal.set_destination(self._original_components_to_new[original_connected_op].input(original_signal.destination.index)) + new_signal.set_destination(self._original_components_to_new[original_connected_op].input( + original_signal.destination.index)) else: # Create new operation, set destination to it. new_connected_op = self._add_component_unconnected_copy(original_connected_op) new_signal.set_destination(new_connected_op.input(original_signal.destination.index)) - self._components_ordered.append(new_connected_op) - self._operations_ordered.append(new_connected_op) + + self._components_dfs_order.append(new_connected_op) + self._operations_dfs_order.append(new_connected_op) # Add connected operation to the queue of operations to visit. op_stack.append(original_connected_op) - def replace_component(self, component: Operation, _component: Operation = None, _id: GraphID = None): - """Find and replace all components matching either on GraphID, Type or both. - Then return a new deepcopy of the sfg with the replaced component. - - Arguments: - component: The new component(s), e.g Multiplication - - Keyword arguments: - _component: The specific component to replace. - _id: The GraphID to match the component to replace. - """ - - assert _component is not None or _id is not None, \ - "Define either operation to replace or GraphID of operation" - - if _id is not None: - _component = self.find_by_id(_id) - - assert _component is not None and isinstance(_component, Operation), \ - "No operation matching the criteria found" - assert _component.output_count == component.output_count, \ - "The output count may not differ between the operations" - assert _component.input_count == component.input_count, \ - "The input count may not differ between the operations" - - for index_in, _inp in enumerate(_component.inputs): - for _signal in _inp.signals: - _signal.remove_destination() - _signal.set_destination(component.input(index_in)) - - for index_out, _out in enumerate(_component.outputs): - for _signal in _out.signals: - _signal.remove_source() - _signal.set_source(component.output(index_out)) - - # The old SFG will be deleted by Python GC - return self() - def _evaluate_source(self, src: OutputPort, results: MutableResultMap, delays: MutableDelayMap, prefix: str, bits_override: Optional[int], truncate: bool, deferred_delays: DelayQueue) -> Number: key_base = (prefix + "." + src.operation.graph_id) if prefix else src.operation.graph_id key = src.operation.key(src.index, key_base) diff --git a/b_asic/simulation.py b/b_asic/simulation.py index decc6f4da2c1feefde82d9c7430319d92dcf2d17..7829d171a38ff74c9a90b513e5a21034727e7492 100644 --- a/b_asic/simulation.py +++ b/b_asic/simulation.py @@ -44,7 +44,8 @@ class Simulation: def set_input(self, index: int, input_provider: InputProvider) -> None: """Set the input function used to get values for the specific input at the given index to the internal SFG.""" if index < 0 or index >= len(self._input_functions): - raise IndexError(f"Input index out of range (expected 0-{len(self._input_functions) - 1}, got {index})") + raise IndexError( + f"Input index out of range (expected 0-{len(self._input_functions) - 1}, got {index})") if callable(input_provider): self._input_functions[index] = input_provider elif isinstance(input_provider, Number): @@ -113,4 +114,4 @@ class Simulation: def clear_state(self) -> None: """Clear all current state of the simulation, except for the results and iteration.""" - self._delays.clear() \ No newline at end of file + self._delays.clear() diff --git a/b_asic/special_operations.py b/b_asic/special_operations.py index 32c6d022facd80c268ef14e7ff46f83a108ac8e7..8c15c55aed2ab7821467b51e84750a86743bde7a 100644 --- a/b_asic/special_operations.py +++ b/b_asic/special_operations.py @@ -17,13 +17,13 @@ class Input(AbstractOperation): """ def __init__(self, name: Name = ""): - super().__init__(input_count = 0, output_count = 1, name = name) + super().__init__(input_count=0, output_count=1, name=name) self.set_param("value", 0) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "in" - + def evaluate(self): return self.param("value") @@ -44,10 +44,11 @@ class Output(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""): - super().__init__(input_count = 1, output_count = 0, name = name, input_sources = [src0]) + super().__init__(input_count=1, output_count=0, + name=name, input_sources=[src0]) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "out" def evaluate(self, _): @@ -60,11 +61,12 @@ class Delay(AbstractOperation): """ def __init__(self, src0: Optional[SignalSourceProvider] = None, initial_value: Number = 0, name: Name = ""): - super().__init__(input_count = 1, output_count = 1, name = name, input_sources = [src0]) + super().__init__(input_count=1, output_count=1, + name=name, input_sources=[src0]) self.set_param("initial_value", initial_value) - @property - def type_name(self) -> TypeName: + @classmethod + def type_name(cls) -> TypeName: return "t" def evaluate(self, a): @@ -77,10 +79,12 @@ class Delay(AbstractOperation): def evaluate_output(self, index: int, input_values: Sequence[Number], results: Optional[MutableResultMap] = None, delays: Optional[MutableDelayMap] = None, prefix: str = "", bits_override: Optional[int] = None, truncate: bool = True) -> Number: if index != 0: - raise IndexError(f"Output index out of range (expected 0-0, got {index})") + raise IndexError( + f"Output index out of range (expected 0-0, got {index})") if len(input_values) != 1: - raise ValueError(f"Wrong number of inputs supplied to SFG for evaluation (expected 1, got {len(input_values)})") - + raise ValueError( + f"Wrong number of inputs supplied to SFG for evaluation (expected 1, got {len(input_values)})") + key = self.key(index, prefix) value = self.param("initial_value") if delays is not None: @@ -98,4 +102,4 @@ class Delay(AbstractOperation): @initial_value.setter def initial_value(self, value: Number) -> None: """Set the initial value of this delay.""" - self.set_param("initial_value", value) \ No newline at end of file + self.set_param("initial_value", value) diff --git a/legacy/simulation_oop/signal_flow_graph.cpp b/legacy/simulation_oop/signal_flow_graph.cpp index d62ff6d33c09f04abee63659e8fcf4c0f18ac5c7..4c3763c81e8f97f22caf42a47d88c1186b9a874d 100644 --- a/legacy/simulation_oop/signal_flow_graph.cpp +++ b/legacy/simulation_oop/signal_flow_graph.cpp @@ -85,7 +85,7 @@ std::shared_ptr<operation> signal_flow_graph_operation::make_operation(pybind11: return it->second; } auto const graph_id = op.attr("graph_id").cast<std::string_view>(); - auto const type_name = op.attr("type_name").cast<std::string_view>(); + auto const type_name = op.attr("type_name")().cast<std::string_view>(); auto key = (prefix.empty()) ? result_key{graph_id} : fmt::format("{}.{}", prefix, graph_id); if (type_name == "c") { auto const value = op.attr("value").cast<number>(); diff --git a/setup.py b/setup.py index f9d4cf7e6a6f8c5b2895e63da824bcb71994701d..0568514756180c02b81a7b323789fb72a6e5bf26 100644 --- a/setup.py +++ b/setup.py @@ -37,9 +37,9 @@ class CMakeBuild(build_ext): if not os.path.exists(self.build_temp): os.makedirs(self.build_temp) - + env = os.environ.copy() - + print(f"=== Configuring {ext.name} ===") print(f"Temp dir: {self.build_temp}") print(f"Output dir: {cmake_output_dir}") @@ -72,7 +72,9 @@ setuptools.setup( install_requires = [ "pybind11>=2.3.0", "numpy", - "install_qt_binding" + "pyside2", + "graphviz", + "matplotlib" ], packages = ["b_asic"], ext_modules = [CMakeExtension("b_asic")], diff --git a/src/simulation/compile.cpp b/src/simulation/compile.cpp index 33f9cc0c1f5f26dc5f4b2d0b70d7cc0dbae8a163..95ab167bf73117a1d56e2c26e28aa7ff4352d421 100644 --- a/src/simulation/compile.cpp +++ b/src/simulation/compile.cpp @@ -139,7 +139,7 @@ private: auto const pointer = op.attr("outputs")[py::int_{output_index}].ptr(); if (m_incomplete_outputs.count(pointer) != 0) { // Make sure the output doesn't depend on its own value, unless it's a delay operation. - if (op.attr("type_name").cast<std::string_view>() != "t") { + if (op.attr("type_name")().cast<std::string_view>() != "t") { throw py::value_error{"Direct feedback loop detected in simulation SFG"}; } } @@ -217,7 +217,7 @@ private: void add_operation_output(py::handle op, std::size_t output_index, std::string_view prefix, sfg_info_stack const& sfg_stack, delay_queue& deferred_delays) { - auto const type_name = op.attr("type_name").cast<std::string_view>(); + auto const type_name = op.attr("type_name")().cast<std::string_view>(); if (type_name == "out") { this->add_source(op, 0, prefix, sfg_stack, deferred_delays); } else if (auto const result_index = this->begin_operation_output(op, output_index, prefix)) { diff --git a/test/fixtures/operation_tree.py b/test/fixtures/operation_tree.py index fc8008fa4098ca488e23766f5ff7d05711300685..695979c65ab56eda3baf992b3b99963ee1fe7c9a 100644 --- a/test/fixtures/operation_tree.py +++ b/test/fixtures/operation_tree.py @@ -1,6 +1,6 @@ import pytest -from b_asic import Addition, Constant, Signal +from b_asic import Addition, Constant, Signal, Butterfly @pytest.fixture @@ -41,6 +41,41 @@ def large_operation_tree(): """ return Addition(Addition(Constant(2), Constant(3)), Addition(Constant(4), Constant(5))) +@pytest.fixture +def large_operation_tree_names(): + """Valid addition operation connected with a large operation tree with 2 other additions and 4 constants. + With names. + 2---+ + | + v + add---+ + ^ | + | | + 3---+ v + add = (2 + 3) + (4 + 5) = 14 + 4---+ ^ + | | + v | + add---+ + ^ + | + 5---+ + """ + return Addition(Addition(Constant(2, name="constant2"), Constant(3, name="constant3")), Addition(Constant(4, name="constant4"), Constant(5, name="constant5"))) + +@pytest.fixture +def butterfly_operation_tree(): + """Valid butterfly operations connected to eachother with 3 butterfly operations and 2 constants as inputs and 2 outputs. + 2 ---+ +--- (2 + 4) ---+ +--- (6 + (-2)) ---+ +--- (4 + 8) ---> out1 = 12 + | | | | | | + v ^ v ^ v ^ + butterfly butterfly butterfly + ^ v ^ v ^ v + | | | | | | + 4 ---+ +--- (2 - 4) ---+ +--- (6 - (-2)) ---+ +--- (4 - 8) ---> out2 = -4 + """ + return Butterfly(*(Butterfly(*(Butterfly(Constant(2), Constant(4), name="bfly3").outputs), name="bfly2").outputs), name="bfly1") + @pytest.fixture def operation_graph_with_cycle(): """Invalid addition operation connected with an operation graph containing a cycle. diff --git a/test/fixtures/signal_flow_graph.py b/test/fixtures/signal_flow_graph.py index 959fd944db5ddc12eba5fba3019bee9b2b8eb254..c18924d7f5e78e3af93a478774b500755b4c11a5 100644 --- a/test/fixtures/signal_flow_graph.py +++ b/test/fixtures/signal_flow_graph.py @@ -1,13 +1,13 @@ import pytest -from b_asic import SFG, Input, Output, Constant, Delay, ConstantMultiplication, AbstractOperation, Name, TypeName, SignalSourceProvider +from b_asic import SFG, Input, Output, Constant, Delay, Addition, ConstantMultiplication, Butterfly, AbstractOperation, Name, TypeName, SignalSourceProvider from typing import Optional @pytest.fixture def sfg_two_inputs_two_outputs(): """Valid SFG with two inputs and two outputs. - . . + . . in1-------+ +--------->out1 . | | . . v | . @@ -18,9 +18,9 @@ def sfg_two_inputs_two_outputs(): | . ^ . | . | . +------------+ . - . . + . . out1 = in1 + in2 - out2 = in1 + 2 * in2 + out2 = in1 + 2 * in2 """ in1 = Input() in2 = Input() @@ -28,13 +28,14 @@ def sfg_two_inputs_two_outputs(): add2 = add1 + in2 out1 = Output(add1) out2 = Output(add2) - return SFG(inputs = [in1, in2], outputs = [out1, out2]) + return SFG(inputs=[in1, in2], outputs=[out1, out2]) + @pytest.fixture def sfg_two_inputs_two_outputs_independent(): """Valid SFG with two inputs and two outputs, where the first output only depends on the first input and the second output only depends on the second input. - . . + . . in1-------------------->out1 . . . . @@ -45,17 +46,18 @@ def sfg_two_inputs_two_outputs_independent(): . | ^ . . | | . . +------+ . - . . + . . out1 = in1 - out2 = in2 + 3 + out2 = in2 + 3 """ - in1 = Input() - in2 = Input() - c1 = Constant(3) - add1 = in2 + c1 - out1 = Output(in1) - out2 = Output(add1) - return SFG(inputs = [in1, in2], outputs = [out1, out2]) + in1 = Input("IN1") + in2 = Input("IN2") + c1 = Constant(3, "C1") + add1 = Addition(in2, c1, "ADD1") + out1 = Output(in1, "OUT1") + out2 = Output(add1, "OUT2") + return SFG(inputs=[in1, in2], outputs=[out1, out2]) + @pytest.fixture def sfg_nested(): @@ -66,7 +68,7 @@ def sfg_nested(): mac_in2 = Input() mac_in3 = Input() mac_out1 = Output(mac_in1 + mac_in2 * mac_in3) - MAC = SFG(inputs = [mac_in1, mac_in2, mac_in3], outputs = [mac_out1]) + MAC = SFG(inputs=[mac_in1, mac_in2, mac_in3], outputs=[mac_out1]) in1 = Input() in2 = Input() @@ -74,7 +76,8 @@ def sfg_nested(): mac2 = MAC(in1, in2, mac1) mac3 = MAC(in1, mac1, mac2) out1 = Output(mac3) - return SFG(inputs = [in1, in2], outputs = [out1]) + return SFG(inputs=[in1, in2], outputs=[out1]) + @pytest.fixture def sfg_delay(): @@ -126,13 +129,14 @@ def sfg_simple_filter(): in1---->add1----->t1+---->out1 . . """ - in1 = Input() - t1 = Delay() - cmul1 = ConstantMultiplication(0.5, t1) - add1 = in1 + cmul1 - t1 << add1 - out1 = Output(t1) - return SFG(inputs=[in1], outputs=[out1]) + in1 = Input("IN1") + cmul1 = ConstantMultiplication(0.5, name="CMUL1") + add1 = Addition(in1, cmul1, "ADD1") + add1.input(1).signals[0].name = "S2" + t1 = Delay(add1, name="T1") + cmul1.input(0).connect(t1, "S1") + out1 = Output(t1, "OUT1") + return SFG(inputs=[in1], outputs=[out1], name="simple_filter") @pytest.fixture def sfg_custom_operation(): @@ -141,7 +145,7 @@ def sfg_custom_operation(): def __init__(self, src0: Optional[SignalSourceProvider] = None, name: Name = ""): super().__init__(input_count = 1, output_count = 2, name = name, input_sources = [src0]) - @property + @classmethod def type_name(self) -> TypeName: return "custom" @@ -152,4 +156,64 @@ def sfg_custom_operation(): custom1 = CustomOperation(in1) out1 = Output(custom1.output(0)) out2 = Output(custom1.output(1)) - return SFG(inputs=[in1], outputs=[out1, out2]) \ No newline at end of file + return SFG(inputs=[in1], outputs=[out1, out2]) + + +@pytest.fixture +def precedence_sfg_delays(): + """A sfg with delays and interesting layout for precednce list generation. + + IN1>--->C0>--->ADD1>--->Q1>---+--->A0>--->ADD4>--->OUT1 + ^ | ^ + | T1 | + | | | + ADD2<---<B1<---+--->A1>--->ADD3 + ^ | ^ + | T2 | + | | | + +-----<B2<---+--->A2>-----+ + """ + in1 = Input("IN1") + c0 = ConstantMultiplication(5, in1, "C0") + add1 = Addition(c0, None, "ADD1") + # Not sure what operation "Q" is supposed to be in the example + Q1 = ConstantMultiplication(1, add1, "Q1") + T1 = Delay(Q1, 0, "T1") + T2 = Delay(T1, 0, "T2") + b2 = ConstantMultiplication(2, T2, "B2") + b1 = ConstantMultiplication(3, T1, "B1") + add2 = Addition(b1, b2, "ADD2") + add1.input(1).connect(add2) + a1 = ConstantMultiplication(4, T1, "A1") + a2 = ConstantMultiplication(6, T2, "A2") + add3 = Addition(a1, a2, "ADD3") + a0 = ConstantMultiplication(7, Q1, "A0") + add4 = Addition(a0, add3, "ADD4") + out1 = Output(add4, "OUT1") + + return SFG(inputs=[in1], outputs=[out1], name="SFG") + + +@pytest.fixture +def precedence_sfg_delays_and_constants(): + in1 = Input("IN1") + c0 = ConstantMultiplication(5, in1, "C0") + add1 = Addition(c0, None, "ADD1") + # Not sure what operation "Q" is supposed to be in the example + Q1 = ConstantMultiplication(1, add1, "Q1") + T1 = Delay(Q1, 0, "T1") + const1 = Constant(10, "CONST1") # Replace T2 delay with a constant + b2 = ConstantMultiplication(2, const1, "B2") + b1 = ConstantMultiplication(3, T1, "B1") + add2 = Addition(b1, b2, "ADD2") + add1.input(1).connect(add2) + a1 = ConstantMultiplication(4, T1, "A1") + a2 = ConstantMultiplication(10, const1, "A2") + add3 = Addition(a1, a2, "ADD3") + a0 = ConstantMultiplication(7, Q1, "A0") + # Replace ADD4 with a butterfly to test multiple output ports + bfly1 = Butterfly(a0, add3, "BFLY1") + out1 = Output(bfly1.output(0), "OUT1") + Output(bfly1.output(1), "OUT2") + + return SFG(inputs=[in1], outputs=[out1], name="SFG") diff --git a/test/test_abstract_operation.py b/test/test_abstract_operation.py index 5423ecdf08c420df5dccc6393c3ad6637961172b..9163fce2a955c7fbc68d5d24de86896d251934da 100644 --- a/test/test_abstract_operation.py +++ b/test/test_abstract_operation.py @@ -89,4 +89,3 @@ def test_division_overload(): assert isinstance(div3, Division) assert div3.input(0).signals[0].source.operation.value == 5 assert div3.input(1).signals == div2.output(0).signals - diff --git a/test/test_core_operations.py b/test/test_core_operations.py index 4d0039b558e81c5cd74f151f93f0bc0194a702d5..6a0493c60965579bd843e0b514bd7f9b9a0e4707 100644 --- a/test/test_core_operations.py +++ b/test/test_core_operations.py @@ -6,7 +6,6 @@ from b_asic import \ Constant, Addition, Subtraction, Multiplication, ConstantMultiplication, Division, \ SquareRoot, ComplexConjugate, Max, Min, Absolute, Butterfly - class TestConstant: def test_constant_positive(self): test_operation = Constant(3) @@ -164,3 +163,14 @@ class TestButterfly: test_operation = Butterfly() assert test_operation.evaluate_output(0, [2+1j, 3-2j]) == 5-1j assert test_operation.evaluate_output(1, [2+1j, 3-2j]) == -1+3j + + +class TestDepends: + def test_depends_addition(self): + add1 = Addition() + assert set(add1.inputs_required_for_output(0)) == {0, 1} + + def test_depends_butterfly(self): + bfly1 = Butterfly() + assert set(bfly1.inputs_required_for_output(0)) == {0, 1} + assert set(bfly1.inputs_required_for_output(1)) == {0, 1} diff --git a/test/test_depends.py b/test/test_depends.py deleted file mode 100644 index e26911054a9604db2f08998f6ecfccd81a012e5a..0000000000000000000000000000000000000000 --- a/test/test_depends.py +++ /dev/null @@ -1,19 +0,0 @@ -from b_asic import Addition, Butterfly - -class TestDepends: - def test_depends_addition(self): - add1 = Addition() - assert set(add1.inputs_required_for_output(0)) == {0, 1} - - def test_depends_butterfly(self): - bfly1 = Butterfly() - assert set(bfly1.inputs_required_for_output(0)) == {0, 1} - assert set(bfly1.inputs_required_for_output(1)) == {0, 1} - - def test_depends_sfg(self, sfg_two_inputs_two_outputs): - assert set(sfg_two_inputs_two_outputs.inputs_required_for_output(0)) == {0, 1} - assert set(sfg_two_inputs_two_outputs.inputs_required_for_output(1)) == {0, 1} - - def test_depends_sfg_independent(self, sfg_two_inputs_two_outputs_independent): - assert set(sfg_two_inputs_two_outputs_independent.inputs_required_for_output(0)) == {0} - assert set(sfg_two_inputs_two_outputs_independent.inputs_required_for_output(1)) == {1} \ No newline at end of file diff --git a/test/test_fast_simulation.py b/test/test_fast_simulation.py index ece9add5891cb47cba7b63f80682bc65d970470e..6eb3b251bec6a2c556230dbedcb8f712c9f56d48 100644 --- a/test/test_fast_simulation.py +++ b/test/test_fast_simulation.py @@ -8,13 +8,11 @@ class TestRunFor: def test_with_lambdas_as_input(self, sfg_two_inputs_two_outputs): simulation = FastSimulation(sfg_two_inputs_two_outputs, [lambda n: n + 3, lambda n: 1 + n * 2]) - output = simulation.run_for(101) + output = simulation.run_for(101, save_results = True) assert output[0] == 304 assert output[1] == 505 - print(simulation.results) - assert simulation.results["0"][100] == 304 assert simulation.results["1"][100] == 505 @@ -51,7 +49,7 @@ class TestRunFor: input1 = np.array([7, 3, 3, 54, 2]) simulation = FastSimulation(sfg_two_inputs_two_outputs, [input0, input1]) - output = simulation.run_for(5) + output = simulation.run_for(5, save_results = True) assert output[0] == 9 assert output[1] == 11 @@ -115,7 +113,7 @@ class TestRunFor: def test_delay(self, sfg_delay): simulation = FastSimulation(sfg_delay) simulation.set_input(0, [5, -2, 25, -6, 7, 0]) - simulation.run_for(6) + simulation.run_for(6, save_results = True) assert simulation.results["0"][0] == 0 assert simulation.results["0"][1] == 5 @@ -178,17 +176,17 @@ class TestRun: assert output3[0] == 28 assert output4[0] == 0 assert output5[0] == 7 - + def test_simple_accumulator(self, sfg_simple_accumulator): data_in = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] simulation = FastSimulation(sfg_simple_accumulator, [data_in]) simulation.run() assert list(simulation.results["0"]) == [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] - + def test_simple_filter(self, sfg_simple_filter): input0 = np.array([1, 2, 3, 4, 5]) simulation = FastSimulation(sfg_simple_filter, [input0]) - simulation.run_for(len(input0)) + simulation.run_for(len(input0), save_results = True) assert all(simulation.results["0"] == np.array([0, 1.0, 2.5, 4.25, 6.125])) def test_custom_operation(self, sfg_custom_operation): @@ -197,6 +195,7 @@ class TestRun: assert all(simulation.results["0"] == np.array([2, 4, 6, 8, 10])) assert all(simulation.results["1"] == np.array([2, 4, 8, 16, 32])) + class TestLarge: def test_1k_additions(self): prev_op = Addition(Constant(1), Constant(1)) diff --git a/test/test_operation.py b/test/test_operation.py index b76ba16d11425c0ce868e4fa0b4c88d9f862e23f..77e9ba3cbd0eaa75886b5a7e5d11f00f6cfeb479 100644 --- a/test/test_operation.py +++ b/test/test_operation.py @@ -1,6 +1,6 @@ import pytest -from b_asic import Constant, Addition +from b_asic import Constant, Addition, MAD, Butterfly, SquareRoot class TestTraverse: def test_traverse_single_tree(self, operation): @@ -22,4 +22,32 @@ class TestTraverse: assert len(list(filter(lambda type_: isinstance(type_, Constant), result))) == 4 def test_traverse_loop(self, operation_graph_with_cycle): - assert len(list(operation_graph_with_cycle.traverse())) == 8 \ No newline at end of file + assert len(list(operation_graph_with_cycle.traverse())) == 8 + +class TestToSfg: + def test_convert_mad_to_sfg(self): + mad1 = MAD() + mad1_sfg = mad1.to_sfg() + + assert mad1.evaluate(1,1,1) == mad1_sfg.evaluate(1,1,1) + assert len(mad1_sfg.operations) == 6 + + def test_butterfly_to_sfg(self): + but1 = Butterfly() + but1_sfg = but1.to_sfg() + + assert but1.evaluate(1,1)[0] == but1_sfg.evaluate(1,1)[0] + assert but1.evaluate(1,1)[1] == but1_sfg.evaluate(1,1)[1] + assert len(but1_sfg.operations) == 8 + + def test_add_to_sfg(self): + add1 = Addition() + add1_sfg = add1.to_sfg() + + assert len(add1_sfg.operations) == 4 + + def test_sqrt_to_sfg(self): + sqrt1 = SquareRoot() + sqrt1_sfg = sqrt1.to_sfg() + + assert len(sqrt1_sfg.operations) == 3 diff --git a/test/test_sfg.py b/test/test_sfg.py index 9e6c07cf98b49d24c73995080fbaccd225574041..e93427bac9fca6b2f3dd71d70a0c5cbf0ebec56f 100644 --- a/test/test_sfg.py +++ b/test/test_sfg.py @@ -1,6 +1,10 @@ import pytest +import io +import sys -from b_asic import SFG, Signal, Input, Output, Constant, Addition, Multiplication + +from b_asic import SFG, Signal, Input, Output, Constant, ConstantMultiplication, Addition, Multiplication, Delay, \ + Butterfly, Subtraction, SquareRoot class TestInit: @@ -9,7 +13,7 @@ class TestInit: out1 = Output(None, "OUT1") out1.input(0).connect(in1, "S1") - sfg = SFG(inputs = [in1], outputs = [out1]) # in1 ---s1---> out1 + sfg = SFG(inputs=[in1], outputs=[out1]) # in1 ---s1---> out1 assert len(list(sfg.components)) == 3 assert len(list(sfg.operations)) == 2 @@ -22,7 +26,8 @@ class TestInit: s1 = add2.input(0).connect(add1, "S1") - sfg = SFG(input_signals = [s1], output_signals = [s1]) # in1 ---s1---> out1 + # in1 ---s1---> out1 + sfg = SFG(input_signals=[s1], output_signals=[s1]) assert len(list(sfg.components)) == 3 assert len(list(sfg.operations)) == 2 @@ -30,7 +35,7 @@ class TestInit: assert sfg.output_count == 1 def test_outputs_construction(self, operation_tree): - sfg = SFG(outputs = [Output(operation_tree)]) + sfg = SFG(outputs=[Output(operation_tree)]) assert len(list(sfg.components)) == 7 assert len(list(sfg.operations)) == 4 @@ -38,26 +43,31 @@ class TestInit: assert sfg.output_count == 1 def test_signals_construction(self, operation_tree): - sfg = SFG(output_signals = [Signal(source = operation_tree.output(0))]) + sfg = SFG(output_signals=[Signal(source=operation_tree.output(0))]) assert len(list(sfg.components)) == 7 assert len(list(sfg.operations)) == 4 assert sfg.input_count == 0 assert sfg.output_count == 1 + class TestPrintSfg: def test_one_addition(self): inp1 = Input("INP1") inp2 = Input("INP2") add1 = Addition(inp1, inp2, "ADD1") out1 = Output(add1, "OUT1") - sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="sf1") + sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="SFG1") assert sfg.__str__() == \ - "id: add1, name: ADD1, input: [s1, s2], output: [s3]\n" + \ - "id: in1, name: INP1, input: [], output: [s1]\n" + \ - "id: in2, name: INP2, input: [], output: [s2]\n" + \ - "id: out1, name: OUT1, input: [s3], output: []\n" + "id: no_id, \tname: SFG1, \tinputs: {0: '-'}, \toutputs: {0: '-'}\n" + \ + "Internal Operations:\n" + \ + "----------------------------------------------------------------------------------------------------\n" + \ + str(sfg.find_by_name("INP1")[0]) + "\n" + \ + str(sfg.find_by_name("INP2")[0]) + "\n" + \ + str(sfg.find_by_name("ADD1")[0]) + "\n" + \ + str(sfg.find_by_name("OUT1")[0]) + "\n" + \ + "----------------------------------------------------------------------------------------------------\n" def test_add_mul(self): inp1 = Input("INP1") @@ -69,12 +79,16 @@ class TestPrintSfg: sfg = SFG(inputs=[inp1, inp2, inp3], outputs=[out1], name="mac_sfg") assert sfg.__str__() == \ - "id: add1, name: ADD1, input: [s1, s2], output: [s5]\n" + \ - "id: in1, name: INP1, input: [], output: [s1]\n" + \ - "id: in2, name: INP2, input: [], output: [s2]\n" + \ - "id: mul1, name: MUL1, input: [s5, s3], output: [s4]\n" + \ - "id: in3, name: INP3, input: [], output: [s3]\n" + \ - "id: out1, name: OUT1, input: [s4], output: []\n" + "id: no_id, \tname: mac_sfg, \tinputs: {0: '-'}, \toutputs: {0: '-'}\n" + \ + "Internal Operations:\n" + \ + "----------------------------------------------------------------------------------------------------\n" + \ + str(sfg.find_by_name("INP1")[0]) + "\n" + \ + str(sfg.find_by_name("INP2")[0]) + "\n" + \ + str(sfg.find_by_name("ADD1")[0]) + "\n" + \ + str(sfg.find_by_name("INP3")[0]) + "\n" + \ + str(sfg.find_by_name("MUL1")[0]) + "\n" + \ + str(sfg.find_by_name("OUT1")[0]) + "\n" + \ + "----------------------------------------------------------------------------------------------------\n" def test_constant(self): inp1 = Input("INP1") @@ -85,18 +99,27 @@ class TestPrintSfg: sfg = SFG(inputs=[inp1], outputs=[out1], name="sfg") assert sfg.__str__() == \ - "id: add1, name: ADD1, input: [s3, s1], output: [s2]\n" + \ - "id: c1, name: CONST, value: 3, input: [], output: [s3]\n" + \ - "id: in1, name: INP1, input: [], output: [s1]\n" + \ - "id: out1, name: OUT1, input: [s2], output: []\n" + "id: no_id, \tname: sfg, \tinputs: {0: '-'}, \toutputs: {0: '-'}\n" + \ + "Internal Operations:\n" + \ + "----------------------------------------------------------------------------------------------------\n" + \ + str(sfg.find_by_name("CONST")[0]) + "\n" + \ + str(sfg.find_by_name("INP1")[0]) + "\n" + \ + str(sfg.find_by_name("ADD1")[0]) + "\n" + \ + str(sfg.find_by_name("OUT1")[0]) + "\n" + \ + "----------------------------------------------------------------------------------------------------\n" def test_simple_filter(self, sfg_simple_filter): - assert sfg_simple_filter.__str__() == \ - 'id: add1, name: , input: [s1, s3], output: [s4]\n' + \ - 'id: in1, name: , input: [], output: [s1]\n' + \ - 'id: cmul1, name: , input: [s5], output: [s3]\n' + \ - 'id: t1, name: , input: [s4], output: [s5, s2]\n' + \ - 'id: out1, name: , input: [s2], output: []\n' + assert sfg_simple_filter.__str__() == \ + "id: no_id, \tname: simple_filter, \tinputs: {0: '-'}, \toutputs: {0: '-'}\n" + \ + "Internal Operations:\n" + \ + "----------------------------------------------------------------------------------------------------\n" + \ + str(sfg_simple_filter.find_by_name("IN1")[0]) + "\n" + \ + str(sfg_simple_filter.find_by_name("ADD1")[0]) + "\n" + \ + str(sfg_simple_filter.find_by_name("T1")[0]) + "\n" + \ + str(sfg_simple_filter.find_by_name("CMUL1")[0]) + "\n" + \ + str(sfg_simple_filter.find_by_name("OUT1")[0]) + "\n" + \ + "----------------------------------------------------------------------------------------------------\n" + class TestDeepCopy: def test_deep_copy_no_duplicates(self): @@ -107,7 +130,7 @@ class TestDeepCopy: mul1 = Multiplication(add1, inp3, "MUL1") out1 = Output(mul1, "OUT1") - mac_sfg = SFG(inputs = [inp1, inp2], outputs = [out1], name = "mac_sfg") + mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="mac_sfg") mac_sfg_new = mac_sfg() assert mac_sfg.name == "mac_sfg" @@ -134,8 +157,9 @@ class TestDeepCopy: mul1.input(1).connect(add2, "S6") out1.input(0).connect(mul1, "S7") - mac_sfg = SFG(inputs = [inp1, inp2], outputs = [out1], id_number_offset = 100, name = "mac_sfg") - mac_sfg_new = mac_sfg(name = "mac_sfg2") + mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], + id_number_offset=100, name="mac_sfg") + mac_sfg_new = mac_sfg(name="mac_sfg2") assert mac_sfg.name == "mac_sfg" assert mac_sfg_new.name == "mac_sfg2" @@ -145,7 +169,7 @@ class TestDeepCopy: for g_id, component in mac_sfg._components_by_id.items(): component_copy = mac_sfg_new.find_by_id(g_id) assert component.name == component_copy.name - + def test_deep_copy_with_new_sources(self): inp1 = Input("INP1") inp2 = Input("INP2") @@ -154,7 +178,7 @@ class TestDeepCopy: mul1 = Multiplication(add1, inp3, "MUL1") out1 = Output(mul1, "OUT1") - mac_sfg = SFG(inputs = [inp1, inp2], outputs = [out1], name = "mac_sfg") + mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="mac_sfg") a = Addition(Constant(3), Constant(5)) b = Constant(2) @@ -162,20 +186,22 @@ class TestDeepCopy: assert mac_sfg_new.input(0).signals[0].source.operation is a assert mac_sfg_new.input(1).signals[0].source.operation is b + class TestEvaluateOutput: def test_evaluate_output(self, operation_tree): - sfg = SFG(outputs = [Output(operation_tree)]) + sfg = SFG(outputs=[Output(operation_tree)]) assert sfg.evaluate_output(0, []) == 5 def test_evaluate_output_large(self, large_operation_tree): - sfg = SFG(outputs = [Output(large_operation_tree)]) + sfg = SFG(outputs=[Output(large_operation_tree)]) assert sfg.evaluate_output(0, []) == 14 def test_evaluate_output_cycle(self, operation_graph_with_cycle): - sfg = SFG(outputs = [Output(operation_graph_with_cycle)]) + sfg = SFG(outputs=[Output(operation_graph_with_cycle)]) with pytest.raises(Exception): sfg.evaluate_output(0, []) + class TestComponents: def test_advanced_components(self): inp1 = Input("INP1") @@ -194,9 +220,10 @@ class TestComponents: mul1.input(1).connect(add2, "S6") out1.input(0).connect(mul1, "S7") - mac_sfg = SFG(inputs = [inp1, inp2], outputs = [out1], name = "mac_sfg") + mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="mac_sfg") - assert set([comp.name for comp in mac_sfg.components]) == {"INP1", "INP2", "INP3", "ADD1", "ADD2", "MUL1", "OUT1", "S1", "S2", "S3", "S4", "S5", "S6", "S7"} + assert set([comp.name for comp in mac_sfg.components]) == { + "INP1", "INP2", "INP3", "ADD1", "ADD2", "MUL1", "OUT1", "S1", "S2", "S3", "S4", "S5", "S6", "S7"} class TestReplaceComponents: @@ -204,16 +231,8 @@ class TestReplaceComponents: sfg = SFG(outputs=[Output(operation_tree)]) component_id = "add1" - sfg = sfg.replace_component(Multiplication(name="Multi"), _id=component_id) - assert component_id not in sfg._components_by_id.keys() - assert "Multi" in sfg._components_by_name.keys() - - def test_replace_addition_by_component(self, operation_tree): - sfg = SFG(outputs=[Output(operation_tree)]) - component_id = "add1" - component = sfg.find_by_id(component_id) - - sfg = sfg.replace_component(Multiplication(name="Multi"), _component=component) + sfg = sfg.replace_component( + Multiplication(name="Multi"), _id=component_id) assert component_id not in sfg._components_by_id.keys() assert "Multi" in sfg._components_by_name.keys() @@ -221,15 +240,16 @@ class TestReplaceComponents: sfg = SFG(outputs=[Output(large_operation_tree)]) component_id = "add3" - sfg = sfg.replace_component(Multiplication(name="Multi"), _id=component_id) + sfg = sfg.replace_component( + Multiplication(name="Multi"), _id=component_id) assert "Multi" in sfg._components_by_name.keys() assert component_id not in sfg._components_by_id.keys() - + def test_replace_no_input_component(self, operation_tree): sfg = SFG(outputs=[Output(operation_tree)]) component_id = "c1" _const = sfg.find_by_id(component_id) - + sfg = sfg.replace_component(Constant(1), _id=component_id) assert _const is not sfg.find_by_id(component_id) @@ -238,7 +258,8 @@ class TestReplaceComponents: component_id = "addd1" try: - sfg = sfg.replace_component(Multiplication(name="Multi"), _id=component_id) + sfg = sfg.replace_component( + Multiplication(name="Multi"), _id=component_id) except AssertionError: assert True else: @@ -249,8 +270,508 @@ class TestReplaceComponents: component_id = "c1" try: - sfg = sfg.replace_component(Multiplication(name="Multi"), _id=component_id) + sfg = sfg.replace_component( + Multiplication(name="Multi"), _id=component_id) except AssertionError: assert True else: assert False + + +class TestInsertComponent: + + def test_insert_component_in_sfg(self, large_operation_tree_names): + sfg = SFG(outputs=[Output(large_operation_tree_names)]) + sqrt = SquareRoot() + + _sfg = sfg.insert_operation(sqrt, sfg.find_by_name("constant4")[0].graph_id) + assert _sfg.evaluate() != sfg.evaluate() + + assert any([isinstance(comp, SquareRoot) for comp in _sfg.operations]) + assert not any([isinstance(comp, SquareRoot) for comp in sfg.operations]) + + assert not isinstance(sfg.find_by_name("constant4")[0].output(0).signals[0].destination.operation, SquareRoot) + assert isinstance(_sfg.find_by_name("constant4")[0].output(0).signals[0].destination.operation, SquareRoot) + + assert sfg.find_by_name("constant4")[0].output(0).signals[0].destination.operation is sfg.find_by_id("add3") + assert _sfg.find_by_name("constant4")[0].output( + 0).signals[0].destination.operation is not _sfg.find_by_id("add3") + assert _sfg.find_by_id("sqrt1").output(0).signals[0].destination.operation is _sfg.find_by_id("add3") + + def test_insert_invalid_component_in_sfg(self, large_operation_tree): + sfg = SFG(outputs=[Output(large_operation_tree)]) + + # Should raise an exception for not matching input count to output count. + add4 = Addition() + with pytest.raises(Exception): + sfg.insert_operation(add4, "c1") + + def test_insert_at_output(self, large_operation_tree): + sfg = SFG(outputs=[Output(large_operation_tree)]) + + # Should raise an exception for trying to insert an operation after an output. + sqrt = SquareRoot() + with pytest.raises(Exception): + _sfg = sfg.insert_operation(sqrt, "out1") + + def test_insert_multiple_output_ports(self, butterfly_operation_tree): + sfg = SFG(outputs=list(map(Output, butterfly_operation_tree.outputs))) + _sfg = sfg.insert_operation(Butterfly(name="n_bfly"), "bfly3") + + assert sfg.evaluate() != _sfg.evaluate() + + assert len(sfg.find_by_name("n_bfly")) == 0 + assert len(_sfg.find_by_name("n_bfly")) == 1 + + # Correctly connected old output -> new input + assert _sfg.find_by_name("bfly3")[0].output( + 0).signals[0].destination.operation is _sfg.find_by_name("n_bfly")[0] + assert _sfg.find_by_name("bfly3")[0].output( + 1).signals[0].destination.operation is _sfg.find_by_name("n_bfly")[0] + + # Correctly connected new input -> old output + assert _sfg.find_by_name("n_bfly")[0].input(0).signals[0].source.operation is _sfg.find_by_name("bfly3")[0] + assert _sfg.find_by_name("n_bfly")[0].input(1).signals[0].source.operation is _sfg.find_by_name("bfly3")[0] + + # Correctly connected new output -> next input + assert _sfg.find_by_name("n_bfly")[0].output( + 0).signals[0].destination.operation is _sfg.find_by_name("bfly2")[0] + assert _sfg.find_by_name("n_bfly")[0].output( + 1).signals[0].destination.operation is _sfg.find_by_name("bfly2")[0] + + # Correctly connected next input -> new output + assert _sfg.find_by_name("bfly2")[0].input(0).signals[0].source.operation is _sfg.find_by_name("n_bfly")[0] + assert _sfg.find_by_name("bfly2")[0].input(1).signals[0].source.operation is _sfg.find_by_name("n_bfly")[0] + + +class TestFindComponentsWithTypeName: + def test_mac_components(self): + inp1 = Input("INP1") + inp2 = Input("INP2") + inp3 = Input("INP3") + add1 = Addition(None, None, "ADD1") + add2 = Addition(None, None, "ADD2") + mul1 = Multiplication(None, None, "MUL1") + out1 = Output(None, "OUT1") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + add2.input(0).connect(add1, "S4") + add2.input(1).connect(inp3, "S3") + mul1.input(0).connect(add1, "S5") + mul1.input(1).connect(add2, "S6") + out1.input(0).connect(mul1, "S7") + + mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="mac_sfg") + + assert {comp.name for comp in mac_sfg.get_components_with_type_name( + inp1.type_name())} == {"INP1", "INP2", "INP3"} + + assert {comp.name for comp in mac_sfg.get_components_with_type_name( + add1.type_name())} == {"ADD1", "ADD2"} + + assert {comp.name for comp in mac_sfg.get_components_with_type_name( + mul1.type_name())} == {"MUL1"} + + assert {comp.name for comp in mac_sfg.get_components_with_type_name( + out1.type_name())} == {"OUT1"} + + assert {comp.name for comp in mac_sfg.get_components_with_type_name( + Signal.type_name())} == {"S1", "S2", "S3", "S4", "S5", "S6", "S7"} + + +class TestGetPrecedenceList: + + def test_inputs_delays(self, precedence_sfg_delays): + + precedence_list = precedence_sfg_delays.get_precedence_list() + + assert len(precedence_list) == 7 + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[0]]) == {"IN1", "T1", "T2"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[1]]) == {"C0", "B1", "B2", "A1", "A2"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[2]]) == {"ADD2", "ADD3"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[3]]) == {"ADD1"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[4]]) == {"Q1"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[5]]) == {"A0"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[6]]) == {"ADD4"} + + def test_inputs_constants_delays_multiple_outputs(self, precedence_sfg_delays_and_constants): + + precedence_list = precedence_sfg_delays_and_constants.get_precedence_list() + + assert len(precedence_list) == 7 + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[0]]) == {"IN1", "T1", "CONST1"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[1]]) == {"C0", "B1", "B2", "A1", "A2"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[2]]) == {"ADD2", "ADD3"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[3]]) == {"ADD1"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[4]]) == {"Q1"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[5]]) == {"A0"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[6]]) == {"BFLY1.0", "BFLY1.1"} + + def test_precedence_multiple_outputs_same_precedence(self, sfg_two_inputs_two_outputs): + sfg_two_inputs_two_outputs.name = "NESTED_SFG" + + in1 = Input("IN1") + sfg_two_inputs_two_outputs.input(0).connect(in1, "S1") + in2 = Input("IN2") + cmul1 = ConstantMultiplication(10, None, "CMUL1") + cmul1.input(0).connect(in2, "S2") + sfg_two_inputs_two_outputs.input(1).connect(cmul1, "S3") + + out1 = Output(sfg_two_inputs_two_outputs.output(0), "OUT1") + out2 = Output(sfg_two_inputs_two_outputs.output(1), "OUT2") + + sfg = SFG(inputs=[in1, in2], outputs=[out1, out2]) + + precedence_list = sfg.get_precedence_list() + + assert len(precedence_list) == 3 + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[0]]) == {"IN1", "IN2"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[1]]) == {"CMUL1"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[2]]) == {"NESTED_SFG.0", "NESTED_SFG.1"} + + def test_precedence_sfg_multiple_outputs_different_precedences(self, sfg_two_inputs_two_outputs_independent): + sfg_two_inputs_two_outputs_independent.name = "NESTED_SFG" + + in1 = Input("IN1") + in2 = Input("IN2") + sfg_two_inputs_two_outputs_independent.input(0).connect(in1, "S1") + cmul1 = ConstantMultiplication(10, None, "CMUL1") + cmul1.input(0).connect(in2, "S2") + sfg_two_inputs_two_outputs_independent.input(1).connect(cmul1, "S3") + out1 = Output(sfg_two_inputs_two_outputs_independent.output(0), "OUT1") + out2 = Output(sfg_two_inputs_two_outputs_independent.output(1), "OUT2") + + sfg = SFG(inputs=[in1, in2], outputs=[out1, out2]) + + precedence_list = sfg.get_precedence_list() + + assert len(precedence_list) == 3 + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[0]]) == {"IN1", "IN2"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[1]]) == {"CMUL1"} + + assert set([port.operation.key(port.index, port.operation.name) + for port in precedence_list[2]]) == {"NESTED_SFG.0", "NESTED_SFG.1"} + + +class TestPrintPrecedence: + def test_delays(self, precedence_sfg_delays): + sfg = precedence_sfg_delays + + captured_output = io.StringIO() + sys.stdout = captured_output + + sfg.print_precedence_graph() + + sys.stdout = sys.__stdout__ + + captured_output = captured_output.getvalue() + + assert captured_output == \ + "-" * 120 + "\n" + \ + "1.1 \t" + str(sfg.find_by_name("IN1")[0]) + "\n" + \ + "1.2 \t" + str(sfg.find_by_name("T1")[0]) + "\n" + \ + "1.3 \t" + str(sfg.find_by_name("T2")[0]) + "\n" + \ + "-" * 120 + "\n" + \ + "2.1 \t" + str(sfg.find_by_name("C0")[0]) + "\n" + \ + "2.2 \t" + str(sfg.find_by_name("A1")[0]) + "\n" + \ + "2.3 \t" + str(sfg.find_by_name("B1")[0]) + "\n" + \ + "2.4 \t" + str(sfg.find_by_name("A2")[0]) + "\n" + \ + "2.5 \t" + str(sfg.find_by_name("B2")[0]) + "\n" + \ + "-" * 120 + "\n" + \ + "3.1 \t" + str(sfg.find_by_name("ADD3")[0]) + "\n" + \ + "3.2 \t" + str(sfg.find_by_name("ADD2")[0]) + "\n" + \ + "-" * 120 + "\n" + \ + "4.1 \t" + str(sfg.find_by_name("ADD1")[0]) + "\n" + \ + "-" * 120 + "\n" + \ + "5.1 \t" + str(sfg.find_by_name("Q1")[0]) + "\n" + \ + "-" * 120 + "\n" + \ + "6.1 \t" + str(sfg.find_by_name("A0")[0]) + "\n" + \ + "-" * 120 + "\n" + \ + "7.1 \t" + str(sfg.find_by_name("ADD4")[0]) + "\n" + \ + "-" * 120 + "\n" + + +class TestDepends: + def test_depends_sfg(self, sfg_two_inputs_two_outputs): + assert set(sfg_two_inputs_two_outputs.inputs_required_for_output(0)) == { + 0, 1} + assert set(sfg_two_inputs_two_outputs.inputs_required_for_output(1)) == { + 0, 1} + + def test_depends_sfg_independent(self, sfg_two_inputs_two_outputs_independent): + assert set( + sfg_two_inputs_two_outputs_independent.inputs_required_for_output(0)) == {0} + assert set( + sfg_two_inputs_two_outputs_independent.inputs_required_for_output(1)) == {1} + + +class TestConnectExternalSignalsToComponentsSoloComp: + + def test_connect_external_signals_to_components_mac(self): + """ Replace a MAC with inner components in an SFG """ + inp1 = Input("INP1") + inp2 = Input("INP2") + inp3 = Input("INP3") + add1 = Addition(None, None, "ADD1") + add2 = Addition(None, None, "ADD2") + mul1 = Multiplication(None, None, "MUL1") + out1 = Output(None, "OUT1") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + add2.input(0).connect(add1, "S3") + add2.input(1).connect(inp3, "S4") + mul1.input(0).connect(add1, "S5") + mul1.input(1).connect(add2, "S6") + out1.input(0).connect(mul1, "S7") + + mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1]) + + inp4 = Input("INP4") + inp5 = Input("INP5") + out2 = Output(None, "OUT2") + + mac_sfg.input(0).connect(inp4, "S8") + mac_sfg.input(1).connect(inp5, "S9") + out2.input(0).connect(mac_sfg.outputs[0], "S10") + + test_sfg = SFG(inputs=[inp4, inp5], outputs=[out2]) + assert test_sfg.evaluate(1, 2) == 9 + mac_sfg.connect_external_signals_to_components() + assert test_sfg.evaluate(1, 2) == 9 + assert not test_sfg.connect_external_signals_to_components() + + def test_connect_external_signals_to_components_operation_tree(self, operation_tree): + """ Replaces an SFG with only a operation_tree component with its inner components """ + sfg1 = SFG(outputs=[Output(operation_tree)]) + out1 = Output(None, "OUT1") + out1.input(0).connect(sfg1.outputs[0], "S1") + test_sfg = SFG(outputs=[out1]) + assert test_sfg.evaluate_output(0, []) == 5 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate_output(0, []) == 5 + assert not test_sfg.connect_external_signals_to_components() + + def test_connect_external_signals_to_components_large_operation_tree(self, large_operation_tree): + """ Replaces an SFG with only a large_operation_tree component with its inner components """ + sfg1 = SFG(outputs=[Output(large_operation_tree)]) + out1 = Output(None, "OUT1") + out1.input(0).connect(sfg1.outputs[0], "S1") + test_sfg = SFG(outputs=[out1]) + assert test_sfg.evaluate_output(0, []) == 14 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate_output(0, []) == 14 + assert not test_sfg.connect_external_signals_to_components() + + +class TestConnectExternalSignalsToComponentsMultipleComp: + + def test_connect_external_signals_to_components_operation_tree(self, operation_tree): + """ Replaces a operation_tree in an SFG with other components """ + sfg1 = SFG(outputs=[Output(operation_tree)]) + + inp1 = Input("INP1") + inp2 = Input("INP2") + out1 = Output(None, "OUT1") + + add1 = Addition(None, None, "ADD1") + add2 = Addition(None, None, "ADD2") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + add2.input(0).connect(add1, "S3") + add2.input(1).connect(sfg1.outputs[0], "S4") + out1.input(0).connect(add2, "S5") + + test_sfg = SFG(inputs=[inp1, inp2], outputs=[out1]) + assert test_sfg.evaluate(1, 2) == 8 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate(1, 2) == 8 + assert not test_sfg.connect_external_signals_to_components() + + def test_connect_external_signals_to_components_large_operation_tree(self, large_operation_tree): + """ Replaces a large_operation_tree in an SFG with other components """ + sfg1 = SFG(outputs=[Output(large_operation_tree)]) + + inp1 = Input("INP1") + inp2 = Input("INP2") + out1 = Output(None, "OUT1") + add1 = Addition(None, None, "ADD1") + add2 = Addition(None, None, "ADD2") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + add2.input(0).connect(add1, "S3") + add2.input(1).connect(sfg1.outputs[0], "S4") + out1.input(0).connect(add2, "S5") + + test_sfg = SFG(inputs=[inp1, inp2], outputs=[out1]) + assert test_sfg.evaluate(1, 2) == 17 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate(1, 2) == 17 + assert not test_sfg.connect_external_signals_to_components() + + def create_sfg(self, op_tree): + """ Create a simple SFG with either operation_tree or large_operation_tree """ + sfg1 = SFG(outputs=[Output(op_tree)]) + + inp1 = Input("INP1") + inp2 = Input("INP2") + out1 = Output(None, "OUT1") + add1 = Addition(None, None, "ADD1") + add2 = Addition(None, None, "ADD2") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + add2.input(0).connect(add1, "S3") + add2.input(1).connect(sfg1.outputs[0], "S4") + out1.input(0).connect(add2, "S5") + + return SFG(inputs=[inp1, inp2], outputs=[out1]) + + def test_connect_external_signals_to_components_many_op(self, large_operation_tree): + """ Replaces an sfg component in a larger SFG with several component operations """ + inp1 = Input("INP1") + inp2 = Input("INP2") + inp3 = Input("INP3") + inp4 = Input("INP4") + out1 = Output(None, "OUT1") + add1 = Addition(None, None, "ADD1") + sub1 = Subtraction(None, None, "SUB1") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + + sfg1 = self.create_sfg(large_operation_tree) + + sfg1.input(0).connect(add1, "S3") + sfg1.input(1).connect(inp3, "S4") + sub1.input(0).connect(sfg1.outputs[0], "S5") + sub1.input(1).connect(inp4, "S6") + out1.input(0).connect(sub1, "S7") + + test_sfg = SFG(inputs=[inp1, inp2, inp3, inp4], outputs=[out1]) + + assert test_sfg.evaluate(1, 2, 3, 4) == 16 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate(1, 2, 3, 4) == 16 + assert not test_sfg.connect_external_signals_to_components() + +class TestTopologicalOrderOperations: + def test_feedback_sfg(self, sfg_simple_filter): + topological_order = sfg_simple_filter.get_operations_topological_order() + + assert [comp.name for comp in topological_order] == ["IN1", "ADD1", "T1", "CMUL1", "OUT1"] + + def test_multiple_independent_inputs(self, sfg_two_inputs_two_outputs_independent): + topological_order = sfg_two_inputs_two_outputs_independent.get_operations_topological_order() + + assert [comp.name for comp in topological_order] == ["IN1", "OUT1", "IN2", "C1", "ADD1", "OUT2"] + + +class TestRemove: + def test_remove_single_input_outputs(self, sfg_simple_filter): + new_sfg = sfg_simple_filter.remove_operation("cmul1") + + assert set(op.name for op in sfg_simple_filter.find_by_name("T1")[0].subsequent_operations) == {"CMUL1", "OUT1"} + assert set(op.name for op in new_sfg.find_by_name("T1")[0].subsequent_operations) == {"ADD1", "OUT1"} + + assert set(op.name for op in sfg_simple_filter.find_by_name("ADD1")[0].preceding_operations) == {"CMUL1", "IN1"} + assert set(op.name for op in new_sfg.find_by_name("ADD1")[0].preceding_operations) == {"T1", "IN1"} + + assert "S1" in set([sig.name for sig in sfg_simple_filter.find_by_name("T1")[0].output(0).signals]) + assert "S2" in set([sig.name for sig in new_sfg.find_by_name("T1")[0].output(0).signals]) + + def test_remove_multiple_inputs_outputs(self, butterfly_operation_tree): + out1 = Output(butterfly_operation_tree.output(0), "OUT1") + out2 = Output(butterfly_operation_tree.output(1), "OUT2") + + sfg = SFG(outputs=[out1, out2]) + + new_sfg = sfg.remove_operation(sfg.find_by_name("bfly2")[0].graph_id) + + assert sfg.find_by_name("bfly3")[0].output(0).signal_count == 1 + assert new_sfg.find_by_name("bfly3")[0].output(0).signal_count == 1 + + sfg_dest_0 = sfg.find_by_name("bfly3")[0].output(0).signals[0].destination + new_sfg_dest_0 = new_sfg.find_by_name("bfly3")[0].output(0).signals[0].destination + + assert sfg_dest_0.index == 0 + assert new_sfg_dest_0.index == 0 + assert sfg_dest_0.operation.name == "bfly2" + assert new_sfg_dest_0.operation.name == "bfly1" + + assert sfg.find_by_name("bfly3")[0].output(1).signal_count == 1 + assert new_sfg.find_by_name("bfly3")[0].output(1).signal_count == 1 + + sfg_dest_1 = sfg.find_by_name("bfly3")[0].output(1).signals[0].destination + new_sfg_dest_1 = new_sfg.find_by_name("bfly3")[0].output(1).signals[0].destination + + assert sfg_dest_1.index == 1 + assert new_sfg_dest_1.index == 1 + assert sfg_dest_1.operation.name == "bfly2" + assert new_sfg_dest_1.operation.name == "bfly1" + + assert sfg.find_by_name("bfly1")[0].input(0).signal_count == 1 + assert new_sfg.find_by_name("bfly1")[0].input(0).signal_count == 1 + + sfg_source_0 = sfg.find_by_name("bfly1")[0].input(0).signals[0].source + new_sfg_source_0 = new_sfg.find_by_name("bfly1")[0].input(0).signals[0].source + + assert sfg_source_0.index == 0 + assert new_sfg_source_0.index == 0 + assert sfg_source_0.operation.name == "bfly2" + assert new_sfg_source_0.operation.name == "bfly3" + + sfg_source_1 = sfg.find_by_name("bfly1")[0].input(1).signals[0].source + new_sfg_source_1 = new_sfg.find_by_name("bfly1")[0].input(1).signals[0].source + + assert sfg_source_1.index == 1 + assert new_sfg_source_1.index == 1 + assert sfg_source_1.operation.name == "bfly2" + assert new_sfg_source_1.operation.name == "bfly3" + + assert "bfly2" not in set(op.name for op in new_sfg.operations) + + def remove_different_number_inputs_outputs(self, sfg_simple_filter): + with pytest.raises(ValueError): + sfg_simple_filter.remove_operation("add1")