diff --git a/spinnman/extended/extended_transceiver.py b/spinnman/extended/extended_transceiver.py index 5615bbe68..c032f7b6b 100644 --- a/spinnman/extended/extended_transceiver.py +++ b/spinnman/extended/extended_transceiver.py @@ -17,10 +17,12 @@ import io import os import logging -from threading import Condition, RLock +import struct import time +from spinn_utilities.abstract_base import AbstractBase from spinn_utilities.log import FormatAdapter from spinn_utilities.logger_utils import warn_once +from spinn_utilities.require_subclass import require_subclass from spinn_machine import CoreSubsets from spinnman.constants import ( ROUTER_REGISTER_BASE_ADDRESS, ROUTER_FILTER_CONTROLS_OFFSET, @@ -33,88 +35,22 @@ from spinnman.messages.scp.enums import Signal from spinnman.messages.scp.impl import ( ReadMemory, ApplicationRun) +from spinnman.connections.udp_packet_connections import SCAMPConnection +from spinnman.constants import SYSTEM_VARIABLE_BASE_ADDRESS +from spinnman.data import SpiNNManDataView from spinnman.messages.spinnaker_boot import SystemVariableDefinition -from spinnman.connections.udp_packet_connections import ( - BMPConnection, BootConnection, SCAMPConnection) from spinnman.processes import ( GetHeapProcess, ReadMemoryProcess, SendSingleCommandProcess, WriteMemoryProcess) -from spinnman.transceiver.version5Transceiver import Version5Transceiver -from spinnman.transceiver.watchdog_setter import WatchdogSetter -from spinnman.utilities.utility_functions import ( - work_out_bmp_from_machine_details) +from spinnman.transceiver.extendable_transceiver import ExtendableTransceiver -logger = FormatAdapter(logging.getLogger(__name__)) - - -def create_transceiver_from_hostname( - hostname, version, bmp_connection_data=None, number_of_boards=None, - auto_detect_bmp=False): - """ - Create a Transceiver by creating a :py:class:`~.UDPConnection` to the - given hostname on port 17893 (the default SCAMP port), and a - :py:class:`~.BootConnection` on port 54321 (the default boot port), - optionally discovering any additional links using the UDPConnection, - and then returning the transceiver created with the conjunction of - the created UDPConnection and the discovered connections. - - :param hostname: The hostname or IP address of the board or `None` if - only the BMP connections are of interest - :type hostname: str or None - :param number_of_boards: a number of boards expected to be supported, or - ``None``, which defaults to a single board - :type number_of_boards: int or None - :param int version: the type of SpiNNaker board used within the SpiNNaker - machine being used. If a Spinn-5 board, then the version will be 5, - Spinn-3 would equal 3 and so on. - :param list(BMPConnectionData) bmp_connection_data: - the details of the BMP connections used to boot multi-board systems - :param bool auto_detect_bmp: - ``True`` if the BMP of version 4 or 5 boards should be - automatically determined from the board IP address - :param scamp_connections: - the list of connections used for SCAMP communications - :return: The created transceiver - :rtype: Transceiver - :raise SpinnmanIOException: - If there is an error communicating with the board - :raise SpinnmanInvalidPacketException: - If a packet is received that is not in the valid format - :raise SpinnmanInvalidParameterException: - If a packet is received that has invalid parameters - :raise SpinnmanUnexpectedResponseCodeException: - If a response indicates an error during the exchange - """ - if hostname is not None: - logger.info("Creating transceiver for {}", hostname) - connections = list() - - # if no BMP has been supplied, but the board is a spinn4 or a spinn5 - # machine, then an assumption can be made that the BMP is at -1 on the - # final value of the IP address - if (version >= 4 and auto_detect_bmp is True and - (bmp_connection_data is None or not bmp_connection_data)): - bmp_connection_data = [ - work_out_bmp_from_machine_details(hostname, number_of_boards)] - - # handle BMP connections - if bmp_connection_data is not None: - bmp_ip_list = list() - for conn_data in bmp_connection_data: - bmp_connection = BMPConnection(conn_data) - connections.append(bmp_connection) - bmp_ip_list.append(bmp_connection.remote_ip_address) - logger.info("Transceiver using BMPs: {}", bmp_ip_list) - - connections.append(SCAMPConnection(remote_host=hostname)) - - # handle the boot connection - connections.append(BootConnection(remote_host=hostname)) +_ONE_BYTE = struct.Struct("B") - return ExtendedTransceiver(version, connections=connections) +logger = FormatAdapter(logging.getLogger(__name__)) -class ExtendedTransceiver(Version5Transceiver, WatchdogSetter): +@require_subclass(ExtendableTransceiver) +class ExtendedTransceiver(object, metaclass=AbstractBase): """ An encapsulation of various communications with the SpiNNaker board. @@ -130,8 +66,7 @@ class ExtendedTransceiver(Version5Transceiver, WatchdogSetter): the multiple calls may be made separately over the set of given connections. """ - __slots__ = ["_flood_write_lock", "_nearest_neighbour_id", - "_nearest_neighbour_lock"] + __slots__ = [] def __init__(self, connections=None): """ @@ -150,13 +85,6 @@ def __init__(self, connections=None): """ super().__init__(connections) - # A lock against multiple flood fill writes - needed as SCAMP cannot - # cope with this - self._flood_write_lock = Condition() - - # The nearest neighbour start ID and lock - self._nearest_neighbour_id = 1 - self._nearest_neighbour_lock = RLock() def send_scp_message(self, message, connection=None): """ @@ -185,31 +113,22 @@ def send_scp_message(self, message, connection=None): connection = self._get_random_connection(self._scamp_connections) connection.send_scp_request(message) - def get_connections(self): - """ - Get the currently known connections to the board, made up of those - passed in to the transceiver and those that are discovered during - calls to discover_connections. No further discovery is done here. - - :return: An iterable of connections known to the transceiver - :rtype: list(Connection) - """ - return self._all_connections - def is_connected(self, connection=None): """ - Determines if the board can be contacted. + Determines if the board can be contacted via SCAMP :param Connection connection: The connection which is to be tested. If `None`, - all connections will be tested, and the board will be considered + all Scamp connections will be tested, + and the board will be considered to be connected if any one connection works. :return: True if the board can be contacted, False otherwise :rtype: bool """ if connection is not None: return connection.is_connected() - return any(c.is_connected() for c in self._scamp_connections) + return any(c.is_connected() and isinstance(c, SCAMPConnection) + for c in self._scamp_connections) def get_iobuf_from_core(self, x, y, p): """ @@ -392,7 +311,7 @@ def set_led(self, led, action, board): """ warn_once(logger, "The set_led method is deprecated and " "untested due to no known use.") - process = SendSingleCommandProcess(self._bmp_selector) + process = SendSingleCommandProcess(self.bmp_selector) process.execute(BMPSetLed(led, action, board)) def read_adc_data(self, board): @@ -410,7 +329,7 @@ def read_adc_data(self, board): """ warn_once(logger, "The read_adc_data method is deprecated and " "untested due to no known use.") - process = SendSingleCommandProcess(self._bmp_selector) + process = SendSingleCommandProcess(self.bmp_selector) response = process.execute(ReadADC(board)) return response.adc_info # pylint: disable=no-member @@ -468,7 +387,7 @@ def write_neighbour_memory(self, x, y, link, base_address, data, """ warn_once(logger, "The write_neighbour_memory method is deprecated " "and untested due to no known use.") - process = WriteMemoryProcess(self._scamp_connection_selector) + process = WriteMemoryProcess(self.scamp_connection_selector) if isinstance(data, io.RawIOBase): process.write_link_memory_from_reader( x, y, cpu, link, base_address, data, n_bytes) @@ -519,11 +438,11 @@ def read_neighbour_memory(self, x, y, link, base_address, length, cpu=0): try: warn_once(logger, "The read_neighbour_memory method is deprecated " "and untested due to no known use.") - process = ReadMemoryProcess(self._scamp_connection_selector) + process = ReadMemoryProcess(self.scamp_connection_selector) return process.read_link_memory( x, y, cpu, link, base_address, length) except Exception: - logger.info(self._where_is_xy(x, y)) + logger.info(self.where_is_xy(x, y)) raise def _get_next_nearest_neighbour_id(self): @@ -627,7 +546,7 @@ def set_leds(self, x, y, cpu, led_states): process = SendSingleCommandProcess(self._scamp_connection_selector) process.execute(SetLED(x, y, cpu, led_states)) except Exception: - logger.info(self._where_is_xy(x, y)) + logger.info(self.where_is_xy(x, y)) raise def free_sdram(self, x, y, base_address, app_id): @@ -648,7 +567,7 @@ def free_sdram(self, x, y, base_address, app_id): process = DeAllocSDRAMProcess(self._scamp_connection_selector) process.de_alloc_sdram(x, y, app_id, base_address) except Exception: - logger.info(self._where_is_xy(x, y)) + logger.info(self.where_is_xy(x, y)) raise def free_sdram_by_app_id(self, x, y, app_id): @@ -673,7 +592,7 @@ def free_sdram_by_app_id(self, x, y, app_id): process.de_alloc_sdram(x, y, app_id) return process.no_blocks_freed except Exception: - logger.info(self._where_is_xy(x, y)) + logger.info(self.where_is_xy(x, y)) raise def get_router_diagnostic_filter(self, x, y, position): @@ -708,13 +627,13 @@ def get_router_diagnostic_filter(self, x, y, position): position * ROUTER_DIAGNOSTIC_FILTER_SIZE) process = SendSingleCommandProcess( - self._scamp_connection_selector) + self.scamp_connection_selector) response = process.execute(ReadMemory(x, y, memory_position, 4)) return DiagnosticFilter.read_from_int(self._ONE_WORD.unpack_from( response.data, response.offset)[0]) # pylint: disable=no-member except Exception: - logger.info(self._where_is_xy(x, y)) + logger.info(self.where_is_xy(x, y)) raise @property @@ -729,8 +648,8 @@ def number_of_boards_located(self): """ warn_once(logger, "The number_of_boards_located method is deprecated " "and likely to be removed.") - if self._bmp_connection is not None: - return max(1, len(self._bmp_connection.boards)) + if self.bmp_connection is not None: + return max(1, len(self.bmp_connection.boards)) else: # if no BMPs are available, then there's still at least one board return 1 @@ -746,8 +665,61 @@ def get_heap(self, x, y, heap=SystemVariableDefinition.sdram_heap_address): :rtype: list(HeapElement) """ try: - process = GetHeapProcess(self._scamp_connection_selector) + process = GetHeapProcess(self.scamp_connection_selector) return process.get_heap((x, y), heap) except Exception: - logger.info(self._where_is_xy(x, y)) + logger.info(self.where_is_xy(x, y)) raise + + def __set_watch_dog_on_chip(self, x, y, watch_dog): + """ + Enable, disable or set the value of the watch dog timer on a + specific chip. + + .. warning:: + This method is currently deprecated and untested as there is no + known use. Same functionality provided by ybug and bmpc. + Retained in case needed for hardware debugging. + + :param int x: chip X coordinate to write new watchdog parameter to + :param int y: chip Y coordinate to write new watchdog parameter to + :param watch_dog: + Either a boolean indicating whether to enable (True) or + disable (False) the watchdog timer, or an int value to set the + timer count to + :type watch_dog: bool or int + """ + # build what we expect it to be + warn_once(logger, "The set_watch_dog_on_chip method is deprecated " + "and untested due to no known use.") + value_to_set = watch_dog + watchdog = SystemVariableDefinition.software_watchdog_count + if isinstance(watch_dog, bool): + value_to_set = watchdog.default if watch_dog else 0 + + # build data holder + data = _ONE_BYTE.pack(value_to_set) + + # write data + address = SYSTEM_VARIABLE_BASE_ADDRESS + watchdog.offset + self.write_memory(x=x, y=y, base_address=address, data=data) + + def set_watch_dog(self, watch_dog): + """ + Enable, disable or set the value of the watch dog timer. + + .. warning:: + This method is currently deprecated and untested as there is no + known use. Same functionality provided by ybug and bmpc. + Retained in case needed for hardware debugging. + + :param watch_dog: + Either a boolean indicating whether to enable (True) or + disable (False) the watch dog timer, or an int value to set the + timer count to. + :type watch_dog: bool or int + """ + warn_once(logger, "The set_watch_dog method is deprecated and " + "untested due to no known use.") + for x, y in SpiNNManDataView.get_machine().chip_coordinates: + self.__set_watch_dog_on_chip(x, y, watch_dog) diff --git a/spinnman/extended/version3transceiver.py b/spinnman/extended/version3transceiver.py new file mode 100644 index 000000000..c2b8e6603 --- /dev/null +++ b/spinnman/extended/version3transceiver.py @@ -0,0 +1,19 @@ +# Copyright (c) 2023 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from .extended_transceiver import ExtendedTransceiver +from spinnman.transceiver.version3transceiver import Version3Transceiver + + +class ExtendedVersion3Transceiver(Version3Transceiver, ExtendedTransceiver): + pass diff --git a/spinnman/extended/version5transceiver.py b/spinnman/extended/version5transceiver.py new file mode 100644 index 000000000..24e1a7718 --- /dev/null +++ b/spinnman/extended/version5transceiver.py @@ -0,0 +1,19 @@ +# Copyright (c) 2023 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from .extended_transceiver import ExtendedTransceiver +from spinnman.transceiver.version5transceiver import Version5Transceiver + + +class ExtendedVersion5Transceiver(Version5Transceiver, ExtendedTransceiver): + pass diff --git a/spinnman/messages/spinnaker_boot/spinnaker_boot_messages.py b/spinnman/messages/spinnaker_boot/spinnaker_boot_messages.py index 30e327c84..e7fad9022 100644 --- a/spinnman/messages/spinnaker_boot/spinnaker_boot_messages.py +++ b/spinnman/messages/spinnaker_boot/spinnaker_boot_messages.py @@ -41,7 +41,7 @@ class SpinnakerBootMessages(object): "_n_bytes_to_read", "_no_data_packets"] - def __init__(self, led_0, extra_boot_values=None): + def __init__(self, extra_boot_values=None): """ :param extra_boot_values: Any additional or overwrite values to set during boot. diff --git a/spinnman/transceiver/abstract_transceiver.py b/spinnman/transceiver/abstract_transceiver.py index e010b5a14..f64fdf893 100644 --- a/spinnman/transceiver/abstract_transceiver.py +++ b/spinnman/transceiver/abstract_transceiver.py @@ -15,7 +15,7 @@ # pylint: disable=too-many-arguments from spinn_utilities.abstract_base import ( - AbstractBase, abstractmethod, abstractproperty) + AbstractBase, abstractmethod) from spinn_utilities.abstract_context_manager import AbstractContextManager from spinnman.model.enums import CPUState @@ -87,6 +87,17 @@ def add_scamp_connections(self, connections): If a response indicates an error during the exchange """ + @abstractmethod + def get_connections(self): + """ + Get the currently known connections to the board, made up of those + passed in to the transceiver and those that are discovered during + calls to discover_connections. No further discovery is done here. + + :return: An iterable of connections known to the transceiver + :rtype: set(Connection) + """ + @abstractmethod def get_machine_details(self): """ diff --git a/spinnman/transceiver/base_transceiver.py b/spinnman/transceiver/base_transceiver.py index 8f91b64e7..bf89d8dfb 100644 --- a/spinnman/transceiver/base_transceiver.py +++ b/spinnman/transceiver/base_transceiver.py @@ -15,10 +15,7 @@ # pylint: disable=too-many-arguments import io import os -import random import struct -from threading import Condition -from collections import defaultdict from contextlib import contextmanager, suppress import logging import socket @@ -27,6 +24,7 @@ AbstractBase, abstractproperty) from spinn_utilities.config_holder import get_config_bool from spinn_utilities.log import FormatAdapter +from spinn_utilities.overrides import overrides from spinn_machine import CoreSubsets from spinnman.constants import ( BMP_POST_POWER_ON_SLEEP_TIME, BMP_POWER_ON_TIMEOUT, BMP_TIMEOUT, @@ -70,6 +68,7 @@ MostDirectConnectionSelector, ApplicationCopyRunProcess) from spinnman.utilities.utility_functions import get_vcpu_address from spinnman.transceiver.abstract_transceiver import AbstractTransceiver +from spinnman.transceiver.extendable_transceiver import ExtendableTransceiver logger = FormatAdapter(logging.getLogger(__name__)) @@ -89,33 +88,17 @@ _EXECUTABLE_ADDRESS = 0x67800000 -class BaseTransceiver(AbstractTransceiver, metaclass=AbstractBase): +class BaseTransceiver(ExtendableTransceiver, metaclass=AbstractBase): """ - An encapsulation of various communications with the SpiNNaker board. - - The methods of this class are designed to be thread-safe (provided they do - not access a BMP, as access to those is never thread-safe); - thus you can make multiple calls to the same (or different) methods - from multiple threads and expect each call to work as if it had been - called sequentially, although the order of returns is not guaranteed. - - .. note:: - With multiple connections to the board, using multiple threads in this - way may result in an increase in the overall speed of operation, since - the multiple calls may be made separately over the set of given - connections. """ __slots__ = [ "_all_connections", "_bmp_selector", "_bmp_connection", "_boot_send_connection", - "_chip_execute_lock_condition", - "_chip_execute_locks", "_height", "_iobuf_size", "_machine_off", - "_n_chip_execute_locks", "_scamp_connection_selector", "_scamp_connections", "_udp_scamp_connections", @@ -136,6 +119,8 @@ def __init__(self, connections=None): :raise SpinnmanUnexpectedResponseCodeException: If a response indicates an error during the exchange """ + super().__init__() + # Place to keep the current machine self._width = None self._height = None @@ -170,30 +155,27 @@ def __init__(self, connections=None): self._scamp_connection_selector = \ self.__identify_connections(connections) - # A lock against single chip executions (entry is (x, y)) - # The condition should be acquired before the locks are - # checked or updated - # The write lock condition should also be acquired to avoid a flood - # fill during an individual chip execute - self._chip_execute_locks = defaultdict(Condition) - self._chip_execute_lock_condition = Condition() - self._n_chip_execute_locks = 0 - # Check that the BMP connections are valid self.__check_bmp_connection() self._machine_off = False - def _where_is_xy(self, x, y): - """ - Attempts to get where_is_x_y info from the machine + @property + @overrides(ExtendableTransceiver.bmp_selector) + def bmp_selector(self): + return self._bmp_selector - If no machine will do its best. + @property + @overrides(ExtendableTransceiver.scamp_connection_selector) + def scamp_connection_selector(self): + return self._scamp_connection_selector - :param int x: - :param int y: - :rtype: str - """ + @property + @overrides(ExtendableTransceiver.bmp_connection) + def bmp_connection(self): + return self._bmp_connection + + def where_is_xy(self, x, y): try: if SpiNNManDataView.has_machine(): return SpiNNManDataView.get_machine().where_is_xy(x, y) @@ -298,32 +280,6 @@ def _check_connection( break return None - @contextmanager - def __flood_execute_lock(self): - """ - Get a lock for executing a flood fill of an executable. - """ - # Get the execute lock all together, so nothing can access it - with self._chip_execute_lock_condition: - # Wait until nothing is executing - self._chip_execute_lock_condition.wait_for( - lambda: self._n_chip_execute_locks < 1) - yield self._chip_execute_lock_condition - - @staticmethod - def _get_random_connection(connections): - """ - Returns the given connection, or else picks one at random. - - :param list(Connection) connections: - the list of connections to locate a random one from - :return: a connection object - :rtype: Connection or None - """ - if not connections: - return None - return connections[random.randint(0, len(connections) - 1)] - def send_sdp_message(self, message, connection=None): """ Sends an SDP message using one of the connections. @@ -434,6 +390,10 @@ def add_scamp_connections(self, connections): self._scamp_connection_selector = MostDirectConnectionSelector( self._scamp_connections) + @overrides(AbstractTransceiver.get_connections) + def get_connections(self): + return self._all_connections + def _get_machine_dimensions(self): """ Get the maximum chip X-coordinate and maximum chip Y-coordinate of @@ -552,9 +512,11 @@ def _boot_board(self, extra_boot_values=None): if not self._boot_send_connection: # No can do. Can't boot without a boot connection. raise SpinnmanIOException("no boot connection available") + if extra_boot_values is None: + extra_boot_values = dict() if SystemVariableDefinition.led_0 not in extra_boot_values: - extra_boot_values.set_value( - SystemVariableDefinition.led_0, self.boot_led_0_valu) + extra_boot_values[SystemVariableDefinition.led_0] = \ + self.boot_led_0_value boot_messages = SpinnakerBootMessages( extra_boot_values=extra_boot_values) for boot_message in boot_messages.messages: @@ -969,7 +931,7 @@ def execute_flood( If a response indicates an error during the exchange """ # Lock against other executable's - with self.__flood_execute_lock(): + with self._flood_execute_lock(): # Flood fill the system with the binary n_bytes, chksum = self.write_memory( 0, 0, _EXECUTABLE_ADDRESS, executable, n_bytes, diff --git a/spinnman/transceiver/extendable_transceiver.py b/spinnman/transceiver/extendable_transceiver.py new file mode 100644 index 000000000..b024aba83 --- /dev/null +++ b/spinnman/transceiver/extendable_transceiver.py @@ -0,0 +1,119 @@ +# Copyright (c) 2014 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# pylint: disable=too-many-arguments +from contextlib import contextmanager, suppress + +from collections import defaultdict +import logging +import random +from threading import Condition, RLock +from spinn_utilities.abstract_base import ( + AbstractBase, abstractproperty, abstractmethod) +from spinn_utilities.log import FormatAdapter +from spinnman.transceiver.abstract_transceiver import AbstractTransceiver + +logger = FormatAdapter(logging.getLogger(__name__)) + + +class ExtendableTransceiver(AbstractTransceiver, metaclass=AbstractBase): + """ + + """ + __slots__ = [ + "_chip_execute_lock_condition", + "_chip_execute_locks", + "_flood_write_lock", + "_n_chip_execute_locks", + "_nearest_neighbour_id", + "_nearest_neighbour_lock" + ] + + def __init__(self): + # A lock against single chip executions (entry is (x, y)) + # The condition should be acquired before the locks are + # checked or updated + # The write lock condition should also be acquired to avoid a flood + # fill during an individual chip execute + self._chip_execute_locks = defaultdict(Condition) + self._chip_execute_lock_condition = Condition() + self._n_chip_execute_locks = 0 + # A lock against multiple flood fill writes - needed as SCAMP cannot + # cope with this + self._flood_write_lock = Condition() + + # The nearest neighbour start ID and lock + self._nearest_neighbour_id = 1 + self._nearest_neighbour_lock = RLock() + + @abstractproperty + def bmp_connection(self): + """ + Returns the BMP connection if there is one + :rtype: BMPConnection or None + """ + + @abstractproperty + def bmp_selector(self): + """ + Returns the bmp selector + + :rtype: AbstractMultiConnectionProcessConnectionSelector + """ + + @abstractproperty + def scamp_connection_selector(self): + """ + Returns the scamp selector + + :rtype: AbstractMultiConnectionProcessConnectionSelector + """ + + @abstractmethod + def where_is_xy(self, x, y): + """ + Attempts to get where_is_x_y info from the machine + + If no machine will do its best. + + :param int x: + :param int y: + :rtype: str + """ + + @contextmanager + def _flood_execute_lock(self): + """ + Get a lock for executing a flood fill of an executable. + """ + # Get the execute lock all together, so nothing can access it + with self._chip_execute_lock_condition: + # Wait until nothing is executing + self._chip_execute_lock_condition.wait_for( + lambda: self._n_chip_execute_locks < 1) + yield self._chip_execute_lock_condition + + @staticmethod + def _get_random_connection(connections): + """ + Returns the given connection, or else picks one at random. + + :param list(Connection) connections: + the list of connections to locate a random one from + :return: a connection object + :rtype: Connection or None + """ + if not connections: + return None + return connections[random.randint(0, len(connections) - 1)] diff --git a/spinnman/transceiver/mockable_transceiver.py b/spinnman/transceiver/mockable_transceiver.py index 8fc9d6cfa..4d5b992ea 100644 --- a/spinnman/transceiver/mockable_transceiver.py +++ b/spinnman/transceiver/mockable_transceiver.py @@ -18,9 +18,10 @@ from spinnman.data import SpiNNManDataView from spinnman.model.enums import CPUState from spinnman.transceiver.abstract_transceiver import AbstractTransceiver +from spinnman.transceiver.extendable_transceiver import ExtendableTransceiver -class MockableTransceiver(AbstractTransceiver): +class MockableTransceiver(ExtendableTransceiver): """ A based for Mock Transceivers """ @@ -45,6 +46,10 @@ def add_scamp_connections(self, connections): def get_machine_details(self): return SpiNNManDataView.get_machine() + @overrides(AbstractTransceiver.get_connections) + def get_connections(self): + raise NotImplementedError("Needs to be mocked") + @overrides(AbstractTransceiver.ensure_board_is_ready) def ensure_board_is_ready(self, n_retries=5, extra_boot_values=None): raise NotImplementedError("Needs to be mocked") @@ -215,3 +220,19 @@ def control_sync(self, do_sync): @overrides(AbstractTransceiver.update_provenance_and_exit) def update_provenance_and_exit(self, x, y, p): pass + + @overrides(ExtendableTransceiver.bmp_connection) + def bmp_connection(self): + raise NotImplementedError("Needs to be mocked") + + @overrides(ExtendableTransceiver.bmp_selector) + def bmp_selector(self): + raise NotImplementedError("Needs to be mocked") + + @overrides(ExtendableTransceiver.scamp_connection_selector) + def scamp_connection_selector(self): + raise NotImplementedError("Needs to be mocked") + + @overrides(ExtendableTransceiver.where_is_xy) + def where_is_xy(self, x, y): + return f"Mocked {x=} {y=}" diff --git a/spinnman/transceiver/transceiver_factory.py b/spinnman/transceiver/transceiver_factory.py index 5f8f4be75..455f0bbd5 100644 --- a/spinnman/transceiver/transceiver_factory.py +++ b/spinnman/transceiver/transceiver_factory.py @@ -17,19 +17,21 @@ from spinn_machine.version.version_3 import Version3 from spinn_machine.version.version_5 import Version5 from spinnman.data import SpiNNManDataView +from spinnman.extended.version3transceiver import ExtendedVersion3Transceiver +from spinnman.extended.version5transceiver import ExtendedVersion5Transceiver from spinnman.utilities.utility_functions import ( work_out_bmp_from_machine_details) from spinnman.connections.udp_packet_connections import ( BMPConnection, BootConnection, SCAMPConnection) -from spinnman.transceiver.version3Transceiver import Version3Transceiver -from spinnman.transceiver.version5Transceiver import Version5Transceiver +from spinnman.transceiver.version3transceiver import Version3Transceiver +from spinnman.transceiver.version5transceiver import Version5Transceiver logger = FormatAdapter(logging.getLogger(__name__)) def create_transceiver_from_hostname( hostname, bmp_connection_data=None, number_of_boards=None, - auto_detect_bmp=False): + auto_detect_bmp=False, extended=False): """ Create a Transceiver by creating a :py:class:`~.UDPConnection` to the given hostname on port 17893 (the default SCAMP port), and a @@ -51,6 +53,8 @@ def create_transceiver_from_hostname( automatically determined from the board IP address :param scamp_connections: the list of connections used for SCAMP communications + :param bool extended: + If True will return an Extended version of the Transceiver :return: The created transceiver :rtype: spinnman.transceiver.AbstractTransceiver :raise SpinnmanIOException: @@ -89,16 +93,17 @@ def create_transceiver_from_hostname( # handle the boot connection connections.append(BootConnection(remote_host=hostname)) - return create_transceiver_from_connections(connections) + return create_transceiver_from_connections(connections, extended) -def create_transceiver_from_connections(connections): +def create_transceiver_from_connections(connections, extended=False): """ Create a Transceiver with these connections :param list(Connection) connections: An iterable of connections to the board. If not specified, no communication will be possible until connections are found. + :param bool extended: :return: The created transceiver :rtype: spinnman.transceiver.AbstractTransceiver :raise SpinnmanIOException: @@ -112,7 +117,11 @@ def create_transceiver_from_connections(connections): """ version = SpiNNManDataView.get_machine_version() if isinstance(version, Version3): + if extended: + return ExtendedVersion3Transceiver(connections=connections) return Version3Transceiver(connections=connections) if isinstance(version, Version5): + if extended: + return ExtendedVersion5Transceiver(connections=connections) return Version5Transceiver(connections=connections) raise NotImplementedError(f"No Transceiver for {version=}") diff --git a/spinnman/transceiver/version3Transceiver.py b/spinnman/transceiver/version3transceiver.py similarity index 89% rename from spinnman/transceiver/version3Transceiver.py rename to spinnman/transceiver/version3transceiver.py index 46e0a8a4e..e0f883101 100644 --- a/spinnman/transceiver/version3Transceiver.py +++ b/spinnman/transceiver/version3transceiver.py @@ -19,7 +19,9 @@ class Version3Transceiver(BaseTransceiver): """ - Implementation of the Transceiver classes for Version 5 boards + Implementation of the Transceiver classes for Version 3 boards + + This class should ONLY be created via by transceiver_factory.py """ @overrides(BaseTransceiver.__init__) diff --git a/spinnman/transceiver/version5Transceiver.py b/spinnman/transceiver/version5transceiver.py similarity index 94% rename from spinnman/transceiver/version5Transceiver.py rename to spinnman/transceiver/version5transceiver.py index 726f2cdb4..29c92532b 100644 --- a/spinnman/transceiver/version5Transceiver.py +++ b/spinnman/transceiver/version5transceiver.py @@ -20,6 +20,8 @@ class Version5Transceiver(BaseTransceiver): """ Implementation of the Transceiver classes for Version 5 boards + + This class should ONLY be created via by transceiver_factory.py """ pass diff --git a/spinnman/transceiver/watchdog_setter.py b/spinnman/transceiver/watchdog_setter.py deleted file mode 100644 index 254f1aff7..000000000 --- a/spinnman/transceiver/watchdog_setter.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2024 The University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import struct -from spinn_utilities.abstract_base import AbstractBase, abstractmethod -from spinn_utilities.log import FormatAdapter -from spinn_utilities.logger_utils import warn_once -from spinn_utilities.require_subclass import require_subclass -from spinnman.constants import SYSTEM_VARIABLE_BASE_ADDRESS -from spinnman.data import SpiNNManDataView -from spinnman.messages.spinnaker_boot import SystemVariableDefinition -from spinnman.transceiver.abstract_transceiver import AbstractTransceiver - -_ONE_BYTE = struct.Struct("B") - -logger = FormatAdapter(logging.getLogger(__name__)) - - -@require_subclass(AbstractTransceiver) -class WatchdogSetter(object, metaclass=AbstractBase): - - def __set_watch_dog_on_chip(self, x, y, watch_dog): - """ - Enable, disable or set the value of the watch dog timer on a - specific chip. - - .. warning:: - This method is currently deprecated and untested as there is no - known use. Same functionality provided by ybug and bmpc. - Retained in case needed for hardware debugging. - - :param int x: chip X coordinate to write new watchdog parameter to - :param int y: chip Y coordinate to write new watchdog parameter to - :param watch_dog: - Either a boolean indicating whether to enable (True) or - disable (False) the watchdog timer, or an int value to set the - timer count to - :type watch_dog: bool or int - """ - # build what we expect it to be - warn_once(logger, "The set_watch_dog_on_chip method is deprecated " - "and untested due to no known use.") - value_to_set = watch_dog - watchdog = SystemVariableDefinition.software_watchdog_count - if isinstance(watch_dog, bool): - value_to_set = watchdog.default if watch_dog else 0 - - # build data holder - data = _ONE_BYTE.pack(value_to_set) - - # write data - address = SYSTEM_VARIABLE_BASE_ADDRESS + watchdog.offset - self.write_memory(x=x, y=y, base_address=address, data=data) - - def set_watch_dog(self, watch_dog): - """ - Enable, disable or set the value of the watch dog timer. - - .. warning:: - This method is currently deprecated and untested as there is no - known use. Same functionality provided by ybug and bmpc. - Retained in case needed for hardware debugging. - - :param watch_dog: - Either a boolean indicating whether to enable (True) or - disable (False) the watch dog timer, or an int value to set the - timer count to. - :type watch_dog: bool or int - """ - warn_once(logger, "The set_watch_dog method is deprecated and " - "untested due to no known use.") - for x, y in SpiNNManDataView.get_machine().chip_coordinates: - self.__set_watch_dog_on_chip(x, y, watch_dog) diff --git a/unittests/test_transceiver.py b/unittests/test_transceiver.py index ea4e85f4c..b73c89fd8 100644 --- a/unittests/test_transceiver.py +++ b/unittests/test_transceiver.py @@ -21,17 +21,16 @@ from spinnman.transceiver import ( create_transceiver_from_connections, create_transceiver_from_hostname, MockableTransceiver) -from spinnman.transceiver.watchdog_setter import WatchdogSetter +from spinnman.extended.extended_transceiver import ExtendedTransceiver from spinnman import constants from spinnman.messages.spinnaker_boot.system_variable_boot_values import ( SystemVariableDefinition) from spinnman.connections.udp_packet_connections import ( BootConnection, SCAMPConnection) -import spinnman.extended.extended_transceiver as extended from spinnman.board_test_configuration import BoardTestConfiguration -class MockExtendedTransceiver(MockableTransceiver, WatchdogSetter): +class MockExtendedTransceiver(MockableTransceiver, ExtendedTransceiver): pass @@ -47,6 +46,7 @@ def test_create_new_transceiver_to_board(self): connections.append(SCAMPConnection( remote_host=self.board_config.remotehost)) trans = create_transceiver_from_connections(connections=connections) + trans.get_connections() == connections trans.close() def test_create_new_transceiver_one_connection(self): @@ -54,21 +54,9 @@ def test_create_new_transceiver_one_connection(self): connections = set() connections.add(SCAMPConnection( remote_host=self.board_config.remotehost)) - if self.board_config.board_version == 5: - with extended.ExtendedTransceiver(connections=connections) as trans: - assert trans._all_connections == connections - - def test_create_new_transceiver_from_list_connections(self): - self.board_config.set_up_remote_board() - connections = list() - connections.append(SCAMPConnection( - remote_host=self.board_config.remotehost)) - connections.append(BootConnection(remote_host="127.0.0.1")) trans = create_transceiver_from_connections(connections=connections) - instantiated_connections = trans._all_connections - for connection in connections: - assert connection in instantiated_connections - #assert trans.get_connections() == connections + self.assertSetEqual(connections, trans.get_connections()) + trans.close() def test_retrieving_machine_details(self): self.board_config.set_up_remote_board()