# Copyright (c) 2022 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.
"""
Devices connected to the SpiNNaker peripheral interface (`SPIF`_).
.. _SPIF: https://github.com/SpiNNakerManchester/spif
"""
from enum import IntEnum
from spinn_front_end_common.utility_models import MultiCastCommand
from spinn_utilities.overrides import overrides
_REPEATS = 2
_DELAY_BETWEEN_REPEATS = 1
#: Base key to send packets to SpiNNaker FPGA (add register offset)
_LC_KEY = 0xFFFFFE00
#: Base key to send packets to SPIF (add register offset)
_RC_KEY = 0xFFFFFF00
#: The number of pipes
N_PIPES = 2
#: The number of fields supported for each pipe
N_FIELDS = 4
#: The number of filters supported for each pipe
N_FILTERS = 8
#: SPIF is always connected to FPGA 0
SPIF_FPGA_ID = 0
#: SPIF always outputs to FPGA link 15 on FPGA 0
SPIF_OUTPUT_FPGA_LINK = 15
#: SPIF always gets input from odd links on FPGA 0 (1, 3, 5, 7, 9, 11, 13, 15)
SPIF_INPUT_FPGA_LINKS = range(1, 16, 2)
#: The number of FPGA inputs per pipe
#:
#: .. note::
#: The inputs are not actually separated in the hardware,
#: but a logical separation per pipe is useful.
N_INPUTS = len(SPIF_INPUT_FPGA_LINKS)
[docs]
class SPIFRegister(IntEnum):
"""
The register offsets on a SPIF device.
"""
#: The key to send messages back when requested
REPLY_KEY = 2
#: The input key register base (8 inputs per pipe)
IR_KEY_BASE = 16
#: The input mask register base (8 inputs per pipe)
IR_MASK_BASE = 32
#: The input route register base (8 inputs per pipe)
IR_ROUTE_BASE = 48
#: The output peripheral packet count register
OUT_PERIPH_PKT_CNT = 64
#: The configuration packet count register
CONFIG_PKT_CNT = 65
#: The dropped packet count register
DROPPED_PKT_CNT = 66
#: The input peripheral packet count register
IN_PERIPH_PKT_CNT = 67
#: The output mapper key base register (2 pipes)
MP_KEY_BASE = 80
#: The output mapper field mask base register (4 fields per pipe)
MP_FLD_MASK_BASE = 96
#: The output mapper field shift base register (4 fields per pipe)
MP_FLD_SHIFT_BASE = 112
#: The output mapper field limit base register (4 fields per pipe)
MP_FLD_LIMIT_BASE = 128
#: The filter value base register (8 filters per pipe)
FL_VALUE_BASE = 144
#: The filter mask base register (8 filters per pipe)
FL_MASK_BASE = 176
# The distiller key base register (6 distillers)
DIST_KEY_BASE = 208
# The distiller key mask register (6 distillers)
DIST_MASK_BASE = 224
# The distiller key shift register (6 distillers)
DIST_SHIFT_BASE = 240
[docs]
def cmd(self, payload=None, index=0):
"""
Make a command to send to a SPIF device to set a register value.
:param payload:
The payload to use in the command, or `None` for no payload
:type payload: int or None
:param int index:
The index of the register to send to when there are multiple
registers starting from a base
:rtype: ~spinn_front_end_common.utility_models.MultiCastCommand
"""
return MultiCastCommand(
_RC_KEY + self.value + index, payload, time=None, repeat=_REPEATS,
delay_between_repeats=_DELAY_BETWEEN_REPEATS)
[docs]
def delayed_command(self, get_payload, index=0):
""" Make a command to send to a SPIF device to set a register value,
where the value itself is currently unknown
:param callable()->int get_payload:
A function to call to get the payload later
:param int index:
The index of the register to use when using a multi-indexed
register (default is 0 which works for all registers)
:rtype: MultiCastCommand
"""
return _DelayedMultiCastCommand(
_RC_KEY + self.value + index, get_payload, repeat=_REPEATS,
delay_between_repeats=_DELAY_BETWEEN_REPEATS, index=index)
[docs]
def set_mapper_key(pipe, key):
"""
Get a command to set the output base key for packets from SPIF. This
will be added to the keys determined by the mapper output.
:param int pipe: The SPIF pipe to set the key of (0-1)
:param int key: The output key to set
:rtype: ~spinn_front_end_common.utility_models.MultiCastCommand
"""
return SPIFRegister.MP_KEY_BASE.cmd(key, pipe)
[docs]
def set_field_mask(pipe, index, mask):
"""
Get a command to set the mask of a mapper field on SPIF. This masks
off the bits of the field from the incoming UDP or USB packet values
(which are 32-bits each).
:param int pipe: The SPIF pipe to set the mask of (0-1)
:param int index: The index of the field to set (0-3)
:param int mask: The mask to set
:rtype: ~spinn_front_end_common.utility_models.MultiCastCommand
"""
return SPIFRegister.MP_FLD_MASK_BASE.cmd(mask, (pipe * N_FIELDS) + index)
[docs]
def set_field_shift(pipe, index, shift):
"""
Get a command to set the shift of a mapper field on SPIF. This shifts
the masked bits of the field from the incoming UDP or USB packet values
(which are 32-bits each).
:param int pipe: The SPIF pipe to set the shift of (0-1)
:param int index: The index of the field to set (0-3)
:param int shift:
The shift value to set (0-31); positive = right, negative = left
:rtype: ~spinn_front_end_common.utility_models.MultiCastCommand
"""
return SPIFRegister.MP_FLD_SHIFT_BASE.cmd(
shift, (pipe * N_FIELDS) + index)
[docs]
def set_field_limit(pipe, index, limit):
"""
Get a command to set the limit of a mapper field on SPIF. This sets
a limit on the value of the field after shifting and masking.
:param int pipe: The SPIF pipe to set the limit of (0-1)
:param int index: The index of the field to set (0-3)
:param int limit: The maximum value of the field
:rtype: ~spinn_front_end_common.utility_models.MultiCastCommand
"""
return SPIFRegister.MP_FLD_LIMIT_BASE.cmd(
limit, (pipe * N_FIELDS) + index)
[docs]
def set_filter_value(pipe, index, value):
"""
Get a command to set the value of a filter of SPIF. This will drop
input events from the UDP or USB packets where filter value ==
filter mask & event value.
:param int pipe: The SPIF pipe to set the filter of (0-1)
:param int index: The index of the filter to set (0-7)
:param int value: The filter value to set
:rtype: ~spinn_front_end_common.utility_models.MultiCastCommand
"""
return SPIFRegister.FL_VALUE_BASE.cmd(
value, (pipe * N_FILTERS) + index)
[docs]
def set_filter_mask(pipe, index, mask):
"""
Get a command to set the mask of a filter of SPIF. This will drop
input events from the UDP or USB packets where filter value ==
filter mask & event value.
:param int pipe: The SPIF pipe to set the filter of (0-1)
:param int index: The index of the filter to set (0-7)
:param int mask: The filter mask to set
:rtype: ~spinn_front_end_common.utility_models.MultiCastCommand
"""
return SPIFRegister.FL_MASK_BASE.cmd(
mask, (pipe * N_FILTERS) + index)
[docs]
def set_distiller_key(index, key):
""" Get a command to set the key of the distiller of the output via SPIF.
This tells SPIF which bits to put at the top of the 32-bit output for
each spike received on the given distiller channel, defined by the
peripheral routes in the SpiNNaker FPGA.
:param int index: The index of the channel to set (0-5)
:param int key: The key to set
:rtype: MulticastCommand
"""
return SPIFRegister.DIST_KEY_BASE.cmd(key, index)
[docs]
def set_distiller_mask(index, mask):
""" Get a command to set the mask of the distiller of the output via SPIF.
This tells SPIF which bits to use from the key in the 32-bit output for
each spike received on the given distiller channel, defined by the
peripheral routes in the SpiNNaker FPGA.
:param int index: The index of the channel to set (0-5)
:param int mask: The mask to set
:rtype: MulticastCommand
"""
return SPIFRegister.DIST_MASK_BASE.cmd(mask, index)
[docs]
def set_distiller_mask_delayed(index, mask_func):
""" Get a command to set the mask of the distiller of the output via SPIF.
This tells SPIF which bits to use from the key in the 32-bit output for
each spike received on the given distiller channel, defined by the
peripheral routes in the SpiNNaker FPGA.
:param int index: The index of the channel to set (0-5)
:param callable(int)->int mask_func:
The function to call to set the mask - takes index as argument
:rtype: MulticastCommand
"""
return SPIFRegister.DIST_MASK_BASE.delayed_command(mask_func, index)
[docs]
def set_distiller_shift(index, shift):
""" Get a command to set the shift of the distiller of the output via SPIF.
This tells SPIF how much to shift the key after masking but before
applying the distiller key.
:param int index: The index of the channel to set (0-5)
:param int shift: The shift to set
:rtype: MulticastCommand
"""
return SPIFRegister.DIST_SHIFT_BASE.cmd(shift, index)
class _DelayedMultiCastCommand(MultiCastCommand):
"""
A command where the getting of the payload is delayed.
"""
__slots__ = ("__get_payload", "__index")
def __init__(self, key, get_payload, repeat, delay_between_repeats, index):
"""
:param int key: The key to send
:param callable()->int get_payload:
A function to call that returns a payload.
May be called multiple times; should produce the same value each
time.
:param int repeat: The number of times to repeat the command
:param int delay_between_repeats: The delay between the repeats
:param int index: The index to pass to get_payload when called
"""
super().__init__(
key, repeat=repeat, delay_between_repeats=delay_between_repeats)
self.__get_payload = get_payload
self.__index = index
@property
@overrides(MultiCastCommand.payload)
def payload(self) -> int:
return self.__get_payload(self.__index)
@property
@overrides(MultiCastCommand.is_payload)
def is_payload(self) -> bool:
return True
[docs]
class SpiNNFPGARegister(IntEnum):
"""
The register offsets on the SpiNNaker FPGAs for devices.
"""
#: The base key which identifies packets to send out to the peripheral
# (deprecated - use XP_KEY_0)
P_KEY = 2
#: The mask which identifies packets to send out to the peripheral
# (deprecated - use XP_MASK_0)
P_MASK = 3
#: The base key which identifies packets to write to the FPGA registers
LC_KEY = 12
#: The mask which identifies packets to write to the FPGA registers
LC_MASK = 13
#: The base key which identifies packets to write to the peripheral
#: registers
RC_KEY = 14
#: The mask which identifies packets to write to the peripheral registers
RC_MASK = 15
#: The register to write to to stop the sending of data from the peripheral
#: to SpiNNaker
STOP = 16
#: The register to write to to start the sending of data from the
#: peripheral to SpiNNaker
START = 17
#: The base of the keys that can be sent out of SpiNNaker (up to 6)
XP_KEY_BASE = 32
#: The base of the masks that can be sent out of SpiNNake (up to 6)
XP_MASK_BASE = 48
[docs]
def cmd(self, payload=None, index=0):
"""
Make a command to send to the FPGA to set a register value.
:param payload:
The payload to use in the command, or `None` for no payload
:type payload: int or None
:param int index:
The index of the register to send to when there are multiple
registers starting from a base
:rtype: ~spinn_front_end_common.utility_models.MultiCastCommand
"""
return MultiCastCommand(
_LC_KEY + self.value + index, payload, time=None, repeat=_REPEATS,
delay_between_repeats=_DELAY_BETWEEN_REPEATS)
[docs]
def delayed_command(self, get_payload, index=0):
"""
Make a command to send to the FPGA to set a register value,
where the value itself is currently unknown.
:param callable(int)->int get_payload:
A function to call to get the payload later, passing in the index
:param int index:
The index of the register to send to when there are multiple
registers starting from a base
:rtype: ~spinn_front_end_common.utility_models.MultiCastCommand
"""
return _DelayedMultiCastCommand(
_LC_KEY + self.value + index, get_payload, repeat=_REPEATS,
delay_between_repeats=_DELAY_BETWEEN_REPEATS, index=index)
[docs]
def set_xp_key(index, key):
""" Get a command to set the key of the output via the FPGA.
This tells the FPGA to route this key to the external device.
:param int index: The index of the channel to set (0-5)
:param int key: The key to set
:rtype: MulticastCommand
"""
return SpiNNFPGARegister.XP_KEY_BASE.cmd(key, index)
[docs]
def set_xp_key_delayed(index, key_func):
""" Get a command to set the key of the output via the FPGA later.
This tells the FPGA to route this key to the external device.
:param int index: The index of the channel to set (0-5)
:param callable(int)->int key_func: The function to call to get the key
:rtype: MulticastCommand
"""
return SpiNNFPGARegister.XP_KEY_BASE.delayed_command(key_func, index)
[docs]
def set_xp_mask(index, mask):
""" Get a command to set the mask the output via the FPGA.
This tells the FPGA to route keys after using this mask to the external
device.
:param int index: The index of the channel to set (0-5)
:param int mask: The mask to set
:rtype: MulticastCommand
"""
return SpiNNFPGARegister.XP_MASK_BASE.cmd(mask, index)
[docs]
def set_xp_mask_delayed(index, mask_func):
""" Get a command to set the mask of the output via the FPGA later.
This tells the FPGA to route keys after using this mask to the external
device.
:param int index: The index of the channel to set (0-5)
:param callable(int)->int mask_func: The function to call to get the mask
:rtype: MulticastCommand
"""
return SpiNNFPGARegister.XP_MASK_BASE.delayed_command(mask_func, index)