def connect_quartets(quartet_a, quartet_b, direction_a_to_b): """ Use the processing element connection function to conveniently connect two quartets along an axis defined by direction_a_to_b. :param quartet_a: the first quartet :param quartet_b: the second quartet :param direction_a_to_b: the cardinal direction between processing elements a and b """ # Possibly something to refactor at some point. if direction_a_to_b == Direction.north: for j in range(2): connect_processing_elements( quartet_a.processing_elements[0 * 2 + j], quartet_b.processing_elements[1 * 2 + j], Direction.north) elif direction_a_to_b == Direction.east: for i in range(2): connect_processing_elements( quartet_a.processing_elements[i * 2 + 1], quartet_b.processing_elements[i * 2 + 0], Direction.east) elif direction_a_to_b == Direction.south: for j in range(2): connect_processing_elements( quartet_a.processing_elements[1 * 2 + j], quartet_b.processing_elements[0 * 2 + j], Direction.south) elif direction_a_to_b == Direction.west: for i in range(2): connect_processing_elements( quartet_a.processing_elements[i * 2 + 0], quartet_b.processing_elements[i * 2 + 1], Direction.west) else: raise SimulatorException("Invalid direction to connect two quartets.")
def peek(self): """ Return the next item in the channel. :return: head of the channel's FIFO """ # Perform a quick check then just return what we see. if self.empty: raise SimulatorException( "Attempted to peek the next value on an empty buffer.") return self.deque[0]
def dequeue(self): """ Dequeue the next value on the channel. Will raise an exception if the channel is empty. :return: dequeued packet """ # Perform a quick check then stage a packet to dequeue, and return the contents of said packet. if self.empty: raise SimulatorException( "Attempted to dequeue a packet from an empty buffer.") self.staged_dequeue = True self.pending = True return self.peek()
def enqueue(self, packet): """ Enqueue a packet on the channel. Will raise an exception if the channel is full. :param packet: packet to enqueue """ # Perform a quick check then stage a packet to be enqueued. if self.full: raise SimulatorException( "Attempted to enqueue a packet on a full buffer.") self.staged_enqueue = True self.staged_packet = packet self.pending = True
def initialize_registers(self, register_values): """ Initialize registers. :param register_values: abstract Python integer representations of data register contents """ # Check usage an perform explicit datatype conversion. if len(register_values) != len(self.registers): raise SimulatorException( "Register initialization data length and register file size do not match." ) for i, register_value in enumerate(register_values): self.registers[i] = np.uint32(register_value)
def connect_to_receiver_channel_buffer(self, direction, receiver_channel_buffer): """ Connect the router to a buffer that receives packets. :param direction: direction from the current processing element to the buffer :param receiver_channel_buffer: receiving buffer """ # Make sure we are connecting to the correct type of buffer. if not isinstance(receiver_channel_buffer, ReceiverChannelBuffer): exception_string = f"Cannot connect a channel buffer interface to a {type(receiver_channel_buffer)}." raise SimulatorException(exception_string) # The buffer is now one of our immediate destinations. self.destination_buffers[direction] = receiver_channel_buffer
def connect_to_sender_channel_buffer(self, direction, sender_channel_buffer): """ Connect the router to a buffer that emits packets. :param direction: direction from the current processing element to the buffer :param sender_channel_buffer: sending buffer """ # Make sure we are connecting to the correct type of buffer. if not isinstance(sender_channel_buffer, SenderChannelBuffer): exception_string = f"Cannot connect a channel buffer interface to a {type(sender_channel_buffer)}." raise SimulatorException(exception_string) # The buffer is now one of our immediate sources. self.source_buffers[direction] = sender_channel_buffer
def __init__(self, name, cp, ip): """ Initialize a processing element. :param name: an English name :param cp: a CoreParameters instance :param ip: an InterconnectParameters instance """ # Initialize each component. self.name = name self.core = Core(name, cp) if ip.router_type not in router_type_name_type_map: raise SimulatorException("Unsupported router type.") self.router = router_type_map[router_type_name_type_map[ip.router_type]](ip, self.core)
def register(self, element): """ Register a functional unit (processing element, memory, etc.) with the event loop. :param element: functional unit """ # Make sure the functional unit has a special registration method. registration_operation = getattr(element, "_register") if not callable(registration_operation): exception_string = f"The functional unit of type {type(element)} does not have internal system " \ + f"registration method." raise SimulatorException(exception_string) # Call the functional unit's internal method. element._register(self)
def connect_to_processing_element(self, direction, processing_element): """ Link a router to another software router/core processing element. :param direction: direction from the current processing element to the neighboring processing element :param processing_element: neighboring processing element """ # Make sure we are connecting to a processing element that is also using a software router. if not isinstance(processing_element.router, SoftwareRouter): raise SimulatorException(f"Cannot connect a {type(self)} to a {type(processing_element.router)}.") # Associate the two processing elements' source and destination buffers directly with their input and output # channel buffers. self.destination_buffers[direction] \ = processing_element.core.input_channel_buffers[reverse_direction_map[direction]] self.source_buffers[direction] \ = processing_element.core.output_channel_buffers[reverse_direction_map[direction]]
def __init__(self, name, row_base_index, column_base_index, num_columns, cp, ip): """ Initialize a quartet. :param name: the quartet name :param row_base_index: base index for row iteration :param column_base_index: base index for column iteration :param num_columns: number of columns of processing elements in the entire array :param cp: a CoreParameters instance :param ip: an InterconnectParameters instance """ # Fill in name, and instantiate underlying processing elements. self.name = name processing_element_names = [] for i in range(row_base_index, row_base_index + 2): for j in range(column_base_index, column_base_index + 2): processing_element_index = i * num_columns + j processing_element_names.append( f"processing_element_{processing_element_index}") if len(processing_element_names) != 4: raise SimulatorException( "Every processing element must have a name.") self.processing_elements = [] for processing_element_name in processing_element_names: self.processing_elements.append( ProcessingElement(name=processing_element_name, cp=cp, ip=ip)) # Wire up the processing elements. for i in range(2): for j in range(2): if j < 2 - 1: connect_processing_elements( self.processing_elements[i * 2 + j], self.processing_elements[i * 2 + j + 1], Direction.east) if i < 2 - 1: connect_processing_elements( self.processing_elements[i * 2 + j], self.processing_elements[(i + 1) * 2 + j], Direction.south)
def finalize(self): """ Alphabetize components in the event loop for clean debug output and make sure all processing elements are indexed. """ # The numerical strings are the ones we care about. def natural_number_sort_key(entity): name = entity.name key_string_list = re.findall(r"(\d+)", name) if len(key_string_list) > 0: return [int(key_string) for key_string in key_string_list] else: return [] # Sort all the entities. self.processing_elements = sorted(self.processing_elements, key=natural_number_sort_key) for i, processing_element in enumerate(self.processing_elements): if processing_element.name != f"processing_element_{i}": exception_string = f"Missing processing element {i}." raise SimulatorException(exception_string) self.memories = sorted(self.memories, key=natural_number_sort_key) self.buffers = sorted(self.buffers, key=natural_number_sort_key)
def iterate(self, debug, keep_execution_trace): """ Perform a single cycle of execution. :param debug: whether to print out information about internal state :param keep_execution_trace: whether to maintain a running log of indices of fired instructions """ # Show PE state for debugging purposes if necessary. if debug: print(f"name: {self.name}") predicate_string_list = [] for predicate in self.predicates: if predicate: predicate_string_list.append("1") else: predicate_string_list.append("0") predicate_string = "".join(predicate_string_list) predicate_string = predicate_string[::-1] print(f"predicates: {predicate_string}") print("registers:") register_strings = [ f"{i:02d}: 0x{register:08x}" for i, register in enumerate(self.registers) ] print("\n".join(register_strings)) print(f"number of instructions: {len(self.instructions)}") i = None valid_instruction = None for i, instruction in enumerate(self.instructions): if self.check_trigger(instruction.trigger): valid_instruction = instruction break if i is not None and valid_instruction is not None: print(f"valid instruction: {i}") print(f"triggered instruction: {valid_instruction.op.name}") else: print("valid instruction: None") print("triggered instruction: nop") print(f"halt register: {self.halt_register}") print(f"instructions retired: {self.instructions_retired}") print(f"untriggered cycles: {self.untriggered_cycles}\n") # Check for an halt condition. valid_instruction = None if not self.halt_register: # Find a valid instruction if one exists, searching the list of instructions in priority order. for instruction in self.instructions: if self.check_trigger(instruction.trigger): valid_instruction = instruction break # Execute any valid instruction. if valid_instruction is not None: # Increment the performance counter. self.instructions_retired += 1 # Set the halt register if required. if valid_instruction.op == Op.halt: self.halt_register = True # Get operands from their sources and perform any source actions. a_type, b_type, c_type = valid_instruction.source_types a_index, b_index, c_index = valid_instruction.source_indices if a_type == SourceType.immediate: a = valid_instruction.immediate elif a_type == SourceType.channel: a = self.input_channel_buffers[a_index].peek().value elif a_type == SourceType.register: a = self.registers[a_index] elif a_type == SourceType.null: a = 0 else: raise SimulatorException( "Unknown source type for operand a.") if b_type == SourceType.immediate: b = valid_instruction.immediate elif b_type == SourceType.channel: b = self.input_channel_buffers[b_index].peek().value elif b_type == SourceType.register: b = self.registers[b_index] elif b_type == SourceType.null: b = 0 else: raise SimulatorException( "Unknown source type for operand b.") if c_type == SourceType.immediate: c = valid_instruction.immediate elif c_type == SourceType.channel: c = self.input_channel_buffers[c_index].peek().value elif c_type == SourceType.register: c = self.registers[c_index] elif c_type == SourceType.null: c = 0 else: raise SimulatorException( "Unknown source type for operand b.") # Perform operation. if valid_instruction.op == Op.lsw: if self.scratchpad is None: raise SimulatorException( "Attempting to load a word in a core that has no scratchpad." ) result = self.scratchpad[a] elif valid_instruction.op == Op.ssw: if self.scratchpad is None: raise SimulatorException( "Attempting to store a word in a core that has no scratchpad." ) self.scratchpad[b] = a result = 0 else: result = op_implementation_map[valid_instruction.op](a, b, c) # Store results in the destination. destination_type = valid_instruction.destination_type destination_index = valid_instruction.destination_index if destination_type == DestinationType.channel: result_packet = Packet( valid_instruction.output_channel_tag, result) for i in valid_instruction.output_channel_indices: self.output_channel_buffers[i].enqueue(result_packet) elif destination_type == DestinationType.register: self.registers[destination_index] = result elif destination_type == DestinationType.predicate: self.predicates[destination_index] = bool(result) elif destination_type == DestinationType.null: pass else: raise SimulatorException("Unknown destination type.") # Dequeue any required input channels. for i in valid_instruction.input_channels_to_dequeue: self.input_channel_buffers[i].dequeue() # Perform any PE updates necessary. for i, index in enumerate( valid_instruction.predicate_update_indices): self.predicates[ index] = valid_instruction.predicate_update_values[i] else: # No valid instruction this cycle. self.untriggered_cycles += 1 # Internal execution trace. if keep_execution_trace: self.execution_trace.append(valid_instruction.number if valid_instruction is not None else -1)