diff --git a/mia_uppg3.txt b/mia_uppg3.txt index d61dbb62df11c36dc704cb7cd0ed5c2711dc54a6..b752d1a8049af126da4c59eb188d6765e8bb78ee 100644 --- a/mia_uppg3.txt +++ b/mia_uppg3.txt @@ -387,22 +387,22 @@ uM: 7f: 0000000 K1: -00: 0a ADD -01: 0b SUB -02: 0c MULT -03: 0f NOP -04: 12 TEST -05: 15 D -06: 1a R -07: 1d T -08: 1f TT -09: 20 TT -0a: 22 TTT -0b: 27 TTTT -0c: 00 FR -0d: 00 FH -0e: 00 LF -0f: 00 FFF +00: 0a LOAD +01: 0b STORE +02: 0c ADD +03: 0f SUB +04: 12 AND +05: 15 LSR +06: 1a BRA +07: 1d BNE +08: 1f HALT +09: 20 CMP +0a: 22 BGE +0b: 27 BEQ +0c: 00 +0d: 00 +0e: 00 +0f: 00 K2: 00: 03 diff --git a/src/simudator/core/modules/memory.py b/src/simudator/core/modules/memory.py index 682f9041c63cb69183ca115a47138e2cd74c0317..5cdce8bb5fbd2889388573d81e75c5147087fbc4 100644 --- a/src/simudator/core/modules/memory.py +++ b/src/simudator/core/modules/memory.py @@ -102,12 +102,15 @@ class Memory(Module): for i in range(len(self.memory)): self.memory[i] = 0 - def get_longest_line_len(self, ignore_keys=[]) -> int: + def get_longest_line_len(self, ignore_keys=None) -> int: """ Helper function for pretty_print that returns the length of the longest value in the memory to print for a module. """ + if ignore_keys is None: + ignore_keys = [] + longest_memory_line = 0 for value in self.memory: diff --git a/src/simudator/core/processor.py b/src/simudator/core/processor.py index 5fd22bef0fa4ed840b1bfd4dc79ac666d5e29483..8b7d31aaa2f97635bdf98fff9b09c2af6dc202ca 100644 --- a/src/simudator/core/processor.py +++ b/src/simudator/core/processor.py @@ -48,7 +48,13 @@ class Processor: self.line_separator = "-" self.is_stopped = False self.new_instruction = False - self.current_instructions: list[str] = [] + + # It is the responsibility of the CPU to tell the gui:s pipeline diagram + # what information to be displayed and how to display it. + # Thus each CPU keeps track of its current instructions together with each + # instructions position in the pipeline diagram. + self.current_instructions: list[tuple[str, int, int]] = [] + # TODO: keeping track of what pieces of info not to show # show not be done at the processor level. # Maybe implement a 'get_pretty_print_state' at module @@ -104,14 +110,34 @@ class Processor: # new assembly instruction if self.is_new_instruction(): self.new_instruction = True - self.current_instructions = self.get_current_instruction() + self.current_instructions = self.get_current_instructions() self.assembly_cycles.append(self.get_clock()) else: self.new_instruction = False - def get_current_instruction(self) -> list[str]: + def get_current_instructions(self) -> list[tuple[str, int, int]]: + """ + Return a list of the current instructions with their positions. + + Each entry in the list is a tuple of a instruction with its column and row position + for the pipeline diagram. + """ + raise NotImplemented + + def get_pipeline_dimensions(self) -> tuple[int, int]: """ - Return the current instruction. Useful for pipeline diagram. + Return a tuple on the form (x, y) for the processors preffered pipeline display size. + + (x, y) represents the number of columns respective rows the pipeline should have. + """ + raise NotImplemented + + def get_asm_instruction(self) -> str: + """ + Return a string containing the 'code' for the current assembly instruction. + + What 'code' refers to is up to each processor to define, could be a memory address, + an instruction label or something else. """ raise NotImplemented @@ -407,7 +433,7 @@ class Processor: memory_modules = [] other_modules = [] - if ignore_keys == None: + if ignore_keys is None: ignore_keys = [] # TODO: remove isinstance(module, micro_memory) diff --git a/src/simudator/gui/gui.py b/src/simudator/gui/gui.py index 88f8a27d084ea39cd1d6e3c769693f0ef165f56f..94e61e82608032f45b76d977261749b13b65120c 100644 --- a/src/simudator/gui/gui.py +++ b/src/simudator/gui/gui.py @@ -132,8 +132,7 @@ class GUI(QMainWindow): self.cpu_running = False self.pipeline = PipeLine() - # Hide pipeline for now since it is not implemented yet - # self.pipeline.show() + self.init_pipeline() # Set Style, THESE ARE TEST AND DONT WORK app = QApplication.instance() @@ -226,14 +225,18 @@ class GUI(QMainWindow): self.breakpoint_action.triggered.connect(self.openBreakpointWindow) # Create 'update value' window button - self.update_value_action = QAction("Update values while running", self, checkable=True) + self.update_value_action = QAction( + "Update values while running", self, checkable=True + ) self.update_value_action.setChecked(False) self.update_value_action.setStatusTip("Toggle value updates while running.") self.update_value_action.triggered.connect(self.toggle_value_update_on_run) # Create 'set delay' window button self.set_delay_action = QAction("Set update delay", self) - self.set_delay_action.setStatusTip("Sets the delay between each update when the cpu is running.") + self.set_delay_action.setStatusTip( + "Sets the delay between each update when the cpu is running." + ) self.set_delay_action.triggered.connect(self.set_update_delay) # Create Tools menu for tool actions actions @@ -256,7 +259,7 @@ class GUI(QMainWindow): self.stop_action.triggered.connect(self.stopToolBarButtonClick) toolbar.addAction(self.stop_action) - # Add Asm label + # Add Asm label self.clock_cycle_label = QLabel("Clock cycle: ", self) toolbar.addWidget(self.clock_cycle_label) @@ -303,7 +306,7 @@ class GUI(QMainWindow): spacing.setFixedWidth(10) toolbar.addWidget(spacing) - # Add Asm label + # Add Asm label self.asm_label = QLabel("Assembler Instructions: ", self) toolbar.addWidget(self.asm_label) @@ -359,18 +362,35 @@ class GUI(QMainWindow): case "CLOCKUPDATE": signal.connect(self.updateCpuClockCycle) + def init_pipeline(self) -> None: + """Initialize the pipeline diagram. + + Sets its height, width and instructions. + """ + size = self.cpu.get_pipeline_dimensions() + + self.pipeline.set_height(size[0]) + self.pipeline.set_width(size[1]) + self.update_pipeline() + + self.pipeline.show() + def updateCpuListeners(self) -> None: """ - Updates the graphics items in the scene and the clock. + Updates the graphics items in the scene, the clock, and the pipeline diagram. Used after the cpu has run or when the user has edited somehting. """ self.cpu_graphics_scene.updateGraphicsItems() self.updateCpuClockCycle() + self.update_pipeline() + + def update_pipeline(self) -> None: + self.pipeline.set_instructions(self.cpu.get_current_instructions()) def updateCpuClockCycle(self) -> None: """ - Update the clock cycle counter. + Update the clock cycle counter. Used while the program is running to show the user nothing has crashed. """ @@ -392,7 +412,6 @@ class GUI(QMainWindow): if self.breakpoint_window is not None: self.breakpoint_window.update() - """ @Slot is used to explicitly mark a python method as a Qt slot and specify a C++ signature for it, which is used most commonly @@ -503,7 +522,9 @@ class GUI(QMainWindow): steps = self.jump_value_box.value() self.cpu_running = True self.setDisabledWhenRunning(True) - simulation_thread = RunThread(self.cpu, self.cpu_tick_signal, self.update_delay, False, False, steps) + simulation_thread = RunThread( + self.cpu, self.cpu_tick_signal, self.update_delay, False, False, steps + ) self.threadpool.start(simulation_thread) def stepAsmToolBarButtonClick(self): @@ -518,7 +539,9 @@ class GUI(QMainWindow): steps = self.asm_jump_value_box.value() self.cpu_running = True self.setDisabledWhenRunning(True) - simultaion_thread = RunThread(self.cpu, self.cpu_tick_signal, self.update_delay, False, True, steps) + simultaion_thread = RunThread( + self.cpu, self.cpu_tick_signal, self.update_delay, False, True, steps + ) self.threadpool.start(simultaion_thread) self.updateCpuListeners() @@ -550,8 +573,8 @@ class GUI(QMainWindow): if self.update_all_values: self.updateCpuListeners() - # A signal of 0 steps signifies end of execution, i.e. the CPU has - # halted or run the specified amount of ticks + # A signal of 0 steps signifies end of execution, i.e. the CPU has + # halted or run the specified amount of ticks # => Enable the relevant parts of the GUI again if steps == 0: self.cpu_running = False @@ -560,7 +583,9 @@ class GUI(QMainWindow): # Inform user of reached break point if self.cpu.breakpoint_reached: - self.messageBox("Reached breakpoint: " + self.cpu.last_breakpoint.__str__()) + self.messageBox( + "Reached breakpoint: " + self.cpu.last_breakpoint.__str__() + ) # Inform user of halt if self.cpu.should_halt(): @@ -836,13 +861,15 @@ class GUI(QMainWindow): """ Toggles whether all values or only clock cycle is being updated each tick. """ - self.update_all_values = not self.update_all_values + self.update_all_values = not self.update_all_values def set_update_delay(self): """ Sets the update delay for the visual updates while the cpu is running. """ - delay, ok = QInputDialog.getDouble(self, "Input Dialog", "Enter a float value:", decimals=5) + delay, ok = QInputDialog.getDouble( + self, "Input Dialog", "Enter a float value:", decimals=5 + ) if ok: self.update_delay = delay diff --git a/src/simudator/gui/module_graphics_item/memory_graphic.py b/src/simudator/gui/module_graphics_item/memory_graphic.py index a39999626666c8470049c8676190c6b588cd9652..2817042cc772ef63fbbce36395542b585fda8eec 100644 --- a/src/simudator/gui/module_graphics_item/memory_graphic.py +++ b/src/simudator/gui/module_graphics_item/memory_graphic.py @@ -1,3 +1,5 @@ +from math import ceil + from qtpy.QtCore import Qt from qtpy.QtCore import Signal as pyqtSignal from qtpy.QtCore import Slot @@ -5,9 +7,8 @@ from qtpy.QtWidgets import ( QAction, QGraphicsRectItem, QGraphicsSimpleTextItem, - QTextEdit, - QVBoxLayout, - QWidget, + QTableWidget, + QTableWidgetItem, ) from simudator.core.modules import Memory @@ -17,36 +18,84 @@ from simudator.gui.orientation import Orientation from simudator.gui.port_graphics_item import PortGraphicsItem -class MemoryWindow(QWidget): - """ - Widget for showing content of memory +class MemoryWindow(QTableWidget): """ + A class showing the contents of a memory module in a QTableWidget. + + This class assumes that the size of the memory module will remain constant. - _ROW_LENGTH = 5 + Parameters + ---------- + memory_module: An instance of the Memory base class. + column_size: An integer specifying the number of columns, optional. + + """ - def __init__(self, memory_module: Memory): + def __init__(self, memory_module: Memory, column_size=-1): super().__init__() - self.module = memory_module + self._memory = memory_module + self._column_size = column_size + self._memory_size = len(self._memory.get_state()["memory"]) + self._set_column_size() + self.setColumnCount(self._column_size) + self.setRowCount(ceil(self._memory_size / self._column_size)) + self.setHorizontalHeaderLabels(["+" + str(i) for i in range(4)]) - self.text = QTextEdit("") - layout = QVBoxLayout() - layout.addWidget(self.text) - self.setLayout(layout) + vertical_headers = [] + for i in range(0, self._memory_size, self._column_size): + vertical_headers.append(str(hex(i))) + + self.setVerticalHeaderLabels(vertical_headers) self.update() def update(self): - memory_str = "" - for address, value in enumerate(self.module.memory): - # Add address and content to string - # Make sure its unifrom lenght so rows are consistent - memory_str += f"{address}" + ": " + f"{value}" + " " + "\t" + """ + Update the content of this widget to reflect the content of the memory module. + """ + memory_content = self._memory.get_state()["memory"] + for i in range(self._memory_size): + value = memory_content[i] + row = i // self._column_size + col = i % self._column_size + self.set_item(row, col, str(value)) - # Make new line when we reach end of row - if address % self._ROW_LENGTH == self._ROW_LENGTH - 1: - memory_str += "\n" + def set_item(self, row: int, col: int, text: str) -> None: + """Set the text at specified table cell to the given text. + + Parameters + ---------- + row: int + The items row position in the pipeline diagram. + col: int + The items column position in the pipeline diagram. + text: str + The text to be displayed. + """ + item = QTableWidgetItem(text) - self.text.setText(memory_str) + self.setItem(row, col, item) + + def _set_column_size(self) -> None: + """ + Set the column size to a reasonable value if the size was not given to the constructor. + + This function assumes that the attributes `column_size` and `memory_size` are set before it is called. + """ + + if not self._column_size == -1: + return + + if self._memory_size > 200: + self._column_size = 4 + return + + if self._memory_size > 100: + self._column_size = 2 + return + + self._column_size = 1 + return class MemoryGraphicsItem(ModuleGraphicsItem): diff --git a/src/simudator/gui/pipeline.py b/src/simudator/gui/pipeline.py index 2c400c06f93411d368b117796136d577687c94f0..fc9d78012115a011bfc6574414ba6c8a478b6a67 100644 --- a/src/simudator/gui/pipeline.py +++ b/src/simudator/gui/pipeline.py @@ -1,7 +1,8 @@ -from qtpy.QtWidgets import QTableWidget, QTableWidgetItem from typing import Any -# TODO: Make the table unediatable +from qtpy.QtWidgets import QTableWidget, QTableWidgetItem + + class PipeLine(QTableWidget): """ A class showing the current CPU instructions in a seperate window. @@ -12,28 +13,40 @@ class PipeLine(QTableWidget): def __init__(self): super().__init__() - # Values below are temporary test values + + def set_instructions(self, instructions: list[tuple[str, int, int]]) -> None: """ - self.setRowCount(5) - self.setColumnCount(5) - self.instructions = ["Add", "Sub", "Mult", "Fetch", "Nop"] - self.set_item(0, 0, self.instructions[0]) - self.set_item(1, 1, self.instructions[1]) - self.set_item(2, 2, self.instructions[2]) - self.set_item(3, 3, self.instructions[3]) + Give the pipeline the current CPU instructions. + + Parameters + ---------- + instructions: A list of tuples of a string and two ints. + The string containing the current instruction where the two ints + represents the instructions col and row position. + + See Also + -------- + simudator.core.processor.get_current_instructions : + Instructions are set from get_current_instructions() in the Processor class. """ - - def set_instruction(self, instructions: list[str]) -> None: - """Give the pipeline the current CPU instructions.""" self.instructions = instructions + for i in range(len(self.instructions)): + instruction = self.instructions[i][0] + col = self.instructions[i][1] + row = self.instructions[i][2] + self.set_item(row, col, instruction) def set_height(self, height: int) -> None: - """Sets the height of the pipeline.""" + """ + Sets the height of the pipeline. + """ self.setColumnCount(height) def set_width(self, width: int) -> None: - """Sets the width of the pipeline.""" + """ + Sets the width of the pipeline. + """ self.setRowCount(width) def set_row_labels(self, labels: list[str]) -> None: @@ -43,7 +56,7 @@ class PipeLine(QTableWidget): Depending on the CPU architecture the labels for each row can change format. In a pipelined CPU we want to assign a clock cycle to each instruction. In an ARM cpu we want the current - CPU clock cycle for the first instruciton, and something else for the + CPU clock cycle for the first instruciton, and something else for the micro instrucitons. """ raise NotImplemented @@ -59,10 +72,9 @@ class PipeLine(QTableWidget): raise NotImplemented def set_item(self, row: int, col: int, text: str) -> None: - """Sets the text at position col row to the given text.""" + """ + Sets the text at position col row to the given text. + """ item = QTableWidgetItem(text) self.setItem(row, col, item) - - - diff --git a/src/simudator/processor/mia/gui/mia_grx_graphic.py b/src/simudator/processor/mia/gui/mia_grx_graphic.py index 5474f915cc91a374f4fcccebadba008389e3a073..34f2ddbe29885e4a9e702164ac9c114a88d1f259 100644 --- a/src/simudator/processor/mia/gui/mia_grx_graphic.py +++ b/src/simudator/processor/mia/gui/mia_grx_graphic.py @@ -43,7 +43,9 @@ class GrxGraphicsItem(ModuleGraphicsItem): for index, register_value in enumerate(self.module.registers): # Make text to display for register name = "Gr" + str(index) - hex_length = math.ceil(self.state["bit_length"] / 4) + + # Mia uses 4 hex numbers to represent the registers + hex_length = 4 value_text = f"0x{register_value:0{hex_length}x}" full_text = name + ": " + value_text[2:] @@ -145,7 +147,9 @@ class GrxGraphicsItem(ModuleGraphicsItem): for index, register_value in enumerate(self.module.registers): text = self.register_text_labels[index] name = "Gr" + str(index) - hex_length = math.ceil(self.state["bit_length"] / 4) + + # Mia uses 4 hex numbers for each register + hex_length = 4 value_text = f"0x{register_value:0{hex_length}x}" full_text = name + ": " + value_text[2:] diff --git a/src/simudator/processor/mia/gui/mia_memory_graphic.py b/src/simudator/processor/mia/gui/mia_memory_graphic.py index 07c7b46ed5d4db658e161506978dc4ec50de2c02..ec29b7717ebc86efe8b0762dc07f3abef6ca34a1 100644 --- a/src/simudator/processor/mia/gui/mia_memory_graphic.py +++ b/src/simudator/processor/mia/gui/mia_memory_graphic.py @@ -2,6 +2,7 @@ import ast from qtpy.QtCore import Slot from qtpy.QtWidgets import ( + QErrorMessage, QGraphicsRectItem, QGraphicsSimpleTextItem, QTextEdit, @@ -11,44 +12,15 @@ from qtpy.QtWidgets import ( from simudator.core.modules import Memory from simudator.gui.color_scheme import ColorScheme as CS -from simudator.gui.module_graphics_item.memory_graphic import MemoryGraphicsItem +from simudator.gui.module_graphics_item.memory_graphic import ( + MemoryGraphicsItem, + MemoryWindow, +) from simudator.gui.orientation import Orientation from simudator.gui.port_graphics_item import PortGraphicsItem from simudator.processor.mia.gui.mia_memory_content_dialog import MiaMemoryContentDialog -class MiaMemoryWindow(QWidget): - """ - Widget for showing content of memory - """ - - ROW_LENGTH = 4 - - def __init__(self, memory_module: Memory): - super().__init__() - self.module = memory_module - - self.text = QTextEdit("") - layout = QVBoxLayout() - layout.addWidget(self.text) - self.setLayout(layout) - - self.update() - - def update(self): - memory_str = "" - for address, value in enumerate(self.module.memory): - # Add address and content to string - # Make sure its unifrom lenght so rows are consistent - memory_str += f"0x{address:02x}" + ": " + f"0x{value:04x}" + " " + "\t" - - # Make new line when we reach end of row - if address % self.ROW_LENGTH == self.ROW_LENGTH - 1: - memory_str += "\n" - - self.text.setText(memory_str) - - class MiaMemoryGraphicsItem(MemoryGraphicsItem): """ Graphics module for a Memory module. @@ -57,6 +29,7 @@ class MiaMemoryGraphicsItem(MemoryGraphicsItem): def __init__(self, memory_module: Memory, **kwargs): super().__init__(memory_module, **kwargs) self.memory_window = None + self._errorMessageWidget = QErrorMessage() def draw_graphics_item(self) -> None: # Same as normal memory but no control signal @@ -99,7 +72,7 @@ class MiaMemoryGraphicsItem(MemoryGraphicsItem): Create and show a MemoryWindow that displays the contents of the memory module associated with this graphics item. """ - self.memory_window = MiaMemoryWindow(self.module) + self.memory_window = MemoryWindow(self.module) self.memory_window.show() def memoryBreakpointDialog(self) -> None: @@ -123,9 +96,9 @@ class MiaMemoryGraphicsItem(MemoryGraphicsItem): module_name, str(parsed_address), str(parsed_value) ) except SyntaxError as e: - self.errorMessageWidget.showMessage(str(e)) + self._errorMessageWidget.showMessage(str(e)) except ValueError: - self.errorMessageWidget.showMessage( + self._errorMessageWidget.showMessage( "You must enter a hexadecimal" "number preceeded by '0x' (e.g." "0xc3)." ) @@ -135,16 +108,27 @@ class MiaMemoryGraphicsItem(MemoryGraphicsItem): Same as prent function but also prases data so it is hexadecimal. """ try: - parsed_address = int(address, 16) + parsed_adress = int(address, 16) parsed_value = ast.literal_eval(value) except SyntaxError as e: - self.errorMessageWidget.showMessage(str(e)) + self._errorMessageWidget.showMessage(str(e)) except ValueError: - self.errorMessageWidget.showMessage( + self._errorMessageWidget.showMessage( "You must enter a hexadecimal" "number preceeded by '0x' (e.g." "0xc3)." ) else: module_state = self.module.get_state() - module_state['memory'][parsed_address] = parsed_value + try: + if parsed_adress < 0: + raise IndexError + module_state['memory'][parsed_adress] = parsed_value + except IndexError: + self._errorMessageWidget.showMessage( + "Address entered was outside the memory space. Adress must be between 0 and 0xff." + ) + else: + self.module.set_state(module_state) + self.update_graphics_signal.emit() + module_state['memory'][parsed_adress] = parsed_value self.module.set_state(module_state) self.update_graphics_signal.emit() diff --git a/src/simudator/processor/mia/mia.py b/src/simudator/processor/mia/mia.py index 724ad94bedb46e5ab898ce607003f193b5a94f93..99f7b7aafa81ee8372ee9ccb27e5cb2456932dc1 100644 --- a/src/simudator/processor/mia/mia.py +++ b/src/simudator/processor/mia/mia.py @@ -1,5 +1,7 @@ import sys +from typing import Any + from simudator.cli.cli import CLI from simudator.core.modules import Flag from simudator.core.modules.register import IntegerRegister @@ -281,9 +283,28 @@ class MIA_CPU(Processor): def is_new_instruction(self) -> bool: return self.get_module("uPC").value == 0 - def get_current_instruction(self) -> str: + def get_current_instructions(self) -> list[tuple[str, int, int]]: + asm_instr = self.get_asm_instruction() + micro_instr = self.get_micro_instruction() + asm = (asm_instr, 1, 0) + micro = (micro_instr, 1, 1) + asm_title = ("Assembler", 0, 0) + micro_title = ("Micro", 0, 1) + return [asm, micro, asm_title, micro_title] + + def get_micro_instruction(self) -> str: + """ + Return a string containing the address for the current micro instruction in hex, e.g. 0a4b. + """ + uM = self.get_module("uM").get_state() + curr_instr = uM["curr_instr"] + memory = uM["memory"] + + return '0x' + format(memory[curr_instr], '07x') + + def get_asm_instruction(self) -> str: """ - Return a string containing the label for the current instruction. + Return a string containing the label for the current assembly instruction. If the label doesnt exist, the string is empty. """ @@ -291,6 +312,14 @@ class MIA_CPU(Processor): op_code = ir.op return self.get_module("K1").get_label(int(op_code)) + def get_pipeline_dimensions(self) -> tuple[int, int]: + """ + Mia should show the current assembly instruction and the current micro instruction. + + One Extra column to specify what the values represent. + """ + return (2, 2) + def run_asm_instruction(self, num_instructions=1) -> None: """ Runs 'num_instructions' assembler instructions on Mia. diff --git a/src/simudator/processor/mia/modules/micro_memory.py b/src/simudator/processor/mia/modules/micro_memory.py index d919ef88182af45375794cbcfaffdd3e1147ea12..c118500e3bac6b0a9ad85d45f7bddbd44f85d892 100644 --- a/src/simudator/processor/mia/modules/micro_memory.py +++ b/src/simudator/processor/mia/modules/micro_memory.py @@ -286,6 +286,7 @@ class MicroMemory(Module): state = super().get_state() state["memory"] = self.memory[:] state["halt"] = self.halt + state["curr_instr"] = self.curr_instr return state def get_gui_state(self) -> dict[str, Any]: