• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SpiNNakerManchester / SpiNNMan / 6574804013

19 Oct 2023 12:47PM UTC coverage: 51.937% (+1.2%) from 50.777%
6574804013

Pull #327

github

Christian-B
typing changes
Pull Request #327: Type Annotations and Checking

105 of 1288 branches covered (0.0%)

Branch coverage included in aggregate %.

2375 of 2375 new or added lines in 180 files covered. (100.0%)

4775 of 8108 relevant lines covered (58.89%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

22.18
/spinnman/transceiver/base_transceiver.py
1
# Copyright (c) 2014 The University of Manchester
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#     https://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

15
# pylint: disable=too-many-arguments
16
from collections import defaultdict
1✔
17
import io
1✔
18
import os
1✔
19
import random
1✔
20
import struct
1✔
21
from contextlib import contextmanager, suppress
1✔
22
import logging
1✔
23
import socket
1✔
24
from threading import Condition
1✔
25
import time
1✔
26
from typing import (
1✔
27
    BinaryIO, Collection, Dict, FrozenSet, Iterable, Iterator, List, Optional,
28
    Sequence, Tuple, TypeVar, Union, cast)
29
from spinn_utilities.abstract_base import (
1✔
30
    AbstractBase, abstractmethod)
31
from spinn_utilities.config_holder import get_config_bool
1✔
32
from spinn_utilities.log import FormatAdapter
1✔
33
from spinn_utilities.overrides import overrides
1✔
34
from spinn_utilities.progress_bar import ProgressBar
1✔
35
from spinn_utilities.typing.coords import XY
1✔
36
from spinn_machine import (
1✔
37
    CoreSubsets, FixedRouteEntry, Machine, MulticastRoutingEntry)
38
from spinn_machine.tags import AbstractTag, IPTag, ReverseIPTag
1✔
39
from spinnman.connections.abstract_classes import Connection
1✔
40
from spinnman.connections.udp_packet_connections import SDPConnection
1✔
41
from spinnman.constants import (
1✔
42
    BMP_POST_POWER_ON_SLEEP_TIME, BMP_POWER_ON_TIMEOUT, BMP_TIMEOUT,
43
    CPU_MAX_USER, CPU_USER_OFFSET, CPU_USER_START_ADDRESS,
44
    IPTAG_TIME_OUT_WAIT_TIMES, SCP_SCAMP_PORT, SYSTEM_VARIABLE_BASE_ADDRESS,
45
    UDP_BOOT_CONNECTION_DEFAULT_PORT, NO_ROUTER_DIAGNOSTIC_FILTERS,
46
    ROUTER_REGISTER_BASE_ADDRESS, ROUTER_DEFAULT_FILTERS_MAX_POSITION,
47
    ROUTER_FILTER_CONTROLS_OFFSET, ROUTER_DIAGNOSTIC_FILTER_SIZE, N_RETRIES,
48
    BOOT_RETRIES, POWER_CYCLE_WAIT_TIME_IN_SECONDS)
49
from spinnman.data import SpiNNManDataView
1✔
50
from spinnman.exceptions import (
1✔
51
    SpinnmanInvalidParameterException, SpinnmanException, SpinnmanIOException,
52
    SpinnmanTimeoutException, SpinnmanGenericProcessException,
53
    SpinnmanUnexpectedResponseCodeException,
54
    SpiNNManCoresNotInStateException)
55
from spinnman.model import (
1✔
56
    CPUInfo, CPUInfos, DiagnosticFilter, ChipSummaryInfo,
57
    IOBuffer, MachineDimensions, RouterDiagnostics, VersionInfo)
58
from spinnman.model.enums import (
1✔
59
    CPUState, SDP_PORTS, SDP_RUNNING_MESSAGE_CODES, UserRegister)
60
from spinnman.messages.scp.abstract_messages import AbstractSCPResponse
1✔
61
from spinnman.messages.scp.enums import Signal
1✔
62
from spinnman.messages.scp.impl.get_chip_info import GetChipInfo
1✔
63
from spinnman.messages.scp.impl.get_chip_info_response import (
1✔
64
    GetChipInfoResponse)
65
from spinnman.messages.sdp import SDPFlag, SDPHeader, SDPMessage
1✔
66
from spinnman.messages.spinnaker_boot import (
1✔
67
    SystemVariableDefinition, SpinnakerBootMessages)
68
from spinnman.messages.scp.enums import PowerCommand
1✔
69
from spinnman.messages.scp.abstract_messages import AbstractSCPRequest
1✔
70
from spinnman.messages.scp.impl import (
1✔
71
    BMPGetVersion, SetPower, ReadFPGARegister,
72
    WriteFPGARegister, IPTagSetTTO, ReverseIPTagSet,
73
    WriteMemory, SendSignal, AppStop,
74
    IPTagSet, IPTagClear, RouterClear, DoSync)
75
from spinnman.processes import ConnectionSelector
1✔
76
from spinnman.connections.udp_packet_connections import (
1✔
77
    BMPConnection, BootConnection, SCAMPConnection)
78
from spinnman.processes import (
1✔
79
    GetMachineProcess, GetVersionProcess,
80
    MallocSDRAMProcess, WriteMemoryProcess, ReadMemoryProcess,
81
    GetCPUInfoProcess, GetExcludeCPUInfoProcess, GetIncludeCPUInfoProcess,
82
    ReadIOBufProcess, ApplicationRunProcess,
83
    LoadFixedRouteRoutingEntryProcess, FixedConnectionSelector,
84
    ReadFixedRouteRoutingEntryProcess,
85
    LoadMultiCastRoutesProcess, GetTagsProcess, GetMultiCastRoutesProcess,
86
    SendSingleCommandProcess, ReadRouterDiagnosticsProcess,
87
    MostDirectConnectionSelector, ApplicationCopyRunProcess,
88
    GetNCoresInStateProcess)
89
from spinnman.transceiver.transceiver import Transceiver
1✔
90
from spinnman.transceiver.extendable_transceiver import ExtendableTransceiver
1✔
91
from spinnman.utilities.utility_functions import get_vcpu_address
1✔
92

93
#: Type of a response.
94
# This allows subclasses to be used
95
_AbstractSCPResponse = TypeVar(
1✔
96
    "_AbstractSCPResponse", bound=AbstractSCPResponse)
97

98
logger = FormatAdapter(logging.getLogger(__name__))
1✔
99

100
_SCAMP_NAME = "SC&MP"
1✔
101
_SCAMP_VERSION = (4, 0, 0)
1✔
102

103
_BMP_NAME = "BC&MP"
1✔
104
_BMP_MAJOR_VERSIONS = [1, 2]
1✔
105

106
_CONNECTION_CHECK_RETRIES = 3
1✔
107
INITIAL_FIND_SCAMP_RETRIES_COUNT = 3
1✔
108

109
_TWO_BYTES = struct.Struct("<BB")
1✔
110
_FOUR_BYTES = struct.Struct("<BBBB")
1✔
111
_ONE_WORD = struct.Struct("<I")
1✔
112
_ONE_LONG = struct.Struct("<Q")
1✔
113
_EXECUTABLE_ADDRESS = 0x67800000
1✔
114

115
_POWER_CYCLE_WARNING = (
1✔
116
    "When power-cycling a board, it is recommended that you wait for 30 "
117
    "seconds before attempting a reboot. Therefore, the tools will now "
118
    "wait for 30 seconds. If you wish to avoid this wait, please set "
119
    "reset_machine_on_startup = False in the [Machine] section of the "
120
    "relevant configuration (cfg) file.")
121

122
_POWER_CYCLE_FAILURE_WARNING = (
1✔
123
    "The end user requested the power-cycling of the board. But the "
124
    "tools did not have the required BMP connection to facilitate a "
125
    "power-cycling, and therefore will not do so. please set the "
126
    "bmp_names accordingly in the [Machine] section of the relevant "
127
    "configuration (cfg) file. Or use a machine assess process which "
128
    "provides the BMP data (such as a spalloc system) or finally set "
129
    "reset_machine_on_startup = False in the [Machine] section of the "
130
    "relevant configuration (cfg) file to avoid this warning in future.")
131

132

133
class BaseTransceiver(ExtendableTransceiver, metaclass=AbstractBase):
1✔
134
    """
135
    A base for all the code shared by all Version of the Transceiver.
136

137
    """
138
    __slots__ = (
1✔
139
        "_all_connections",
140
        "_bmp_selector",
141
        "_bmp_connection",
142
        "_boot_send_connection",
143
        "_chip_execute_lock_condition",
144
        "_chip_execute_locks",
145
        "_height",
146
        "_iobuf_size",
147
        "_machine_off",
148
        "_n_chip_execute_locks",
149
        "_scamp_connection_selector",
150
        "_scamp_connections",
151
        "_udp_scamp_connections",
152
        "_width")
153

154
    def __init__(self, connections: Optional[List[Connection]] = None,
1✔
155
                 power_cycle: bool = False):
156
        """
157
        :param list(Connection) connections:
158
            An iterable of connections to the board.  If not specified, no
159
            communication will be possible until connections are found.
160
        :param bool power_cycle: If True will power cycle the machine:
161
        :raise SpinnmanIOException:
162
            If there is an error communicating with the board, or if no
163
            connections to the board can be found (if connections is ``None``)
164
        :raise SpinnmanInvalidPacketException:
165
            If a packet is received that is not in the valid format
166
        :raise SpinnmanInvalidParameterException:
167
            If a packet is received that has invalid parameters
168
        :raise SpinnmanUnexpectedResponseCodeException:
169
            If a response indicates an error during the exchange
170
        """
171
        super().__init__()
×
172

173
        # Place to keep the current machine
174
        self._width: Optional[int] = None
×
175
        self._height: Optional[int] = None
×
176
        self._iobuf_size: Optional[int] = None
×
177

178
        # A set of the original connections - used to determine what can
179
        # be closed
180
        if connections is None:
×
181
            connections = list()
×
182

183
        # A set of all connection - used for closing
184
        self._all_connections = set(connections)
×
185

186
        # A boot send connection - there can only be one in the current system,
187
        # or otherwise bad things can happen!
188
        self._boot_send_connection: Optional[BootConnection] = None
×
189

190
        # A dict of IP address -> SCAMP connection
191
        # These are those that can be used for setting up IP Tags
192
        self._udp_scamp_connections: Dict[str, SCAMPConnection] = dict()
×
193

194
        # A list of all connections that can be used to send and receive SCP
195
        # messages for SCAMP interaction
196
        self._scamp_connections: List[SCAMPConnection] = list()
×
197

198
        # The BMP connections
199
        self._bmp_connection: Optional[BMPConnection] = None
×
200

201
        # A lock against single chip executions (entry is (x, y))
202
        # The condition should be acquired before the locks are
203
        # checked or updated
204
        # The write lock condition should also be acquired to avoid a flood
205
        # fill during an individual chip execute
206
        self._chip_execute_locks: Dict[
×
207
            Tuple[int, int], Condition] = defaultdict(Condition)
208
        self._chip_execute_lock_condition = Condition()
×
209
        self._n_chip_execute_locks = 0
×
210

211
        # build connection selectors for the processes.
212
        self._bmp_selector: Optional[
×
213
            FixedConnectionSelector[BMPConnection]] = None
214
        self._scamp_connection_selector = \
×
215
            self.__identify_connections(connections)
216

217
        # Check that the BMP connections are valid
218
        self.__check_bmp_connection()
×
219

220
        self._machine_off = False
×
221
        if power_cycle:
×
222
            self._power_off_machine()
×
223
        self._ensure_board_is_ready()
×
224

225
    @property
1✔
226
    @overrides(ExtendableTransceiver.bmp_selector)
1✔
227
    def bmp_selector(self) -> Optional[FixedConnectionSelector[BMPConnection]]:
1✔
228
        return self._bmp_selector
×
229

230
    @property
1✔
231
    @overrides(ExtendableTransceiver.scamp_connection_selector)
1✔
232
    def scamp_connection_selector(self) -> MostDirectConnectionSelector:
1✔
233
        return self._scamp_connection_selector
×
234

235
    def _where_is_xy(self, x: int, y: int):
1✔
236
        """
237
        Attempts to get where_is_x_y info from the machine
238

239
        If no machine will do its best.
240

241
        :param int x:
242
        :param int y:
243
        :rtype: str
244
        """
245
        try:
×
246
            if SpiNNManDataView.has_machine():
×
247
                return SpiNNManDataView.get_machine().where_is_xy(x, y)
×
248
            return (f"No Machine. "
×
249
                    f"Root IP:{self._scamp_connections[0].remote_ip_address}"
250
                    f"x:{x} y:{y}")
251
        except Exception as ex:  # pylint: disable=broad-except
×
252
            return str(ex)
×
253

254
    def __identify_connections(
1✔
255
            self, connections: Iterable[Connection]
256
            ) -> MostDirectConnectionSelector:
257
        for conn in connections:
×
258

259
            # locate the only boot send conn
260
            if isinstance(conn, BootConnection):
×
261
                if self._boot_send_connection is not None:
×
262
                    raise SpinnmanInvalidParameterException(
×
263
                        "connections", f"[... {conn} ...]",
264
                        "Only a single SpinnakerBootSender can be specified")
265
                self._boot_send_connection = conn
×
266

267
            # Locate any connections that talk to a BMP
268
            if isinstance(conn, BMPConnection):
×
269
                # If it is a BMP conn, add it here
270
                if self._bmp_connection is not None:
×
271
                    raise NotImplementedError(
272
                        "Only one BMP connection supported")
273
                self._bmp_connection = conn
×
274
                self._bmp_selector = FixedConnectionSelector(conn)
×
275
            # Otherwise, check if it can send and receive SCP (talk to SCAMP)
276
            elif isinstance(conn, SCAMPConnection):
×
277
                self._scamp_connections.append(conn)
×
278

279
        # update the transceiver with the conn selectors.
280
        return MostDirectConnectionSelector(self._scamp_connections)
×
281

282
    def __check_bmp_connection(self) -> None:
1✔
283
        """
284
        Check that the BMP connections are actually connected to valid BMPs.
285

286
        :raise SpinnmanIOException: when a connection is not linked to a BMP
287
        """
288
        # check that the UDP BMP conn is actually connected to a BMP
289
        # via the sver command
290
        if self._bmp_connection is not None:
×
291
            conn = self._bmp_connection
×
292

293
            # try to send a BMP sver to check if it responds as expected
294
            try:
×
295
                version_info = self._get_scamp_version(
×
296
                    conn.chip_x, conn.chip_y, self._bmp_selector)
297
                fail_version_name = version_info.name != _BMP_NAME
×
298
                fail_version_num = \
×
299
                    version_info.version_number[0] not in _BMP_MAJOR_VERSIONS
300
                if fail_version_name or fail_version_num:
×
301
                    raise SpinnmanIOException(
×
302
                        f"The BMP at {conn.remote_ip_address} is running "
303
                        f"{version_info.name} {version_info.version_string} "
304
                        "which is incompatible with this transceiver, required"
305
                        f" version is {_BMP_NAME} {_BMP_MAJOR_VERSIONS}")
306

307
                logger.info("Using BMP at {} with version {} {}",
×
308
                            conn.remote_ip_address, version_info.name,
309
                            version_info.version_string)
310

311
            # If it fails to respond due to timeout, maybe that the connection
312
            # isn't valid
313
            except SpinnmanTimeoutException as e:
×
314
                raise SpinnmanException(
×
315
                    f"BMP connection to {conn.remote_ip_address} is "
316
                    "not responding") from e
317
            except Exception:
×
318
                logger.exception("Failed to speak to BMP at {}",
×
319
                                 conn.remote_ip_address)
320
                raise
×
321

322
    @staticmethod
1✔
323
    def _check_connection(
1✔
324
            connection: SCAMPConnection) -> Optional[ChipSummaryInfo]:
325
        """
326
        Check that the given connection to the given chip works.
327

328
        :param ConnectionSelector connection_selector:
329
            the connection selector to use
330
        :param int chip_x: the chip x coordinate to try to talk to
331
        :param int chip_y: the chip y coordinate to try to talk to
332
        :return: True if a valid response is received, False otherwise
333
        :rtype: ChipInfo or None
334
        """
335
        chip_x, chip_y = connection.chip_x, connection.chip_y
×
336
        connection_selector = FixedConnectionSelector(connection)
×
337
        for _ in range(_CONNECTION_CHECK_RETRIES):
×
338
            try:
×
339
                sender: SendSingleCommandProcess[GetChipInfoResponse] = \
×
340
                    SendSingleCommandProcess(connection_selector)
341
                chip_info = sender.execute(  # pylint: disable=no-member
×
342
                    GetChipInfo(chip_x, chip_y)).chip_info
343
                if not chip_info.is_ethernet_available:
×
344
                    time.sleep(0.1)
×
345
                else:
346
                    return chip_info
×
347
            except (SpinnmanGenericProcessException, SpinnmanTimeoutException,
×
348
                    SpinnmanUnexpectedResponseCodeException):
349
                pass
×
350
            except SpinnmanIOException:
×
351
                break
×
352
        return None
×
353

354
    @overrides(Transceiver.send_sdp_message)
1✔
355
    def send_sdp_message(self, message: SDPMessage,
1✔
356
                         connection: Optional[SDPConnection] = None):
357
        if connection is None:
×
358
            connection_to_use: SDPConnection = self._scamp_connections[
×
359
                random.randint(0, len(self._scamp_connections) - 1)]
360
        else:
361
            connection_to_use = connection
×
362
        connection_to_use.send_sdp_message(message)
×
363

364
    def _check_and_add_scamp_connections(
1✔
365
            self, x: int, y: int, ip_address: str):
366
        """
367
        :param int x: X coordinate of target chip
368
        :param int y: Y coordinate of target chip
369
        :param str ip_address: IP address of target chip
370

371
        :raise SpinnmanIOException:
372
            If there is an error communicating with the board
373
        :raise SpinnmanInvalidPacketException:
374
            If a packet is received that is not in the valid format
375
        :raise SpinnmanInvalidParameterException:
376
            If a packet is received that has invalid parameters
377
        :raise SpinnmanUnexpectedResponseCodeException:
378
            If a response indicates an error during the exchange
379
        """
380
        conn = SCAMPConnection(remote_host=ip_address, chip_x=x, chip_y=y)
×
381

382
        # check if it works
383
        chip_info = self._check_connection(conn)
×
384
        if chip_info is not None and chip_info.ethernet_ip_address is not None:
×
385
            self._all_connections.add(conn)
×
386
            self._udp_scamp_connections[chip_info.ethernet_ip_address] = conn
×
387
            self._scamp_connections.append(conn)
×
388
        else:
389
            logger.warning(
×
390
                "Additional Ethernet connection on {} at chip {}, {} "
391
                "cannot be contacted", ip_address, x, y)
392

393
    @overrides(Transceiver.discover_scamp_connections)
1✔
394
    def discover_scamp_connections(self) -> None:
1✔
395
        # Get the machine dimensions
396
        dims = self._get_machine_dimensions()
×
397

398
        # Find all the new connections via the machine Ethernet-connected chips
399
        version = SpiNNManDataView.get_machine_version()
×
400
        for x, y in version.get_potential_ethernet_chips(
×
401
                dims.width, dims.height):
402
            ip_addr_item = SystemVariableDefinition.ethernet_ip_address
×
403
            try:
×
404
                # TODO avoid here_is_x,y if read_memory fails
405
                data = self.read_memory(
×
406
                    x, y,
407
                    SYSTEM_VARIABLE_BASE_ADDRESS + ip_addr_item.offset, 4)
408
            except SpinnmanGenericProcessException:
×
409
                continue
×
410
            ip = _FOUR_BYTES.unpack_from(data)
×
411
            ip_address = f"{ip[0]}.{ip[1]}.{ip[2]}.{ip[3]}"
×
412
            logger.info(ip_address)
×
413
            self._check_and_add_scamp_connections(x, y, ip_address)
×
414
        self._scamp_connection_selector = MostDirectConnectionSelector(
×
415
            self._scamp_connections)
416

417
    @overrides(Transceiver.add_scamp_connections)
1✔
418
    def add_scamp_connections(self, connections: Dict[XY, str]):
1✔
419
        for ((x, y), ip_address) in connections.items():
×
420
            self._check_and_add_scamp_connections(x, y, ip_address)
×
421
        self._scamp_connection_selector = MostDirectConnectionSelector(
×
422
            self._scamp_connections)
423

424
    @overrides(Transceiver.get_connections)
1✔
425
    def get_connections(self):
1✔
426
        return self._all_connections
×
427

428
    def _get_machine_dimensions(self) -> MachineDimensions:
1✔
429
        """
430
        Get the maximum chip X-coordinate and maximum chip Y-coordinate of
431
        the chips in the machine.
432

433
        :return: The dimensions of the machine
434
        :rtype: MachineDimensions
435
        :raise SpinnmanIOException:
436
            If there is an error communicating with the board
437
        :raise SpinnmanInvalidPacketException:
438
            If a packet is received that is not in the valid format
439
        :raise SpinnmanInvalidParameterException:
440
            If a packet is received that has invalid parameters
441
        :raise SpinnmanUnexpectedResponseCodeException:
442
            If a response indicates an error during the exchange
443
        """
444
        if self._width is None or self._height is None:
×
445
            height_item = SystemVariableDefinition.y_size
×
446
            self._height, self._width = _TWO_BYTES.unpack_from(
×
447
                self.read_memory(
448
                    AbstractSCPRequest.DEFAULT_DEST_X_COORD,
449
                    AbstractSCPRequest.DEFAULT_DEST_Y_COORD,
450
                    SYSTEM_VARIABLE_BASE_ADDRESS + height_item.offset,
451
                    2))
452
        assert self._width is not None and self._height is not None
×
453
        return MachineDimensions(self._width, self._height)
×
454

455
    @overrides(Transceiver.get_machine_details)
1✔
456
    def get_machine_details(self) -> Machine:
1✔
457
        # Get the width and height of the machine
458
        dims = self._get_machine_dimensions()
×
459

460
        # Get the coordinates of the boot chip
461
        version_info = self._get_scamp_version()
×
462

463
        # Get the details of all the chips
464
        get_machine_process = GetMachineProcess(
×
465
            self._scamp_connection_selector)
466
        machine = get_machine_process.get_machine_details(
×
467
            version_info.x, version_info.y, dims.width, dims.height)
468

469
        # Work out and add the SpiNNaker links and FPGA links
470
        machine.add_spinnaker_links()
×
471
        machine.add_fpga_links()
×
472

473
        if self._boot_send_connection:
×
474
            logger.info(f"Detected {machine.summary_string()}")
×
475
        return machine
×
476

477
    def _get_scamp_version(
1✔
478
            self, chip_x: int = AbstractSCPRequest.DEFAULT_DEST_X_COORD,
479
            chip_y: int = AbstractSCPRequest.DEFAULT_DEST_Y_COORD,
480
            connection_selector: Optional[ConnectionSelector] = None,
481
            n_retries: int = N_RETRIES) -> VersionInfo:
482
        """
483
        Get the version of SCAMP which is running on the board.
484

485
        :param int chip_x: the chip's x coordinate to query for SCAMP version
486
        :param int chip_y: the chip's y coordinate to query for SCAMP version
487
        :param ConnectionSelector connection_selector:
488
            the connection to send the SCAMP version
489
            or `None` (if `None` then a random SCAMP connection is used).
490
        :param int n_retries:
491
        :return: The version identifier
492
        :rtype: VersionInfo
493
        :raise SpinnmanIOException:
494
            If there is an error communicating with the board
495
        :raise SpinnmanInvalidParameterException:
496
            If the timeout is less than 1
497
        :raise SpinnmanTimeoutException:
498
            If none of the retries resulted in a response before the timeout
499
            (suggesting that the board is not booted).
500
        """
501
        if connection_selector is None:
×
502
            connection_selector = self._scamp_connection_selector
×
503
        process = GetVersionProcess(connection_selector, n_retries)
×
504
        return process.get_version(x=chip_x, y=chip_y, p=0)
×
505

506
    @property
1✔
507
    @abstractmethod
1✔
508
    def boot_led_0_value(self) -> int:
1✔
509
        """
510
        The Values to be set in SpinnakerBootMessages for led_0
511

512
        :rtype int:
513
        """
514
        raise NotImplementedError
515

516
    def _boot_board(self, extra_boot_values: Optional[Dict[
1✔
517
            SystemVariableDefinition, object]] = None):
518
        """
519
        Attempt to boot the board. No check is performed to see if the
520
        board is already booted.
521

522
        :param dict(SystemVariableDefinition,object) extra_boot_values:
523
            extra values to set during boot
524
            Any additional or overwrite values to set during boot.
525
            This should only be used for values which are not standard
526
            based on the board version.
527
        :raise SpinnmanInvalidParameterException:
528
            If the board version is not known
529
        :raise SpinnmanIOException:
530
            If there is an error communicating with the board
531
        """
532
        if not self._boot_send_connection:
×
533
            # No can do. Can't boot without a boot connection.
534
            raise SpinnmanIOException("no boot connection available")
×
535
        if extra_boot_values is None:
×
536
            extra_boot_values = dict()
×
537
        if SystemVariableDefinition.led_0 not in extra_boot_values:
×
538
            extra_boot_values[SystemVariableDefinition.led_0] = \
×
539
                self.boot_led_0_value
540
        boot_messages = SpinnakerBootMessages(
×
541
            extra_boot_values=extra_boot_values)
542
        for boot_message in boot_messages.messages:
×
543
            self._boot_send_connection.send_boot_message(boot_message)
×
544
        time.sleep(2.0)
×
545

546
    def _call(self, req: AbstractSCPRequest[_AbstractSCPResponse],
1✔
547
              **kwargs) -> _AbstractSCPResponse:
548
        """
549
        Wrapper that makes doing simple SCP calls easier,
550
        especially with types.
551
        """
552
        proc: SendSingleCommandProcess[_AbstractSCPResponse] = \
×
553
            SendSingleCommandProcess(self._scamp_connection_selector, **kwargs)
554
        return proc.execute(req)
×
555

556
    @staticmethod
1✔
557
    def _is_scamp_version_compabible(version: Tuple[int, int, int]) -> bool:
1✔
558
        """
559
        Determine if the version of SCAMP is compatible with this transceiver.
560

561
        :param tuple(int,int,int) version: The version to test
562
        :rtype: bool
563
        """
564
        # The major version must match exactly
565
        if version[0] != _SCAMP_VERSION[0]:
1✔
566
            return False
1✔
567

568
        # If the minor version matches, the patch version must be >= the
569
        # required version
570
        if version[1] == _SCAMP_VERSION[1]:
1✔
571
            return version[2] >= _SCAMP_VERSION[2]
1✔
572

573
        # If the minor version is > than the required version, the patch
574
        # version is irrelevant
575
        return version[1] > _SCAMP_VERSION[1]
1✔
576

577
    def _ensure_board_is_ready(
1✔
578
            self, n_retries: int = 5, extra_boot_values: Optional[Dict[
579
            SystemVariableDefinition, object]] = None):
580
        """
581
        Ensure that the board is ready to interact with this version of the
582
        transceiver. Boots the board if not already booted and verifies that
583
        the version of SCAMP running is compatible with this transceiver.
584

585
        :param int n_retries: The number of times to retry booting
586
        :param dict(SystemVariableDefinition,object) extra_boot_values:
587
            Any additional or overwrite values to set during boot.
588
            This should only be used for values which are not standard
589
            based on the board version.
590
        :raise SpinnmanIOException:
591
            * If there is a problem booting the board
592
            * If the version of software on the board is not compatible with
593
              this transceiver
594
        """
595
        logger.info("Working out if machine is booted")
×
596
        if self._machine_off:
×
597
            version_info = None
×
598
        else:
599
            version_info = self._try_to_find_scamp_and_boot(
×
600
                INITIAL_FIND_SCAMP_RETRIES_COUNT, extra_boot_values)
601

602
        # If we fail to get a SCAMP version this time, try other things
603
        if version_info is None and self._bmp_connection is not None:
×
604
            # start by powering up each BMP connection
605
            logger.info("Attempting to power on machine")
×
606
            self._power_on_machine()
×
607

608
            # Sleep a bit to let things get going
609
            time.sleep(2.0)
×
610
            logger.info("Attempting to boot machine")
×
611

612
            # retry to get a SCAMP version, this time trying multiple times
613
            version_info = self._try_to_find_scamp_and_boot(
×
614
                n_retries, extra_boot_values)
615

616
        # verify that the version is the expected one for this transceiver
617
        if version_info is None:
×
618
            raise SpinnmanIOException("Failed to communicate with the machine")
×
619
        if (version_info.name != _SCAMP_NAME or
×
620
                not self._is_scamp_version_compabible(
621
                    version_info.version_number)):
622
            raise SpinnmanIOException(
×
623
                f"The machine is currently booted with {version_info.name}"
624
                f" {version_info.version_number} which is incompatible with "
625
                "this transceiver, required version is "
626
                f"{_SCAMP_NAME} {_SCAMP_VERSION}")
627

628
        logger.info("Machine communication successful")
×
629

630
        # Change the default SCP timeout on the machine, keeping the old one to
631
        # revert at close
632
        for scamp_connection in self._scamp_connections:
×
633
            self._call(IPTagSetTTO(
×
634
                scamp_connection.chip_x, scamp_connection.chip_y,
635
                IPTAG_TIME_OUT_WAIT_TIMES.TIMEOUT_2560_ms))
636

637
            chip_info = self._check_connection(scamp_connection)
×
638
            if chip_info is not None and chip_info.ethernet_ip_address:
×
639
                self._udp_scamp_connections[chip_info.ethernet_ip_address] = \
×
640
                    scamp_connection
641

642
        # Update the connection selector so that it can ask for processor ids
643
        self._scamp_connection_selector = MostDirectConnectionSelector(
×
644
            self._scamp_connections)
645

646
    def __is_default_destination(self, version_info: VersionInfo) -> bool:
1✔
647
        return (version_info.x == AbstractSCPRequest.DEFAULT_DEST_X_COORD
×
648
                and version_info.y == AbstractSCPRequest.DEFAULT_DEST_Y_COORD)
649

650
    def _try_to_find_scamp_and_boot(
1✔
651
            self, tries_to_go: int, extra_boot_values: Optional[Dict[
652
                SystemVariableDefinition, object]]) -> Optional[VersionInfo]:
653
        """
654
        Try to detect if SCAMP is running, and if not, boot the machine.
655

656
        :param int tries_to_go: how many attempts should be supported
657
        :param dict(SystemVariableDefinition,object) extra_boot_values:
658
            Any additional or overwrite values to set during boot.
659
            This should only be used for values which are not standard
660
            based on the board version.
661
        :return: version info
662
        :rtype: VersionInfo
663
        :raise SpinnmanIOException:
664
            If there is a problem communicating with the machine
665
        """
666
        version_info = None
×
667
        current_tries_to_go = tries_to_go
×
668
        while version_info is None and current_tries_to_go > 0:
×
669
            try:
×
670
                version_info = self._get_scamp_version(n_retries=BOOT_RETRIES)
×
671
                if self.__is_default_destination(version_info):
×
672
                    version_info = None
×
673
                    time.sleep(0.1)
×
674
            except SpinnmanGenericProcessException as e:
×
675
                if isinstance(e.exception, SpinnmanTimeoutException):
×
676
                    logger.info("Attempting to boot machine")
×
677
                    self._boot_board(extra_boot_values)
×
678
                    current_tries_to_go -= 1
×
679
                elif isinstance(e.exception, SpinnmanIOException):
×
680
                    raise SpinnmanIOException(
×
681
                        "Failed to communicate with the machine") from e
682
                else:
683
                    raise
×
684
            except SpinnmanTimeoutException:
×
685
                logger.info("Attempting to boot machine")
×
686
                self._boot_board(extra_boot_values)
×
687
                current_tries_to_go -= 1
×
688
            except SpinnmanIOException as e:
×
689
                raise SpinnmanIOException(
×
690
                    "Failed to communicate with the machine") from e
691

692
        # The last thing we tried was booting, so try again to get the version
693
        if version_info is None:
×
694
            with suppress(SpinnmanException):
×
695
                version_info = self._get_scamp_version()
×
696
                if self.__is_default_destination(version_info):
×
697
                    version_info = None
×
698
        if version_info is not None:
×
699
            logger.info("Found board with version {}", version_info)
×
700
        return version_info
×
701

702
    @overrides(Transceiver.get_cpu_infos)
1✔
703
    def get_cpu_infos(
1✔
704
            self, core_subsets: Optional[CoreSubsets] = None,
705
            states: Union[CPUState, Iterable[CPUState], None] = None,
706
            include: bool = True) -> CPUInfos:
707
        # Get all the cores if the subsets are not given
708
        if core_subsets is None:
×
709
            core_subsets = CoreSubsets()
×
710
            for chip in SpiNNManDataView.get_machine().chips:
×
711
                for processor in chip.processors:
×
712
                    core_subsets.add_processor(
×
713
                        chip.x, chip.y, processor.processor_id)
714

715
        if states is None:
×
716
            process = GetCPUInfoProcess(self._scamp_connection_selector)
×
717
        else:
718
            if isinstance(states, CPUState):
×
719
                state_set = frozenset((states, ))
×
720
            else:
721
                state_set = frozenset(states)
×
722
            if include:
×
723
                process = GetIncludeCPUInfoProcess(
×
724
                    self._scamp_connection_selector, state_set)
725
            else:
726
                process = GetExcludeCPUInfoProcess(
×
727
                    self._scamp_connection_selector, state_set)
728

729
        cpu_info = process.get_cpu_info(core_subsets)
×
730
        return cpu_info
×
731

732
    @overrides(Transceiver.get_clock_drift)
1✔
733
    def get_clock_drift(self, x: int, y: int) -> float:
1✔
734
        DRIFT_FP = 1 << 17
×
735

736
        drift_b = self._get_sv_data(x, y, SystemVariableDefinition.clock_drift)
×
737
        drift = struct.unpack("<i", struct.pack("<I", drift_b))[0]
×
738
        return drift / DRIFT_FP
×
739

740
    def _get_sv_data(
1✔
741
            self, x: int, y: int,
742
            data_item: SystemVariableDefinition) -> Union[int, bytes]:
743
        """
744
        :param int x:
745
        :param int y:
746
        :param SystemVariableDefinition data_item:
747
        """
748
        addr = SYSTEM_VARIABLE_BASE_ADDRESS + data_item.offset
×
749
        if data_item.data_type.is_byte_array:
×
750
            size = cast(int, data_item.array_size)
×
751
            # Do not need to decode the bytes of a byte array
752
            return self.read_memory(x, y, addr, size)
×
753
        return struct.unpack_from(
×
754
            data_item.data_type.struct_code,
755
            self.read_memory(x, y, addr, data_item.data_type.value))[0]
756

757
    @staticmethod
1✔
758
    def __get_user_register_address_from_core(p: int, user: UserRegister):
1✔
759
        """
760
        Get the address of user *N* for a given processor on the board.
761

762
        .. note::
763
            Conventionally, user_0 usually holds the address of the table of
764
            memory regions.
765

766
        :param int p: The ID of the processor to get the user N address from
767
        :param int user: The user "register" number to get the address for
768
        :return: The address for user N register for this processor
769
        :rtype: int
770
        """
771
        if user < 0 or user > CPU_MAX_USER:
×
772
            raise ValueError(
×
773
                f"Incorrect user number {user}")
774
        return (get_vcpu_address(p) + CPU_USER_START_ADDRESS +
×
775
                CPU_USER_OFFSET * user)
776

777
    @overrides(Transceiver.read_user)
1✔
778
    def read_user(self, x: int, y: int, p: int, user: UserRegister):
1✔
779
        addr = self.__get_user_register_address_from_core(p, user)
×
780
        return self.read_word(x, y, addr)
×
781

782
    @overrides(Transceiver.add_cpu_information_from_core)
1✔
783
    def add_cpu_information_from_core(
1✔
784
            self, cpu_infos: CPUInfos, x: int, y: int, p: int,
785
            states: Iterable[CPUState]):
786
        core_subsets = CoreSubsets()
×
787
        core_subsets.add_processor(x, y, p)
×
788
        new_infos = self.get_cpu_infos(core_subsets)
×
789
        cpu_infos.add_infos(new_infos, states)
×
790

791
    @overrides(Transceiver.get_region_base_address)
1✔
792
    def get_region_base_address(self, x: int, y: int, p: int):
1✔
793
        return self.read_user(x, y, p, UserRegister.USER_0)
×
794

795
    @overrides(Transceiver.get_iobuf)
1✔
796
    def get_iobuf(self, core_subsets: Optional[CoreSubsets] = None
1✔
797
                  ) -> Iterable[IOBuffer]:
798
        # making the assumption that all chips have the same iobuf size.
799
        if self._iobuf_size is None:
×
800
            self._iobuf_size = cast(int, self._get_sv_data(
×
801
                AbstractSCPRequest.DEFAULT_DEST_X_COORD,
802
                AbstractSCPRequest.DEFAULT_DEST_Y_COORD,
803
                SystemVariableDefinition.iobuf_size))
804
        # Get all the cores if the subsets are not given
805
        # todo is core_subsets ever None
806
        if core_subsets is None:
×
807
            core_subsets = CoreSubsets()
×
808
            for chip in SpiNNManDataView.get_machine().chips:
×
809
                for processor in chip.processors:
×
810
                    core_subsets.add_processor(
×
811
                        chip.x, chip.y, processor.processor_id)
812

813
        # read iobuf from machine
814
        process = ReadIOBufProcess(self._scamp_connection_selector)
×
815
        return process.read_iobuf(self._iobuf_size, core_subsets)
×
816

817
    @overrides(Transceiver.get_core_state_count)
1✔
818
    def get_core_state_count(
1✔
819
            self, app_id: int, state: CPUState,
820
            xys: Optional[Iterable[Tuple[int, int]]] = None) -> int:
821
        process = GetNCoresInStateProcess(self._scamp_connection_selector)
×
822
        chip_xys = xys
×
823
        if xys is None:
×
824
            machine = SpiNNManDataView.get_machine()
×
825
            chip_xys = [(ch.x, ch.y)
×
826
                        for ch in machine.ethernet_connected_chips]
827
        else:
828
            chip_xys = xys
×
829
        return process.get_n_cores_in_state(chip_xys, app_id, state)
×
830

831
    @contextmanager
1✔
832
    def __flood_execute_lock(self) -> Iterator[Condition]:
1✔
833
        """
834
        Get a lock for executing a flood fill of an executable.
835
        """
836
        # Get the execute lock all together, so nothing can access it
837
        with self._chip_execute_lock_condition:
×
838
            # Wait until nothing is executing
839
            self._chip_execute_lock_condition.wait_for(
×
840
                lambda: self._n_chip_execute_locks < 1)
841
            yield self._chip_execute_lock_condition
×
842

843
    @overrides(Transceiver.execute_flood)
1✔
844
    def execute_flood(
1✔
845
            self, core_subsets: CoreSubsets,
846
            executable: Union[BinaryIO, bytes, str], app_id: int, *,
847
            n_bytes: Optional[int] = None, wait: bool = False,):
848
        if isinstance(executable, int):
×
849
            # No executable is 4 bytes long
850
            raise TypeError("executable may not be int")
×
851
        # Lock against other executable's
852
        with self.__flood_execute_lock():
×
853
            # Flood fill the system with the binary
854
            n_bytes, chksum = self.write_memory(
×
855
                0, 0, _EXECUTABLE_ADDRESS, executable, n_bytes=n_bytes,
856
                get_sum=True)
857

858
            # Execute the binary on the cores on 0, 0 if required
859
            if core_subsets.is_chip(0, 0):
×
860
                boot_subset = CoreSubsets()
×
861
                boot_subset.add_core_subset(
×
862
                    core_subsets.get_core_subset_for_chip(0, 0))
863
                runner = ApplicationRunProcess(
×
864
                    self._scamp_connection_selector)
865
                runner.run(app_id, boot_subset, wait)
×
866

867
            copy_run = ApplicationCopyRunProcess(
×
868
                self._scamp_connection_selector)
869
            copy_run.run(n_bytes, app_id, core_subsets, chksum, wait)
×
870

871
    def _power_on_machine(self) -> None:
1✔
872
        """
873
        Power on the whole machine.
874

875
        :rtype bool
876
        :return success of failure to power on machine
877
        """
878
        self._power(PowerCommand.POWER_ON)
×
879
        # Sleep for 5 seconds as the machine has just been powered on
880
        time.sleep(BMP_POST_POWER_ON_SLEEP_TIME)
×
881

882
    def _power_off_machine(self) -> None:
1✔
883
        """
884
        Power off the whole machine.
885

886
        :rtype bool
887
        :return success or failure to power off the machine
888
        """
889
        self._power(PowerCommand.POWER_OFF)
×
890
        logger.warning(_POWER_CYCLE_WARNING)
×
891
        time.sleep(POWER_CYCLE_WAIT_TIME_IN_SECONDS)
×
892
        logger.warning("Power cycle wait complete")
×
893

894
    def _bmp_call(self, req: AbstractSCPRequest[_AbstractSCPResponse],
1✔
895
                  **kwargs) -> _AbstractSCPResponse:
896
        """
897
        Wrapper that makes doing simple BMP calls easier,
898
        especially with types.
899
        """
900
        if self._bmp_selector is None:
×
901
            raise SpinnmanException(
×
902
                "this transceiver does not support BMP operations")
903
        proc: SendSingleCommandProcess[_AbstractSCPResponse] =\
×
904
            SendSingleCommandProcess(self._bmp_selector, **kwargs)
905
        return proc.execute(req)
×
906

907
    def _power(self, power_command: PowerCommand):
1✔
908
        """
909
        Send a power request to the machine.
910

911
        :param PowerCommand power_command: The power command to send
912
        """
913
        if self._bmp_connection is None:
×
914
            raise NotImplementedError("can not power change without BMP")
915
        timeout = (
×
916
            BMP_POWER_ON_TIMEOUT
917
            if power_command == PowerCommand.POWER_ON
918
            else BMP_TIMEOUT)
919
        self._bmp_call(SetPower(power_command, self._bmp_connection.boards),
×
920
                       timeout=timeout, n_retries=0)
921
        self._machine_off = power_command == PowerCommand.POWER_OFF
×
922

923
    @overrides(Transceiver.read_fpga_register)
1✔
924
    def read_fpga_register(
1✔
925
            self, fpga_num: int, register: int, board: int = 0) -> int:
926
        response = self._bmp_call(
×
927
            ReadFPGARegister(fpga_num, register, board),
928
            timeout=1.0)
929
        return response.fpga_register
×
930

931
    @overrides(Transceiver.write_fpga_register)
1✔
932
    def write_fpga_register(
1✔
933
            self, fpga_num: int, register: int, value: int, board: int = 0):
934
        self._bmp_call(
×
935
            WriteFPGARegister(fpga_num, register, value, board))
936

937
    @overrides(Transceiver.read_bmp_version)
1✔
938
    def read_bmp_version(self, board: int) -> VersionInfo:
1✔
939
        response = self._bmp_call(BMPGetVersion(board))
×
940
        return response.version_info
×
941

942
    @overrides(Transceiver.write_memory)
1✔
943
    def write_memory(
1✔
944
            self, x: int, y: int, base_address: int,
945
            data: Union[BinaryIO, bytes, int, str], *,
946
            n_bytes: Optional[int] = None, offset: int = 0, cpu: int = 0,
947
            get_sum: bool = False) -> Tuple[int, int]:
948
        process = WriteMemoryProcess(self._scamp_connection_selector)
×
949
        if isinstance(data, io.RawIOBase):
×
950
            assert n_bytes is not None
×
951
            chksum = process.write_memory_from_reader(
×
952
                (x, y, cpu), base_address, cast(BinaryIO, data), n_bytes,
953
                get_sum)
954
        elif isinstance(data, str):
×
955
            if n_bytes is None:
×
956
                n_bytes = os.stat(data).st_size
×
957
            with open(data, "rb") as reader:
×
958
                chksum = process.write_memory_from_reader(
×
959
                    (x, y, cpu), base_address, reader, n_bytes, get_sum)
960
        elif isinstance(data, int):
×
961
            n_bytes = 4
×
962
            data_to_write = _ONE_WORD.pack(data)
×
963
            chksum = process.write_memory_from_bytearray(
×
964
                (x, y, cpu), base_address, data_to_write, 0, n_bytes, get_sum)
965
        else:
966
            assert isinstance(data, (bytes, bytearray))
×
967
            if n_bytes is None:
×
968
                n_bytes = len(data)
×
969
            chksum = process.write_memory_from_bytearray(
×
970
                (x, y, cpu), base_address, data, offset, n_bytes, get_sum)
971
        return n_bytes, chksum
×
972

973
    @overrides(Transceiver.write_user)
1✔
974
    def write_user(
1✔
975
            self, x: int, y: int, p: int, user: UserRegister, value: int):
976
        addr = self.__get_user_register_address_from_core(p, user)
×
977
        self.write_memory(x, y, addr, int(value))
×
978

979
    @overrides(Transceiver.read_memory)
1✔
980
    def read_memory(
1✔
981
            self, x: int, y: int, base_address: int, length: int,
982
            cpu: int = 0) -> bytes:
983
        try:
×
984
            process = ReadMemoryProcess(self._scamp_connection_selector)
×
985
            return process.read_memory((x, y, cpu), base_address, length)
×
986
        except Exception:
×
987
            logger.info(self._where_is_xy(x, y))
×
988
            raise
×
989

990
    @overrides(Transceiver.read_word)
1✔
991
    def read_word(
1✔
992
            self, x: int, y: int, base_address: int, cpu: int = 0) -> int:
993
        try:
×
994
            process = ReadMemoryProcess(self._scamp_connection_selector)
×
995
            data = process.read_memory(
×
996
                (x, y, cpu), base_address, _ONE_WORD.size)
997
            (value, ) = _ONE_WORD.unpack(data)
×
998
            return value
×
999
        except Exception:
×
1000
            logger.info(self._where_is_xy(x, y))
×
1001
            raise
×
1002

1003
    @overrides(Transceiver.stop_application)
1✔
1004
    def stop_application(self, app_id: int):
1✔
1005
        if not self._machine_off:
×
1006
            self._call(AppStop(app_id))
×
1007
        else:
1008
            logger.warning(
×
1009
                "You are calling a app stop on a turned off machine. "
1010
                "Please fix and try again")
1011

1012
    def __log_where_is_info(self, cpu_infos: Iterable[
1✔
1013
            Union[CPUInfo, Sequence[int]]]):
1014
        """
1015
        Logs the where_is info for each chip in cpu_infos.
1016

1017
        :param cpu_infos:
1018
        """
1019
        xys = set()
×
1020
        for cpu_info in cpu_infos:
×
1021
            # todo: Is it ever not a CPUInfo
1022
            if isinstance(cpu_info, CPUInfo):
×
1023
                xys.add((cpu_info.x, cpu_info.y))
×
1024
            else:
1025
                xys.add((cpu_info[0], cpu_info[1]))
×
1026
        for (x, y) in xys:
×
1027
            logger.info(self._where_is_xy(x, y))
×
1028

1029
    @staticmethod
1✔
1030
    def __state_set(cpu_states: Union[CPUState, Iterable[CPUState]])\
1✔
1031
            -> FrozenSet[CPUState]:
1032
        if isinstance(cpu_states, CPUState):
×
1033
            return frozenset((cpu_states,))
×
1034
        else:
1035
            return frozenset(cpu_states)
×
1036

1037
    @overrides(Transceiver.wait_for_cores_to_be_in_state)
1✔
1038
    def wait_for_cores_to_be_in_state(
1✔
1039
            self, all_core_subsets: CoreSubsets, app_id: int,
1040
            cpu_states: Union[CPUState, Iterable[CPUState]], *,
1041
            timeout: Optional[float] = None,
1042
            time_between_polls: float = 0.1,
1043
            error_states: FrozenSet[CPUState] = frozenset((
1044
                CPUState.RUN_TIME_EXCEPTION, CPUState.WATCHDOG)),
1045
            counts_between_full_check: int = 100,
1046
            progress_bar: Optional[ProgressBar] = None):
1047
        processors_ready = 0
×
1048
        max_processors_ready = 0
×
1049
        timeout_time = None if timeout is None else time.time() + timeout
×
1050
        tries = 0
×
1051
        target_states = self.__state_set(cpu_states)
×
1052
        while (processors_ready < len(all_core_subsets) and
×
1053
               (timeout_time is None or time.time() < timeout_time)):
1054

1055
            # Get the number of processors in the ready states
1056
            processors_ready = 0
×
1057
            for cpu_state in target_states:
×
1058
                processors_ready += self.get_core_state_count(
×
1059
                    app_id, cpu_state)
1060
            if progress_bar:
×
1061
                if processors_ready > max_processors_ready:
×
1062
                    progress_bar.update(
×
1063
                        processors_ready - max_processors_ready)
1064
                    max_processors_ready = processors_ready
×
1065
            # If the count is too small, check for error states
1066
            if processors_ready < len(all_core_subsets):
×
1067
                is_error = False
×
1068
                for cpu_state in error_states:
×
1069
                    error_cores = self.get_core_state_count(app_id, cpu_state)
×
1070
                    if error_cores > 0:
×
1071
                        is_error = True
×
1072
                if is_error:
×
1073
                    error_core_states = self.get_cpu_infos(
×
1074
                        all_core_subsets, error_states, True)
1075
                    if len(error_core_states) > 0:
×
1076
                        self.__log_where_is_info(error_core_states)
×
1077
                        raise SpiNNManCoresNotInStateException(
×
1078
                            timeout, target_states, error_core_states)
1079

1080
                # If we haven't seen an error, increase the tries, and
1081
                # do a full check if required
1082
                tries += 1
×
1083
                if tries >= counts_between_full_check:
×
1084
                    cores_in_state = self.get_cpu_infos(
×
1085
                        all_core_subsets, target_states, include=True)
1086
                    # convert to a list of xyp values
1087
                    cores_in_state_xyps = list(cores_in_state)
×
1088
                    processors_ready = len(cores_in_state_xyps)
×
1089
                    tries = 0
×
1090

1091
                    # iterate over the cores waiting to finish and see
1092
                    # which ones we're missing
1093
                    if get_config_bool("Machine", "report_waiting_logs"):
×
1094
                        for core_subset in all_core_subsets.core_subsets:
×
1095
                            for p in core_subset.processor_ids:
×
1096
                                if ((core_subset.x, core_subset.y, p) not in
×
1097
                                        cores_in_state_xyps):
1098
                                    logger.warning(
×
1099
                                        "waiting on {}:{}:{}",
1100
                                        core_subset.x, core_subset.y, p)
1101

1102
                # If we're still not in the correct state, wait a bit
1103
                if processors_ready < len(all_core_subsets):
×
1104
                    time.sleep(time_between_polls)
×
1105

1106
        # If we haven't reached the final state, do a final full check
1107
        if processors_ready < len(all_core_subsets):
×
1108
            cores_not_in_state = self.get_cpu_infos(
×
1109
                all_core_subsets, cpu_states, include=False)
1110

1111
            # If we are sure we haven't reached the final state,
1112
            # report a timeout error
1113
            if len(cores_not_in_state) != 0:
×
1114
                self.__log_where_is_info(cores_not_in_state)
×
1115
                raise SpiNNManCoresNotInStateException(
×
1116
                    timeout, target_states, cores_not_in_state)
1117

1118
    @overrides(Transceiver.send_signal)
1✔
1119
    def send_signal(self, app_id: int, signal: Signal):
1✔
1120
        self._call(SendSignal(app_id, signal))
×
1121

1122
    def _locate_spinnaker_connection_for_board_address(
1✔
1123
            self, board_address: str) -> Optional[SCAMPConnection]:
1124
        """
1125
        Find a connection that matches the given board IP address.
1126

1127
        :param str board_address:
1128
            The IP address of the Ethernet connection on the board
1129
        :return: A connection for the given IP address, or `None` if no such
1130
            connection exists
1131
        :rtype: SCAMPConnection
1132
        """
1133
        return self._udp_scamp_connections.get(board_address, None)
×
1134

1135
    @overrides(Transceiver.set_ip_tag)
1✔
1136
    def set_ip_tag(self, ip_tag: IPTag, use_sender: bool = False):
1✔
1137
        # Check that the tag has a port assigned
1138
        if ip_tag.port is None:
×
1139
            raise SpinnmanInvalidParameterException(
×
1140
                "ip_tag.port", "None", "The tag port must have been set")
1141

1142
        # Get the connections - if the tag specifies a connection, use that,
1143
        # otherwise apply the tag to all connections
1144
        connections = self.__get_connection_list(None, ip_tag.board_address)
×
1145
        if not connections:
×
1146
            raise SpinnmanInvalidParameterException(
×
1147
                "ip_tag", str(ip_tag),
1148
                "The given board address is not recognised")
1149

1150
        for connection in connections:
×
1151
            # Convert the host string
1152
            host_string = ip_tag.ip_address
×
1153
            if host_string in ("localhost", ".", "0.0.0.0"):
×
1154
                host_string = connection.local_ip_address
×
1155
            ip_string = socket.gethostbyname(host_string)
×
1156
            ip_address = bytearray(socket.inet_aton(ip_string))
×
1157

1158
            self._call(IPTagSet(
×
1159
                connection.chip_x, connection.chip_y, ip_address, ip_tag.port,
1160
                ip_tag.tag, strip=ip_tag.strip_sdp, use_sender=use_sender))
1161

1162
    def __get_connection_list(
1✔
1163
            self, connection: Optional[SCAMPConnection] = None,
1164
            board_address: Optional[str] = None) -> List[SCAMPConnection]:
1165
        """
1166
        Get the connections for talking to a board.
1167

1168
        :param SCAMPConnection connection:
1169
            Optional param that directly gives the connection to use.
1170
        :param str board_address:
1171
            Optional param that gives the address of the board to talk to.
1172
        :return: List of length 1 or 0 (the latter only if the search for
1173
            the given board address fails).
1174
        :rtype: list(SCAMPConnection)
1175
        """
1176
        if connection is not None:
×
1177
            return [connection]
×
1178
        elif board_address is None:
×
1179
            return self._scamp_connections
×
1180

1181
        connection = self._locate_spinnaker_connection_for_board_address(
×
1182
            board_address)
1183
        if connection is None:
×
1184
            return []
×
1185
        return [connection]
×
1186

1187
    @overrides(Transceiver.set_reverse_ip_tag)
1✔
1188
    def set_reverse_ip_tag(self, reverse_ip_tag: ReverseIPTag):
1✔
1189
        if reverse_ip_tag.port is None:
×
1190
            raise SpinnmanInvalidParameterException(
×
1191
                "reverse_ip_tag.port", "None",
1192
                "The tag port must have been set!")
1193

1194
        if (reverse_ip_tag.port == SCP_SCAMP_PORT or
×
1195
                reverse_ip_tag.port == UDP_BOOT_CONNECTION_DEFAULT_PORT):
1196
            raise SpinnmanInvalidParameterException(
×
1197
                "reverse_ip_tag.port", reverse_ip_tag.port,
1198
                "The port number for the reverse IP tag conflicts with"
1199
                f" the SpiNNaker system ports ({SCP_SCAMP_PORT} and "
1200
                f"{UDP_BOOT_CONNECTION_DEFAULT_PORT})")
1201

1202
        # Get the connections - if the tag specifies a connection, use that,
1203
        # otherwise apply the tag to all connections
1204
        connections = self.__get_connection_list(
×
1205
            None, reverse_ip_tag.board_address)
1206
        if not connections:
×
1207
            raise SpinnmanInvalidParameterException(
×
1208
                "reverse_ip_tag", str(reverse_ip_tag),
1209
                "The given board address is not recognised")
1210

1211
        for connection in connections:
×
1212
            self._call(ReverseIPTagSet(
×
1213
                connection.chip_x, connection.chip_y,
1214
                reverse_ip_tag.destination_x, reverse_ip_tag.destination_y,
1215
                reverse_ip_tag.destination_p,
1216
                reverse_ip_tag.port, reverse_ip_tag.tag,
1217
                reverse_ip_tag.sdp_port))
1218

1219
    @overrides(Transceiver.clear_ip_tag)
1✔
1220
    def clear_ip_tag(self, tag: int, board_address: Optional[str] = None):
1✔
1221
        for conn in self.__get_connection_list(board_address=board_address):
×
1222
            self._call(IPTagClear(conn.chip_x, conn.chip_y, tag))
×
1223

1224
    @overrides(Transceiver.get_tags)
1✔
1225
    def get_tags(self, connection: Optional[SCAMPConnection] = None
1✔
1226
                 ) -> Iterable[AbstractTag]:
1227
        all_tags = list()
×
1228
        for conn in self.__get_connection_list(connection):
×
1229
            process = GetTagsProcess(self._scamp_connection_selector)
×
1230
            all_tags.extend(process.get_tags(conn))
×
1231
        return all_tags
×
1232

1233
    @overrides(Transceiver.malloc_sdram)
1✔
1234
    def malloc_sdram(
1✔
1235
            self, x: int, y: int, size: int, app_id: int, tag=0) -> int:
1236
        try:
×
1237
            process = MallocSDRAMProcess(self._scamp_connection_selector)
×
1238
            process.malloc_sdram(x, y, size, app_id, tag)
×
1239
            return process.base_address
×
1240
        except Exception:
×
1241
            logger.info(self._where_is_xy(x, y))
×
1242
            raise
×
1243

1244
    @overrides(Transceiver.load_multicast_routes)
1✔
1245
    def load_multicast_routes(
1✔
1246
            self, x: int, y: int, routes: Collection[MulticastRoutingEntry],
1247
            app_id: int):
1248
        try:
×
1249
            process = LoadMultiCastRoutesProcess(
×
1250
                self._scamp_connection_selector)
1251
            process.load_routes(x, y, routes, app_id)
×
1252
        except Exception:
×
1253
            logger.info(self._where_is_xy(x, y))
×
1254
            raise
×
1255

1256
    @overrides(Transceiver.load_fixed_route)
1✔
1257
    def load_fixed_route(
1✔
1258
            self, x: int, y: int, fixed_route: FixedRouteEntry, app_id: int):
1259
        try:
×
1260
            process = LoadFixedRouteRoutingEntryProcess(
×
1261
                self._scamp_connection_selector)
1262
            process.load_fixed_route(x, y, fixed_route, app_id)
×
1263
        except Exception:
×
1264
            logger.info(self._where_is_xy(x, y))
×
1265
            raise
×
1266

1267
    @overrides(Transceiver.read_fixed_route)
1✔
1268
    def read_fixed_route(self, x: int, y: int, app_id: int) -> FixedRouteEntry:
1✔
1269
        try:
×
1270
            process = ReadFixedRouteRoutingEntryProcess(
×
1271
                self._scamp_connection_selector)
1272
            return process.read_fixed_route(x, y, app_id)
×
1273
        except Exception:
×
1274
            logger.info(self._where_is_xy(x, y))
×
1275
            raise
×
1276

1277
    @overrides(Transceiver.get_multicast_routes)
1✔
1278
    def get_multicast_routes(
1✔
1279
            self, x: int, y: int,
1280
            app_id: Optional[int] = None) -> List[MulticastRoutingEntry]:
1281
        try:
×
1282
            base_address = cast(int, self._get_sv_data(
×
1283
                x, y, SystemVariableDefinition.router_table_copy_address))
1284
            process = GetMultiCastRoutesProcess(
×
1285
                self._scamp_connection_selector, app_id)
1286
            return process.get_routes(x, y, base_address)
×
1287
        except Exception:
×
1288
            logger.info(self._where_is_xy(x, y))
×
1289
            raise
×
1290

1291
    @overrides(Transceiver.clear_multicast_routes)
1✔
1292
    def clear_multicast_routes(self, x: int, y: int):
1✔
1293
        try:
×
1294
            self._call(RouterClear(x, y))
×
1295
        except Exception:
×
1296
            logger.info(self._where_is_xy(x, y))
×
1297
            raise
×
1298

1299
    @overrides(Transceiver.get_router_diagnostics)
1✔
1300
    def get_router_diagnostics(self, x: int, y: int) -> RouterDiagnostics:
1✔
1301
        try:
×
1302
            process = ReadRouterDiagnosticsProcess(
×
1303
                self._scamp_connection_selector)
1304
            return process.get_router_diagnostics(x, y)
×
1305
        except Exception:
×
1306
            logger.info(self._where_is_xy(x, y))
×
1307
            raise
×
1308

1309
    @overrides(Transceiver.get_scamp_connection_selector)
1✔
1310
    def get_scamp_connection_selector(self) -> MostDirectConnectionSelector:
1✔
1311
        return self._scamp_connection_selector
×
1312

1313
    @overrides(Transceiver.set_router_diagnostic_filter)
1✔
1314
    def set_router_diagnostic_filter(
1✔
1315
            self, x: int, y: int, position: int,
1316
            diagnostic_filter: DiagnosticFilter):
1317
        try:
×
1318
            self.__set_router_diagnostic_filter(
×
1319
                x, y, position, diagnostic_filter)
1320
        except Exception:
×
1321
            logger.info(self._where_is_xy(x, y))
×
1322
            raise
×
1323

1324
    def __set_router_diagnostic_filter(
1✔
1325
            self, x: int, y: int, position: int,
1326
            diagnostic_filter: DiagnosticFilter):
1327
        data_to_send = diagnostic_filter.filter_word
×
1328
        if position > NO_ROUTER_DIAGNOSTIC_FILTERS:
×
1329
            raise SpinnmanInvalidParameterException(
×
1330
                "position", str(position),
1331
                "the range of the position of a router filter is 0 and 16.")
1332
        if position <= ROUTER_DEFAULT_FILTERS_MAX_POSITION:
×
1333
            logger.warning(
×
1334
                "You are planning to change a filter which is set by default. "
1335
                "By doing this, other runs occurring on this machine will be "
1336
                "forced to use this new configuration until the machine is "
1337
                "reset. Please also note that these changes will make the "
1338
                "the reports from ybug not correct. This has been executed "
1339
                "and is trusted that the end user knows what they are doing.")
1340
        memory_position = (
×
1341
            ROUTER_REGISTER_BASE_ADDRESS + ROUTER_FILTER_CONTROLS_OFFSET +
1342
            position * ROUTER_DIAGNOSTIC_FILTER_SIZE)
1343

1344
        self._call(WriteMemory(
×
1345
            (x, y, 0), memory_position, _ONE_WORD.pack(data_to_send)))
1346

1347
    @overrides(Transceiver.clear_router_diagnostic_counters)
1✔
1348
    def clear_router_diagnostic_counters(self, x: int, y: int):
1✔
1349
        try:
×
1350
            # Clear all
1351
            self._call(WriteMemory(
×
1352
                (x, y, 0), 0xf100002c, _ONE_WORD.pack(0xFFFFFFFF)))
1353
        except Exception:
×
1354
            logger.info(self._where_is_xy(x, y))
×
1355
            raise
×
1356

1357
    @overrides(Transceiver.close)
1✔
1358
    def close(self) -> None:
1✔
1359
        if self._bmp_connection is not None:
×
1360
            if get_config_bool("Machine", "turn_off_machine"):
×
1361
                self._power_off_machine()
×
1362

1363
        for connection in self._all_connections:
×
1364
            connection.close()
×
1365

1366
    @overrides(Transceiver.control_sync)
1✔
1367
    def control_sync(self, do_sync: bool):
1✔
1368
        self._call(DoSync(do_sync))
×
1369

1370
    @overrides(Transceiver.update_provenance_and_exit)
1✔
1371
    def update_provenance_and_exit(self, x: int, y: int, p: int):
1✔
1372
        # Send these signals to make sure the application isn't stuck
1373
        self.send_sdp_message(SDPMessage(
×
1374
            sdp_header=SDPHeader(
1375
                flags=SDPFlag.REPLY_NOT_EXPECTED,
1376
                destination_port=SDP_PORTS.RUNNING_COMMAND_SDP_PORT.value,
1377
                destination_chip_x=x, destination_chip_y=y, destination_cpu=p),
1378
            data=_ONE_WORD.pack(SDP_RUNNING_MESSAGE_CODES
1379
                                .SDP_UPDATE_PROVENCE_REGION_AND_EXIT.value)))
1380

1381
    @overrides(Transceiver.send_chip_update_provenance_and_exit)
1✔
1382
    def send_chip_update_provenance_and_exit(self, x: int, y: int, p: int):
1✔
1383
        cmd = SDP_RUNNING_MESSAGE_CODES.SDP_UPDATE_PROVENCE_REGION_AND_EXIT
×
1384
        port = SDP_PORTS.RUNNING_COMMAND_SDP_PORT
×
1385

1386
        self.send_sdp_message(SDPMessage(
×
1387
            SDPHeader(
1388
                flags=SDPFlag.REPLY_NOT_EXPECTED,
1389
                destination_port=port.value, destination_cpu=p,
1390
                destination_chip_x=x, destination_chip_y=y),
1391
            data=_ONE_WORD.pack(cmd.value)))
1392

1393
    def __str__(self) -> str:
1✔
1394
        addr = self._scamp_connections[0].remote_ip_address
×
1395
        n = len(self._all_connections)
×
1396
        return f"transceiver object connected to {addr} with {n} connections"
×
1397

1398
    def __repr__(self) -> str:
1✔
1399
        return self.__str__()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc