def main(args=None): parser = argparse.ArgumentParser( description="Print a summary of basic SpiNNaker machine " "and BMP information") parser.add_argument("--version", "-V", action="version", version="%(prog)s {}".format(rig.__version__)) parser.add_argument("hostname", type=str, help="hostname or IP of SpiNNaker system or BMP") args = parser.parse_args(args) # Determine what type of machine this is and print information accordingly try: mc = MachineController(args.hostname) info = mc.get_software_version(255, 255) if "SpiNNaker" in info.version_string: for line in get_spinnaker_info(mc): print(line) elif "BMP" in info.version_string: bc = BMPController(args.hostname) for line in get_bmp_info(bc): print(line) else: sys.stderr.write("{}: error: unknown architecture '{}'\n".format( parser.prog, info.version_string)) return 2 except TimeoutError: sys.stderr.write("{}: error: command timed out\n".format( parser.prog)) return 1 return 0
def __init__(self, hostname, on_thread_start=None): """Start a new asynchronous BMP Controller Parameters ---------- hostname : str The hostname/IP of the BMP to connect to. on_thread_start : function() or None *Optional.* A function to be called by the controller's background thread before it starts. This can be used to ensure propper sequencing/handing-over between two AsyncBMPControllers connected to the same machine. """ self._on_thread_start = on_thread_start self._bc = BMPController(hostname) self._stop = False # A lock which must be held when modifying the state of this object self._lock = threading.RLock() # An event fired whenever some new interaction with the BMP is # required. self._requests_pending = threading.Event() # A queue of power change states self._power_requests = deque() # A queue of link-enabled state changes self._link_requests = deque() self._thread = threading.Thread( target=self._run, name="<BMP control thread for {}>".format(hostname)) self._thread.start()
class AsyncBMPController(object): """An object which provides an asynchronous interface to a power and link control commands of a SpiNNaker BMP. Since BMP commands, particularly power-on commands, take some time to complete, it is desirable for them to be executed asynchronously. This object uses a Rig :py:class:`~rig.machine_control.BMPController` object to communicate with a BMP controlling a single frame of boards. Power and link configuration commands are queued and executed in a background thread. When a command completes, a user-supplied callback is called. Sequential power commands of the same type (on/off) are coalesced into a single power on command. When a power command is sent, all previous link configuration commands queued for that board are skipped. Additionally, all power commands are completed before link configuration commands are carried out. """ def __init__(self, hostname, on_thread_start=None): """Start a new asynchronous BMP Controller Parameters ---------- hostname : str The hostname/IP of the BMP to connect to. on_thread_start : function() or None *Optional.* A function to be called by the controller's background thread before it starts. This can be used to ensure propper sequencing/handing-over between two AsyncBMPControllers connected to the same machine. """ self._on_thread_start = on_thread_start self._bc = BMPController(hostname) self._stop = False # A lock which must be held when modifying the state of this object self._lock = threading.RLock() # An event fired whenever some new interaction with the BMP is # required. self._requests_pending = threading.Event() # A queue of power change states self._power_requests = deque() # A queue of link-enabled state changes self._link_requests = deque() self._thread = threading.Thread( target=self._run, name="<BMP control thread for {}>".format(hostname)) self._thread.start() def __enter__(self): """When used as a context manager, make requests 'atomic'.""" self._lock.acquire() def __exit__(self, type=None, value=None, traceback=None): self._lock.release() def set_power(self, board, state, on_done): """Set the power state of a single board. Parameters ---------- board : int The board to control. state : bool True = on, False = off. on_done : function(success) Function to call when the command completes. May be called from another thread. Success is a bool which is True if the command completed successfully and False if it did not (or was cancelled). """ with self._lock: assert not self._stop # Enqueue the request self._power_requests.append(_PowerRequest(state, board, on_done)) self._requests_pending.set() # Cancel any existing link enable commands for this board cancelled = [] for request in list(self._link_requests): if request.board == board: self._link_requests.remove(request) cancelled.append(request) for request in cancelled: request.on_done(False) def set_link_enable(self, board, link, enable, on_done): """Enable or disable a link. Parameters ---------- board : int The board on which the link resides. link : :py:class:`rig.links.Links` The link to configure. enable : bool True = link enabled, False = link disabled. on_done : function(success) Function to call when the command completes. May be called from another thread. Success is a bool which is True if the command completed successfully and False if it did not (or was cancelled). """ with self._lock: assert not self._stop # Enqueue the request self._link_requests.append( _LinkRequest(board, link, enable, on_done)) self._requests_pending.set() def stop(self): """Stop the background thread, as soon as possible after completing all queued actions. """ with self._lock: self._stop = True self._requests_pending.set() def join(self): """Wait for the thread to actually stop.""" self._thread.join() def _run(self): """The background thread for interacting with the BMP. """ try: if self._on_thread_start is not None: self._on_thread_start() while True: self._requests_pending.wait() # Priority 0: Power commands power_request = self._get_atomic_power_request() if power_request: # Send the power command try: self._bc.set_power(state=power_request.state, board=power_request.board) success = True except IOError: # Communication issue with the machine, log it but not # much we can do for the end-user. logging.exception("Failed to set board power.") success = False # Alert all waiting threads for on_done in power_request.on_done: on_done(success) continue # Priority 1: Link enable/disable commands link_request = self._get_atomic_link_request() if link_request: # Set the link state, as required try: fpga, addr \ = FPGA_LINK_STOP_REGISTERS[link_request.link] self._bc.write_fpga_reg(fpga, addr, not link_request.enable, board=link_request.board) success = True except IOError: # Communication issue with the machine, log it but not # much we can do for the end-user. logging.exception("Failed to set link state.") success = False # Alert waiting thread link_request.on_done(success) continue # If nothing left in the queues, clear the request flag and # break out of queue-processing loop. with self._lock: if (not self._power_requests and # pragma: no branch not self._link_requests): self._requests_pending.clear() # If we've been told to stop, actually stop the thread # now if self._stop: # pragma: no branch return except: # pragma: no cover # If the thread crashes something has gone wrong with this program # (not the machine), setting _stop will cause set_power and # set_link_enable to fail, hopefully propogating news of this # crash.. with self._lock: self._stop = True raise def _get_atomic_power_request(self): """If any power requests are outstanding, return a (boards, state) tuple which combines as many of the requests at the head of the queue as possible. Returns ------- :py:class:`._PowerRequest` or None """ with self._lock: # Special case: no requests if not self._power_requests: return None # Otherwise, accumulate as many boards as possible state = self._power_requests[0].state boards = set() on_done = [] while (self._power_requests and self._power_requests[0].state == state): request = self._power_requests.popleft() boards.add(request.board) on_done.append(request.on_done) return _PowerRequest(state, boards, on_done) def _get_atomic_link_request(self): """Pop the next link state change request, if one exists. Returns ------- :py:class:`._LinkRequest` or None """ with self._lock: if not self._link_requests: return None else: return self._link_requests.popleft()
def main(args=None): parser = argparse.ArgumentParser( description= "Interactively guide the user through the process of wiring up a " "SpiNNaker machine.") arguments.add_version_args(parser) parser.add_argument("--no-tts", action="store_true", default=False, help="disable text-to-speech announcements of wiring " "steps") parser.add_argument("--no-auto-advance", action="store_true", default=False, help="disable auto-advancing through wiring steps") parser.add_argument("--fix", action="store_true", default=False, help="detect errors in existing wiring and just show " "corrective steps") parser.add_argument( "--log", type=str, metavar="LOGFILE", help="record the times at which each cable is installed") arguments.add_topology_args(parser) arguments.add_cabinet_args(parser) arguments.add_wire_length_args(parser) arguments.add_bmp_args(parser) arguments.add_proxy_args(parser) arguments.add_subset_args(parser) # Process command-line arguments args = parser.parse_args(args) (w, h), transformation, uncrinkle_direction, folds =\ arguments.get_topology_from_args(parser, args) cabinet, num_frames = arguments.get_cabinets_from_args(parser, args) wire_lengths, min_slack = arguments.get_wire_lengths_from_args( parser, args, mandatory=True) bmp_ips = arguments.get_bmps_from_args(parser, args, cabinet.num_cabinets, num_frames) proxy_host_port = arguments.get_proxy_from_args(parser, args) wire_filter = arguments.get_subset_from_args(parser, args) if cabinet.num_cabinets == num_frames == 1: num_boards = 3 * w * h else: num_boards = cabinet.boards_per_frame # Generate folded system hex_boards, folded_boards = folded_torus(w, h, transformation, uncrinkle_direction, folds) # Divide into cabinets cabinetised_boards = transforms.cabinetise(folded_boards, cabinet.num_cabinets, num_frames, cabinet.boards_per_frame) cabinetised_boards = transforms.remove_gaps(cabinetised_boards) physical_boards = transforms.cabinet_to_physical(cabinetised_boards, cabinet) # Focus on only the boards which are part of the system if cabinet.num_cabinets > 1: focus = [slice(0, cabinet.num_cabinets)] elif num_frames > 1: focus = [0, slice(0, num_frames)] else: focus = [0, 0, slice(0, w * h * 3)] # Generate wiring plan wires_between_boards, wires_between_frames, wires_between_cabinets =\ generate_wiring_plan(cabinetised_boards, physical_boards, cabinet.board_wire_offset, wire_lengths, min_slack) flat_wiring_plan = flatten_wiring_plan(wires_between_boards, wires_between_frames, wires_between_cabinets, cabinet.board_wire_offset) # Create a BMP connection/wiring probe or connect to a proxy if proxy_host_port is None: if len(bmp_ips) == 0: if args.fix: parser.error( "--fix requires that all BMPs be listed with --bmp") bmp_controller = None wiring_probe = None else: bmp_controller = BMPController(bmp_ips) # Create a wiring probe if bmp_controller is not None and (not args.no_auto_advance or args.fix): wiring_probe = WiringProbe(bmp_controller, cabinet.num_cabinets, num_frames, num_boards) else: # Fix is not supported since the proxy client does not recreate the # discover_wires method of WiringProbe. if args.fix: parser.error("--fix cannot be used with --proxy") # The proxy object provides a get_link_target and set_led method compatible # with those provided by bmp_controller and wiring_probe. Since these are # the only methods used, we use the proxy client object in place of # bmp_controller and wiring_probe. bmp_controller = wiring_probe = ProxyClient(*proxy_host_port) # Create a TimingLogger if required if args.log: if os.path.isfile(args.log): logfile = open(args.log, "a") add_header = False else: logfile = open(args.log, "w") add_header = True timing_logger = TimingLogger(logfile, add_header) else: logfile = None timing_logger = None # Convert wiring plan into cabinet coordinates b2c = dict(cabinetised_boards) wires = [] for ((src_board, src_direction), (dst_board, dst_direction), wire_length) \ in flat_wiring_plan: sc, sf, sb = b2c[src_board] dc, df, db = b2c[dst_board] wires.append(((sc, sf, sb, src_direction), (dc, df, db, dst_direction), wire_length)) # Filter wires according to user-specified rules wires = list(filter(wire_filter, wires)) if len(wires) == 0: parser.error("--subset selects no wires") if not args.fix: # If running normally, just run through the full set of wires wiring_plan = wires else: # If running in fix mode, generate a list of fixes to make correct_wires = set((src, dst) for src, dst, length in wires) actual_wires = set(wiring_probe.discover_wires()) to_remove = actual_wires - correct_wires to_add = correct_wires - actual_wires # Remove all bad wires first, then re-add good ones (note ordering now is # just reset to cabinets right-to-left, frames top-to-bottom and boards # left-to-right). wiring_plan = [(src, dst, None) for src, dst in sorted(to_remove)] for src, dst, length in wires: if (src, dst) in to_add: wiring_plan.append((src, dst, length)) if len(wiring_plan) == 0: print("No corrections required.") return 0 # Intialise the GUI and launch the mainloop ui = InteractiveWiringGuide(cabinet=cabinet, wire_lengths=wire_lengths, wires=wiring_plan, bmp_controller=bmp_controller, use_tts=not args.no_tts, focus=focus, wiring_probe=wiring_probe, auto_advance=not args.no_auto_advance, timing_logger=timing_logger) ui.mainloop() if logfile is not None: logfile.close() return 0
def main(args=None): parser = argparse.ArgumentParser( description="Validate the wiring of a SpiNNaker system.") arguments.add_version_args(parser) parser.add_argument("--verbose", "-v", action="store_true", default=False, help="list all incorrect and missing wires") arguments.add_topology_args(parser) arguments.add_cabinet_args(parser) arguments.add_bmp_args(parser) # Process command-line arguments args = parser.parse_args(args) (w, h), transformation, uncrinkle_direction, folds =\ arguments.get_topology_from_args(parser, args) cabinet, num_frames = arguments.get_cabinets_from_args(parser, args) bmp_ips = arguments.get_bmps_from_args(parser, args, cabinet.num_cabinets, num_frames) if len(bmp_ips) == 0: parser.error("BMP host names must be provided for every frame.") # Generate folded system hex_boards, folded_boards = folded_torus(w, h, transformation, uncrinkle_direction, folds) # Divide into cabinets cabinetised_boards = transforms.cabinetise(folded_boards, cabinet.num_cabinets, num_frames, cabinet.boards_per_frame) cabinetised_boards = transforms.remove_gaps(cabinetised_boards) # Generate list of wires wires = plan.enumerate_wires(cabinetised_boards) # Set up the wiring probe bmp_controller = BMPController(bmp_ips) if cabinet.num_cabinets == 1 and num_frames == 1: num_boards = 3 * w * h else: num_boards = cabinet.boards_per_frame wiring_probe = probe.WiringProbe(bmp_controller, cabinet.num_cabinets, num_frames, num_boards) # Check for the presence of every wire missing = [] b2c = dict(cabinetised_boards) for ((src_board, src_direction), (dst_board, dst_direction)) in wires: src = tuple(list(b2c[src_board]) + [src_direction]) dst = tuple(list(b2c[dst_board]) + [dst_direction]) actual_dst = wiring_probe.get_link_target(*src) actual_src = wiring_probe.get_link_target(*dst) if actual_dst != dst or actual_src != src: missing.append((src, dst)) if missing: sys.stderr.write("{} wires missing or erroneously connected.\n".format( len(missing), len(wires))) if args.verbose: for src, dst in missing: print("C:{} F:{} B:{} {} <--> C:{} F:{} B:{} {}".format( src[0], src[1], src[2], src[3].name.replace("_", " "), dst[0], dst[1], dst[2], dst[3].name.replace("_", " "))) else: print("Add --verbose for a complete list.") return -1 else: sys.stderr.write("All {} wires correctly connected.\n".format(len(wires))) return 0
def main(args=None): parser = argparse.ArgumentParser( description="Start a proxy server to enable multiple interactive wiring " "sessions to interact with the same SpiNNaker machine.") arguments.add_version_args(parser) parser.add_argument("--host", "-H", type=str, default="", help="Host interface to listen on (default: any)") parser.add_argument("--port", "-p", type=int, default=DEFAULT_PORT, help="Port listen on (default: %(default)d)") parser.add_argument("--verbose", "-v", action="count", default=0, help="Increase verbosity.") arguments.add_topology_args(parser) arguments.add_cabinet_args(parser) arguments.add_bmp_args(parser) # Process command-line arguments args = parser.parse_args(args) (w, h), transformation, uncrinkle_direction, folds =\ arguments.get_topology_from_args(parser, args) cabinet, num_frames = arguments.get_cabinets_from_args(parser, args) bmp_ips = arguments.get_bmps_from_args(parser, args, cabinet.num_cabinets, num_frames) if cabinet.num_cabinets == num_frames == 1: num_boards = 3 * w * h else: num_boards = cabinet.boards_per_frame # Set verbosity level if args.verbose == 1: logging.basicConfig(level=logging.INFO) elif args.verbose >= 2: logging.basicConfig(level=logging.DEBUG) # Create a BMP connection if len(bmp_ips) == 0: parser.error("All BMPs must be supplied using --bmp") bmp_controller = BMPController(bmp_ips) # Create a wiring probe wiring_probe = WiringProbe(bmp_controller, cabinet.num_cabinets, num_frames, num_boards) proxy_server = ProxyServer(bmp_controller, wiring_probe, args.host, args.port) print("Proxy server starting...") proxy_server.main() return 0
def main(args=None): parser = argparse.ArgumentParser( description="Control SpiNNaker board power (via a BMP)") parser.add_argument("--version", "-V", action="version", version="%(prog)s {}".format(rig.__version__)) parser.add_argument("hostname", type=str, help="hostname or IP of a SpiNNaker board BMP") parser.add_argument("state", type=str, default=ON_CHOICES[0], nargs="?", choices=ON_CHOICES + OFF_CHOICES) parser.add_argument("-b", "--board", type=str, default="0-23", help="board number (e.g. 0) " "or range of boards (e.g. 1,3,4-6)") parser.add_argument("-d", "--power-on-delay", type=float, default=None, help="specify delay (seconds) after power on " "command completes") args = parser.parse_args(args) # To power on, or to power off, that is the question if args.state in ON_CHOICES: state = True elif args.state in OFF_CHOICES: # pragma: no branch state = False # Check power-on-delay range if args.power_on_delay is not None and args.power_on_delay < 0.0: parser.error("power on delay must be positive") # Parse the board number range boards = set() range_specs = args.board.split(",") for range_spec in range_specs: left, sep, right = map(str.strip, range_spec.partition("-")) try: if sep: if right.startswith("-"): raise ValueError() left = int(left) right = int(right) if left > right or left < 0 or right < 0: raise ValueError() boards.update(range(int(left), int(right) + 1)) else: boards.add(int(left)) except ValueError: parser.error("'{}' is not a valid board/range".format( range_spec)) bc = BMPController(args.hostname) try: # Check that the device is a actually BMP info = bc.get_software_version(board=next(iter(boards))) if "BMP" not in info.version_string: sys.stderr.write("{}: error: device is not a BMP\n".format( parser.prog)) return 2 # Actually send the command if args.power_on_delay is None: bc.set_power(state=state, board=boards) else: bc.set_power(state=state, board=boards, post_power_on_delay=args.power_on_delay) except TimeoutError: sys.stderr.write("{}: error: bmp did not respond to command\n".format( parser.prog)) return 1 return 0