Source code for spynnaker.pyNN.external_devices_models.spif_retina_device

# Copyright (c) 2021 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 typing import Iterable, List, Tuple
from spinn_utilities.overrides import overrides
from pacman.model.graphs.machine import MachineFPGAVertex
from pacman.model.graphs.application import (
    Application2DFPGAVertex, FPGAConnection)
from pacman.model.graphs.common import Slice
from pacman.model.graphs.machine import MachineVertex
from pacman.model.routing_info import BaseKeyAndMask, RoutingInfo
from pacman.utilities.constants import BITS_IN_KEY
from pacman.utilities.utility_calls import is_power_of_2
from spinn_front_end_common.abstract_models import (
    AbstractSendMeMulticastCommandsVertex)
from spinn_front_end_common.utilities.exceptions import ConfigurationException
from spinn_front_end_common.utility_models import MultiCastCommand
from spynnaker.pyNN.models.common import PopulationApplicationVertex
from .spif_devices import (
    SPIF_FPGA_ID, SPIF_OUTPUT_FPGA_LINK, SPIF_INPUT_FPGA_LINKS,
    N_PIPES, N_FILTERS, SpiNNFPGARegister, SPIFRegister,
    set_field_mask, set_field_shift, set_field_limit,
    set_filter_mask, set_filter_value, set_mapper_key,
    set_input_key, set_input_mask, set_input_route)


