diff --git a/src/simudator/core/processor.py b/src/simudator/core/processor.py index 0fd905e24236fe9ce6533d4a001b1839f30199b9..264ff98777b6e8fff486c5076246d4ba784309e1 100644 --- a/src/simudator/core/processor.py +++ b/src/simudator/core/processor.py @@ -116,34 +116,54 @@ class Processor: Each entry in the list is a tuple of a instruction with its column and row position for the pipeline diagram. + + Returns + ------- + list[tuple[str, int, int]] + A list of instructions represented as tuples. Each tuple contains + the instruction and its position in the pipeline. """ raise NotImplementedError def get_pipeline_dimensions(self) -> tuple[int, int]: - """Return a tuple on the form (x, y) for the processors preferred - pipeline display size. + """Return the dimensions of the pipeline of the processor. - (x, y) represents the number of columns respective rows the pipeline - should have. + Returns + ------- + width : int + The width of the pipeline (number of cycles to display at a time). + depth : int + Depth of the pipeline. """ raise NotImplementedError def get_asm_instruction(self) -> str: - """ - Return a string containing the 'code' for the current assembly + """Return a string containing the 'code' for the current assembly instruction. What 'code' refers to is up to each processor to define. It could be a memory address, an instruction label or something else. + + Returns + ------- + str + The current assembly instruction. """ raise NotImplementedError def run_asm_instruction(self, num_instructions=1) -> None: - """ - Runs assembler instructions on the processors 'num_instructions' times. + """Run a number of assembly instructions. - Default argument is one, but it is possible to specify any number of - instructions. This should be implemented per processor and thus we + Defaults to running 1 instruction unless some other number is provided. + + Parameters + ---------- + num_instructions : int + Number of assembly instructions to perform. + + Notes + ----- + This should be implemented per processor and thus we raise NotImplementedError. """ @@ -151,14 +171,15 @@ class Processor: def undo_asm_instruction(self, num_instructions=1) -> None: """ - Undos 'num_instructions' numbers of assembler instructions on the CPU. + Undo a number of assembly instructions on the CPU. Default argument is one, but it is possible to specify any number of - instructions. Undo assembler instruction assumes the CPU is always at - the end of the list 'self.assembly_cycles'. + instructions. - Undos asm instructions by finding the corresponding clock cycle before - said number of asm instructions started and loading that clock cycle. + Parameters + ---------- + num_instructions : int + Number of assembly instructions to undo. """ current_clock_cycle = self.clock @@ -196,7 +217,12 @@ class Processor: def should_halt(self) -> bool: """ - Return True when the processor should halt, otherwise False. + Return whether the processor should halt this clock cycle or not. + + Returns + ------- + bool + ``True`` if the processor should halt, ``False`` otherwise. Notes ---- @@ -208,16 +234,13 @@ class Processor: return False def stop(self) -> None: - """ - Signal to stop the execution of the processor until the processor is + """Signal to stop the execution of the processor until the processor is instructed to run continuously or do some ticks again. """ self.is_stopped = True def unstop(self) -> None: - """ - Reset the stop execution signal. - """ + """Reset the stop execution signal.""" self.is_stopped = False def is_new_instruction(self) -> bool: @@ -267,45 +290,83 @@ class Processor: module.update_logic() def add_modules_to_update(self, module: Module) -> None: - """ - Queues module to be updated at the end of clock cycle. + """Queue a module to be updated at the end of clock cycle. + + Parameters + ---------- + module : Module + Module to queue for update. + + See Also + -------- + Module.update_logic : + Method for updating a module. """ if module not in self.update_queue: self.update_queue.append(module) def add_module(self, module: Module) -> None: - """ - Add module into the processor. + """Add module to be simulated by the processor. + + Parameters + ---------- + module : Module + Module to add. """ self.modules[module.name] = module def get_module(self, name: str) -> Module: - """ - Get module with specific name. + """Get module with specific name. + + Parameters + ---------- + name : str + Name of the module to get. + + Returns + ------- + Module + The module with the specified name. """ return self.modules[name] def get_modules(self) -> list[Module]: - """ - Get list of all modules. + """Get list of all modules. + + Returns + ------- + list[Module] + List of all modules in the processor. """ return list(self.modules.values()) def add_signals(self, signals: list[Signal]) -> None: - """ - Add signals into the processor. + """Add signals to the processor. + + Parameters + ---------- + list[Signal] + List of signals to add for simulation. """ self.signals += signals def get_clock(self) -> int: - """ - Get current clockcycle. + """Get the current clockcycle number. + + Returns + ------- + int + Current clock cycle number of the processor. """ return self.clock def set_clock(self, value: int) -> None: - """ - Set current clockcycle. + """Set current clockcycle number. + + Parameters + ---------- + value : int + Cycle number to set the clock to. """ self.clock = value @@ -327,10 +388,15 @@ class Processor: 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 cycle. + """Load the state of all modules as they were at the specified clock + cycle. This does not erase the history of states for the later cycles. + + Parameters + ---------- + cycle : int + Number of the cycle to load. """ cycle_index = cycle - self.removed_cycles @@ -355,13 +421,13 @@ class Processor: module = self.update_queue.pop(0) module.update_logic() - def load_state_from_file(self, file_path) -> None: - """ - Loads states for modules from a file. + def load_state_from_file(self, file_path: str) -> None: + """Load states for modules from a file. - Appends the lines from the loaded file to a long string. This string - is given to each module in the function 'load_from_str'. Each module - will thus load itself. + Parameters + ---------- + file_path : str + Path to the file containing the processor state to load. """ self.reset() @@ -374,6 +440,9 @@ class Processor: else: raise ValueError # Else raise error + # Appends the lines from the loaded file to a long string. This string + # is given to each module in the function 'load_from_str'. Each module + # will thus load itself. module_name = None module_str = "" @@ -405,10 +474,14 @@ class Processor: module.load_from_str(module_str) def save_state_to_file(self, file_path: str) -> None: - """ - Saves the current state of the cpu to a given file. + """Save the current state of the cpu to file. Will overwrite the given file. + + Parameters + ---------- + file_path : str + Path to the file to write to. """ file = open(file_path, "w") file.write("") @@ -419,13 +492,21 @@ class Processor: module.save_state_to_file(file_path) 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: """ - Prints the most relevant information about each module - compact and pretty. Fields of modules can be ignored with the - optional argument 'ignore_keys'. + 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 = [] @@ -455,7 +536,8 @@ class Processor: module_to_line_length = {} # get the longest line length for all other_modules for module in other_modules: - line_len = module.get_longest_line_len(ignore_keys) + 3 # +3 for padding + # +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 @@ -464,7 +546,7 @@ class Processor: print(Processor.LINE_SEPARATOR * Processor.MAX_LINE_LEN) - # print each group seperate + # print each group separate for group in groups: self.pretty_print_names(group) @@ -493,8 +575,7 @@ class Processor: real_keys[row] + ": " + str(module_state[real_keys[row]]) ) - # padd the string so each string has the same - # length + # 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 @@ -506,7 +587,14 @@ class Processor: for memory_module in memory_modules: self.pretty_print_memory(memory_module) - def pretty_print_memory(self, module: Module) -> None: + 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) @@ -515,11 +603,11 @@ class Processor: string = "" last_mem_len = module.get_largest_mem_adr() - for i, value in enumerate(module.memory): + for i, value in enumerate(module.get_state()["memory"]): - # create a new string containg the adress and the value - # of the memory adress formattet to fit with the largest - # adress and largest memory value + # 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 ) + "|" @@ -542,11 +630,16 @@ class Processor: def pretty_print_names(self, module_to_line_length: dict[Module, int]) -> None: """ - Prints the name of the modules in one row, but pretty. + 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. - Adds spacing between the names so that the modules - longest field will have space to be printed below. + 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 @@ -561,9 +654,20 @@ class Processor: def group_pp_modules( self, module_to_line_length: dict[Module, int] ) -> list[dict[Module, int]]: - """ - Groups the modules to be pretty printed into groups with a + """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 = [{}] @@ -586,8 +690,21 @@ class Processor: def fields_to_list(self, module: Module, ignore_keys=[]) -> list[str]: """ - Return a list containing all the keys for a module except the name. - Optinal argument to ignore specific keys. + 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 @@ -595,11 +712,24 @@ class Processor: if key != "name" and key not in ignore_keys ] - def get_most_fields(self, modules: dict[Module,int], ignore_keys=[]) -> int: - """ - Return the most number of a fields a module has. + def get_most_fields(self, modules: dict[Module, int], ignore_keys=[]) -> 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. """ fields = 0 for module in modules: @@ -612,6 +742,24 @@ class Processor: def add_state_breakpoint( self, module_name: str, state_name: str, value: Any ) -> None: + """Add a module state breakpoint to the processor. + + Parameters + ---------- + module_name : str + Name of the module for which a breakpoint will be added. + state_name : str + Name of the state variable of the module to put a breakpoint + on. + value : Any + Value of the state variable that triggers the breakpoint. + + Raises + ------ + ValueError + If the module does not exist or if the state variable does not + exist in the module. + """ if module_name not in self.modules: raise ValueError(f"No module named {module_name}") @@ -626,19 +774,43 @@ class Processor: self.breakpoint_id_counter += 1 def remove_breakpoint(self, id: int) -> bool: + """Remove a specific breakpoint. + + Parameters + ---------- + id : int + ID of the breakpoint to be removed. + """ 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: print("There are no breakpoints.") else: for bp_id, bp in self.breakpoints.items(): print(f"BP {bp_id}: {bp}") - def add_memory_breakpoint(self, module_name: str, adress: int, value: Any) -> None: + def add_memory_breakpoint(self, module_name: str, address: int, value: Any) -> None: + """Add a breakpoint for a memory module. + + Parameters + ---------- + module_name : str + Name of the memory module for which a breakpoint will be added. + address : int + Address of the memory module to break at. + value : Any + Value of the address that triggers the breakpoint. + + Raises + ------ + ValueError + If the module does not exist or is not a memory. + """ if module_name not in self.modules: raise ValueError(f"No module named {module_name}") @@ -647,14 +819,42 @@ class Processor: if "memory" not in module.get_state(): raise ValueError(f"Module {module_name} is not a memory.") - bp = MemoryBreakpoint(module, adress, value) + bp = MemoryBreakpoint(module, address, value) 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. + + Returns + ------- + list[str] + List of names of all functions of the processor that are available + for adding lambda breakpoints. + """ + return list(self.lambdas.keys()) def add_lambda_breakpoint(self, lambda_name: str, **kwargs) -> None: + """Add a lambda breakpoint to the processor. + + Module names used in the keyword arguments are substituted by the + actual modules before being passed on to the lambda breakpoint + function. + + Parameters + ---------- + lambda_name : str + Name of the lambda breakpoint function to be used by the + breakpoint. + **kwargs + Arguments to pass to the breakpoint function. + + Raises + ------ + ValueError + If the lambda breakpoint function does not exist. + """ # Substitute any module names for the module instances # This is to be able to externally create general lambda breakpoints # that can take any values as arguments while also having a way to take @@ -673,4 +873,13 @@ class Processor: self.breakpoint_id_counter += 1 def set_enabled_breakpoint(self, bp_id: int, is_enabled: bool) -> None: + """Toggle a breakpoint to enabled or disabled. + + Parameters + ---------- + bp_id : int + ID of the breakpoint to toggle. + is_enabled : bool + ``True`` to enable, ``False`` to disable the breakpoint. + """ self.breakpoints[bp_id].set_enabled(is_enabled)