diff --git a/src/simudator/cli/cli.py b/src/simudator/cli/cli.py index 8cece34ab1733200d85c52e342092bc5f697c8fc..25017547485eddca37d2b7846442f12c377d712f 100644 --- a/src/simudator/cli/cli.py +++ b/src/simudator/cli/cli.py @@ -3,6 +3,7 @@ from __future__ import annotations import ast from os.path import exists, isdir +from simudator.cli.printing import pretty_print, pretty_print_verbose from simudator.core.processor import Processor HELP_TEXT = """Here is a list of possible commands: @@ -195,12 +196,12 @@ class CLI: case ["pp"] | ["pretty print"]: # pretty print the modules of the processor - self._processor.pretty_print() + pretty_print(self._processor) case ["ppv"] | ["pretty print verbose"]: # pretty print the modules of the processor # with all available information - self._processor.pretty_print_verbose() + pretty_print_verbose(self._processor) # Breakpoints ------------------------------------------------- case ["p", "br"] | ["print", "breaks"]: diff --git a/src/simudator/cli/printing.py b/src/simudator/cli/printing.py new file mode 100644 index 0000000000000000000000000000000000000000..42530c15ce3f01555278069070a2a8767fafb0af --- /dev/null +++ b/src/simudator/cli/printing.py @@ -0,0 +1,262 @@ +from simudator.core.module import Module +from simudator.core.modules.memory import Memory +from simudator.core.processor import Processor +from simudator.processor.mia.modules.bus import Bus +from simudator.processor.mia.modules.micro_memory import MicroMemory + + +def pretty_print(processor: Processor) -> None: + """Print the processor state in a readable and compact format.""" + pretty_print_verbose(processor, processor._ignore_keys) + + +def pretty_print_verbose( + processor: Processor, ignore_keys: list[str] | None = None +) -> None: + """ + Print the most relevant information about each module in a compact and + readable format. + + State variables of modules can be ignored with the optional argument. + + Parameters + ---------- + ignore_keys : list[str] + List of names of state variables of modules to exclude when + printing module states. + """ + # TODO: ignore keys per module and not for all modules + memory_modules = [] + other_modules = [] + + if ignore_keys is None: + ignore_keys = [] + + # TODO: remove isinstance(module, micro_memory) + for module in processor.get_modules(): + if not isinstance(module, Bus): + if isinstance(module, Memory) or isinstance(module, MicroMemory): + memory_modules.append(module) + else: + other_modules.append(module) + + # sort the modules by name to ensure that they appear in the + # same order + memory_modules.sort(key=lambda x: x.name, reverse=True) + other_modules.sort(key=lambda x: x.name, reverse=True) + + # specify which keys to ignore from the modules 'get_state' + # function + # TODO: ignore fields per module, this will ignore 'mask' + # for every module + + module_to_line_length = {} + # get the longest line length for all other_modules + for module in other_modules: + # +3 for padding + line_len = module.get_longest_line_len(ignore_keys) + 3 + + # TODO: what to do if two or more modules has the same name + module_to_line_length[module] = line_len + + groups = group_pp_modules(module_to_line_length) + + print(Processor.LINE_SEPARATOR * Processor.MAX_LINE_LEN) + + # print each group separate + for group in groups: + pretty_print_names(group) + + # Build a new string containing the information to show + # the user + for row in range(get_most_fields(group, ignore_keys)): + string = "" + total_padding = 0 + + # Get the information from each module + for module in group: + module_state = module.get_state() + keys = fields_to_list(module) + + # Get the keys that we want to print to the user + # If this is not done, the keys we want to print + # could end up on an index that is higher than the + # number of rows printed and will therefore be missed + real_keys = [key for key in keys if key not in ignore_keys] + + # Dont go out of index + # Needed since one module might want to print 4 + # fields and another only 1 + if row < len(real_keys) and real_keys[row] not in ignore_keys: + string += real_keys[row] + ": " + str(module_state[real_keys[row]]) + + # pad the string so each string has the same length + total_padding += module_to_line_length[module] + string = string.ljust(total_padding) + # replace last to chars with a separator and padding + string = string[0:-2] + "| " + + print(string) + print(Processor.LINE_SEPARATOR * Processor.MAX_LINE_LEN) + + for memory_module in memory_modules: + pretty_print_memory(memory_module) + + +def pretty_print_memory(module: Memory) -> None: + """Print a memory module in a compact and readable format. + + Parameters + ---------- + module : Memory + Memory module to print the state of. + """ + print(module.name) + print(Processor.LINE_SEPARATOR * Processor.MAX_LINE_LEN) + + longest_line_len = module.get_longest_line_len() + # longest_line_len = processor.get_longest_memory_value(module.memory) + string = "" + last_mem_len = module.get_largest_mem_adr() + + for i, value in enumerate(module.get_state()["memory"]): + + # create a new string containing the address and the value + # of the memory address formatted to fit with the largest + # address and largest memory value + new_row = (str(i).ljust(last_mem_len) + ": " + str(value)).ljust( + longest_line_len + last_mem_len + 3 + ) + "|" + + # only add the string if there is space for it, else + # print the string and start a new + if len(string + new_row) + 1 > Processor.MAX_LINE_LEN: + print(string) + string = new_row + else: + # First iteration string will be + # empty and should not be padded + if string: + string += " " + new_row + else: + string = new_row + print(string) + print(Processor.LINE_SEPARATOR * Processor.MAX_LINE_LEN) + print() + + +def pretty_print_names(module_to_line_length: dict[Module, int]) -> None: + """ + Print the name of the modules in one row with formatting. + + Adds spacing between the names so that the longest state variable of + each module has space to be printed below the name. + + Parameters + ---------- + module_to_line_length : dict[Module, int] + Mapping from module to length of the longest state variable of + the module. + """ + name_string = "" + total_len = 0 + for module in module_to_line_length: + name_string += module.name + total_len += module_to_line_length[module] + name_string = name_string.ljust(total_len) + name_string = name_string[0:-2] + "| " + + print(name_string) + + +def group_pp_modules( + module_to_line_length: dict[Module, int] +) -> list[dict[Module, int]]: + """Group the modules to be pretty printed into groups with a + total line length lower than 'Processor.MAX_LINE_LEN'. + + Parameters + ---------- + module_to_line_length : dict[Module, int] + Mapping from module to length of the longest state variable of + the module (takes as the line length). + + Returns + ------- + list[dict[Module, int]] + List of mappings from module to line length, each mapping + representing a group of modules for printing. + """ + + groups = [{}] + group_index = 0 + line_len = 0 + for module in module_to_line_length: + + # Make sure the line is not to long + if line_len + module_to_line_length[module] < Processor.MAX_LINE_LEN: + line_len += module_to_line_length[module] + groups[group_index][module] = module_to_line_length[module] + + # If it would have been, start a new group + else: + groups.append({}) + group_index += 1 + groups[group_index][module] = module_to_line_length[module] + line_len = module_to_line_length[module] + return groups + + +def fields_to_list(module: Module, ignore_keys=[]) -> list[str]: + """ + Return a list containing all state variable names, excluding module + name, for a module. + Optional argument to ignore specific state variables. + + Parameters + ---------- + module : Module + Module to get state variable names from. + ignore_keys : list[str] + List of state variable names to exclude. + + Returns + ------- + list[str] + List of state variable names, excluding module name, of a module. + """ + return [ + key for key in module.get_state() if key != "name" and key not in ignore_keys + ] + + +def get_most_fields(modules: dict[Module, int], ignore_keys=None) -> int: + """Get the maximum number of state variables among all modules. + + Can optionally ignore keys. + + Parameters + ---------- + modules : dict[Module, int] + Mapping from module to length of the longest state variable of + the module. Used as a list of modules to take the maximum over. + ignore_keys : list[str] + State variables to exclude. + + Returns + ------- + int + The maximum of the number of module state variables among all + modules of the processor. + """ + + if ignore_keys is None: + ignore_keys = [] + + fields = 0 + for module in modules: + module_fields = len(fields_to_list(module, ignore_keys)) + if module_fields > fields: + fields = module_fields + + return fields diff --git a/src/simudator/core/processor.py b/src/simudator/core/processor.py index 35044c6cea2684e02ff4bc9e28be9839b3aec122..2485c5389b0f328ace9742e30eb5b25a93e485de 100644 --- a/src/simudator/core/processor.py +++ b/src/simudator/core/processor.py @@ -7,9 +7,6 @@ from simudator.core.breakpoint_lambda import LambdaBreakpoint from simudator.core.breakpoint_memory import MemoryBreakpoint from simudator.core.breakpoint_state import StateBreakpoint from simudator.core.module import Module -from simudator.core.modules.memory import Memory -from simudator.processor.mia.modules.bus import Bus -from simudator.processor.mia.modules.micro_memory import MicroMemory from .signal import Signal @@ -19,30 +16,38 @@ class Processor: Main class for controlling the processor. Uses modules and signals to simulate processor components behaviour. + + Attributes + ---------- + is_stopped : bool + Used to indicate that the processor has stopped, e.g. from a halt + instruction. + breakpoint_reached : bool + Used to indicate that the processor has reached a breakpoint at + the current clock cycle. + last_breakpoint : Breakpoint | None + Stores the last reached breakpoint if any. + """ __slots__ = ( - "modules", - "signals", - "clock", - "update_queue", - "module_history", - "signal_history", - "breakpoint_id_counter", - "breakpoints", + "_modules", + "_signals", + "_clock", + "_update_queue", + "_module_history", + "_signal_history", + "_breakpoint_id_counter", + "_breakpoints", "breakpoint_reached", "last_breakpoint", - "cycles_to_save", - "removed_cycles", - "max_line_len", - "line_separator", + "_removed_cycles", "is_stopped", - "new_instruction", - "current_instructions", - "ignore_keys", - "lambdas", - "assembly_cycles", - "line_separator", + "_new_instruction", + "_current_instructions", + "_ignore_keys", + "_lambdas", + "_assembly_cycles", ) LINE_SEPARATOR = "-" @@ -51,47 +56,42 @@ class Processor: def __init__(self) -> None: # For simulation - self.modules: dict[str, Module] = dict() - self.signals = [] - self.clock = 0 - self.update_queue = [] - self.is_stopped = False + self._modules: dict[str, Module] = dict() + self._signals: list[Signal] = [] + self._clock: int = 0 + self._update_queue: list[Module] = [] + self.is_stopped: bool = False # Breakpoint handling - self.breakpoint_id_counter = 1 - self.breakpoints: dict[int, Breakpoint] = {} - self.breakpoint_reached = False - self.last_breakpoint = None - self.lambdas: dict[str, Callable[..., bool]] = {} + self._breakpoint_id_counter: int = 1 + self._breakpoints: dict[int, Breakpoint] = {} + self.breakpoint_reached: bool = False + self.last_breakpoint: Breakpoint | None = None + self._lambdas: dict[str, Callable[..., bool]] = {} # Saving processor state for saving/loading to file and undoing ticks - self.removed_cycles = 0 - self.assembly_cycles = [0] # Map asm instruction to clock cycle - self.module_history: list[dict[str, dict[str, Any]]] = [] - self.signal_history: list[list] = [] # TODO: Is this needed? + self._removed_cycles: int = 0 + self._assembly_cycles: list[int] = [0] # Map asm instruction to clock cycle + self._module_history: list[dict[str, dict[str, Any]]] = [] + self._signal_history: list[list] = [] # TODO: Is this needed? # For showing which instructions are being done and signalling # the start of a new one - self.new_instruction = False + self._new_instruction: bool = False # 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]] = [] + 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 # level? - self.ignore_keys = [ + self._ignore_keys: list[str] = [ "bit_length", "mask", - "increment", - "read_from_bus", - "read_from_uADR", - "decrement_by_one", - "bus_id", ] def do_tick(self) -> None: @@ -101,25 +101,25 @@ class Processor: Also check for breakpoints that are reached in this cycle, and save clock cycles when new assembly instructions are started. """ - if len(self.module_history) > self.clock - self.removed_cycles: + if len(self._module_history) > self._clock - self._removed_cycles: # If a previous stored cycle has been loaded, discard # all stored cycles from that cycle and onward in # the history of saved cycles - self.module_history = self.module_history[0 : self.clock] - self.signal_history = self.signal_history[0 : self.clock] + self._module_history = self._module_history[0 : self._clock] + self._signal_history = self._signal_history[0 : self._clock] self.unstop() self.save_cycle() - self.clock += 1 + self._clock += 1 - for module in self.modules.values(): + for module in self._modules.values(): module.update_register() - for module in self.modules.values(): + for module in self._modules.values(): module.output_register() - while self.update_queue: - module = self.update_queue.pop(0) + while self._update_queue: + module = self._update_queue.pop(0) module.update_logic() self.stop_at_breakpoints() @@ -129,11 +129,11 @@ class Processor: # Set new_instruction and save clock cycle for each # new assembly instruction if self.is_new_instruction(): - self.new_instruction = True - self.current_instructions = self.get_current_instructions() - self.assembly_cycles.append(self.get_clock()) + self._new_instruction = True + self._current_instructions = self.get_current_instructions() + self._assembly_cycles.append(self.get_clock()) else: - self.new_instruction = False + self._new_instruction = False def get_current_instructions(self) -> list[tuple[str, int, int]]: """Return a list of the current instructions with their positions. @@ -207,14 +207,14 @@ class Processor: Number of assembly instructions to undo. """ - current_clock_cycle = self.clock - index = len(self.assembly_cycles) - saved_cycle = self.assembly_cycles[index - 1] + current_clock_cycle = self._clock + index = len(self._assembly_cycles) + saved_cycle = self._assembly_cycles[index - 1] # Make sure we are undoing the instruction(s) we are currently on while saved_cycle >= current_clock_cycle: index -= 1 - saved_cycle = self.assembly_cycles[index - 1] + saved_cycle = self._assembly_cycles[index - 1] index -= num_instructions @@ -222,7 +222,7 @@ class Processor: if index < 0: raise IndexError - clockcycle = self.assembly_cycles[index] + clockcycle = self._assembly_cycles[index] self.load_cycle(clockcycle) @@ -230,7 +230,7 @@ class Processor: # load the start state. This is done since we only append clock # cycles to the list self.assembly_cycles when we reach a new state # that has uPC set to 0, which wont happen when we load a new file. - self.assembly_cycles = self.assembly_cycles[: index + 1] + self._assembly_cycles = self._assembly_cycles[: index + 1] def run_continuously(self) -> None: """ @@ -275,7 +275,7 @@ class Processor: Should be implemented per CPU, return field new_instruction. It is up to each processor to set this field to True/False correctly. """ - return self.new_instruction + return self._new_instruction def stop_at_breakpoints(self) -> None: """Stop the execution if any breakpoint has been reached during this @@ -284,7 +284,7 @@ class Processor: Also record the breakpoint that was reached. """ self.breakpoint_reached = False - for _, bp in self.breakpoints.items(): + for _, bp in self._breakpoints.items(): # TODO: Can make this more efficient by only checking enabled # breakpoints if bp.is_break() and bp.is_enabled: @@ -300,18 +300,18 @@ class Processor: This resets all modules and removes any saved states for undoing clock cycles. A round of value propagation is done to reset signals too. """ - self.clock = 0 - self.module_history.clear() - self.signal_history.clear() - self.removed_cycles = 0 - self.assembly_cycles = [0] + self._clock = 0 + self._module_history.clear() + self._signal_history.clear() + self._removed_cycles = 0 + self._assembly_cycles = [0] - for module in self.modules.values(): + for module in self._modules.values(): module.reset() module.output_register() - while self.update_queue: - module = self.update_queue.pop(0) + while self._update_queue: + module = self._update_queue.pop(0) module.update_logic() def add_modules_to_update(self, module: Module) -> None: @@ -327,8 +327,8 @@ class Processor: Module.update_logic : Method for updating a module. """ - if module not in self.update_queue: - self.update_queue.append(module) + if module not in self._update_queue: + self._update_queue.append(module) def add_module(self, module: Module) -> None: """Add module to be simulated by the processor. @@ -338,7 +338,7 @@ class Processor: module : Module Module to add. """ - self.modules[module.name] = module + self._modules[module.name] = module def get_module(self, name: str) -> Module: """Get module with specific name. @@ -353,7 +353,7 @@ class Processor: Module The module with the specified name. """ - return self.modules[name] + return self._modules[name] def get_modules(self) -> list[Module]: """Get list of all modules. @@ -363,7 +363,7 @@ class Processor: list[Module] List of all modules in the processor. """ - return list(self.modules.values()) + return list(self._modules.values()) def add_signals(self, signals: list[Signal]) -> None: """Add signals to the processor. @@ -373,7 +373,7 @@ class Processor: list[Signal] List of signals to add for simulation. """ - self.signals += signals + self._signals += signals def get_clock(self) -> int: """Get the current clockcycle number. @@ -383,7 +383,7 @@ class Processor: int Current clock cycle number of the processor. """ - return self.clock + return self._clock def set_clock(self, value: int) -> None: """Set current clockcycle number. @@ -393,7 +393,7 @@ class Processor: value : int Cycle number to set the clock to. """ - self.clock = value + self._clock = value def save_cycle(self) -> None: """ @@ -404,13 +404,13 @@ class Processor: # Only save a specified number of cycles, # saving every cycle can easily eat all ram - if len(self.module_history) > Processor.CYCLES_TO_SAVE: - self.module_history.pop(0) - self.removed_cycles += 1 + if len(self._module_history) > Processor.CYCLES_TO_SAVE: + self._module_history.pop(0) + self._removed_cycles += 1 - for module in self.modules.values(): + for module in self._modules.values(): module_states[module.name] = module.get_state() - self.module_history.append(module_states) + self._module_history.append(module_states) def load_cycle(self, cycle: int) -> None: """Load the state of all modules as they were at the specified clock @@ -424,26 +424,26 @@ class Processor: Number of the cycle to load. """ - cycle_index = cycle - self.removed_cycles + cycle_index = cycle - self._removed_cycles if cycle_index < 0: raise ValueError("The cycle to be loaded is not saved") try: - module_states = self.module_history[cycle_index] + module_states = self._module_history[cycle_index] except IndexError: raise IndexError for module_name, module_state in module_states.items(): - self.modules[module_name].set_state(module_state) + self._modules[module_name].set_state(module_state) - self.clock = cycle + self._clock = cycle - for module in self.modules.values(): + for module in self._modules.values(): module.output_register() - while self.update_queue: - module = self.update_queue.pop(0) + while self._update_queue: + module = self._update_queue.pop(0) module.update_logic() def load_state_from_file(self, file_path: str) -> None: @@ -512,7 +512,7 @@ class Processor: file.write("") file.close() - for module in self.modules.values(): + for module in self._modules.values(): res = module.save_state_to_file(file_path) if not res: @@ -520,257 +520,18 @@ class Processor: return True - def pretty_print(self) -> None: - """Print the processor state in a readable and compact format.""" - self.pretty_print_verbose(self.ignore_keys) - - def pretty_print_verbose(self, ignore_keys=None) -> None: - """ - Print the most relevant information about each module in a compact and - readable format. - - State variables of modules can be ignored with the optional argument. - - Parameters - ---------- - ignore_keys : list[str] - List of names of state variables of modules to exclude when - printing module states. - """ - # TODO: ignore keys per module and not for all modules - memory_modules = [] - other_modules = [] - - if ignore_keys is None: - ignore_keys = [] - - # TODO: remove isinstance(module, micro_memory) - for module in self.modules.values(): - if not isinstance(module, Bus): - if isinstance(module, Memory) or isinstance(module, MicroMemory): - memory_modules.append(module) - else: - other_modules.append(module) - - # sort the modules by name to ensure that they appear in the - # same order - memory_modules.sort(key=lambda x: x.name, reverse=True) - other_modules.sort(key=lambda x: x.name, reverse=True) - - # specify which keys to ignore from the modules 'get_state' - # function - # TODO: ignore fields per module, this will ignore 'mask' - # for every module - - module_to_line_length = {} - # get the longest line length for all other_modules - for module in other_modules: - # +3 for padding - line_len = module.get_longest_line_len(ignore_keys) + 3 - - # TODO: what to do if two or more modules has the same name - module_to_line_length[module] = line_len - - groups = self.group_pp_modules(module_to_line_length) - - print(Processor.LINE_SEPARATOR * Processor.MAX_LINE_LEN) - - # print each group separate - for group in groups: - self.pretty_print_names(group) - - # Build a new string containing the information to show - # the user - for row in range(self.get_most_fields(group, ignore_keys)): - string = "" - total_padding = 0 - - # Get the information from each module - for module in group: - module_state = module.get_state() - keys = self.fields_to_list(module) - - # Get the keys that we want to print to the user - # If this is not done, the keys we want to print - # could end up on an index that is higher than the - # number of rows printed and will therefore be missed - real_keys = [key for key in keys if key not in ignore_keys] - - # Dont go out of index - # Needed since one module might want to print 4 - # fields and another only 1 - if row < len(real_keys) and real_keys[row] not in ignore_keys: - string += ( - real_keys[row] + ": " + str(module_state[real_keys[row]]) - ) - - # pad the string so each string has the same length - total_padding += module_to_line_length[module] - string = string.ljust(total_padding) - # replace last to chars with a separator and padding - string = string[0:-2] + "| " - - print(string) - print(Processor.LINE_SEPARATOR * Processor.MAX_LINE_LEN) - - for memory_module in memory_modules: - self.pretty_print_memory(memory_module) - - def pretty_print_memory(self, module: Memory) -> None: - """Print a memory module in a compact and readable format. - - Parameters - ---------- - module : Memory - Memory module to print the state of. - """ - print(module.name) - print(Processor.LINE_SEPARATOR * Processor.MAX_LINE_LEN) - - longest_line_len = module.get_longest_line_len() - # longest_line_len = self.get_longest_memory_value(module.memory) - string = "" - last_mem_len = module.get_largest_mem_adr() - - for i, value in enumerate(module.get_state()["memory"]): - - # create a new string containing the address and the value - # of the memory address formatted to fit with the largest - # address and largest memory value - new_row = (str(i).ljust(last_mem_len) + ": " + str(value)).ljust( - longest_line_len + last_mem_len + 3 - ) + "|" - - # only add the string if there is space for it, else - # print the string and start a new - if len(string + new_row) + 1 > Processor.MAX_LINE_LEN: - print(string) - string = new_row - else: - # First iteration string will be - # empty and should not be padded - if string: - string += " " + new_row - else: - string = new_row - print(string) - print(Processor.LINE_SEPARATOR * Processor.MAX_LINE_LEN) - print() - - def pretty_print_names(self, module_to_line_length: dict[Module, int]) -> None: - """ - Print the name of the modules in one row with formatting. - - Adds spacing between the names so that the longest state variable of - each module has space to be printed below the name. - - Parameters - ---------- - module_to_line_length : dict[Module, int] - Mapping from module to length of the longest state variable of - the module. - """ - name_string = "" - total_len = 0 - for module in module_to_line_length: - name_string += module.name - total_len += module_to_line_length[module] - name_string = name_string.ljust(total_len) - name_string = name_string[0:-2] + "| " - - print(name_string) - - def group_pp_modules( - self, module_to_line_length: dict[Module, int] - ) -> list[dict[Module, int]]: - """Group the modules to be pretty printed into groups with a - total line length lower than 'Processor.MAX_LINE_LEN'. - - Parameters - ---------- - module_to_line_length : dict[Module, int] - Mapping from module to length of the longest state variable of - the module (takes as the line length). - - Returns - ------- - list[dict[Module, int]] - List of mappings from module to line length, each mapping - representing a group of modules for printing. - """ - - groups = [{}] - group_index = 0 - line_len = 0 - for module in module_to_line_length: - - # Make sure the line is not to long - if line_len + module_to_line_length[module] < Processor.MAX_LINE_LEN: - line_len += module_to_line_length[module] - groups[group_index][module] = module_to_line_length[module] - - # If it would have been, start a new group - else: - groups.append({}) - group_index += 1 - groups[group_index][module] = module_to_line_length[module] - line_len = module_to_line_length[module] - return groups - - def fields_to_list(self, module: Module, ignore_keys=[]) -> list[str]: + def get_breakpoints(self) -> dict[int, Breakpoint]: """ - Return a list containing all state variable names, excluding module - name, for a module. - Optional argument to ignore specific state variables. - - Parameters - ---------- - module : Module - Module to get state variable names from. - ignore_keys : list[str] - List of state variable names to exclude. + Get the breakpoints in the processor as a map from breakpoint id to + breakpoint. Returns ------- - list[str] - List of state variable names, excluding module name, of a module. + dict[int, Breakpoint] + Map from breakpoint id to breakpoint, containing all breakpoints + in the processor. """ - return [ - key - for key in module.get_state() - if key != "name" and key not in ignore_keys - ] - - def get_most_fields(self, modules: dict[Module, int], ignore_keys=None) -> int: - """Get the maximum number of state variables among all modules. - - Can optionally ignore keys. - - Parameters - ---------- - modules : dict[Module, int] - Mapping from module to length of the longest state variable of - the module. Used as a list of modules to take the maximum over. - ignore_keys : list[str] - State variables to exclude. - - Returns - ------- - int - The maximum of the number of module state variables among all - modules of the processor. - """ - - if ignore_keys is None: - ignore_keys = [] - - fields = 0 - for module in modules: - module_fields = len(self.fields_to_list(module, ignore_keys)) - if module_fields > fields: - fields = module_fields - - return fields + return self._breakpoints def add_state_breakpoint( self, module_name: str, state_name: str, value: Any @@ -794,17 +555,17 @@ class Processor: exist in the module. """ - if module_name not in self.modules: + if module_name not in self._modules: raise ValueError(f"No module named {module_name}") - module = self.modules[module_name] + module = self._modules[module_name] if state_name not in module.get_state(): raise ValueError(f"No state named {state_name} in " f"module {module_name}") bp = StateBreakpoint(module, state_name, value) - self.breakpoints[self.breakpoint_id_counter] = bp - self.breakpoint_id_counter += 1 + self._breakpoints[self._breakpoint_id_counter] = bp + self._breakpoint_id_counter += 1 def remove_breakpoint(self, id: int) -> bool: """Remove a specific breakpoint. @@ -814,17 +575,17 @@ class Processor: id : int ID of the breakpoint to be removed. """ - if id in self.breakpoints: - del self.breakpoints[id] + if id in self._breakpoints: + del self._breakpoints[id] return True return False def print_breakpoints(self) -> None: """Print all breakpoints of the processor.""" - if not self.breakpoints: + if not self._breakpoints: print("There are no breakpoints.") else: - for bp_id, bp in self.breakpoints.items(): + for bp_id, bp in self._breakpoints.items(): print(f"BP {bp_id}: {bp}") def add_memory_breakpoint(self, module_name: str, address: int, value: Any) -> None: @@ -845,16 +606,16 @@ class Processor: If the module does not exist or is not a memory. """ - if module_name not in self.modules: + if module_name not in self._modules: raise ValueError(f"No module named {module_name}") - module = self.modules[module_name] + module = self._modules[module_name] if "memory" not in module.get_state(): raise ValueError(f"Module {module_name} is not a memory.") bp = MemoryBreakpoint(module, address, value) - self.breakpoints[self.breakpoint_id_counter] = bp - self.breakpoint_id_counter += 1 + self._breakpoints[self._breakpoint_id_counter] = bp + self._breakpoint_id_counter += 1 def get_breakpoint_lambdas(self) -> list[str]: """Get all functions available for lambda breakpoints. @@ -866,7 +627,7 @@ class Processor: for adding lambda breakpoints. """ - return list(self.lambdas.keys()) + return list(self._lambdas.keys()) def add_lambda_breakpoint(self, lambda_name: str, **kwargs) -> None: """Add a lambda breakpoint to the processor. @@ -894,16 +655,16 @@ class Processor: # modules as arguments without having direct access to them for key in kwargs.keys(): value = kwargs[key] - if value in self.modules: - kwargs[key] = self.modules[value] + if value in self._modules: + kwargs[key] = self._modules[value] - if lambda_name not in self.lambdas: + if lambda_name not in self._lambdas: raise ValueError(f"No lambda named {lambda_name}.") - lambda_func = self.lambdas[lambda_name] + lambda_func = self._lambdas[lambda_name] bp = LambdaBreakpoint(lambda_func, **kwargs) - self.breakpoints[self.breakpoint_id_counter] = bp - self.breakpoint_id_counter += 1 + self._breakpoints[self._breakpoint_id_counter] = bp + self._breakpoint_id_counter += 1 def set_enabled_breakpoint(self, bp_id: int, is_enabled: bool) -> None: """Toggle a breakpoint to enabled or disabled. @@ -915,4 +676,4 @@ class Processor: is_enabled : bool ``True`` to enable, ``False`` to disable the breakpoint. """ - self.breakpoints[bp_id].set_enabled(is_enabled) + self._breakpoints[bp_id].set_enabled(is_enabled) diff --git a/src/simudator/gui/breakpoint_window.py b/src/simudator/gui/breakpoint_window.py index ca19345436f974868d30ba1bdd3539bd02b60068..649364e0b6690d87e959c3161a9df2f72770c008 100644 --- a/src/simudator/gui/breakpoint_window.py +++ b/src/simudator/gui/breakpoint_window.py @@ -53,7 +53,7 @@ class BreakpointWindow(QWidget): stop_icon = self.style().standardIcon(QStyle.SP_DialogCancelButton) # Add an list item for each breakpoint in cpu - for bp_id, breakpoint in self.cpu.breakpoints.items(): + for bp_id, breakpoint in self.cpu.get_breakpoints().items(): bp_str = str(bp_id) + ": " + breakpoint.__str__() bp_item = QListWidgetItem(bp_str) @@ -95,7 +95,7 @@ class BreakpointWindow(QWidget): if bp_id is None: return - del self.cpu.breakpoints[bp_id] + self.cpu.remove_breakpoint(bp_id) self.update() def getSelectedItemId(self) -> int: @@ -120,4 +120,4 @@ class BreakpointWindow(QWidget): # Do nothing if there are no breakpoints or if id is nonetype if bp_id is None or bp_id == -1: return - return self.cpu.breakpoints[bp_id] + return self.cpu.get_breakpoints()[bp_id] diff --git a/src/simudator/processor/mia/mia.py b/src/simudator/processor/mia/mia.py index b90e2f5efdc0b4514ea70a73595531ab1905b540..39087f65727b12c8ddfc8318aa01f86c92b9bafc 100644 --- a/src/simudator/processor/mia/mia.py +++ b/src/simudator/processor/mia/mia.py @@ -40,7 +40,16 @@ class MIA_CPU(Processor): super().__init__() # Creating all signals # Signals follow the naming convention 'to_from' - self.max_line_len = 100 + + # Add state variable names to skip when pretty printing the MIA + # processor in the CLI + self._ignore_keys += [ + "increment", + "read_from_bus", + "read_from_uADR", + "decrement_by_one", + "bus_id", + ] # A signal used by all modules connected to the bus bus_control = Signal(self, "bus_control", (0, 0)) @@ -262,9 +271,9 @@ class MIA_CPU(Processor): for module in ALLTHEMODULES: self.add_module(module) - self.micro_memory = uM + self._micro_memory = uM - self.lambdas = {} + self._lambdas = {} def is_new_instruction(self) -> bool: return self.get_module("uPC").value == 0 @@ -307,7 +316,7 @@ class MIA_CPU(Processor): return (2, 2) def should_halt(self) -> bool: - micro_memory_state = self.micro_memory.get_state() + micro_memory_state = self._micro_memory.get_state() return micro_memory_state["halt"] def launch_gui(self):