From 73da216adb1befcccb08b11a17a17cbb4456067e Mon Sep 17 00:00:00 2001 From: DrakRosmann Date: Wed, 23 Sep 2026 12:54:16 -0300 Subject: [PATCH 1/5] Clean up imports, naming and dependencies. [enhancement] - main.py: drop duplicate/unused imports, rename updadeSlide/CheckB/taskMap, guard open_taskmap when no debugging is loaded - Roteador.py: remove hand-added sys/tkinter imports - MPSoCConfig.py: remove unused os import and getTaskNameHash, WEAST -> WEST - pyproject.toml: drop unused SortedDict dep, require pyside6>=6.7 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015JamNMtFFg5KPQ98CKHsGD --- Roteador.py | 2 -- main.py | 20 +++++++++++--------- pyproject.toml | 3 +-- util/MPSoCConfig.py | 13 ++++--------- uv.lock | 18 +----------------- 5 files changed, 17 insertions(+), 39 deletions(-) diff --git a/Roteador.py b/Roteador.py index 0ea253b..dc0716e 100644 --- a/Roteador.py +++ b/Roteador.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -import sys -#from tkinter.tix import Form ################################################################################ ## Form generated from reading UI file 'RoteadorjxHyuV.ui' diff --git a/main.py b/main.py index 301f929..f5a18d1 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,8 @@ import sys -import util.MPSoCConfig -from PySide6.QtGui import QIcon, QPixmap, Qt, QPalette, QColor, QAction, QKeySequence +from PySide6.QtGui import Qt, QPalette, QColor, QAction, QKeySequence from PySide6.QtWidgets import (QApplication, QMainWindow, QFileDialog, QMessageBox, QWidget, - QVBoxLayout, QGridLayout, QFrame, QScrollArea, QTableWidgetItem, QTableWidget) + QVBoxLayout, QScrollArea, QTableWidgetItem, QTableWidget) from ui_mainwindow import MainWindow from commsUi import Ui_Form @@ -52,7 +51,7 @@ def __init__(self): self.actionExit.triggered.connect(self.close) self.actionNew_Debugging.triggered.connect(self.open_file) - self.horizontalSlider.valueChanged.connect(self.updadeSlide) + self.horizontalSlider.valueChanged.connect(self.update_slide) self.label_2.setText(f"{self.horizontalSlider.value()}") self.actionCommunication_Overview.triggered.connect(self.open_communication) self.actionTask_Mapping_Overview.triggered.connect(self.open_taskmap) @@ -123,10 +122,13 @@ def open_communication(self): self.janela_secundaria.show() def open_taskmap(self): - self.janela_taskmap = taskMap(self.mpconfig) + if self.filePath == "": + QMessageBox.warning(self, "Attention", "Please, load a debugging before") + return + self.janela_taskmap = TaskMapWindow(self.mpconfig) self.janela_taskmap.show() - def updadeSlide(self, valor): + def update_slide(self, valor): self.label_2.setText(str(valor)) def open_file(self): @@ -230,7 +232,7 @@ def __init__(self, mpconfig=None): super().__init__() self.setupUi(self) self.mpconfig = mpconfig - self.checkBox.checkStateChanged.connect(self.CheckB) + self.checkBox.checkStateChanged.connect(self.on_checkbox_changed) self.build_slave_matrix() @@ -250,7 +252,7 @@ def build_slave_matrix(self): layout.addWidget(matrix) self.scrollAreaWidgetContents.setLayout(layout) - def CheckB(self): + def on_checkbox_changed(self): if (self.checkBox.isChecked()): self.radioButton.setEnabled(False) self.radioButton_2.setEnabled(False) @@ -263,7 +265,7 @@ def CheckB(self): self.comboBox.setEnabled(True) -class taskMap(QWidget, Ui_TaskMap): +class TaskMapWindow(QWidget, Ui_TaskMap): def __init__(self, mpconfig=None): super().__init__() self.setupUi(self) diff --git a/pyproject.toml b/pyproject.toml index 72d836f..3f7833c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,6 @@ name = "project-memphis-py" version = "0.1.0" requires-python = ">=3.14" dependencies = [ - "pyside6", + "pyside6>=6.7", "sortedcontainers>=2.4.0", - "SortedDict", ] diff --git a/util/MPSoCConfig.py b/util/MPSoCConfig.py index b2a9e67..96c847e 100644 --- a/util/MPSoCConfig.py +++ b/util/MPSoCConfig.py @@ -1,4 +1,3 @@ -import os from sortedcontainers import SortedDict @@ -6,16 +5,16 @@ class MPSoCConfig: # Constantes de Direção (Variáveis de Classe) EAST_IN_HIGH = 0 EAST_OUT_HIGH = 1 - WEAST_IN_HIGH = 2 - WEAST_OUT_HIGH = 3 + WEST_IN_HIGH = 2 + WEST_OUT_HIGH = 3 NORTH_IN_HIGH = 4 NORTH_OUT_HIGH = 5 SOUTH_IN_HIGH = 6 SOUTH_OUT_HIGH = 7 EAST_IN_LOW = 8 EAST_OUT_LOW = 9 - WEAST_IN_LOW = 10 - WEAST_OUT_LOW = 11 + WEST_IN_LOW = 10 + WEST_OUT_LOW = 11 NORTH_IN_LOW = 12 NORTH_OUT_LOW = 13 SOUTH_IN_LOW = 14 @@ -116,7 +115,3 @@ def initialize_task_naming(self, platform_file): # Método de acesso necessário para compatibilidade com sua interface gráfica def get_task_name_hash(self): return self.task_name_hash - - # Se você preferir manter o nome antigo estilo Java no seu código PySide - def getTaskNameHash(self): - return self.task_name_hash \ No newline at end of file diff --git a/uv.lock b/uv.lock index a1f33d7..092019e 100644 --- a/uv.lock +++ b/uv.lock @@ -9,14 +9,12 @@ source = { virtual = "." } dependencies = [ { name = "pyside6" }, { name = "sortedcontainers" }, - { name = "sorteddict" }, ] [package.metadata] requires-dist = [ - { name = "pyside6" }, + { name = "pyside6", specifier = ">=6.7" }, { name = "sortedcontainers", specifier = ">=2.4.0" }, - { name = "sorteddict" }, ] [[package]] @@ -87,17 +85,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f233 wheels = [ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, ] - -[[package]] -name = "sorteddict" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/ee/3748fbecfc3c0fb5959a3ebda513573aec5dc92b3492745cf5b1ab0c47d1/sorteddict-0.1.0.tar.gz", hash = "sha256:efaf837b75cede1df906d27baa45008b0fc9f844903389e50b4d10ef2725bbd5", size = 10042, upload-time = "2026-07-13T14:21:14.723Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/b0/23a2ffca30690ddb95cfb2569585c631a1ebfedbad219541c95823d1a611/sorteddict-0.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d48d8d7ecc4853ce19f1b905bfd2f8bf7c41085582839d0cfb57145f7fd300ac", size = 9873, upload-time = "2026-07-13T14:21:08.81Z" }, - { url = "https://files.pythonhosted.org/packages/d3/54/f52f0576698ca1a1f50ea4cdaaca57e7fd24f99bb2fb8d3ab158af5f72d2/sorteddict-0.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39f74e77b24ba200b95ce9bb5a505d5ead4ba0088cc155701c96ba199814038a", size = 10248, upload-time = "2026-07-13T14:21:09.738Z" }, - { url = "https://files.pythonhosted.org/packages/ec/1c/368485fb51140fb6a7d488c265e5a0bce62e6ce97ae7586e04eee87f25de/sorteddict-0.1.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:571fa9dc0796bc01277579e55050e9aeb8069f1286bb5b100a7fa6cce10de0be", size = 41632, upload-time = "2026-07-13T14:21:10.765Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0d/276a92b22a6f48a30a34f76cb315387f6c274ce68424aa216e3813729ddc/sorteddict-0.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eaa1232c32dd9ed1c5a86c27213234ddf85eb1f840a015b5a60de0497c0abec0", size = 41740, upload-time = "2026-07-13T14:21:11.82Z" }, - { url = "https://files.pythonhosted.org/packages/51/d4/a664cec8a9591b810eef2cb977d670c50213bc123b4dffa882bf36a02db1/sorteddict-0.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:5b4a314f6db895674f40486d648365974a4df1f1ca681105692f8283fac32290", size = 11791, upload-time = "2026-07-13T14:21:12.807Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ef/ad8c6338122718764d680402890123c8d9ecd355fbf97bad6b93ee400dcb/sorteddict-0.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:6c7d127407ae5a2ab53ffb7e20488c9b62b37a743bc907de16943629fbe99b9b", size = 12013, upload-time = "2026-07-13T14:21:13.778Z" }, -] From c594d15632bc00224677721cb314ddcf4e7eacfb Mon Sep 17 00:00:00 2001 From: DrakRosmann Date: Wed, 23 Sep 2026 12:55:22 -0300 Subject: [PATCH 2/5] Add .gitignore for Python caches and virtualenv. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015JamNMtFFg5KPQ98CKHsGD --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00f2d38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +.venv/ From eddfc481bafb29c7e693c6f58092a9a7a5ca194c Mon Sep 17 00:00:00 2001 From: DrakRosmann Date: Thu, 24 Sep 2026 10:53:37 -0300 Subject: [PATCH 3/5] Atualizado sistema de abertura de projetos --- main.py | 27 +++++++++++---- util/MPSoCConfig.py | 80 ++++++++++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 44 deletions(-) diff --git a/main.py b/main.py index f5a18d1..f08b0f6 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +import os import sys from PySide6.QtGui import Qt, QPalette, QColor, QAction, QKeySequence @@ -19,6 +20,9 @@ class MinhaJanela(QMainWindow, MainWindow): router_matrix_widget = None frame_scroll_area = None + # Arquivos obrigatórios em um diretório de debug + REQUIRED_FILES = ("platform.cfg", "services.cfg", "traffic_router.txt") + # Variável de controle do tema is_dark_mode = False @@ -132,12 +136,23 @@ def update_slide(self, valor): self.label_2.setText(str(valor)) def open_file(self): - self.filePath, _ = QFileDialog.getOpenFileName( - self, - "Open File", - "./", - "Config Files (*.cfg);;All Files (*)" - ) + caminho = QFileDialog.getExistingDirectory(self, "Open Debug Directory", "./") + if caminho == "": + return + + # Verifica se o diretório possui todos os arquivos necessários + faltando = [arquivo for arquivo in self.REQUIRED_FILES + if not os.path.isfile(os.path.join(caminho, arquivo))] + if faltando: + QMessageBox.critical( + self, + "Invalid Directory", + "The selected directory is not a valid debug directory.\n\n" + "Missing files:\n" + "\n".join(f"• {arquivo}" for arquivo in faltando) + ) + return + + self.filePath = caminho if self.filePath != "": self.actionCommunication_Overview.setEnabled(True) self.actionDeloream.setEnabled(True) diff --git a/util/MPSoCConfig.py b/util/MPSoCConfig.py index 96c847e..a112b28 100644 --- a/util/MPSoCConfig.py +++ b/util/MPSoCConfig.py @@ -48,44 +48,47 @@ def __init__(self, debug_file_path): self.task_name_hash = SortedDict() # Abre o arquivo em modo leitura de texto garantindo o encoding UTF-8 - with open(debug_file_path, "r", encoding="utf-8") as platform_file: - for line in platform_file: - config_info = line.strip().split() - - if not config_info: - continue - - match config_info[0]: - case "router_addressing": - if config_info[1].upper() == "XY": - self.router_addressing = self.XY - else: - self.router_addressing = self.HAMILTONIAN - case "mpsoc_x": - self.mpsoc_x = int(config_info[1]) - case "mpsoc_y": - self.mpsoc_y = int(config_info[1]) - case "cluster_x": - self.cluster_x = int(config_info[1]) - case "cluster_y": - self.cluster_y = int(config_info[1]) - case "manager_position_x": - self.manager_position_x = int(config_info[1]) - case "manager_position_y": - self.manager_position_y = int(config_info[1]) - case "global_manager_cluster": - self.global_manager_cluster = int(config_info[1]) - case "flit_size": - self.flit_size = int(config_info[1]) - case "clock_period_ns": - self.clock_period_in_ns = int(config_info[1]) - case "channel_number": - self.channel_number = int(config_info[1]) - case "BEGIN_task_name_relation": - # Passamos o OBJETO do arquivo, não a string do caminho - self.initialize_task_naming(platform_file) - case _: - pass + try: + with open(debug_file_path+"/platform.cfg", "r", encoding="utf-8") as platform_file: + for line in platform_file: + config_info = line.strip().split() + + if not config_info: + continue + + match config_info[0]: + case "router_addressing": + if config_info[1].upper() == "XY": + self.router_addressing = self.XY + else: + self.router_addressing = self.HAMILTONIAN + case "mpsoc_x": + self.mpsoc_x = int(config_info[1]) + case "mpsoc_y": + self.mpsoc_y = int(config_info[1]) + case "cluster_x": + self.cluster_x = int(config_info[1]) + case "cluster_y": + self.cluster_y = int(config_info[1]) + case "manager_position_x": + self.manager_position_x = int(config_info[1]) + case "manager_position_y": + self.manager_position_y = int(config_info[1]) + case "global_manager_cluster": + self.global_manager_cluster = int(config_info[1]) + case "flit_size": + self.flit_size = int(config_info[1]) + case "clock_period_ns": + self.clock_period_in_ns = int(config_info[1]) + case "channel_number": + self.channel_number = int(config_info[1]) + case "BEGIN_task_name_relation": + # Passamos o OBJETO do arquivo, não a string do caminho + self.initialize_task_naming(platform_file) + case _: + pass + except FileNotFoundError: + print("Erro") def initialize_task_naming(self, platform_file): """Lê as tarefas diretamente do objeto de arquivo aberto.""" @@ -112,6 +115,7 @@ def initialize_task_naming(self, platform_file): # Ignora caso o ID não seja um número válido continue + # Método de acesso necessário para compatibilidade com sua interface gráfica def get_task_name_hash(self): return self.task_name_hash From 15b128891733dcfa3a9e88c3bb9b3c0fbcf4717b Mon Sep 17 00:00:00 2001 From: DrakRosmann Date: Thu, 24 Sep 2026 10:59:33 -0300 Subject: [PATCH 4/5] Aba Services list funcionando --- main.py | 49 +++++++++++++++++++++++++-------------------- util/MPSoCConfig.py | 30 ++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/main.py b/main.py index f08b0f6..d6eb08c 100644 --- a/main.py +++ b/main.py @@ -60,6 +60,7 @@ def __init__(self): self.actionCommunication_Overview.triggered.connect(self.open_communication) self.actionTask_Mapping_Overview.triggered.connect(self.open_taskmap) self.actionTask_List.triggered.connect(self.taskList) + self.actionServices_List.triggered.connect(self.servicesList) # ========================================== # LÓGICA DE MUDANÇA DE TEMA @@ -146,9 +147,9 @@ def open_file(self): if faltando: QMessageBox.critical( self, - "Invalid Directory", - "The selected directory is not a valid debug directory.\n\n" - "Missing files:\n" + "\n".join(f"• {arquivo}" for arquivo in faltando) + "Diretório invalido", + "O diretório selecionado não é um diretório de debug valido.\n\n" + "Selecione um diretório valido" ) return @@ -211,35 +212,39 @@ def build_router_matrix(self): self.frame_scroll_area.setWidget(self.router_matrix_widget) def taskList(self): - services_hash = self.mpconfig.get_task_name_hash() + task_hash = self.mpconfig.get_task_name_hash() + rows = [(task_hash[key], str(key)) for key in sorted(task_hash.keys())] + self.task_list_frame = self.create_table_window("Current Task List", ["Task Name", "ID"], rows) - self.task_list_frame = QWidget() - self.task_list_frame.setWindowTitle("Current Task List") + def servicesList(self): + services_hash = self.mpconfig.get_services_hash() + rows = [(services_hash[key], f"{key:X}") for key in sorted(services_hash.keys())] + self.services_list_frame = self.create_table_window("Services List", ["Service Name", "ID (Hex)"], rows) - self.task_list_frame.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) + def create_table_window(self, title, headers, rows): + """Cria e exibe uma janela com uma tabela de duas colunas (nome e ID).""" + table_frame = QWidget() + table_frame.setWindowTitle(title) - self.task_list_frame.setGeometry(100, 100, 500, 500) + table_frame.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) - table_widget = QTableWidget() - table_widget.setColumnCount(2) - table_widget.setHorizontalHeaderLabels(["Task Name", "ID"]) - table_widget.setRowCount(len(services_hash)) - - for row, key in enumerate(sorted(services_hash.keys())): - task_name = services_hash[key] + table_frame.setGeometry(100, 100, 500, 500) - name_item = QTableWidgetItem(task_name) - id_item = QTableWidgetItem(str(key)) + table_widget = QTableWidget() + table_widget.setColumnCount(len(headers)) + table_widget.setHorizontalHeaderLabels(headers) + table_widget.setRowCount(len(rows)) - table_widget.setItem(row, 0, name_item) - table_widget.setItem(row, 1, id_item) + for row, values in enumerate(rows): + for column, value in enumerate(values): + table_widget.setItem(row, column, QTableWidgetItem(value)) layout = QVBoxLayout() layout.addWidget(table_widget) - self.task_list_frame.setLayout(layout) - - self.task_list_frame.show() + table_frame.setLayout(layout) + table_frame.show() + return table_frame class NovaJanela(QWidget, Ui_Form): diff --git a/util/MPSoCConfig.py b/util/MPSoCConfig.py index a112b28..e56bf7a 100644 --- a/util/MPSoCConfig.py +++ b/util/MPSoCConfig.py @@ -90,6 +90,8 @@ def __init__(self, debug_file_path): except FileNotFoundError: print("Erro") + self.initialize_services(debug_file_path + "/services.cfg") + def initialize_task_naming(self, platform_file): """Lê as tarefas diretamente do objeto de arquivo aberto.""" self.task_name_hash = SortedDict() @@ -116,6 +118,32 @@ def initialize_task_naming(self, platform_file): continue - # Método de acesso necessário para compatibilidade com sua interface gráfica + def initialize_services(self, services_file_path): + """Lê os serviços (nome e código hexadecimal) do arquivo services.cfg.""" + self.services_hash = SortedDict() + + try: + with open(services_file_path, "r", encoding="utf-8") as services_file: + for line in services_file: + service_info = line.strip().split() + + # Ignora linhas vazias e as definições especiais iniciadas com "$" + if len(service_info) < 2 or service_info[0].startswith("$"): + continue + + try: + service_id = int(service_info[1], 16) + except ValueError: + # Ignora caso o código não seja um hexadecimal válido + continue + + self.services_hash[service_id] = service_info[0] + except FileNotFoundError: + print("Erro") + + # Métodos de acesso necessários para compatibilidade com sua interface gráfica def get_task_name_hash(self): return self.task_name_hash + + def get_services_hash(self): + return self.services_hash From 8fe9e6b5acf6cc0ba5de38b9cf743ee384c3cb36 Mon Sep 17 00:00:00 2001 From: DrakRosmann Date: Thu, 24 Sep 2026 16:36:36 -0300 Subject: [PATCH 5/5] Adicionado a capacidade de simular [feature] --- main.py | 185 ++++++++++++++++++++++++++++++++++- router_matrix.py | 229 ++++++++++++++++++++++++++++++++++++++++++-- util/MPSoCConfig.py | 167 ++++++++++++++++++++++++++++++-- 3 files changed, 562 insertions(+), 19 deletions(-) diff --git a/main.py b/main.py index d6eb08c..af9de2d 100644 --- a/main.py +++ b/main.py @@ -3,13 +3,16 @@ from PySide6.QtGui import Qt, QPalette, QColor, QAction, QKeySequence from PySide6.QtWidgets import (QApplication, QMainWindow, QFileDialog, QMessageBox, QWidget, - QVBoxLayout, QScrollArea, QTableWidgetItem, QTableWidget) + QVBoxLayout, QHBoxLayout, QScrollArea, QTableWidgetItem, QTableWidget, QLabel, + QHeaderView) from ui_mainwindow import MainWindow from commsUi import Ui_Form from taskmap import Ui_TaskMap from util import MPSoCConfig +from information import MPSoCInformation from router_matrix import RouterMatrixWidget +from simulation import SimulationController, MAX_REPAINT_SPEED, ticks_to_ms from slave_matrix import SlaveMatrixWidget from pe_matrix import PEMatrixWidget @@ -17,8 +20,10 @@ class MinhaJanela(QMainWindow, MainWindow): filePath = "" mpconfig = None + mpsoc_information = None router_matrix_widget = None frame_scroll_area = None + simulation = None # Arquivos obrigatórios em um diretório de debug REQUIRED_FILES = ("platform.cfg", "services.cfg", "traffic_router.txt") @@ -62,6 +67,34 @@ def __init__(self): self.actionTask_List.triggered.connect(self.taskList) self.actionServices_List.triggered.connect(self.servicesList) + # --------------------------------------------------------- + # CONTROLE DA SIMULAÇÃO + # --------------------------------------------------------- + self.horizontalSlider.setRange(0, 100) + self.horizontalSlider.setValue(MAX_REPAINT_SPEED) + + self.pushButton_2.clicked.connect(self.next_packet) # >|| + self.pushButton.clicked.connect(self.play_simulation) # > + self.pushButton_3.clicked.connect(self.stop_simulation) # STOP + self.toolButton.setText("Go") + self.toolButton.clicked.connect(self.go_to_time) + self.lineEdit.setPlaceholderText("Time in ticks") + self.lineEdit.returnPressed.connect(self.go_to_time) + self.actionReset_Simulation.triggered.connect(self.reset_simulation) + self.actionRest_Graphical_Path.triggered.connect(self.reset_graphical_path) + + # Tabela "Current Packet Information" com uma única linha + self.tableWidget.setRowCount(1) + self.tableWidget.verticalHeader().setVisible(False) + self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) + self.reset_current_packet_table() + + # Tempo de simulação exibido na caixa "Speed Control" + self.build_speed_control() + self.update_simulation_time(0) + + self.set_simulation_buttons(loaded=False) + # ========================================== # LÓGICA DE MUDANÇA DE TEMA # ========================================== @@ -135,6 +168,8 @@ def open_taskmap(self): def update_slide(self, valor): self.label_2.setText(str(valor)) + if self.simulation is not None: + self.simulation.set_speed(valor) def open_file(self): caminho = QFileDialog.getExistingDirectory(self, "Open Debug Directory", "./") @@ -163,7 +198,13 @@ def open_file(self): self.actionTask_List.setEnabled(True) self.mpconfig = MPSoCConfig.MPSoCConfig(self.filePath) + + # Fecha o traffic_router.txt do debug anterior antes de abrir o novo + if self.mpsoc_information is not None: + self.mpsoc_information.close() + self.mpsoc_information = MPSoCInformation(self.mpconfig) self.build_router_matrix() + self.start_simulation() def build_router_matrix(self): """ @@ -180,6 +221,7 @@ def build_router_matrix(self): self.mpconfig.mpsoc_y, self.mpconfig.cluster_x, self.mpconfig.cluster_y, + mpsoc_config=self.mpconfig, ) # Primeira vez: cria um QScrollArea dentro do self.frame para @@ -211,6 +253,143 @@ def build_router_matrix(self): self.router_matrix_widget.adjustSize() self.frame_scroll_area.setWidget(self.router_matrix_widget) + # ========================================== + # SIMULAÇÃO DOS ROTEADORES + # ========================================== + def start_simulation(self): + """Cria o controlador da simulação para o debug carregado (ou recarregado).""" + if self.simulation is not None: + self.simulation.stop() + self.simulation.deleteLater() + + self.simulation = SimulationController(self.mpconfig, self.mpsoc_information, + self.router_matrix_widget, self) + self.simulation.set_speed(self.horizontalSlider.value()) + self.simulation.time_changed.connect(self.update_simulation_time) + self.simulation.packet_changed.connect(self.update_current_packet_table) + self.simulation.running_changed.connect(self.on_simulation_running_changed) + self.simulation.finished.connect(self.on_simulation_finished) + self.simulation.unknown_service.connect(self.on_unknown_service) + + self.update_simulation_time(0) + self.reset_current_packet_table() + self.set_simulation_buttons(loaded=True) + + def set_simulation_buttons(self, loaded, running=False): + self.pushButton.setEnabled(loaded and not running) + self.pushButton_2.setEnabled(loaded and not running) + self.pushButton_3.setEnabled(loaded and running) + self.toolButton.setEnabled(loaded and not running) + self.lineEdit.setEnabled(loaded and not running) + + def play_simulation(self): + if self.simulation is not None: + self.simulation.play() + + def stop_simulation(self): + if self.simulation is not None: + self.simulation.stop() + + def next_packet(self): + if self.simulation is not None: + self.simulation.step() + + def go_to_time(self): + if self.simulation is None: + return + try: + self.simulation.go_to_time(int(self.lineEdit.text())) + except ValueError: + QMessageBox.warning(self, "Attention", "Enter only numbers and valid times!\n\n" + "It is only possible to go to a time already simulated.") + + def reset_simulation(self): + """Recarrega o debug atual e volta a simulação para o início.""" + if self.mpconfig is None: + QMessageBox.warning(self, "Attention", "Please, load a debugging before") + return + + self.stop_simulation() + self.mpconfig = MPSoCConfig.MPSoCConfig(self.filePath) + self.mpsoc_information.close() + self.mpsoc_information = MPSoCInformation(self.mpconfig) + self.build_router_matrix() + self.start_simulation() + + def reset_graphical_path(self): + if self.simulation is None: + QMessageBox.warning(self, "Attention", "Please, load a debugging before") + return + self.simulation.reset_graphical_path() + + def on_simulation_running_changed(self, running): + self.set_simulation_buttons(loaded=True, running=running) + + def on_simulation_finished(self): + self.statusbar.showMessage("End of traffic_router.txt reached", 5000) + + def on_unknown_service(self, packet): + if self.mpconfig.router_addressing == MPSoCConfig.MPSoCConfig.XY: + router = self.mpconfig.ham_address_to_xy_label(packet.router_address) + else: + router = str(packet.router_address) + + QMessageBox.critical(self, "Error", + f"ERROR: Service <{packet.service}> packet unidentified.\n" + f"Router: {router} Input port: {packet.get_input_port_string()}") + + def build_speed_control(self): + """ + Reorganiza a caixa "Speed Control" igual ao debugger original: + velocidade no canto superior direito, slider no meio e, embaixo, + o tempo de simulação em ms (esquerda) e em ticks (direita). + """ + for widget in (self.horizontalSlider, self.label_2, self.label): + self.horizontalLayout_4.removeWidget(widget) + + # O antigo rótulo fixo "Ticks" passa a mostrar os ticks da simulação + self.simulation_ticks_label = self.label + self.simulation_time_label = QLabel(self.groupBox_2) + + speed_row = QHBoxLayout() + speed_row.addStretch() + speed_row.addWidget(self.label_2) + + time_row = QHBoxLayout() + time_row.addWidget(self.simulation_time_label) + time_row.addStretch() + time_row.addWidget(self.simulation_ticks_label) + + speed_layout = QVBoxLayout() + speed_layout.setSpacing(0) + speed_layout.addLayout(speed_row) + speed_layout.addWidget(self.horizontalSlider) + speed_layout.addLayout(time_row) + self.horizontalLayout_4.addLayout(speed_layout) + + def update_simulation_time(self, ticks): + clock_period = self.mpconfig.clock_period_in_ns if self.mpconfig else 0 + self.simulation_time_label.setText(f"{ticks_to_ms(ticks, clock_period):.5f} ms") + self.simulation_ticks_label.setText(f"{ticks} ticks") + + def update_current_packet_table(self, packet): + if self.mpconfig.router_addressing == MPSoCConfig.MPSoCConfig.HAMILTONIAN: + source = str(packet.router_address) + target = str(packet.target_router) + else: + source = self.mpconfig.ham_address_to_xy_label(packet.router_address) + target = self.mpconfig.ham_address_to_xy_label(packet.target_router) + + values = (source, target, + self.mpconfig.get_string_service_name(packet.service), + f"{packet.size} | {packet.bandwidth_cycles}") + for column, value in enumerate(values): + self.tableWidget.setItem(0, column, QTableWidgetItem(value)) + + def reset_current_packet_table(self): + for column in range(self.tableWidget.columnCount()): + self.tableWidget.setItem(0, column, QTableWidgetItem("-")) + def taskList(self): task_hash = self.mpconfig.get_task_name_hash() rows = [(task_hash[key], str(key)) for key in sorted(task_hash.keys())] @@ -218,8 +397,8 @@ def taskList(self): def servicesList(self): services_hash = self.mpconfig.get_services_hash() - rows = [(services_hash[key], f"{key:X}") for key in sorted(services_hash.keys())] - self.services_list_frame = self.create_table_window("Services List", ["Service Name", "ID (Hex)"], rows) + rows = [(services_hash[key], str(key)) for key in sorted(services_hash.keys())] + self.services_list_frame = self.create_table_window("Services List", ["Service Name", "ID"], rows) def create_table_window(self, title, headers, rows): """Cria e exibe uma janela com uma tabela de duas colunas (nome e ID).""" diff --git a/router_matrix.py b/router_matrix.py index 01c9f53..8d6f332 100644 --- a/router_matrix.py +++ b/router_matrix.py @@ -22,14 +22,118 @@ A numeração de cada roteador segue a mesma convenção do print de referência: o roteador "0x0" fica no canto inferior esquerdo e o eixo Y cresce para cima (0x0, 0x1, 0x2, ... de baixo para cima). + +Durante a simulação, cada roteador pinta de vermelho as setas por onde o +pacote atual passou (paint_arrow) e mostra a taxa de uso de cada enlace +(update_throughput), igual ao Roteador.java do debugger original. """ import math +import os -from PySide6.QtCore import Qt +from PySide6.QtCore import QEvent, QObject, QRect, Qt, Signal +from PySide6.QtGui import QFont, QPainter, QPixmap from PySide6.QtWidgets import QFrame, QGridLayout, QWidget from Roteador import Ui_Form +from util.MPSoCConfig import MPSoCConfig + + +IMAGES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "images") + +# Cache das imagens já carregadas (QPixmap só pode ser criado com a QApplication ativa) +_pixmap_cache: dict[str, QPixmap] = {} + + +def _pixmap(name: str) -> QPixmap: + if name not in _pixmap_cache: + _pixmap_cache[name] = QPixmap(os.path.join(IMAGES_DIR, name)) + return _pixmap_cache[name] + + +# Imagem do roteador de acordo com o tipo de PE +_ROUTER_IMAGES = { + MPSoCConfig.SLAVE: "Router.png", + MPSoCConfig.CLUSTER_MASTER: "Router_cluster_master.png", + MPSoCConfig.GLOBAL_MASTER: "Router_master.png", +} + +# Seta -> (widget do Roteador.ui, imagem quando pintada de vermelho) +_ARROWS = { + MPSoCConfig.EAST_IN_HIGH: ("in_east_HIGH", "red_left.png"), + MPSoCConfig.EAST_OUT_HIGH: ("out_east_HIGH", "red_undirected_h.png"), + MPSoCConfig.EAST_IN_LOW: ("in_east_LOW", "red_left.png"), + MPSoCConfig.EAST_OUT_LOW: ("out_east_LOW", "red_undirected_h.png"), + MPSoCConfig.NORTH_IN_HIGH: ("in_north_HIGH", "red_down.png"), + MPSoCConfig.NORTH_OUT_HIGH: ("out_north_HIGH", "red_undirected_v.png"), + MPSoCConfig.NORTH_IN_LOW: ("in_north_LOW", "red_down.png"), + MPSoCConfig.NORTH_OUT_LOW: ("out_north_LOW", "red_undirected_v.png"), + MPSoCConfig.WEST_IN_HIGH: ("in_weast_HIGH", "red_right.png"), + MPSoCConfig.WEST_OUT_HIGH: ("out_weast_HIGH", "red_undirected_h.png"), + MPSoCConfig.WEST_IN_LOW: ("in_weast_LOW", "red_right.png"), + MPSoCConfig.WEST_OUT_LOW: ("out_weast_LOW", "red_undirected_h.png"), + MPSoCConfig.SOUTH_IN_HIGH: ("in_south_HIGH", "red_up.png"), + MPSoCConfig.SOUTH_OUT_HIGH: ("out_south_HIGH", "red_undirected_v.png"), + MPSoCConfig.SOUTH_IN_LOW: ("in_south_LOW", "red_up.png"), + MPSoCConfig.SOUTH_OUT_LOW: ("out_south_LOW", "red_undirected_v.png"), + MPSoCConfig.LOCAL_IN: ("local_in", "red_local_in.png"), + MPSoCConfig.LOCAL_OUT: ("local_out", "red_local_out.png"), +} + +# Setas (e imagens em estado normal) de cada lado do roteador; só são +# desenhadas quando existe um vizinho naquele lado +_WEST_ARROWS = {"out_weast_HIGH": "undirected_h.png", "in_weast_HIGH": "right.png", + "out_weast_LOW": "undirected_h.png", "in_weast_LOW": "right.png"} +_EAST_ARROWS = {"in_east_LOW": "left.png", "out_east_LOW": "undirected_h.png", + "in_east_HIGH": "left.png", "out_east_HIGH": "undirected_h.png"} +_SOUTH_ARROWS = {"in_south_LOW": "up.png", "out_south_LOW": "undirected_v.png", + "in_south_HIGH": "up.png", "out_south_HIGH": "undirected_v.png"} +_NORTH_ARROWS = {"out_north_HIGH": "undirected_v.png", "in_north_HIGH": "down.png", + "out_north_LOW": "undirected_v.png", "in_north_LOW": "down.png"} + +# Porta física -> rótulo com a taxa de uso do enlace +_PORT_LABELS = { + MPSoCConfig.EAST0: "east_HIGH_Label", + MPSoCConfig.EAST1: "east_LOW_Label", + MPSoCConfig.NORTH0: "north_HIGH_Label", + MPSoCConfig.NORTH1: "north_LOW_Label", + MPSoCConfig.WEST0: "weast_HIGH_Label", + MPSoCConfig.WEST1: "weast_LOW_Label", + MPSoCConfig.SOUTH0: "south_HIGH_Label", + MPSoCConfig.SOUTH1: "south_LOW_Label", + MPSoCConfig.LOCAL0: "local_Label", + MPSoCConfig.LOCAL1: "local_Label", +} + + +class _ImagePainter(QObject): + """ + Desenha uma imagem esticada sobre os QWidgets "vazios" do Roteador.ui + (setas e corpo do roteador), no lugar do UJPanelImagem do Java. + """ + + def __init__(self, parent=None): + super().__init__(parent) + self.images: dict[QWidget, str] = {} + + def set_image(self, widget: QWidget, name: str | None) -> None: + """Define a imagem do widget; None deixa o widget vazio.""" + if self.images.get(widget) == name: + return + if widget not in self.images: + widget.installEventFilter(self) + self.images[widget] = name + widget.update() + + def eventFilter(self, obj, event): + if event.type() == QEvent.Type.Paint and obj in self.images: + if self.images[obj] is None: + return True + painter = QPainter(obj) + painter.drawPixmap(obj.rect(), _pixmap(self.images[obj])) + painter.end() + return True + return super().eventFilter(obj, event) # Cores fixas do roteador, independentes do tema (claro/escuro) que o @@ -57,14 +161,44 @@ class RouterWidget(QWidget, Ui_Form): "XxY" do roteador dentro da malha, igual ao print de referência. """ - def __init__(self, pos_x: int, pos_y: int, parent=None): + # Emitido com o endereço hamiltoniano quando o roteador é clicado + clicked = Signal(int) + + def __init__(self, pos_x: int, pos_y: int, parent=None, + mpsoc_x: int = 1, mpsoc_y: int = 1, mpsoc_config=None): super().__init__(parent) self.setupUi(self) self.pos_x = pos_x self.pos_y = pos_y - - self.routerLabel.setText(f"{pos_x}x{pos_y}") + self.mpsoc_x = mpsoc_x + self.mpsoc_y = mpsoc_y + self.need_reset = True + + if mpsoc_config is not None: + self.router_address = mpsoc_config.xy_to_ham_addr((pos_x << 8) | pos_y) + self.pe_type = mpsoc_config.get_pe_type(self.router_address) + if mpsoc_config.channel_number == 1: + self._set_single_channel_geometry() + else: + self.router_address = pos_y * mpsoc_x + pos_x + self.pe_type = MPSoCConfig.SLAVE + + if mpsoc_config is not None and mpsoc_config.router_addressing == MPSoCConfig.HAMILTONIAN: + self.routerLabel.setText(str(self.router_address)) + else: + self.routerLabel.setText(f"{pos_x}x{pos_y}") + + self.router.setCursor(Qt.CursorShape.PointingHandCursor) + self._painter = _ImagePainter(self) + + # "Andale Mono" nem sempre está instalada; usa qualquer fonte + # monoespaçada em tamanho fixo para o "00.00%" caber no rótulo + percent_font = QFont("Monospace") + percent_font.setStyleHint(QFont.StyleHint.Monospace) + percent_font.setPixelSize(10) + for label_name in set(_PORT_LABELS.values()): + getattr(self, label_name).setFont(percent_font) # Isola este roteador do tema global do app (ver comentário acima # de _ROUTER_STYLESHEET): fixa cores próprias em toda a subárvore, @@ -74,12 +208,73 @@ def __init__(self, pos_x: int, pos_y: int, parent=None): child.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) self.setStyleSheet(_ROUTER_STYLESHEET) + self.reset_arrows() + + def _set_single_channel_geometry(self) -> None: + """Com apenas um canal físico, as setas LOW ocupam o centro de cada lado.""" + self.north_LOW_Label.setGeometry(QRect(70, 0, 40, 20)) + self.in_north_LOW.setGeometry(QRect(110, 0, 20, 30)) + self.out_north_LOW.setGeometry(QRect(77, 0, 6, 30)) + + self.east_LOW_Label.setGeometry(QRect(90, 30, 40, 20)) + self.in_east_LOW.setGeometry(QRect(160, 63, 33, 20)) + self.out_east_LOW.setGeometry(QRect(160, 107, 30, 6)) + + self.south_LOW_Label.setGeometry(QRect(40, 110, 40, 20)) + self.in_south_LOW.setGeometry(QRect(70, 160, 20, 30)) + self.out_south_LOW.setGeometry(QRect(117, 160, 6, 30)) + + self.weast_LOW_Label.setGeometry(QRect(1, 70, 40, 20)) + self.in_weast_LOW.setGeometry(QRect(0, 100, 30, 20)) + self.out_weast_LOW.setGeometry(QRect(0, 70, 30, 6)) + + def mouseReleaseEvent(self, event): + if self.router.geometry().contains(event.position().toPoint()): + self.clicked.emit(self.router_address) + super().mouseReleaseEvent(event) + + def reset_arrows(self) -> None: + """Volta todas as setas para o estado normal (sem pacote passando).""" + self.need_reset = False + + self._painter.set_image(self.router, _ROUTER_IMAGES.get(self.pe_type, "Router.png")) + self._painter.set_image(self.local_in, "local_in.png") + self._painter.set_image(self.local_out, "local_out.png") + + # Setas de bordas sem vizinho ficam vazias; só aparecem (em vermelho) + # quando um pacote de periférico passa por elas + sides = ( + (_WEST_ARROWS, self.pos_x != 0), + (_EAST_ARROWS, self.pos_x != self.mpsoc_x - 1), + (_SOUTH_ARROWS, self.pos_y != 0), + (_NORTH_ARROWS, self.pos_y != self.mpsoc_y - 1), + ) + + for side, has_neighbor in sides: + for widget_name, image in side.items(): + self._painter.set_image(getattr(self, widget_name), image if has_neighbor else None) + + def paint_arrow(self, arrow: int) -> None: + """Pinta de vermelho a seta indicada (constantes *_IN_*/*_OUT_* do MPSoCConfig).""" + if arrow not in _ARROWS: + return + + widget_name, image = _ARROWS[arrow] + self.need_reset = True + self._painter.set_image(getattr(self, widget_name), image) + + def update_throughput(self, port: int, value: float) -> None: + """Atualiza o percentual de uso do enlace da porta física informada.""" + label_name = _PORT_LABELS.get(port) + if label_name is not None: + self.set_link_load(getattr(self, label_name), value) + def set_link_load(self, direction_label: QWidget, value: float) -> None: """ Atualiza o percentual mostrado em um dos rótulos de enlace - (north_HIGH_Label, south_LOW_Label, etc.), útil para quando os dados - reais de tráfego estiverem disponíveis. + (north_HIGH_Label, south_LOW_Label, etc.). """ + value = min(value, 99.99) direction_label.setText(f"{value:05.2f}%") @@ -91,9 +286,11 @@ class RouterMatrixWidget(QWidget): """ def __init__(self, mpsoc_x: int, mpsoc_y: int, - cluster_x: int, cluster_y: int, parent=None): + cluster_x: int, cluster_y: int, parent=None, mpsoc_config=None): super().__init__(parent) + self.mpsoc_config = mpsoc_config + # mpsoc_x/mpsoc_y é o tamanho TOTAL da malha. self.total_cols = max(1, mpsoc_x) self.total_rows = max(1, mpsoc_y) @@ -105,7 +302,9 @@ def __init__(self, mpsoc_x: int, mpsoc_y: int, self.cluster_y = max(1, min(cluster_y, self.total_rows)) if cluster_y else self.total_rows # Guarda referência de cada roteador por posição global (x, y) + # e pelo endereço hamiltoniano usado no traffic_router.txt self.routers: dict[tuple[int, int], RouterWidget] = {} + self.routers_by_address: dict[int, RouterWidget] = {} self._build_matrix() @@ -175,8 +374,10 @@ def _build_cluster(self, cluster_x: int, cluster_y: int, for global_y in range(y_start, y_end): for global_x in range(x_start, x_end): - router = RouterWidget(global_x, global_y, cluster_frame) + router = RouterWidget(global_x, global_y, cluster_frame, + self.total_cols, self.total_rows, self.mpsoc_config) self.routers[(global_x, global_y)] = router + self.routers_by_address[router.router_address] = router local_x = global_x - x_start # Linha 0 do grid = topo, então invertemos o Y local também @@ -188,4 +389,14 @@ def _build_cluster(self, cluster_x: int, cluster_y: int, def get_router(self, x: int, y: int) -> RouterWidget | None: """Retorna o RouterWidget na posição global (x, y), se existir.""" - return self.routers.get((x, y)) \ No newline at end of file + return self.routers.get((x, y)) + + def get_router_by_address(self, router_address: int) -> RouterWidget | None: + """Retorna o RouterWidget pelo endereço hamiltoniano, se existir.""" + return self.routers_by_address.get(router_address) + + def reset_routers(self) -> None: + """Apaga as setas pintadas de todos os roteadores.""" + for router in self.routers.values(): + if router.need_reset: + router.reset_arrows() \ No newline at end of file diff --git a/util/MPSoCConfig.py b/util/MPSoCConfig.py index e56bf7a..406b1b7 100644 --- a/util/MPSoCConfig.py +++ b/util/MPSoCConfig.py @@ -2,6 +2,32 @@ class MPSoCConfig: + # Portas físicas do roteador (índice da porta no traffic_router.txt) + EAST0 = 0 + EAST1 = 1 + WEST0 = 2 + WEST1 = 3 + NORTH0 = 4 + NORTH1 = 5 + SOUTH0 = 6 + SOUTH1 = 7 + LOCAL0 = 8 + LOCAL1 = 9 + + NPORT = 10 + + PORT_NAMES = ("EAST 0", "EAST 1", "WEST 0", "WEST 1", "NORTH 0", + "NORTH 1", "SOUTH 0", "SOUTH 1", "LOCAL 0", "LOCAL 1") + + # Canais + LOW = 0x100 + HIGH = 0 + + # Tipos de PE + GLOBAL_MASTER = 0 + CLUSTER_MASTER = 1 + SLAVE = 2 + # Constantes de Direção (Variáveis de Classe) EAST_IN_HIGH = 0 EAST_OUT_HIGH = 1 @@ -42,10 +68,13 @@ def __init__(self, debug_file_path): self.global_manager_cluster = 0 self.flit_size = 0 self.clock_period_in_ns = 0 - self.channel_number = 0 + self.channel_number = 2 self.services_hash = SortedDict() self.task_name_hash = SortedDict() + self.service_reference = [] + self.task_allocation_services = [] + self.task_terminated_services = [] # Abre o arquivo em modo leitura de texto garantindo o encoding UTF-8 try: @@ -90,6 +119,7 @@ def __init__(self, debug_file_path): except FileNotFoundError: print("Erro") + self.discovery_global_xy_position() self.initialize_services(debug_file_path + "/services.cfg") def initialize_task_naming(self, platform_file): @@ -119,28 +149,151 @@ def initialize_task_naming(self, platform_file): def initialize_services(self, services_file_path): - """Lê os serviços (nome e código hexadecimal) do arquivo services.cfg.""" + """Lê os serviços (nome e código decimal) do arquivo services.cfg.""" self.services_hash = SortedDict() + self.task_allocation_services = [] + self.task_terminated_services = [] try: with open(services_file_path, "r", encoding="utf-8") as services_file: for line in services_file: service_info = line.strip().split() - # Ignora linhas vazias e as definições especiais iniciadas com "$" - if len(service_info) < 2 or service_info[0].startswith("$"): + if len(service_info) < 2: continue try: - service_id = int(service_info[1], 16) + values = [int(value) for value in service_info[1:]] except ValueError: - # Ignora caso o código não seja um hexadecimal válido + # Ignora caso o código não seja um número válido continue - self.services_hash[service_id] = service_info[0] + # Linhas especiais com a lista de serviços de alocação/término de tarefa + match service_info[0]: + case "$TASK_ALLOCATION_SERVICE": + self.task_allocation_services.extend(values) + case "$TASK_TERMINATED_SERVICE": + self.task_terminated_services.extend(values) + case _: + self.services_hash[values[0]] = service_info[0] except FileNotFoundError: print("Erro") + self.service_reference = list(self.services_hash.keys()) + + def discovery_global_xy_position(self): + """Descobre a posição XY do gerente global a partir do cluster configurado.""" + if self.cluster_x == 0 or self.mpsoc_x == 0: + return + + cluster_master = self.global_manager_cluster + 1 + x_master = 0 + y_master = 0 + + while cluster_master > 0: + if self.mpsoc_x % (cluster_master * self.cluster_x) == 0: + x_master = cluster_master - 1 + else: + y_master += 1 + cluster_master -= self.mpsoc_x // self.cluster_x + + self.global_position_x = x_master * self.cluster_x + self.manager_position_x + self.global_position_y = y_master * self.cluster_y + self.manager_position_y + + # ========================================== + # PORTAS E CANAIS + # ========================================== + @staticmethod + def get_channel(port): + """Portas pares (EAST0, WEST0, NORTH0, SOUTH0, LOCAL0) são do canal HIGH.""" + return MPSoCConfig.HIGH if port % 2 == 0 else MPSoCConfig.LOW + + @staticmethod + def get_port_string(port): + if 0 <= port < MPSoCConfig.NPORT: + return MPSoCConfig.PORT_NAMES[port] + return None + + # ========================================== + # SERVIÇOS + # ========================================== + def get_string_service_name(self, service): + return self.services_hash.get(service, "---") + + def get_service_value(self, name): + for service_value, service_name in self.services_hash.items(): + if service_name == name: + return service_value + return -1 + + # ========================================== + # ENDEREÇAMENTO (XY <-> HAMILTONIANO) + # ========================================== + 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.mpsoc_x) + (self.mpsoc_x - x) - 1 + return (y * self.mpsoc_x) + x + + def ham_to_xy_addr(self, ham_addr): + ham_addr &= 0xFF # limpa o header + y, addr = divmod(ham_addr, self.mpsoc_x) + + x = self.mpsoc_x - addr - 1 if y % 2 == 1 else addr + return (x << 8) | y + + def ham_address_to_xy_label(self, ham_addr): + xy_addr = self.ham_to_xy_addr(ham_addr) + return f"{xy_addr >> 8}x{xy_addr & 0xFF}" + + def xy_label_to_ham_address(self, xy_label): + x, y = (int(value) for value in xy_label.split("x")) + return self.xy_to_ham_addr((x << 8) | y) + + def get_pe_type(self, ham_addr): + xy_addr = self.ham_to_xy_addr(ham_addr) + x = xy_addr >> 8 + y = xy_addr & 0xFF + + if x < self.manager_position_x or y < self.manager_position_y: + return self.SLAVE + + if x == self.global_position_x and y == self.global_position_y: + return self.GLOBAL_MASTER + + if ((x - self.manager_position_x) % self.cluster_x == 0 + and (y - self.manager_position_y) % self.cluster_y == 0): + return self.CLUSTER_MASTER + + return self.SLAVE + + # ========================================== + # PLATAFORMA + # ========================================== + def get_pe_number(self): + return self.mpsoc_x * self.mpsoc_y + + def get_testcase_path(self): + index = self.debug_file_path.find("debug") + if index < 1: + return None + return self.debug_file_path[:index - 1] + + def get_frequency_in_hz(self): + return 1_000_000_000 // self.clock_period_in_ns + + def get_frequency_in_khz(self): + return self.get_frequency_in_hz() // 1000 + + def get_frequency_in_mhz(self): + return self.get_frequency_in_hz() // 1_000_000 + + def get_link_bandwidth_in_mbps(self): + return self.get_frequency_in_hz() * self.flit_size // 1_000_000 + # Métodos de acesso necessários para compatibilidade com sua interface gráfica def get_task_name_hash(self): return self.task_name_hash