class SPIFRetinaDevice(
        Application2DFPGAVertex, PopulationApplicationVertex,
        AbstractSendMeMulticastCommandsVertex):
    """
    A retina device connected to SpiNNaker using a SPIF board.
    """

    #: SPIF outputs to 8 FPGA output links, so we split into (2 x 4), meaning
    #: a mask of (1 x 3)
    Y_MASK = 1

    #: See Y_MASK for description
    X_MASK = 3

    #: The number of X values per row
    X_PER_ROW = 4

    #: The number of devices in existence, to work out the key
    __n_devices = 0

    __slots__ = (
        "__spif_mask",
        "__index_by_slice",
        "__base_key",
        "__pipe",
        "__input_y_mask",
        "__input_y_shift",
        "__input_x_mask",
        "__input_x_shift")

    @classmethod
    def __issue_device_id(cls, base_key):
        if base_key is None:
            base_key = cls.__n_devices
        cls.__n_devices += 1
        return base_key

    def __init__(self, pipe, width, height, sub_width, sub_height,
                 base_key=None, input_x_shift=16, input_y_shift=0,
                 board_address=None, chip_coords=None):
        """
        :param int pipe: Which pipe on SPIF the retina is connected to
        :param int width: The width of the retina in pixels
        :param int height: The height of the retina in pixels
        :param int sub_width:
            The width of rectangles to split the retina into for efficiency of
            sending
        :param int sub_height:
            The height of rectangles to split the retina into for efficiency of
            sending
        :param base_key:
            The key that is common over the whole vertex,
            or `None` to use the pipe number as the key
        :type base_key: int or None
        :param int input_x_shift:
            The shift to get the x coordinate from the input keys sent to SPIF
        :param int input_y_shift:
            The shift to get the y coordinate from the input keys sent to SPIF
        :param board_address:
            The IP address of the board to which the FPGA is connected,
            or `None` to use the default board or chip_coords.

            .. note::
                chip_coords will be used first if both are specified, with
                board_address then being used if the coordinates don't connect
                to an FPGA.
        :type board_address: str or None
        :param chip_coords:
            The coordinates of the chip to which the FPGA is connected, or
            `None` to use the default board or board_address.

            .. note::
                chip_coords will be used first if board_address is also
                specified, with board_address then being used if the
                coordinates don't connect to an FPGA.
        :type chip_coords: tuple(int, int) or None
        """
        # Do some checks
        if sub_width < self.X_MASK + 1 or sub_height < self.Y_MASK + 1:
            raise ConfigurationException(
                "The sub-squares must be >=4 x >= 2"
                f" ({sub_width} x {sub_height} specified)")
        if pipe >= N_PIPES:
            raise ConfigurationException(
                f"Pipe {pipe} is bigger than maximum allowed {N_PIPES}")

        # The width has to be a power of 2 as otherwise the keys will not line
        # up correctly (x is at the least significant bit of the key,
        # so key then has an x # field).
        # This is an error here as it affects the downstream
        # population calculations also!
        if not is_power_of_2(width):
            raise ConfigurationException(
                "The width of the SPIF retina must be a power of 2.  If the"
                " real retina size is less than this, please round it up."
                " This will ensure that following Populations can decode the"
                " spikes correctly.  Note that you will also have to make the"
                " sizes of the following Populations bigger to match!")

        # Call the super
        super().__init__(
            width, height, sub_width, sub_height,
            self.__incoming_fpgas(board_address, chip_coords),
            self.__outgoing_fpga(board_address, chip_coords))

        # The mask is going to be made up of:
        # | K | P | Y_I | Y_0 | Y_F | X_I | X_0 | X_F |
        # K = base key
        # P = polarity (0 as not cared about)
        # Y_I = y index of sub-square
        # Y_0 = 0s for values not cared about in Y
        # Y_F = FPGA y index
        # X_I = x index of sub-square
        # X_0 = 0s for values not cared about in X
        # X_F = FPGA x index
        # Now - go calculate:
        x_bits = self._x_bits
        y_bits = self._y_bits

        # Mask to apply to route packets at input
        n_key_bits = BITS_IN_KEY - self._key_shift
        key_mask = (1 << n_key_bits) - 1
        self.__spif_mask = (
            (key_mask << self._key_shift) +
            (self.Y_MASK << self._source_y_shift) +
            (self.X_MASK << self._source_x_shift))

        # A dictionary to get vertex index from FPGA and slice
        self.__index_by_slice = dict()

        self.__pipe = pipe
        self.__base_key = self.__issue_device_id(base_key)

        # Generate the shifts and masks to convert the SPIF Ethernet inputs to
        # PYX format
        self.__input_x_mask = ((1 << x_bits) - 1) << input_x_shift
        self.__input_x_shift = self.__unsigned(input_x_shift)
        self.__input_y_mask = ((1 << y_bits) - 1) << input_y_shift
        self.__input_y_shift = self.__unsigned(input_y_shift - x_bits)

    @staticmethod
    def __unsigned(n):
        return n & 0xFFFFFFFF

    def __incoming_fpgas(self, board_address, chip_coords):
        """
        Get the incoming FPGA connections.

        :rtype: list(FPGAConnection)
        """
        # We use every other odd link
        return [FPGAConnection(SPIF_FPGA_ID, i, board_address, chip_coords)
                for i in SPIF_INPUT_FPGA_LINKS]

    def __outgoing_fpga(self, board_address, chip_coords):
        """
        Get the outgoing FPGA connection (for commands).

        :rtype: FGPA_Connection
        """
        return FPGAConnection(
            SPIF_FPGA_ID, SPIF_OUTPUT_FPGA_LINK, board_address, chip_coords)

    def __fpga_indices(self, fpga_link_id):
        # We use every other odd link, so we can work out the "index" of the
        # link in the list as follows, and we can then split the index into
        # x and y components
        fpga_index = (fpga_link_id - 1) // 2
        fpga_x_index = fpga_index % self.X_PER_ROW
        fpga_y_index = fpga_index // self.X_PER_ROW
        return fpga_x_index, fpga_y_index




