diff --git a/util/MPSoCConfig.py b/MPSoCConfig.py similarity index 100% rename from util/MPSoCConfig.py rename to MPSoCConfig.py diff --git a/Roteador.py b/Roteador.py deleted file mode 100644 index dc0716e..0000000 --- a/Roteador.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'RoteadorjxHyuV.ui' -## -## Created by: Qt User Interface Compiler version 6.11.2 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, - QMetaObject, QObject, QPoint, QRect, - QSize, QTime, QUrl, Qt) -from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, - QFont, QFontDatabase, QGradient, QIcon, - QImage, QKeySequence, QLinearGradient, QPainter, - QPalette, QPixmap, QRadialGradient, QTransform) -from PySide6.QtWidgets import (QApplication, QLabel, QSizePolicy, QWidget, QMainWindow) - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(170, 170) - Form.setMinimumSize(QSize(170, 170)) - Form.setMaximumSize(QSize(170, 170)) - self.router = QWidget(Form) - self.router.setObjectName(u"router") - self.router.setGeometry(QRect(30, 30, 130, 130)) - self.routerLabel = QLabel(self.router) - self.routerLabel.setObjectName(u"routerLabel") - self.routerLabel.setGeometry(QRect(40, 50, 50, 30)) - font = QFont() - font.setFamilies([u"Arial"]) - font.setPointSize(14) - font.setBold(True) - self.routerLabel.setFont(font) - self.routerLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.north_HIGH_Label = QLabel(self.router) - self.north_HIGH_Label.setObjectName(u"north_HIGH_Label") - self.north_HIGH_Label.setGeometry(QRect(90, 0, 40, 20)) - font1 = QFont() - font1.setFamilies([u"Andale Mono"]) - font1.setPointSize(10) - self.north_HIGH_Label.setFont(font1) - self.south_HIGH_Label = QLabel(self.router) - self.south_HIGH_Label.setObjectName(u"south_HIGH_Label") - self.south_HIGH_Label.setGeometry(QRect(82, 110, 40, 20)) - self.south_HIGH_Label.setFont(font1) - self.south_LOW_Label = QLabel(self.router) - self.south_LOW_Label.setObjectName(u"south_LOW_Label") - self.south_LOW_Label.setGeometry(QRect(20, 110, 40, 20)) - self.south_LOW_Label.setFont(font1) - self.weast_LOW_Label = QLabel(self.router) - self.weast_LOW_Label.setObjectName(u"weast_LOW_Label") - self.weast_LOW_Label.setGeometry(QRect(1, 70, 40, 20)) - self.weast_LOW_Label.setFont(font1) - self.weast_HIGH_Label = QLabel(self.router) - self.weast_HIGH_Label.setObjectName(u"weast_HIGH_Label") - self.weast_HIGH_Label.setGeometry(QRect(1, 20, 40, 20)) - self.weast_HIGH_Label.setFont(font1) - self.north_LOW_Label = QLabel(self.router) - self.north_LOW_Label.setObjectName(u"north_LOW_Label") - self.north_LOW_Label.setGeometry(QRect(40, 0, 40, 20)) - self.north_LOW_Label.setFont(font1) - self.east_HIGH_Label = QLabel(self.router) - self.east_HIGH_Label.setObjectName(u"east_HIGH_Label") - self.east_HIGH_Label.setGeometry(QRect(90, 40, 40, 20)) - self.east_HIGH_Label.setFont(font1) - self.east_LOW_Label = QLabel(self.router) - self.east_LOW_Label.setObjectName(u"east_LOW_Label") - self.east_LOW_Label.setGeometry(QRect(90, 90, 40, 20)) - self.east_LOW_Label.setFont(font1) - self.out_north_HIGH = QWidget(Form) - self.out_north_HIGH.setObjectName(u"out_north_HIGH") - self.out_north_HIGH.setGeometry(QRect(115, 0, 6, 30)) - self.in_north_HIGH = QWidget(Form) - self.in_north_HIGH.setObjectName(u"in_north_HIGH") - self.in_north_HIGH.setGeometry(QRect(124, 0, 20, 30)) - self.out_weast_HIGH = QWidget(Form) - self.out_weast_HIGH.setObjectName(u"out_weast_HIGH") - self.out_weast_HIGH.setGeometry(QRect(0, 77, 30, 6)) - self.in_weast_HIGH = QWidget(Form) - self.in_weast_HIGH.setObjectName(u"in_weast_HIGH") - self.in_weast_HIGH.setGeometry(QRect(0, 53, 30, 20)) - self.in_east_LOW = QWidget(Form) - self.in_east_LOW.setObjectName(u"in_east_LOW") - self.in_east_LOW.setGeometry(QRect(160, 120, 33, 20)) - self.out_east_LOW = QWidget(Form) - self.out_east_LOW.setObjectName(u"out_east_LOW") - self.out_east_LOW.setGeometry(QRect(160, 110, 33, 6)) - self.in_south_LOW = QWidget(Form) - self.in_south_LOW.setObjectName(u"in_south_LOW") - self.in_south_LOW.setGeometry(QRect(50, 160, 20, 30)) - self.out_south_LOW = QWidget(Form) - self.out_south_LOW.setObjectName(u"out_south_LOW") - self.out_south_LOW.setGeometry(QRect(72, 160, 6, 30)) - self.in_south_HIGH = QWidget(Form) - self.in_south_HIGH.setObjectName(u"in_south_HIGH") - self.in_south_HIGH.setGeometry(QRect(108, 160, 20, 30)) - self.out_south_HIGH = QWidget(Form) - self.out_south_HIGH.setObjectName(u"out_south_HIGH") - self.out_south_HIGH.setGeometry(QRect(131, 160, 6, 30)) - self.out_weast_LOW = QWidget(Form) - self.out_weast_LOW.setObjectName(u"out_weast_LOW") - self.out_weast_LOW.setGeometry(QRect(0, 127, 30, 6)) - self.in_weast_LOW = QWidget(Form) - self.in_weast_LOW.setObjectName(u"in_weast_LOW") - self.in_weast_LOW.setGeometry(QRect(0, 103, 30, 20)) - self.in_east_HIGH = QWidget(Form) - self.in_east_HIGH.setObjectName(u"in_east_HIGH") - self.in_east_HIGH.setGeometry(QRect(160, 70, 20, 20)) - self.out_east_HIGH = QWidget(Form) - self.out_east_HIGH.setObjectName(u"out_east_HIGH") - self.out_east_HIGH.setGeometry(QRect(160, 60, 33, 6)) - self.out_north_LOW = QWidget(Form) - self.out_north_LOW.setObjectName(u"out_north_LOW") - self.out_north_LOW.setGeometry(QRect(57, 0, 6, 30)) - self.in_north_LOW = QWidget(Form) - self.in_north_LOW.setObjectName(u"in_north_LOW") - self.in_north_LOW.setGeometry(QRect(65, 0, 20, 30)) - self.local_out = QWidget(Form) - self.local_out.setObjectName(u"local_out") - self.local_out.setGeometry(QRect(0, 20, 20, 20)) - self.local_Label = QLabel(Form) - self.local_Label.setObjectName(u"local_Label") - self.local_Label.setGeometry(QRect(20, 18, 40, 20)) - self.local_Label.setFont(font1) - self.local_in = QWidget(Form) - self.local_in.setObjectName(u"local_in") - self.local_in.setGeometry(QRect(0, 0, 20, 20)) - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setStyleSheet(QCoreApplication.translate("Form", u"background-color: rgb(255, 255, 255);", None)) - self.routerLabel.setText("") - self.north_HIGH_Label.setText(QCoreApplication.translate("Form", u"00.00%", None)) - self.south_HIGH_Label.setText(QCoreApplication.translate("Form", u"00.00%", None)) - self.south_LOW_Label.setText(QCoreApplication.translate("Form", u"00.00%", None)) - self.weast_LOW_Label.setText(QCoreApplication.translate("Form", u"00.00%", None)) - self.weast_HIGH_Label.setText(QCoreApplication.translate("Form", u"00.00%", None)) - self.north_LOW_Label.setText(QCoreApplication.translate("Form", u"00.00%", None)) - self.east_HIGH_Label.setText(QCoreApplication.translate("Form", u"00.00%", None)) - self.east_LOW_Label.setText(QCoreApplication.translate("Form", u"00.00%", None)) - self.local_Label.setText(QCoreApplication.translate("Form", u"00.00%", None)) - # retranslateUi - diff --git a/analysis.py b/analysis.py index 69da3d9..82020bc 100644 --- a/analysis.py +++ b/analysis.py @@ -15,7 +15,7 @@ from collections import Counter, defaultdict from dataclasses import dataclass, field -from util.MPSoCConfig import MPSoCConfig +from MPSoCConfig import MPSoCConfig # ID usado para "tarefa" do kernel/injetor nos campos de tarefa do traffic_router.txt KERNEL_TASK_ID = 0xFFFF @@ -345,8 +345,7 @@ def pending_count(self): def read_all_packets(mpsoc_config): """Todos os pacotes do traffic_router.txt, com um leitor próprio (não mexe na simulação).""" - from information.read_traffic_data import ReadTrafficData - from information.router_neighbors import RouterNeighbors + from information import ReadTrafficData, RouterNeighbors reader = ReadTrafficData(mpsoc_config, RouterNeighbors.from_config(mpsoc_config)) packets = [] diff --git a/help_dialogs.py b/dialogs.py similarity index 50% rename from help_dialogs.py rename to dialogs.py index c038269..fe8f544 100644 --- a/help_dialogs.py +++ b/dialogs.py @@ -1,17 +1,26 @@ """ -Janelas do menu Help: "About" (util/AboutFrame.java) e "Packet Format" -(MainFrame.packetFormatMenuItemActionPerformed). +Diálogos dos menus Edit e Help. + +- PlatformSetupDialog (Edit -> Platform Setup): refatoração de + PlatformSetupFrame.java do GraphicalDebugger original; permite trocar o + tamanho do flit, o período de clock e a largura da janela de checkpoint + (intervalo em que a % de uso dos enlaces é recalculada) sem editar o + platform.cfg. As mudanças valem até o próximo "Reset Simulation" ou + "New Debugging", que releem os arquivos. +- AboutDialog (util/AboutFrame.java) e show_packet_format + (MainFrame.packetFormatMenuItemActionPerformed), do menu Help. """ import os -from PySide6.QtCore import Qt -from PySide6.QtGui import QFont, QPixmap -from PySide6.QtWidgets import QDialog, QDialogButtonBox, QLabel, QMessageBox, QVBoxLayout +from PySide6.QtCore import QLocale, Qt +from PySide6.QtGui import QDoubleValidator, QFont, QIntValidator, QPixmap +from PySide6.QtWidgets import (QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QMessageBox, + QVBoxLayout) ICON_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icon") -# Formato de cada linha do traffic_router.txt, como lido em information/read_traffic_data.py +# Formato de cada linha do traffic_router.txt, como lido em information.py (ReadTrafficData) PACKET_FORMAT = """\

Each line of traffic_router.txt is a packet header captured by a router, with tab-separated fields:

Time | Current Router Address | Service | Payload size | Packet bandwidth | Input port | @@ -82,3 +91,63 @@ def __init__(self, parent=None): layout.addWidget(label) layout.addWidget(buttons) layout.setSizeConstraint(QVBoxLayout.SizeConstraint.SetFixedSize) + + +class PlatformSetupDialog(QDialog): + def __init__(self, mpsoc_config, checkpoint, parent=None): + super().__init__(parent) + self.mpsoc_config = mpsoc_config + self.checkpoint = checkpoint + self.setWindowTitle("Platform Setup") + + self.flit_size_edit = QLineEdit(str(mpsoc_config.flit_size)) + self.flit_size_edit.setValidator(QIntValidator(1, 1024, self)) + + self.clock_edit = QLineEdit(str(mpsoc_config.clock_period_in_ns)) + self.clock_edit.setValidator(QIntValidator(1, 1_000_000, self)) + + # Largura da janela em us, como no Java (guardada em ms no controlador) + self.window_width_edit = QLineEdit(f"{checkpoint.window_size_ms * 1000:g}") + window_validator = QDoubleValidator(0.001, 1e9, 3, self) + window_validator.setNotation(QDoubleValidator.Notation.StandardNotation) + window_validator.setLocale(QLocale.c()) # aceita ponto decimal em qualquer idioma + self.window_width_edit.setValidator(window_validator) + + form = QGridLayout() + rows = (("Flit Size", self.flit_size_edit, "bits"), + ("Clock Period", self.clock_edit, "ns"), + ("Window Width", self.window_width_edit, "us")) + for row, (caption, edit, unit) in enumerate(rows): + form.addWidget(QLabel(caption), row, 0) + form.addWidget(edit, row, 1) + form.addWidget(QLabel(unit), row, 2) + + buttons = QDialogButtonBox() + buttons.addButton("Confirm", QDialogButtonBox.ButtonRole.AcceptRole) + buttons.addButton("Cancel", QDialogButtonBox.ButtonRole.RejectRole) + buttons.accepted.connect(self.accept) + buttons.rejected.connect(self.reject) + + layout = QVBoxLayout(self) + layout.addLayout(form) + layout.addWidget(buttons) + layout.setSizeConstraint(QVBoxLayout.SizeConstraint.SetFixedSize) # não redimensionável, como no Java + + def accept(self): + edits = (self.flit_size_edit, self.clock_edit, self.window_width_edit) + invalid = next((edit for edit in edits if not edit.hasAcceptableInput()), None) + if invalid is not None: + QMessageBox.warning(self, "Platform Setup", "Enter only valid positive numbers.") + invalid.setFocus() + invalid.selectAll() + return + + clock_period = int(self.clock_edit.text()) + self.mpsoc_config.flit_size = int(self.flit_size_edit.text()) + self.mpsoc_config.clock_period_in_ns = clock_period + # O Java esquecia de repassar o clock ao CheckpointController + self.checkpoint.clock_period_ns = clock_period + self.checkpoint.window_size_ms = float(self.window_width_edit.text()) / 1000.0 + + QMessageBox.information(self, "Platform Setup", "Settings applied") + super().accept() diff --git a/documentation/implementacoes.md b/documentation/implementacoes.md index cb83114..b23e390 100644 --- a/documentation/implementacoes.md +++ b/documentation/implementacoes.md @@ -7,9 +7,9 @@ Este documento descreve as funcionalidades portadas do [GraphicalDebugger origin 1. [Visão geral](#1-visão-geral) 2. [Estrutura de arquivos](#2-estrutura-de-arquivos) 3. [Abrindo um debug](#3-abrindo-um-debug) -4. [Configuração da plataforma — `util/MPSoCConfig.py`](#4-configuração-da-plataforma--utilmpsocconfigpy) +4. [Configuração da plataforma — `MPSoCConfig.py`](#4-configuração-da-plataforma--mpsocconfigpy) 5. [Task List e Services List](#5-task-list-e-services-list) -6. [Modelo de tráfego — pacote `information/`](#6-modelo-de-tráfego--pacote-information) +6. [Modelo de tráfego — `information.py`](#6-modelo-de-tráfego--informationpy) 7. [Matriz de roteadores — `router_matrix.py`](#7-matriz-de-roteadores--router_matrixpy) 8. [Simulação — `simulation.py`](#8-simulação--simulationpy) 9. [Interface da simulação — `main.py`](#9-interface-da-simulação--mainpy) @@ -43,12 +43,11 @@ diretório debug/ | `main.py` | Janela principal: menus, botões, tabelas, ligação com a simulação | `MainFrame.java` | | `simulation.py` | Controle da simulação e janelas de checkpoint | `MainFrame.java` (parte de simulação), `CheckpointController.java` | | `overview_windows.py` | Janelas Communication Overview, Task Mapping Overview e Message Log, atualizadas durante a simulação | `CommunicationOverview.java`, `TaskMappingFrame.java`, `MainFrame.showMessageList` | -| `slave_matrix.py` / `pe_matrix.py` | Células das grades das duas janelas acima | — | +| `overview_matrices.py` | Células das grades das duas janelas acima (`SlaveMatrixWidget`, `PEMatrixWidget`) | — | | `router_info_window.py` | Janela "Router Information" (clique num roteador): abas Log, Applications, Scheduling e Traffic, janela Task Information e gráfico de escalonamento | `RouterInfoFrame.java`, `PETextLog.java`, `SchedulingTab.java`, `scheduling/SchedulingPanel.java`, `TaskInfoFrame.java` | -| `platform_setup.py` | Janela Platform Setup (menu Edit): flit size, período de clock e largura da janela de checkpoint | `PlatformSetupFrame.java` | | `filter_window.py` | Janela Service and PE Filter (menu Filters, Ctrl+F): escolhe quais pacotes a simulação processa, por serviço (Only/Except) e por PE | `FilterForm.java` | | `projects.py` | Projetos salvos (`.hdf`): File → Save / Open / Delete Project | `MainFrame.java` (save/open/deleteMenuItem) | -| `help_dialogs.py` | Help → About e Help → Packet Format | `util/AboutFrame.java`, `MainFrame.packetFormatMenuItemActionPerformed` | +| `dialogs.py` | Janela Platform Setup (menu Edit: flit size, período de clock e largura da janela de checkpoint), Help → About e Help → Packet Format | `PlatformSetupFrame.java`, `util/AboutFrame.java`, `MainFrame.packetFormatMenuItemActionPerformed` | | `analysis.py` | Análises sem interface: aplicação de cada pacote, latência (casamento injeção → entrega), vazão em Mbps e linha do tempo da admissão de aplicações | — (métricas do artigo do Memphis, DAES 2019) | | `statistics_windows.py` | Janelas Traffic Statistics (Links, Applications, Messages, Routing, Blocked) e Application Timeline (menu Tools) | — | | `path_view.py` | Liga as janelas de análise à malha principal: duplo clique num pacote pinta o caminho dele e destaca o roteador | — | @@ -58,15 +57,9 @@ diretório debug/ | `deloream.py` | Deloream: leitor das mensagens `$$$` das tarefas nos logs dos processadores | `deloream/DeloreamMainFrame.java`, `deloream/TaskMessage.java` | | `theme.py` | Paletas dos temas claro/escuro, cores da grade e preferência salva | — | | `router_matrix.py` | Desenho da grade e de cada roteador (imagens, setas, %) | `Roteador.java`, `UJPanelImagem.java`, `MainFrame.createNoCPanel` | -| `util/MPSoCConfig.py` | Leitura de `platform.cfg` e `services.cfg`, constantes e endereçamento | `util/MPSoCConfig.java` | -| `information/packet_information.py` | Um pacote do `traffic_router.txt` | `PacketInformation.java` | -| `information/task_information.py` | Um evento de tarefa em um PE | `TaskInformation.java` | -| `information/port_information.py` | Volume/banda acumulados por porta | `PortInformation.java` | -| `information/router_information.py` | Pacotes, portas e tarefas de um roteador | `RouterInformation.java` | -| `information/router_neighbors.py` | Vizinhos e conversão XY ↔ hamiltoniano | `RouterNeighbors.java` | -| `information/read_traffic_data.py` | Leitura sob demanda do `traffic_router.txt` | `ReadTrafficData.java` | -| `information/mpsoc_information.py` | Estado de todos os roteadores do MPSoC | `MPSoCInformation.java` | -| `Roteador.py` / `ui_mainwindow.py` | Interfaces geradas a partir dos `.ui` (não editar à mão) | — | +| `MPSoCConfig.py` | Leitura de `platform.cfg` e `services.cfg`, constantes e endereçamento | `util/MPSoCConfig.java` | +| `information.py` | Modelo de tráfego: `PacketInformation` (um pacote), `TaskInformation` (evento de tarefa), `PortInformation` (volume/banda por porta), `RouterInformation` (pacotes, portas e tarefas de um roteador), `RouterNeighbors` (vizinhos e conversão XY ↔ hamiltoniano), `ReadTrafficData` (leitura sob demanda do `traffic_router.txt`) e `MPSoCInformation` (estado de todos os roteadores) | `PacketInformation.java`, `TaskInformation.java`, `PortInformation.java`, `RouterInformation.java`, `RouterNeighbors.java`, `ReadTrafficData.java`, `MPSoCInformation.java` | +| `ui_mainwindow.py` / `commsUi.py` / `taskmap.py` | Interfaces geradas a partir dos `.ui` (não editar à mão) | — | --- @@ -90,7 +83,7 @@ diretório debug/ --- -## 4. Configuração da plataforma — `util/MPSoCConfig.py` +## 4. Configuração da plataforma — `MPSoCConfig.py` ### 4.1 Leitura do `platform.cfg` @@ -172,7 +165,7 @@ As linhas `$TASK_…` do `services.cfg` não aparecem na Services List (não sã --- -## 6. Modelo de tráfego — pacote `information/` +## 6. Modelo de tráfego — `information.py` ### 6.1 Formato do `traffic_router.txt` @@ -240,7 +233,7 @@ Com `router_addressing XY`, os endereços das colunas 1 e 6 são convertidos par ### 7.2 `RouterWidget` -Reaproveita o layout do `Roteador.py` (gerado de `Roteador.ui`) e acrescenta: +Reproduz o layout do `Roteador.ui` e acrescenta: | Recurso | Descrição | |---|---| diff --git a/filter_window.py b/filter_window.py index 787a868..b477113 100644 --- a/filter_window.py +++ b/filter_window.py @@ -19,7 +19,7 @@ QMessageBox, QPushButton, QRadioButton, QTabWidget, QVBoxLayout, QWidget) import analysis -from util.MPSoCConfig import MPSoCConfig +from MPSoCConfig import MPSoCConfig # Serviço sempre descartado pelo filtro do Java IGNORED_SERVICE = "SLACK_TIME_REPORT" diff --git a/information.py b/information.py new file mode 100644 index 0000000..cbbcd4e --- /dev/null +++ b/information.py @@ -0,0 +1,443 @@ +""" +Modelo de dados do tráfego da NoC lido de traffic_router.txt. + +Refatoração em Python das classes do pacote "source" do GraphicalDebugger +original em Java (MPSoCInformation, RouterInformation, PacketInformation, +TaskInformation, PortInformation, RouterNeighbors e ReadTrafficData). +""" + +import os +from bisect import bisect_right +from collections import defaultdict +from dataclasses import dataclass, field + +from MPSoCConfig import MPSoCConfig + + +class RouterNeighbors: + """ + Calcula os vizinhos de um roteador a partir do seu endereço hamiltoniano. + Os métodos retornam -1 quando o vizinho não existe (borda da malha). + """ + + def __init__(self, x_dimension, y_dimension): + self.x_dimension = x_dimension + self.y_dimension = y_dimension + + @classmethod + def from_config(cls, mpsoc_config): + return cls(mpsoc_config.mpsoc_x, mpsoc_config.mpsoc_y) + + def xy_to_ham_addr(self, xy_addr): + xy_addr &= 0xFFFF # limpa o header + y = xy_addr & 0xFF + x = xy_addr >> 8 + + if y % 2 == 1: + return (y * self.x_dimension) + (self.x_dimension - x) - 1 + return (y * self.x_dimension) + x + + def ham_to_xy_addr(self, ham_addr): + ham_addr &= 0xFF # limpa o header + y, addr = divmod(ham_addr, self.x_dimension) + + x = self.x_dimension - addr - 1 if y % 2 == 1 else addr + return (x << 8) | y + + def xy_address_to_xy_label(self, ham_addr): + xy_addr = self.ham_to_xy_addr(ham_addr) + return f"{xy_addr >> 8}{xy_addr & 0xFF}" + + def get_x_coordinate(self, ham_addr): + return self.ham_to_xy_addr(ham_addr) >> 8 + + def get_y_coordinate(self, ham_addr): + return self.ham_to_xy_addr(ham_addr) & 0xFF + + def _neighbor(self, ham_addr, dx, dy): + x = self.get_x_coordinate(ham_addr) + dx + y = self.get_y_coordinate(ham_addr) + dy + + if 0 <= x < self.x_dimension and 0 <= y < self.y_dimension: + return self.xy_to_ham_addr((x << 8) | y) + return -1 + + def get_vizinho_cima(self, ham_addr): + return self._neighbor(ham_addr, 0, 1) + + def get_vizinho_baixo(self, ham_addr): + return self._neighbor(ham_addr, 0, -1) + + def get_vizinho_esquerda(self, ham_addr): + return self._neighbor(ham_addr, -1, 0) + + def get_vizinho_direita(self, ham_addr): + return self._neighbor(ham_addr, 1, 0) + + +@dataclass +class PacketInformation: + """Um pacote registrado em traffic_router.txt ao passar por um roteador.""" + + router_address: int + time: int + service: int + size: int + bandwidth_cycles: int + input_port: int + target_router: int + task_source: int = -1 + task_target: int = -1 + channel: int = field(init=False) + + def __post_init__(self): + # O canal é definido pela porta de entrada (portas pares = HIGH) + self.channel = MPSoCConfig.get_channel(self.input_port) + + def get_input_port_string(self): + return MPSoCConfig.get_port_string(self.input_port) + + def get_channel_string(self): + return "HIGH" if self.channel == MPSoCConfig.HIGH else "LOW" + + def print_packet(self): + print("*********PACKET************") + print(f"router_address: {self.router_address}") + print(f"time: {self.time}") + print(f"service: {self.service}") + print(f"size: {self.size}") + print(f"bandwidth cycles: {self.bandwidth_cycles}") + print(f"channel: {self.get_channel_string()}") + print(f"input_port: {self.get_input_port_string()}") + print(f"target_router: {self.target_router}") + print(f"task_source: {self.task_source}") + print(f"task_target: {self.task_target}") + + +@dataclass +class TaskInformation: + """Um evento de tarefa (alocação, término, mensagem) ocorrido em um PE.""" + + id: int + service: str + time: int + remote_task_id: int = -1 + + @property + def task_id(self): + # Os 8 bits menos significativos identificam a tarefa dentro da aplicação + return self.id & 0xFF + + @property + def app_id(self): + return self.id >> 8 + + +class PortInformation: + """Volume e largura de banda acumulados em uma porta de um roteador.""" + + def __init__(self, mpsoc_config, channel): + self.channel = channel + self.total_volume = 0 + self.total_bandwidth = 0 + self.throughput = 0 + self.bandwidth_throughput = 0 + + # Serviços desconhecidos começam em zero em vez de gerar erro + self.service_volume = defaultdict(int, dict.fromkeys(mpsoc_config.service_reference, 0)) + self.service_bandwidth = defaultdict(int, dict.fromkeys(mpsoc_config.service_reference, 0)) + + def add_new_information(self, packet): + volume = packet.size + 1 # +1 por causa do header + + self.total_volume += volume + self.throughput += volume + + self.total_bandwidth += packet.bandwidth_cycles + self.bandwidth_throughput += packet.bandwidth_cycles + + self.service_volume[packet.service] += volume + self.service_bandwidth[packet.service] += packet.bandwidth_cycles + + def get_service_volume_in_flits(self, service): + return self.service_volume.get(service, 0) + + def get_service_bandwidth_in_cycles(self, service): + return self.service_bandwidth.get(service, 0) + + def reset_port_throughput(self): + self.throughput = 0 + + def reset_port_bandwidth_throughput(self): + self.bandwidth_throughput = 0 + + +class RouterInformation: + """Pacotes, portas e eventos de tarefa acumulados em um roteador (PE).""" + + def __init__(self, mpsoc_config, router_address): + self.router_address = router_address + self.mpsoc_config = mpsoc_config + self.task_counter = 0 + self.packets = [] + self.tasks = [] + self.port_informations = [ + PortInformation(mpsoc_config, MPSoCConfig.get_channel(port)) + for port in range(MPSoCConfig.NPORT) + ] + + def add_packet(self, packet): + self.packets.append(packet) + self.port_informations[packet.input_port].add_new_information(packet) + self._update_task_information(packet) + + def _update_task_information(self, packet): + config = self.mpsoc_config + + # Pacote está entrando no PE + if packet.target_router == self.router_address: + if packet.service in config.task_allocation_services: + self.task_counter += 1 + self.tasks.append(TaskInformation(packet.task_source, "ALLOCATED", packet.time)) + elif packet.service == config.get_service_value("MESSAGE_DELIVERY"): + self.tasks.append(TaskInformation(packet.task_target, "MESSAGE_DELIVERY", + packet.time, packet.task_source)) + + # Pacote está saindo do PE + elif packet.input_port in (MPSoCConfig.LOCAL0, MPSoCConfig.LOCAL1): + if packet.service in config.task_terminated_services: + # No Memphis-V a tarefa terminada vem no último campo (task_source = -1); + # no formato do HeMPS, usado pelo Java, vinha no penúltimo + task_id = packet.task_source if packet.task_source >= 0 else packet.task_target + self.tasks.append(TaskInformation(task_id, "TERMINATED", packet.time)) + elif packet.service == config.get_service_value("MESSAGE_REQUEST"): + self.tasks.append(TaskInformation(packet.task_target, "MESSAGE_REQUEST", + packet.time, packet.task_source)) + + def get_tasks_information(self): + return self.tasks + + def get_task_number(self): + return self.task_counter + + # ========================================== + # TOTAIS DO ROTEADOR (SOMA DE TODAS AS PORTAS) + # ========================================== + def get_router_total_throughput_in_flits(self): + return sum(port.throughput for port in self.port_informations) + + def get_router_total_bandwidth_throughput_in_cycles(self): + return sum(port.bandwidth_throughput for port in self.port_informations) + + def get_router_total_volume_in_flits(self): + return sum(port.total_volume for port in self.port_informations) + + def get_router_total_bandwidth_in_cycles(self): + return sum(port.total_bandwidth for port in self.port_informations) + + def get_router_total_services_volume_in_flits(self, services): + return sum(self.get_port_volume_in_flits(port, services) for port in range(MPSoCConfig.NPORT)) + + def get_router_total_services_bandwidth_in_cycles(self, services): + return sum(self.get_port_bandwidth_in_cycles(port, services) for port in range(MPSoCConfig.NPORT)) + + # ========================================== + # INFORMAÇÕES POR PORTA + # ========================================== + def get_port_total_volume_in_flits(self, port): + return self.port_informations[port].total_volume + + def get_port_total_bandwidth_in_cycles(self, port): + return self.port_informations[port].total_bandwidth + + def get_port_volume_in_flits(self, port, services): + port_info = self.port_informations[port] + return sum(port_info.get_service_volume_in_flits(service) for service in services) + + def get_port_bandwidth_in_cycles(self, port, services): + port_info = self.port_informations[port] + return sum(port_info.get_service_bandwidth_in_cycles(service) for service in services) + + def get_port_throughput_in_flits(self, port): + return self.port_informations[port].throughput + + def get_port_bandwidth_throughput_in_cycles(self, port): + return self.port_informations[port].bandwidth_throughput + + def reset_throughput(self): + for port in self.port_informations: + port.reset_port_throughput() + + def reset_bandwidth_throughput(self): + for port in self.port_informations: + port.reset_port_bandwidth_throughput() + + +class ReadTrafficData: + """ + Lê os pacotes de traffic_router.txt sob demanda. + + Os pacotes já lidos ficam em all_packets, assim é possível voltar no tempo + (reset_packet_counter) sem precisar reler o arquivo. + """ + + def __init__(self, mpsoc_config, neighbors): + self.mpsoc_config = mpsoc_config + self.neighbors = neighbors + self.packet_read_control = 0 + self.all_packets = [] + # Arquivo ainda sendo gravado pelo simulador: uma linha sem "\n" no + # fim está incompleta e só é lida quando terminar de ser gravada + self.follow = False + + traffic_path = os.path.join(mpsoc_config.debug_file_path, "traffic_router.txt") + # Gera FileNotFoundError caso o arquivo não exista + self.traffic = open(traffic_path, "r", encoding="utf-8") + + def close(self): + self.traffic.close() + + def reset_packet_counter(self): + self.packet_read_control = 0 + + def get_packet_counter_by_time(self, time): + """Retorna quantos pacotes já lidos ocorreram até o tempo informado (-1 se fora do intervalo).""" + if not self.all_packets or time < 0 or self.all_packets[-1].time < time: + return -1 + + return bisect_right(self.all_packets, time, key=lambda packet: packet.time) + + def get_next_packet(self): + # Ainda não lido: busca a próxima linha válida do arquivo + if self.packet_read_control == len(self.all_packets): + while True: + # tell() em arquivo texto é lento: só é preciso para desfazer uma linha incompleta + position = self.traffic.tell() if self.follow else None + line = self.traffic.readline() + if not line: + return None # fim do arquivo (por enquanto, no modo follow) + if self.follow and not line.endswith("\n"): + self.traffic.seek(position) + return None + packet = self._parse_packet(line) + if packet is not None: + self.all_packets.append(packet) + self.packet_read_control += 1 + return packet + + packet = self.all_packets[self.packet_read_control] + self.packet_read_control += 1 + return packet + + def _parse_packet(self, line): + fields = line.strip().split("\t") + if fields == [""]: + return None + + try: + time = int(fields[0]) + router_address = self._extract_router_address(fields[1]) + service = int(fields[2]) + size = int(fields[3]) + bandwidth_cycles = int(fields[4]) + + if self.mpsoc_config.channel_number == 1: + # Suporte a canal físico duplicado + input_port = int(fields[5]) * 2 + 1 + else: + input_port = int(fields[5]) + + target_router = self._extract_router_address(fields[6]) + task_source = int(fields[7]) if len(fields) > 7 else -1 + task_target = int(fields[8]) if len(fields) > 8 else -1 + except (ValueError, IndexError): + print(f"WARNING: Wrong packet format in traffic_router.txt: {line.strip()}") + return None + + return PacketInformation(router_address, time, service, size, bandwidth_cycles, + input_port, target_router, task_source, task_target) + + def _extract_router_address(self, value): + address = int(value) + if self.mpsoc_config.router_addressing == MPSoCConfig.XY: + return self.neighbors.xy_to_ham_addr(address) + return address + + +class MPSoCInformation: + """Estado de todos os roteadores do MPSoC, alimentado pelos pacotes de traffic_router.txt.""" + + def __init__(self, mpsoc_config): + self.mpsoc_config = mpsoc_config + self.initialize_pe_information() + self.neighbors = RouterNeighbors.from_config(mpsoc_config) + self.read_traffic = ReadTrafficData(mpsoc_config, self.neighbors) + + def initialize_pe_information(self): + self.pe_information = { + address: RouterInformation(self.mpsoc_config, address) + for address in range(self.mpsoc_config.get_pe_number()) + } + + def close(self): + self.read_traffic.close() + + def get_packet_counter_by_time(self, time): + """Retorna o número de pacotes até chegar no tempo desejado.""" + counter = self.read_traffic.get_packet_counter_by_time(time) + + if counter != -1: + self.read_traffic.reset_packet_counter() + + return counter + + def get_router_information(self, router_address): + return self.pe_information.get(router_address) + + def get_router_information_xy(self, x, y): + return self.get_router_information(self.neighbors.xy_to_ham_addr((x << 8) | y)) + + # ========================================== + # TOTAIS DA NOC + # ========================================== + def get_total_noc_volume(self): + return sum(router.get_router_total_volume_in_flits() for router in self.pe_information.values()) + + def get_total_noc_bandwidth(self): + return sum(router.get_router_total_bandwidth_in_cycles() for router in self.pe_information.values()) + + def get_total_noc_service_volume(self, services): + return sum(router.get_router_total_services_volume_in_flits(services) + for router in self.pe_information.values()) + + def get_total_noc_service_bandwidth(self, services): + return sum(router.get_router_total_services_bandwidth_in_cycles(services) + for router in self.pe_information.values()) + + def get_next_packet(self, packet_filter=None, limit_time=0): + """ + Lê o próximo pacote e o registra no roteador em que foi capturado. + + packet_filter é uma função (packet -> bool); pacotes rejeitados são + pulados enquanto estiverem antes de limit_time (0 = sem limite). + Retorna None quando não há mais pacotes. + """ + while True: + packet = self.read_traffic.get_next_packet() + + if packet is None: + return None + + # Serviços desconhecidos são repassados sem atualizar os roteadores + if packet.service not in self.mpsoc_config.services_hash: + return packet + + if packet_filter is not None and not packet_filter(packet): + if limit_time == 0 or limit_time > packet.time: + continue + + router = self.pe_information.get(packet.router_address) + if router is not None: + router.add_packet(packet) + + return packet diff --git a/information/__init__.py b/information/__init__.py deleted file mode 100644 index 6f5585f..0000000 --- a/information/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Modelo de dados do tráfego da NoC lido de traffic_router.txt. - -Refatoração em Python das classes do pacote "source" do GraphicalDebugger -original em Java (MPSoCInformation, RouterInformation, PacketInformation, -TaskInformation, PortInformation, RouterNeighbors e ReadTrafficData). -""" - -from information.packet_information import PacketInformation -from information.task_information import TaskInformation -from information.port_information import PortInformation -from information.router_neighbors import RouterNeighbors -from information.router_information import RouterInformation -from information.read_traffic_data import ReadTrafficData -from information.mpsoc_information import MPSoCInformation - -__all__ = [ - "PacketInformation", - "TaskInformation", - "PortInformation", - "RouterNeighbors", - "RouterInformation", - "ReadTrafficData", - "MPSoCInformation", -] diff --git a/information/mpsoc_information.py b/information/mpsoc_information.py deleted file mode 100644 index acb8b0e..0000000 --- a/information/mpsoc_information.py +++ /dev/null @@ -1,82 +0,0 @@ -from information.read_traffic_data import ReadTrafficData -from information.router_information import RouterInformation -from information.router_neighbors import RouterNeighbors - - -class MPSoCInformation: - """Estado de todos os roteadores do MPSoC, alimentado pelos pacotes de traffic_router.txt.""" - - def __init__(self, mpsoc_config): - self.mpsoc_config = mpsoc_config - self.initialize_pe_information() - self.neighbors = RouterNeighbors.from_config(mpsoc_config) - self.read_traffic = ReadTrafficData(mpsoc_config, self.neighbors) - - def initialize_pe_information(self): - self.pe_information = { - address: RouterInformation(self.mpsoc_config, address) - for address in range(self.mpsoc_config.get_pe_number()) - } - - def close(self): - self.read_traffic.close() - - def get_packet_counter_by_time(self, time): - """Retorna o número de pacotes até chegar no tempo desejado.""" - counter = self.read_traffic.get_packet_counter_by_time(time) - - if counter != -1: - self.read_traffic.reset_packet_counter() - - return counter - - def get_router_information(self, router_address): - return self.pe_information.get(router_address) - - def get_router_information_xy(self, x, y): - return self.get_router_information(self.neighbors.xy_to_ham_addr((x << 8) | y)) - - # ========================================== - # TOTAIS DA NOC - # ========================================== - def get_total_noc_volume(self): - return sum(router.get_router_total_volume_in_flits() for router in self.pe_information.values()) - - def get_total_noc_bandwidth(self): - return sum(router.get_router_total_bandwidth_in_cycles() for router in self.pe_information.values()) - - def get_total_noc_service_volume(self, services): - return sum(router.get_router_total_services_volume_in_flits(services) - for router in self.pe_information.values()) - - def get_total_noc_service_bandwidth(self, services): - return sum(router.get_router_total_services_bandwidth_in_cycles(services) - for router in self.pe_information.values()) - - def get_next_packet(self, packet_filter=None, limit_time=0): - """ - Lê o próximo pacote e o registra no roteador em que foi capturado. - - packet_filter é uma função (packet -> bool); pacotes rejeitados são - pulados enquanto estiverem antes de limit_time (0 = sem limite). - Retorna None quando não há mais pacotes. - """ - while True: - packet = self.read_traffic.get_next_packet() - - if packet is None: - return None - - # Serviços desconhecidos são repassados sem atualizar os roteadores - if packet.service not in self.mpsoc_config.services_hash: - return packet - - if packet_filter is not None and not packet_filter(packet): - if limit_time == 0 or limit_time > packet.time: - continue - - router = self.pe_information.get(packet.router_address) - if router is not None: - router.add_packet(packet) - - return packet diff --git a/information/packet_information.py b/information/packet_information.py deleted file mode 100644 index ff5ae6c..0000000 --- a/information/packet_information.py +++ /dev/null @@ -1,42 +0,0 @@ -from dataclasses import dataclass, field - -from util.MPSoCConfig import MPSoCConfig - - -@dataclass -class PacketInformation: - """Um pacote registrado em traffic_router.txt ao passar por um roteador.""" - - router_address: int - time: int - service: int - size: int - bandwidth_cycles: int - input_port: int - target_router: int - task_source: int = -1 - task_target: int = -1 - channel: int = field(init=False) - - def __post_init__(self): - # O canal é definido pela porta de entrada (portas pares = HIGH) - self.channel = MPSoCConfig.get_channel(self.input_port) - - def get_input_port_string(self): - return MPSoCConfig.get_port_string(self.input_port) - - def get_channel_string(self): - return "HIGH" if self.channel == MPSoCConfig.HIGH else "LOW" - - def print_packet(self): - print("*********PACKET************") - print(f"router_address: {self.router_address}") - print(f"time: {self.time}") - print(f"service: {self.service}") - print(f"size: {self.size}") - print(f"bandwidth cycles: {self.bandwidth_cycles}") - print(f"channel: {self.get_channel_string()}") - print(f"input_port: {self.get_input_port_string()}") - print(f"target_router: {self.target_router}") - print(f"task_source: {self.task_source}") - print(f"task_target: {self.task_target}") diff --git a/information/port_information.py b/information/port_information.py deleted file mode 100644 index a80465f..0000000 --- a/information/port_information.py +++ /dev/null @@ -1,40 +0,0 @@ -from collections import defaultdict - - -class PortInformation: - """Volume e largura de banda acumulados em uma porta de um roteador.""" - - def __init__(self, mpsoc_config, channel): - self.channel = channel - self.total_volume = 0 - self.total_bandwidth = 0 - self.throughput = 0 - self.bandwidth_throughput = 0 - - # Serviços desconhecidos começam em zero em vez de gerar erro - self.service_volume = defaultdict(int, dict.fromkeys(mpsoc_config.service_reference, 0)) - self.service_bandwidth = defaultdict(int, dict.fromkeys(mpsoc_config.service_reference, 0)) - - def add_new_information(self, packet): - volume = packet.size + 1 # +1 por causa do header - - self.total_volume += volume - self.throughput += volume - - self.total_bandwidth += packet.bandwidth_cycles - self.bandwidth_throughput += packet.bandwidth_cycles - - self.service_volume[packet.service] += volume - self.service_bandwidth[packet.service] += packet.bandwidth_cycles - - def get_service_volume_in_flits(self, service): - return self.service_volume.get(service, 0) - - def get_service_bandwidth_in_cycles(self, service): - return self.service_bandwidth.get(service, 0) - - def reset_port_throughput(self): - self.throughput = 0 - - def reset_port_bandwidth_throughput(self): - self.bandwidth_throughput = 0 diff --git a/information/read_traffic_data.py b/information/read_traffic_data.py deleted file mode 100644 index 00a8666..0000000 --- a/information/read_traffic_data.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -from bisect import bisect_right - -from util.MPSoCConfig import MPSoCConfig -from information.packet_information import PacketInformation - - -class ReadTrafficData: - """ - Lê os pacotes de traffic_router.txt sob demanda. - - Os pacotes já lidos ficam em all_packets, assim é possível voltar no tempo - (reset_packet_counter) sem precisar reler o arquivo. - """ - - def __init__(self, mpsoc_config, neighbors): - self.mpsoc_config = mpsoc_config - self.neighbors = neighbors - self.packet_read_control = 0 - self.all_packets = [] - # Arquivo ainda sendo gravado pelo simulador: uma linha sem "\n" no - # fim está incompleta e só é lida quando terminar de ser gravada - self.follow = False - - traffic_path = os.path.join(mpsoc_config.debug_file_path, "traffic_router.txt") - # Gera FileNotFoundError caso o arquivo não exista - self.traffic = open(traffic_path, "r", encoding="utf-8") - - def close(self): - self.traffic.close() - - def reset_packet_counter(self): - self.packet_read_control = 0 - - def get_packet_counter_by_time(self, time): - """Retorna quantos pacotes já lidos ocorreram até o tempo informado (-1 se fora do intervalo).""" - if not self.all_packets or time < 0 or self.all_packets[-1].time < time: - return -1 - - return bisect_right(self.all_packets, time, key=lambda packet: packet.time) - - def get_next_packet(self): - # Ainda não lido: busca a próxima linha válida do arquivo - if self.packet_read_control == len(self.all_packets): - while True: - # tell() em arquivo texto é lento: só é preciso para desfazer uma linha incompleta - position = self.traffic.tell() if self.follow else None - line = self.traffic.readline() - if not line: - return None # fim do arquivo (por enquanto, no modo follow) - if self.follow and not line.endswith("\n"): - self.traffic.seek(position) - return None - packet = self._parse_packet(line) - if packet is not None: - self.all_packets.append(packet) - self.packet_read_control += 1 - return packet - - packet = self.all_packets[self.packet_read_control] - self.packet_read_control += 1 - return packet - - def _parse_packet(self, line): - fields = line.strip().split("\t") - if fields == [""]: - return None - - try: - time = int(fields[0]) - router_address = self._extract_router_address(fields[1]) - service = int(fields[2]) - size = int(fields[3]) - bandwidth_cycles = int(fields[4]) - - if self.mpsoc_config.channel_number == 1: - # Suporte a canal físico duplicado - input_port = int(fields[5]) * 2 + 1 - else: - input_port = int(fields[5]) - - target_router = self._extract_router_address(fields[6]) - task_source = int(fields[7]) if len(fields) > 7 else -1 - task_target = int(fields[8]) if len(fields) > 8 else -1 - except (ValueError, IndexError): - print(f"WARNING: Wrong packet format in traffic_router.txt: {line.strip()}") - return None - - return PacketInformation(router_address, time, service, size, bandwidth_cycles, - input_port, target_router, task_source, task_target) - - def _extract_router_address(self, value): - address = int(value) - if self.mpsoc_config.router_addressing == MPSoCConfig.XY: - return self.neighbors.xy_to_ham_addr(address) - return address diff --git a/information/router_information.py b/information/router_information.py deleted file mode 100644 index 62f5675..0000000 --- a/information/router_information.py +++ /dev/null @@ -1,104 +0,0 @@ -from util.MPSoCConfig import MPSoCConfig -from information.port_information import PortInformation -from information.task_information import TaskInformation - - -class RouterInformation: - """Pacotes, portas e eventos de tarefa acumulados em um roteador (PE).""" - - def __init__(self, mpsoc_config, router_address): - self.router_address = router_address - self.mpsoc_config = mpsoc_config - self.task_counter = 0 - self.packets = [] - self.tasks = [] - self.port_informations = [ - PortInformation(mpsoc_config, MPSoCConfig.get_channel(port)) - for port in range(MPSoCConfig.NPORT) - ] - - def add_packet(self, packet): - self.packets.append(packet) - self.port_informations[packet.input_port].add_new_information(packet) - self._update_task_information(packet) - - def _update_task_information(self, packet): - config = self.mpsoc_config - - # Pacote está entrando no PE - if packet.target_router == self.router_address: - if packet.service in config.task_allocation_services: - self.task_counter += 1 - self.tasks.append(TaskInformation(packet.task_source, "ALLOCATED", packet.time)) - elif packet.service == config.get_service_value("MESSAGE_DELIVERY"): - self.tasks.append(TaskInformation(packet.task_target, "MESSAGE_DELIVERY", - packet.time, packet.task_source)) - - # Pacote está saindo do PE - elif packet.input_port in (MPSoCConfig.LOCAL0, MPSoCConfig.LOCAL1): - if packet.service in config.task_terminated_services: - # No Memphis-V a tarefa terminada vem no último campo (task_source = -1); - # no formato do HeMPS, usado pelo Java, vinha no penúltimo - task_id = packet.task_source if packet.task_source >= 0 else packet.task_target - self.tasks.append(TaskInformation(task_id, "TERMINATED", packet.time)) - elif packet.service == config.get_service_value("MESSAGE_REQUEST"): - self.tasks.append(TaskInformation(packet.task_target, "MESSAGE_REQUEST", - packet.time, packet.task_source)) - - def get_tasks_information(self): - return self.tasks - - def get_task_number(self): - return self.task_counter - - # ========================================== - # TOTAIS DO ROTEADOR (SOMA DE TODAS AS PORTAS) - # ========================================== - def get_router_total_throughput_in_flits(self): - return sum(port.throughput for port in self.port_informations) - - def get_router_total_bandwidth_throughput_in_cycles(self): - return sum(port.bandwidth_throughput for port in self.port_informations) - - def get_router_total_volume_in_flits(self): - return sum(port.total_volume for port in self.port_informations) - - def get_router_total_bandwidth_in_cycles(self): - return sum(port.total_bandwidth for port in self.port_informations) - - def get_router_total_services_volume_in_flits(self, services): - return sum(self.get_port_volume_in_flits(port, services) for port in range(MPSoCConfig.NPORT)) - - def get_router_total_services_bandwidth_in_cycles(self, services): - return sum(self.get_port_bandwidth_in_cycles(port, services) for port in range(MPSoCConfig.NPORT)) - - # ========================================== - # INFORMAÇÕES POR PORTA - # ========================================== - def get_port_total_volume_in_flits(self, port): - return self.port_informations[port].total_volume - - def get_port_total_bandwidth_in_cycles(self, port): - return self.port_informations[port].total_bandwidth - - def get_port_volume_in_flits(self, port, services): - port_info = self.port_informations[port] - return sum(port_info.get_service_volume_in_flits(service) for service in services) - - def get_port_bandwidth_in_cycles(self, port, services): - port_info = self.port_informations[port] - return sum(port_info.get_service_bandwidth_in_cycles(service) for service in services) - - def get_port_throughput_in_flits(self, port): - return self.port_informations[port].throughput - - def get_port_bandwidth_throughput_in_cycles(self, port): - return self.port_informations[port].bandwidth_throughput - - def reset_throughput(self): - for port in self.port_informations: - port.reset_port_throughput() - - def reset_bandwidth_throughput(self): - for port in self.port_informations: - port.reset_port_bandwidth_throughput() diff --git a/information/router_neighbors.py b/information/router_neighbors.py deleted file mode 100644 index fdd67e7..0000000 --- a/information/router_neighbors.py +++ /dev/null @@ -1,59 +0,0 @@ -class RouterNeighbors: - """ - Calcula os vizinhos de um roteador a partir do seu endereço hamiltoniano. - Os métodos retornam -1 quando o vizinho não existe (borda da malha). - """ - - def __init__(self, x_dimension, y_dimension): - self.x_dimension = x_dimension - self.y_dimension = y_dimension - - @classmethod - def from_config(cls, mpsoc_config): - return cls(mpsoc_config.mpsoc_x, mpsoc_config.mpsoc_y) - - def xy_to_ham_addr(self, xy_addr): - xy_addr &= 0xFFFF # limpa o header - y = xy_addr & 0xFF - x = xy_addr >> 8 - - if y % 2 == 1: - return (y * self.x_dimension) + (self.x_dimension - x) - 1 - return (y * self.x_dimension) + x - - def ham_to_xy_addr(self, ham_addr): - ham_addr &= 0xFF # limpa o header - y, addr = divmod(ham_addr, self.x_dimension) - - x = self.x_dimension - addr - 1 if y % 2 == 1 else addr - return (x << 8) | y - - def xy_address_to_xy_label(self, ham_addr): - xy_addr = self.ham_to_xy_addr(ham_addr) - return f"{xy_addr >> 8}{xy_addr & 0xFF}" - - def get_x_coordinate(self, ham_addr): - return self.ham_to_xy_addr(ham_addr) >> 8 - - def get_y_coordinate(self, ham_addr): - return self.ham_to_xy_addr(ham_addr) & 0xFF - - def _neighbor(self, ham_addr, dx, dy): - x = self.get_x_coordinate(ham_addr) + dx - y = self.get_y_coordinate(ham_addr) + dy - - if 0 <= x < self.x_dimension and 0 <= y < self.y_dimension: - return self.xy_to_ham_addr((x << 8) | y) - return -1 - - def get_vizinho_cima(self, ham_addr): - return self._neighbor(ham_addr, 0, 1) - - def get_vizinho_baixo(self, ham_addr): - return self._neighbor(ham_addr, 0, -1) - - def get_vizinho_esquerda(self, ham_addr): - return self._neighbor(ham_addr, -1, 0) - - def get_vizinho_direita(self, ham_addr): - return self._neighbor(ham_addr, 1, 0) diff --git a/information/task_information.py b/information/task_information.py deleted file mode 100644 index 9c88457..0000000 --- a/information/task_information.py +++ /dev/null @@ -1,20 +0,0 @@ -from dataclasses import dataclass - - -@dataclass -class TaskInformation: - """Um evento de tarefa (alocação, término, mensagem) ocorrido em um PE.""" - - id: int - service: str - time: int - remote_task_id: int = -1 - - @property - def task_id(self): - # Os 8 bits menos significativos identificam a tarefa dentro da aplicação - return self.id & 0xFF - - @property - def app_id(self): - return self.id >> 8 diff --git a/main.py b/main.py index bb48294..3e72655 100644 --- a/main.py +++ b/main.py @@ -8,7 +8,7 @@ import theme from ui_mainwindow import MainWindow -from util import MPSoCConfig +import MPSoCConfig from information import MPSoCInformation from router_matrix import RouterMatrixWidget from simulation import SimulationController, MAX_REPAINT_SPEED, ticks_to_ms @@ -16,9 +16,8 @@ from deloream import DeloreamWindow from statistics_windows import ApplicationTimelineWindow, TrafficStatisticsWindow from router_info_window import RouterInfoWindow -from platform_setup import PlatformSetupDialog +from dialogs import AboutDialog, PlatformSetupDialog, show_packet_format from filter_window import FilterWindow -from help_dialogs import AboutDialog, show_packet_format import projects import export import path_view diff --git a/overview_matrices.py b/overview_matrices.py new file mode 100644 index 0000000..44053c6 --- /dev/null +++ b/overview_matrices.py @@ -0,0 +1,317 @@ +""" +Matrizes de células das janelas de visão geral (overview_windows.py). + +- SlaveMatrixWidget: grade "Slave XxY" do Communication Overview (commsUi.py), + com fundo de mapa de calor, percentual de tráfego e unidade ("flits"). +- PEMatrixWidget: grade "PEXxY" do Task Mapping Overview (taskmap.py), com + uma linha por tarefa na cor da aplicação. + +Cada célula é um widget simples e expansível (cresce para ocupar toda a +largura/altura do scrollArea do formulário). Usam apenas mpsoc_x / mpsoc_y de +MPSoCConfig.py, que representam a dimensão TOTAL da malha (mesma convenção de +router_matrix.py): a célula "0x0" fica no canto inferior esquerdo e o eixo Y +cresce para cima. +""" + +from PySide6.QtCore import Qt +from PySide6.QtGui import QColor, QPainter, QPen +from PySide6.QtWidgets import QGridLayout, QLabel, QSizePolicy, QVBoxLayout, QWidget + + +# Cores fixas, independentes do tema (claro/escuro) do MainWindow: sem +# isso, o toggle Dark/Light recoloriria essas células via +# QApplication.setPalette(). +# O fundo (cor do mapa de calor) é pintado em paintEvent, pois muda a +# cada atualização da simulação. +_SLAVE_STYLESHEET = """ +QLabel { + background-color: transparent; + color: #ffffff; +} +QLabel#slaveTitle { + font-weight: bold; + font-size: 11pt; +} +""" + + +class SlaveWidget(QWidget): + """ + Uma célula "Slave XxY" da visão de comunicação: título, percentual de + tráfego e a unidade ("flits"), com fundo azul e borda branca fina — + igual ao print de referência do Communication Overview. + """ + + def __init__(self, pos_x: int, pos_y: int, parent=None): + super().__init__(parent) + self.setObjectName("slaveWidget") + + self.pos_x = pos_x + self.pos_y = pos_y + + # Isola a célula do tema global do app (ver comentário de + # _SLAVE_STYLESHEET acima). + self.setStyleSheet(_SLAVE_STYLESHEET) + self._background = QColor("#0000cc") + + # Cresce para preencher toda a área do scrollArea, como no print + # (diferente do RouterWidget, que tem tamanho fixo 170x170). + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self.setMinimumSize(110, 110) + + layout = QVBoxLayout(self) + layout.setContentsMargins(8, 10, 8, 10) + layout.setSpacing(0) + + self.title_label = QLabel(f"Slave {pos_x}x{pos_y}") + self.title_label.setObjectName("slaveTitle") + self.title_label.setAlignment( + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop + ) + layout.addWidget(self.title_label) + + layout.addStretch(1) + + self.percent_label = QLabel("0%") + self.percent_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.percent_label) + + layout.addStretch(1) + + self.flits_label = QLabel("flits") + self.flits_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.flits_label) + + self.setLayout(layout) + + def _set_background(self, color: QColor) -> None: + if color != self._background: + self._background = QColor(color) + self.update() + + def paintEvent(self, event): + # Fundo com a cor do mapa de calor e borda preta de 1 px + painter = QPainter(self) + painter.fillRect(self.rect(), self._background) + painter.setPen(QPen(QColor("#000000"), 1)) + painter.drawRect(self.rect().adjusted(0, 0, -1, -1)) + painter.end() + + def set_values(self, title: str, value: str, unit: str, background: QColor) -> None: + """Atualiza a célula com os dados do mapa de calor do Communication Overview.""" + self.title_label.setText(title) + self.percent_label.setText(value) + self.flits_label.setText(unit) + self._set_background(background) + + def set_traffic(self, percent: float, flits: int | None = None) -> None: + """ + Atualiza o percentual mostrado (e, opcionalmente, a contagem de + flits), útil para quando os dados reais de tráfego estiverem + disponíveis. + """ + self.percent_label.setText(f"{percent:.0f}%") + if flits is not None: + self.flits_label.setText(f"{flits} flits") + + +class SlaveMatrixWidget(QWidget): + """ + Organiza mpsoc_x * mpsoc_y SlaveWidget em uma única grade que ocupa + toda a área disponível (linhas e colunas com stretch igual), igual ao + print do Communication Overview. + """ + + def __init__(self, mpsoc_x: int, mpsoc_y: int, parent=None): + super().__init__(parent) + + # mpsoc_x/mpsoc_y é o tamanho TOTAL da malha (mesma convenção de + # router_matrix.py) — aqui usado direto, sem noção de cluster, + # pois o Communication Overview mostra a malha inteira de uma vez. + self.total_cols = max(1, mpsoc_x) + self.total_rows = max(1, mpsoc_y) + + self.slaves: dict[tuple[int, int], SlaveWidget] = {} + + self._build_matrix() + + def _build_matrix(self) -> None: + layout = QGridLayout(self) + layout.setSpacing(0) + layout.setContentsMargins(0, 0, 0, 0) + + # Stretch igual em todas as linhas/colunas para as células + # dividirem uniformemente o espaço disponível, esticando para + # preencher a largura/altura do scrollArea como no print. + for col in range(self.total_cols): + layout.setColumnStretch(col, 1) + for row in range(self.total_rows): + layout.setRowStretch(row, 1) + + for y in range(self.total_rows): + for x in range(self.total_cols): + slave = SlaveWidget(x, y, self) + self.slaves[(x, y)] = slave + + # Linha 0 do grid = topo; invertemos Y para 0x0 ficar embaixo. + grid_row = self.total_rows - 1 - y + layout.addWidget(slave, grid_row, x) + + self.setLayout(layout) + + def get_slave(self, x: int, y: int) -> SlaveWidget | None: + """Retorna o SlaveWidget na posição global (x, y), se existir.""" + return self.slaves.get((x, y)) + + +# Cores fixas, independentes do tema (claro/escuro) do MainWindow — mesmo +# motivo do _SLAVE_STYLESHEET acima: sem isso, o toggle +# Dark/Light recoloriria essas células via QApplication.setPalette(). +_PE_STYLESHEET = """ +QWidget#peWidget { + background-color: #ffffff; + border: 1px solid #808080; +} +QLabel { + background-color: transparent; + color: #000000; +} +QLabel#peTitle { + font-weight: bold; +} +""" + +# Uma linha de tarefa: fundo com a cor da aplicação +_TASK_STYLESHEET = "QLabel {{ background-color: {background}; color: {text}; font-weight: bold; padding: 2px; }}" + + +def _text_color_for(background: QColor) -> str: + """Texto preto em fundos claros e branco em fundos escuros.""" + luminance = 0.299 * background.red() + 0.587 * background.green() + 0.114 * background.blue() + return "#000000" if luminance > 140 else "#ffffff" + + +class PEWidget(QWidget): + """ + Uma célula "PEXxY" da visão de mapeamento de tarefas: fundo branco, + borda cinza fina e um rótulo central, igual ao print de referência do + Task Mapping Overview. Pensado para, futuramente, também mostrar a + tarefa/status alocado no PE (ver set_task). + """ + + def __init__(self, pos_x: int, pos_y: int, parent=None): + super().__init__(parent) + self.setObjectName("peWidget") + + self.pos_x = pos_x + self.pos_y = pos_y + + # Isola a célula do tema global do app (ver comentário de + # _PE_STYLESHEET acima). + self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) + self.setStyleSheet(_PE_STYLESHEET) + + # Cresce para preencher toda a área do scrollArea, como no print + # (diferente do RouterWidget, que tem tamanho fixo 170x170). + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self.setMinimumSize(90, 90) + + layout = QVBoxLayout(self) + layout.setContentsMargins(4, 4, 4, 4) + layout.setSpacing(2) + + self.pe_label = QLabel(f"PE{pos_x}x{pos_y}") + self.pe_label.setObjectName("peTitle") + self.pe_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.pe_label) + + # Linhas de tarefas (preenchidas por set_tasks), centralizadas na célula + layout.addStretch(1) + self.tasks_layout = QVBoxLayout() + self.tasks_layout.setSpacing(2) + layout.addLayout(self.tasks_layout) + layout.addStretch(1) + + self.task_labels: list[QLabel] = [] + + self.setLayout(layout) + + def set_tasks(self, title: str, tasks: list[tuple[str, QColor]]) -> None: + """ + Mostra o título do PE e uma linha por tarefa, cada uma com o texto + e a cor da aplicação a que pertence. + """ + self.pe_label.setText(title) + + # Reaproveita os rótulos existentes e cria/remove só a diferença + while len(self.task_labels) < len(tasks): + label = QLabel(self) + label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.tasks_layout.addWidget(label) + self.task_labels.append(label) + while len(self.task_labels) > len(tasks): + self.task_labels.pop().deleteLater() + + for label, (text, color) in zip(self.task_labels, tasks): + label.setText(text) + style = _TASK_STYLESHEET.format(background=color.name(), text=_text_color_for(color)) + if label.styleSheet() != style: + label.setStyleSheet(style) + + def set_task(self, task_name: str | None) -> None: + """ + Atualiza o rótulo para mostrar a tarefa alocada neste PE, além da + posição — útil para quando o mapeamento real de tarefas estiver + disponível (ex.: filtros "Only running" / "Without Task ID"). + """ + base = f"PE{self.pos_x}x{self.pos_y}" + self.pe_label.setText(f"{base}\n{task_name}" if task_name else base) + + +class PEMatrixWidget(QWidget): + """ + Organiza mpsoc_x * mpsoc_y PEWidget em uma única grade que ocupa toda + a área disponível (linhas e colunas com stretch igual), igual ao + print do Task Mapping Overview. + """ + + def __init__(self, mpsoc_x: int, mpsoc_y: int, parent=None): + super().__init__(parent) + + # mpsoc_x/mpsoc_y é o tamanho TOTAL da malha (mesma convenção de + # router_matrix.py / SlaveMatrixWidget) — usado direto, sem noção de + # cluster, pois o Task Mapping Overview mostra a malha inteira. + self.total_cols = max(1, mpsoc_x) + self.total_rows = max(1, mpsoc_y) + + self.pes: dict[tuple[int, int], PEWidget] = {} + + self._build_matrix() + + def _build_matrix(self) -> None: + layout = QGridLayout(self) + layout.setSpacing(0) + layout.setContentsMargins(0, 0, 0, 0) + + # Stretch igual em todas as linhas/colunas para as células + # dividirem uniformemente o espaço disponível, esticando para + # preencher a largura/altura do scrollArea como no print. + for col in range(self.total_cols): + layout.setColumnStretch(col, 1) + for row in range(self.total_rows): + layout.setRowStretch(row, 1) + + for y in range(self.total_rows): + for x in range(self.total_cols): + pe = PEWidget(x, y, self) + self.pes[(x, y)] = pe + + # Linha 0 do grid = topo; invertemos Y para 0x0 ficar embaixo. + grid_row = self.total_rows - 1 - y + layout.addWidget(pe, grid_row, x) + + self.setLayout(layout) + + def get_pe(self, x: int, y: int) -> PEWidget | None: + """Retorna o PEWidget na posição global (x, y), se existir.""" + return self.pes.get((x, y)) diff --git a/overview_windows.py b/overview_windows.py index c0ae0e0..20fa799 100644 --- a/overview_windows.py +++ b/overview_windows.py @@ -19,10 +19,9 @@ import analysis import export from commsUi import Ui_Form as Ui_CommunicationOverview -from pe_matrix import PEMatrixWidget -from slave_matrix import SlaveMatrixWidget +from overview_matrices import PEMatrixWidget, SlaveMatrixWidget from taskmap import Ui_TaskMap -from util.MPSoCConfig import MPSoCConfig +from MPSoCConfig import MPSoCConfig IMAGES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "images") diff --git a/pe_matrix.py b/pe_matrix.py deleted file mode 100644 index c42993a..0000000 --- a/pe_matrix.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- coding: utf-8 -*- -""" -pe_matrix.py - -Monta a matriz de PEs (Processing Elements) exibida na tela de Task -Mapping Overview (taskmap.py), dentro do QScrollArea já existente naquele -formulário (self.scrollArea / self.scrollAreaWidgetContents). - -Segue o mesmo padrão de slave_matrix.py: cada célula é um widget simples e -expansível (cresce para ocupar toda a largura/altura disponível no -scrollArea), com fundo branco, borda cinza e um rótulo central "PEXxY", -igual ao print de referência do Task Mapping Overview. - -Usa apenas mpsoc_x / mpsoc_y de MPSoCConfig.py, que representam a dimensão -TOTAL da malha (mesma convenção de router_matrix.py e slave_matrix.py): o -PE "0x0" fica no canto inferior esquerdo e o eixo Y cresce para cima. -""" - -from PySide6.QtCore import Qt -from PySide6.QtGui import QColor -from PySide6.QtWidgets import QGridLayout, QLabel, QSizePolicy, QVBoxLayout, QWidget - - -# Cores fixas, independentes do tema (claro/escuro) do MainWindow — mesmo -# motivo do _SLAVE_STYLESHEET em slave_matrix.py: sem isso, o toggle -# Dark/Light recoloriria essas células via QApplication.setPalette(). -_PE_STYLESHEET = """ -QWidget#peWidget { - background-color: #ffffff; - border: 1px solid #808080; -} -QLabel { - background-color: transparent; - color: #000000; -} -QLabel#peTitle { - font-weight: bold; -} -""" - -# Uma linha de tarefa: fundo com a cor da aplicação -_TASK_STYLESHEET = "QLabel {{ background-color: {background}; color: {text}; font-weight: bold; padding: 2px; }}" - - -def _text_color_for(background: QColor) -> str: - """Texto preto em fundos claros e branco em fundos escuros.""" - luminance = 0.299 * background.red() + 0.587 * background.green() + 0.114 * background.blue() - return "#000000" if luminance > 140 else "#ffffff" - - -class PEWidget(QWidget): - """ - Uma célula "PEXxY" da visão de mapeamento de tarefas: fundo branco, - borda cinza fina e um rótulo central, igual ao print de referência do - Task Mapping Overview. Pensado para, futuramente, também mostrar a - tarefa/status alocado no PE (ver set_task). - """ - - def __init__(self, pos_x: int, pos_y: int, parent=None): - super().__init__(parent) - self.setObjectName("peWidget") - - self.pos_x = pos_x - self.pos_y = pos_y - - # Isola a célula do tema global do app (ver comentário de - # _PE_STYLESHEET acima). - self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) - self.setStyleSheet(_PE_STYLESHEET) - - # Cresce para preencher toda a área do scrollArea, como no print - # (diferente do RouterWidget, que tem tamanho fixo 170x170). - self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) - self.setMinimumSize(90, 90) - - layout = QVBoxLayout(self) - layout.setContentsMargins(4, 4, 4, 4) - layout.setSpacing(2) - - self.pe_label = QLabel(f"PE{pos_x}x{pos_y}") - self.pe_label.setObjectName("peTitle") - self.pe_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(self.pe_label) - - # Linhas de tarefas (preenchidas por set_tasks), centralizadas na célula - layout.addStretch(1) - self.tasks_layout = QVBoxLayout() - self.tasks_layout.setSpacing(2) - layout.addLayout(self.tasks_layout) - layout.addStretch(1) - - self.task_labels: list[QLabel] = [] - - self.setLayout(layout) - - def set_tasks(self, title: str, tasks: list[tuple[str, QColor]]) -> None: - """ - Mostra o título do PE e uma linha por tarefa, cada uma com o texto - e a cor da aplicação a que pertence. - """ - self.pe_label.setText(title) - - # Reaproveita os rótulos existentes e cria/remove só a diferença - while len(self.task_labels) < len(tasks): - label = QLabel(self) - label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.tasks_layout.addWidget(label) - self.task_labels.append(label) - while len(self.task_labels) > len(tasks): - self.task_labels.pop().deleteLater() - - for label, (text, color) in zip(self.task_labels, tasks): - label.setText(text) - style = _TASK_STYLESHEET.format(background=color.name(), text=_text_color_for(color)) - if label.styleSheet() != style: - label.setStyleSheet(style) - - def set_task(self, task_name: str | None) -> None: - """ - Atualiza o rótulo para mostrar a tarefa alocada neste PE, além da - posição — útil para quando o mapeamento real de tarefas estiver - disponível (ex.: filtros "Only running" / "Without Task ID"). - """ - base = f"PE{self.pos_x}x{self.pos_y}" - self.pe_label.setText(f"{base}\n{task_name}" if task_name else base) - - -class PEMatrixWidget(QWidget): - """ - Organiza mpsoc_x * mpsoc_y PEWidget em uma única grade que ocupa toda - a área disponível (linhas e colunas com stretch igual), igual ao - print do Task Mapping Overview. - """ - - def __init__(self, mpsoc_x: int, mpsoc_y: int, parent=None): - super().__init__(parent) - - # mpsoc_x/mpsoc_y é o tamanho TOTAL da malha (mesma convenção de - # router_matrix.py / slave_matrix.py) — usado direto, sem noção de - # cluster, pois o Task Mapping Overview mostra a malha inteira. - self.total_cols = max(1, mpsoc_x) - self.total_rows = max(1, mpsoc_y) - - self.pes: dict[tuple[int, int], PEWidget] = {} - - self._build_matrix() - - def _build_matrix(self) -> None: - layout = QGridLayout(self) - layout.setSpacing(0) - layout.setContentsMargins(0, 0, 0, 0) - - # Stretch igual em todas as linhas/colunas para as células - # dividirem uniformemente o espaço disponível, esticando para - # preencher a largura/altura do scrollArea como no print. - for col in range(self.total_cols): - layout.setColumnStretch(col, 1) - for row in range(self.total_rows): - layout.setRowStretch(row, 1) - - for y in range(self.total_rows): - for x in range(self.total_cols): - pe = PEWidget(x, y, self) - self.pes[(x, y)] = pe - - # Linha 0 do grid = topo; invertemos Y para 0x0 ficar embaixo. - grid_row = self.total_rows - 1 - y - layout.addWidget(pe, grid_row, x) - - self.setLayout(layout) - - def get_pe(self, x: int, y: int) -> PEWidget | None: - """Retorna o PEWidget na posição global (x, y), se existir.""" - return self.pes.get((x, y)) diff --git a/platform_setup.py b/platform_setup.py deleted file mode 100644 index bb96e7d..0000000 --- a/platform_setup.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -Janela "Platform Setup" (menu Edit). - -Refatoração de PlatformSetupFrame.java do GraphicalDebugger original: permite -trocar o tamanho do flit, o período de clock e a largura da janela de -checkpoint (intervalo em que a % de uso dos enlaces é recalculada) sem -editar o platform.cfg. As mudanças valem até o próximo "Reset Simulation" -ou "New Debugging", que releem os arquivos. -""" - -from PySide6.QtCore import QLocale -from PySide6.QtGui import QDoubleValidator, QIntValidator -from PySide6.QtWidgets import QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QMessageBox, QVBoxLayout - - -class PlatformSetupDialog(QDialog): - def __init__(self, mpsoc_config, checkpoint, parent=None): - super().__init__(parent) - self.mpsoc_config = mpsoc_config - self.checkpoint = checkpoint - self.setWindowTitle("Platform Setup") - - self.flit_size_edit = QLineEdit(str(mpsoc_config.flit_size)) - self.flit_size_edit.setValidator(QIntValidator(1, 1024, self)) - - self.clock_edit = QLineEdit(str(mpsoc_config.clock_period_in_ns)) - self.clock_edit.setValidator(QIntValidator(1, 1_000_000, self)) - - # Largura da janela em us, como no Java (guardada em ms no controlador) - self.window_width_edit = QLineEdit(f"{checkpoint.window_size_ms * 1000:g}") - window_validator = QDoubleValidator(0.001, 1e9, 3, self) - window_validator.setNotation(QDoubleValidator.Notation.StandardNotation) - window_validator.setLocale(QLocale.c()) # aceita ponto decimal em qualquer idioma - self.window_width_edit.setValidator(window_validator) - - form = QGridLayout() - rows = (("Flit Size", self.flit_size_edit, "bits"), - ("Clock Period", self.clock_edit, "ns"), - ("Window Width", self.window_width_edit, "us")) - for row, (caption, edit, unit) in enumerate(rows): - form.addWidget(QLabel(caption), row, 0) - form.addWidget(edit, row, 1) - form.addWidget(QLabel(unit), row, 2) - - buttons = QDialogButtonBox() - buttons.addButton("Confirm", QDialogButtonBox.ButtonRole.AcceptRole) - buttons.addButton("Cancel", QDialogButtonBox.ButtonRole.RejectRole) - buttons.accepted.connect(self.accept) - buttons.rejected.connect(self.reject) - - layout = QVBoxLayout(self) - layout.addLayout(form) - layout.addWidget(buttons) - layout.setSizeConstraint(QVBoxLayout.SizeConstraint.SetFixedSize) # não redimensionável, como no Java - - def accept(self): - edits = (self.flit_size_edit, self.clock_edit, self.window_width_edit) - invalid = next((edit for edit in edits if not edit.hasAcceptableInput()), None) - if invalid is not None: - QMessageBox.warning(self, "Platform Setup", "Enter only valid positive numbers.") - invalid.setFocus() - invalid.selectAll() - return - - clock_period = int(self.clock_edit.text()) - self.mpsoc_config.flit_size = int(self.flit_size_edit.text()) - self.mpsoc_config.clock_period_in_ns = clock_period - # O Java esquecia de repassar o clock ao CheckpointController - self.checkpoint.clock_period_ns = clock_period - self.checkpoint.window_size_ms = float(self.window_width_edit.text()) / 1000.0 - - QMessageBox.information(self, "Platform Setup", "Settings applied") - super().accept() diff --git a/router_info_window.py b/router_info_window.py index 0c2c876..ba9e131 100644 --- a/router_info_window.py +++ b/router_info_window.py @@ -28,7 +28,7 @@ import export import path_view from overview_windows import _LiveOverview, _router_label -from util.MPSoCConfig import MPSoCConfig +from MPSoCConfig import MPSoCConfig ICON_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icon") diff --git a/router_matrix.py b/router_matrix.py index f4a0b95..723742d 100644 --- a/router_matrix.py +++ b/router_matrix.py @@ -43,7 +43,7 @@ QGraphicsView, QHBoxLayout, QLabel, QToolButton) import theme -from util.MPSoCConfig import MPSoCConfig +from MPSoCConfig import MPSoCConfig # ========================================== diff --git a/simulation.py b/simulation.py index 0426772..6ecbd97 100644 --- a/simulation.py +++ b/simulation.py @@ -8,7 +8,7 @@ from PySide6.QtCore import QObject, QTimer, Signal -from util.MPSoCConfig import MPSoCConfig +from MPSoCConfig import MPSoCConfig # Velocidade (slider 0-100) acima da qual os roteadores não são redesenhados, # para avançar o mais rápido possível diff --git a/slave_matrix.py b/slave_matrix.py deleted file mode 100644 index 2680159..0000000 --- a/slave_matrix.py +++ /dev/null @@ -1,168 +0,0 @@ -# -*- coding: utf-8 -*- -""" -slave_matrix.py - -Monta a matriz de "slaves" exibida na tela de Communication Overview -(commsUi.py), dentro do QScrollArea já existente naquele formulário -(self.scrollArea / self.scrollAreaWidgetContents). - -Diferente de router_matrix.py, este módulo NÃO reaproveita o Ui_Form de -Roteador.py — cada célula é um widget simples e expansível (cresce para -ocupar toda a largura/altura disponível no scrollArea), com fundo azul e -três rótulos: nome do slave ("Slave XxY"), percentual de tráfego e a -unidade ("flits"), igual ao print de referência do Communication Overview. - -Usa apenas mpsoc_x / mpsoc_y de MPSoCConfig.py, que já representam a -dimensão TOTAL da malha (mesma convenção usada em router_matrix.py): o -slave "0x0" fica no canto inferior esquerdo e o eixo Y cresce para cima. -""" - -from PySide6.QtCore import Qt -from PySide6.QtGui import QColor, QPainter, QPen -from PySide6.QtWidgets import QGridLayout, QLabel, QSizePolicy, QVBoxLayout, QWidget - - -# Cores fixas, independentes do tema (claro/escuro) do MainWindow: sem -# isso, o toggle Dark/Light recoloriria essas células via -# QApplication.setPalette(). -# O fundo (cor do mapa de calor) é pintado em paintEvent, pois muda a -# cada atualização da simulação. -_SLAVE_STYLESHEET = """ -QLabel { - background-color: transparent; - color: #ffffff; -} -QLabel#slaveTitle { - font-weight: bold; - font-size: 11pt; -} -""" - - -class SlaveWidget(QWidget): - """ - Uma célula "Slave XxY" da visão de comunicação: título, percentual de - tráfego e a unidade ("flits"), com fundo azul e borda branca fina — - igual ao print de referência do Communication Overview. - """ - - def __init__(self, pos_x: int, pos_y: int, parent=None): - super().__init__(parent) - self.setObjectName("slaveWidget") - - self.pos_x = pos_x - self.pos_y = pos_y - - # Isola a célula do tema global do app (ver comentário de - # _SLAVE_STYLESHEET acima). - self.setStyleSheet(_SLAVE_STYLESHEET) - self._background = QColor("#0000cc") - - # Cresce para preencher toda a área do scrollArea, como no print - # (diferente do RouterWidget, que tem tamanho fixo 170x170). - self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) - self.setMinimumSize(110, 110) - - layout = QVBoxLayout(self) - layout.setContentsMargins(8, 10, 8, 10) - layout.setSpacing(0) - - self.title_label = QLabel(f"Slave {pos_x}x{pos_y}") - self.title_label.setObjectName("slaveTitle") - self.title_label.setAlignment( - Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop - ) - layout.addWidget(self.title_label) - - layout.addStretch(1) - - self.percent_label = QLabel("0%") - self.percent_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(self.percent_label) - - layout.addStretch(1) - - self.flits_label = QLabel("flits") - self.flits_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(self.flits_label) - - self.setLayout(layout) - - def _set_background(self, color: QColor) -> None: - if color != self._background: - self._background = QColor(color) - self.update() - - def paintEvent(self, event): - # Fundo com a cor do mapa de calor e borda preta de 1 px - painter = QPainter(self) - painter.fillRect(self.rect(), self._background) - painter.setPen(QPen(QColor("#000000"), 1)) - painter.drawRect(self.rect().adjusted(0, 0, -1, -1)) - painter.end() - - def set_values(self, title: str, value: str, unit: str, background: QColor) -> None: - """Atualiza a célula com os dados do mapa de calor do Communication Overview.""" - self.title_label.setText(title) - self.percent_label.setText(value) - self.flits_label.setText(unit) - self._set_background(background) - - def set_traffic(self, percent: float, flits: int | None = None) -> None: - """ - Atualiza o percentual mostrado (e, opcionalmente, a contagem de - flits), útil para quando os dados reais de tráfego estiverem - disponíveis. - """ - self.percent_label.setText(f"{percent:.0f}%") - if flits is not None: - self.flits_label.setText(f"{flits} flits") - - -class SlaveMatrixWidget(QWidget): - """ - Organiza mpsoc_x * mpsoc_y SlaveWidget em uma única grade que ocupa - toda a área disponível (linhas e colunas com stretch igual), igual ao - print do Communication Overview. - """ - - def __init__(self, mpsoc_x: int, mpsoc_y: int, parent=None): - super().__init__(parent) - - # mpsoc_x/mpsoc_y é o tamanho TOTAL da malha (mesma convenção de - # router_matrix.py) — aqui usado direto, sem noção de cluster, - # pois o Communication Overview mostra a malha inteira de uma vez. - self.total_cols = max(1, mpsoc_x) - self.total_rows = max(1, mpsoc_y) - - self.slaves: dict[tuple[int, int], SlaveWidget] = {} - - self._build_matrix() - - def _build_matrix(self) -> None: - layout = QGridLayout(self) - layout.setSpacing(0) - layout.setContentsMargins(0, 0, 0, 0) - - # Stretch igual em todas as linhas/colunas para as células - # dividirem uniformemente o espaço disponível, esticando para - # preencher a largura/altura do scrollArea como no print. - for col in range(self.total_cols): - layout.setColumnStretch(col, 1) - for row in range(self.total_rows): - layout.setRowStretch(row, 1) - - for y in range(self.total_rows): - for x in range(self.total_cols): - slave = SlaveWidget(x, y, self) - self.slaves[(x, y)] = slave - - # Linha 0 do grid = topo; invertemos Y para 0x0 ficar embaixo. - grid_row = self.total_rows - 1 - y - layout.addWidget(slave, grid_row, x) - - self.setLayout(layout) - - def get_slave(self, x: int, y: int) -> SlaveWidget | None: - """Retorna o SlaveWidget na posição global (x, y), se existir.""" - return self.slaves.get((x, y)) diff --git a/statistics_windows.py b/statistics_windows.py index 278dab9..f760401 100644 --- a/statistics_windows.py +++ b/statistics_windows.py @@ -19,7 +19,7 @@ import export import path_view from overview_windows import _LiveOverview, _router_label -from util.MPSoCConfig import MPSoCConfig +from MPSoCConfig import MPSoCConfig def _ticks_to_us(mpsoc_config, ticks): diff --git a/tests/conftest.py b/tests/conftest.py index 46626ab..841261a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,15 +45,14 @@ def debug_dir(scenario_dir): @pytest.fixture(scope="session") def config(debug_dir): - from util.MPSoCConfig import MPSoCConfig + from MPSoCConfig import MPSoCConfig return MPSoCConfig(debug_dir) @pytest.fixture(scope="session") def packets(config): """Todos os pacotes do traffic_router.txt do exemplo.""" - from information.read_traffic_data import ReadTrafficData - from information.router_neighbors import RouterNeighbors + from information import ReadTrafficData, RouterNeighbors reader = ReadTrafficData(config, RouterNeighbors.from_config(config)) try: result = [] diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 89d94be..fc1d8d4 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -3,8 +3,8 @@ import pytest import analysis -from information.packet_information import PacketInformation -from util.MPSoCConfig import MPSoCConfig +from information import PacketInformation +from MPSoCConfig import MPSoCConfig REQUEST = 41 BROADCAST = 0 diff --git a/tests/test_config.py b/tests/test_config.py index 50991f6..1fb0950 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,7 @@ import pytest -from util.MPSoCConfig import MPSoCConfig +from MPSoCConfig import MPSoCConfig def test_platform(config): diff --git a/tests/test_gui.py b/tests/test_gui.py index 36b672e..56f8046 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -121,7 +121,7 @@ def test_exports(window, tmp_path, monkeypatch): def test_platform_setup(window): - from platform_setup import PlatformSetupDialog + from dialogs import PlatformSetupDialog dialog = PlatformSetupDialog(window.mpconfig, window.simulation.checkpoint, window) dialog.clock_edit.setText("20") dialog.window_width_edit.setText("250") diff --git a/tests/test_information.py b/tests/test_information.py index aa23008..ccbf311 100644 --- a/tests/test_information.py +++ b/tests/test_information.py @@ -1,7 +1,7 @@ """Leitura do traffic_router.txt e estado dos roteadores.""" from information import MPSoCInformation -from util.MPSoCConfig import MPSoCConfig +from MPSoCConfig import MPSoCConfig def test_all_packets_read(packets): @@ -64,9 +64,8 @@ def test_port_names(): def test_follow_reads_lines_as_they_are_written(tmp_path, debug_dir): import shutil - from information.read_traffic_data import ReadTrafficData - from information.router_neighbors import RouterNeighbors - from util.MPSoCConfig import MPSoCConfig + from information import ReadTrafficData, RouterNeighbors + from MPSoCConfig import MPSoCConfig debug = tmp_path / "debug" shutil.copytree(debug_dir, debug) diff --git a/tests/test_testcase_files.py b/tests/test_testcase_files.py index 3d8a44c..def5251 100644 --- a/tests/test_testcase_files.py +++ b/tests/test_testcase_files.py @@ -34,7 +34,7 @@ def test_example_testcase(config): def test_missing_yaml(tmp_path): - from util.MPSoCConfig import MPSoCConfig + from MPSoCConfig import MPSoCConfig debug = tmp_path / "scenario" / "debug" debug.mkdir(parents=True) (debug / "platform.cfg").write_text("mpsoc_x 2\nmpsoc_y 2\n")