[docs] @overrides(Application2DFPGAVertex.get_machine_fixed_key_and_mask) def get_machine_fixed_key_and_mask( self, machine_vertex: MachineVertex, partition_id: str) -> BaseKeyAndMask: assert isinstance(machine_vertex, MachineFPGAVertex) fpga_link_id = machine_vertex.fpga_link_id vertex_slice = machine_vertex.vertex_slice index = self.__index_by_slice[fpga_link_id, vertex_slice] key_and_mask = self._get_key_and_mask(self.__base_key, index) fpga_x, fpga_y = self.__fpga_indices(fpga_link_id) # Build the key from the components fpga_key = key_and_mask.key + ( (fpga_y << self._source_y_shift) + (fpga_x << self._source_x_shift)) fpga_mask = key_and_mask.mask | self.__spif_mask return BaseKeyAndMask(fpga_key, fpga_mask)
[docs] @overrides(Application2DFPGAVertex.get_fixed_key_and_mask) def get_fixed_key_and_mask(self, partition_id: str) -> BaseKeyAndMask: n_key_bits = BITS_IN_KEY - self._key_shift key_mask = ((1 << n_key_bits) - 1) << self._key_shift return BaseKeyAndMask(self.__base_key << self._key_shift, key_mask)
@property @overrides(AbstractSendMeMulticastCommandsVertex.start_resume_commands) def start_resume_commands(self) -> Iterable[MultiCastCommand]: # Make sure everything has stopped commands = [SpiNNFPGARegister.STOP.cmd()] # Clear the counters commands.append(SPIFRegister.OUT_PERIPH_PKT_CNT.cmd(0)) commands.append(SPIFRegister.CONFIG_PKT_CNT.cmd(0)) commands.append(SPIFRegister.DROPPED_PKT_CNT.cmd(0)) commands.append(SPIFRegister.IN_PERIPH_PKT_CNT.cmd(0)) # Configure the creation of packets from fields to keys using the # "standard" input to SPIF (X | P | Y) and convert to (Y | X) commands.extend([ set_field_mask(self.__pipe, 0, self.__input_x_mask), set_field_shift(self.__pipe, 0, self.__input_x_shift), set_field_limit(self.__pipe, 0, (self.width - 1) << self._source_x_shift), set_field_mask(self.__pipe, 1, self.__input_y_mask), set_field_shift(self.__pipe, 1, self.__input_y_shift), set_field_limit(self.__pipe, 1, (self.height - 1) << self._source_y_shift), # These are unused but set them to be sure set_field_mask(self.__pipe, 2, 0), set_field_shift(self.__pipe, 2, 0), set_field_limit(self.__pipe, 2, 0), set_field_mask(self.__pipe, 3, 0), set_field_shift(self.__pipe, 3, 0), set_field_limit(self.__pipe, 3, 0) ]) # Don't filter commands.extend([ set_filter_mask(self.__pipe, i, 0) for i in range(N_FILTERS) ]) commands.extend([ set_filter_value(self.__pipe, i, 1) for i in range(N_FILTERS) ]) # Configure the output routing key commands.append(set_mapper_key( self.__pipe, self.__base_key << self._key_shift)) # Configure the links to send packets to the 8 FPGAs using the # lower bits commands.extend( set_input_key(self.__pipe, i, self.__spif_key(15 - (i * 2))) for i in range(8)) commands.extend( set_input_mask(self.__pipe, i, self.__spif_mask) for i in range(8)) commands.extend( set_input_route(self.__pipe, i, i) for i in range(8)) # Send the start signal commands.append(SpiNNFPGARegister.START.cmd()) return commands def __spif_key(self, fpga_link_id): x, y = self.__fpga_indices(fpga_link_id) return ((self.__base_key << self._key_shift) + (x << self._source_x_shift) + (y << self._source_y_shift)) @property @overrides(AbstractSendMeMulticastCommandsVertex.pause_stop_commands) def pause_stop_commands(self) -> Iterable[MultiCastCommand]: # Send the stop signal yield SpiNNFPGARegister.STOP.cmd() @property @overrides(AbstractSendMeMulticastCommandsVertex.timed_commands) def timed_commands(self) -> List[MultiCastCommand]: return []
[docs] @overrides(PopulationApplicationVertex.get_atom_key_map) def get_atom_key_map( self, pre_vertex: MachineVertex, partition_id: str, routing_info: RoutingInfo) -> Iterable[Tuple[int, int]]: # Work out which machine vertex x_start, y_start = pre_vertex.vertex_slice.start key_and_mask = self.get_machine_fixed_key_and_mask( pre_vertex, partition_id) x_end = x_start + self.sub_width y_end = y_start + self.sub_height key_x = (key_and_mask.key >> self._source_x_shift) & self.X_MASK key_y = (key_and_mask.key >> self._source_y_shift) & self.Y_MASK neuron_id = (pre_vertex.vertex_slice.lo_atom + (key_y * self.X_PER_ROW) + key_x) for x in range(x_start, x_end, self.X_MASK + 1): for y in range(y_start, y_end, self.Y_MASK + 1): key = (key_and_mask.key | (x << self._source_x_shift) | (y << self._source_y_shift)) yield (neuron_id, key) neuron_id += self.X_PER_ROW