Exemple #1
0
    def _install_send_buffer(self, send_buffer_times):
        if len(send_buffer_times) and hasattr(send_buffer_times[0], "__len__"):
            # Working with a list of lists so check length
            if len(send_buffer_times) != self._n_keys:
                raise ConfigurationException(
                    "The array or arrays of times {} does not have the "
                    "expected length of {}".format(send_buffer_times,
                                                   self._n_keys))

        self._send_buffer = BufferedSendingRegion()
        self._send_buffer_times = send_buffer_times
        self._send_buffers = {
            self._REGIONS.SEND_BUFFER.value: self._send_buffer
        }
    def _install_send_buffer(self, n_keys, send_buffer_times, target_address):
        if (len(send_buffer_times) and
                hasattr(send_buffer_times[0], "__len__")):
            # Working with a list of lists so check length
            if len(send_buffer_times) != n_keys:
                raise ConfigurationException(
                    "The array or arrays of times {} does not have the "
                    "expected length of {}".format(send_buffer_times, n_keys))

        self._send_buffer = BufferedSendingRegion(self._send_buffer_max_space)
        self._send_buffer_times = send_buffer_times

        (ip_address, port, tag, board_address) = target_address
        self._iptags = [IPtagResource(
            ip_address=ip_address, port=port, strip_sdp=True, tag=tag,
            traffic_identifier=TRAFFIC_IDENTIFIER)]
        if board_address is not None:
            self.add_constraint(BoardConstraint(board_address))
        self._send_buffers = {
            self._REGIONS.SEND_BUFFER.value:
            self._send_buffer
        }
    def _install_send_buffer(self, n_keys, send_buffer_times, target_address):
        if (len(send_buffer_times) and
                hasattr(send_buffer_times[0], "__len__")):
            # Working with a list of lists so check length
            if len(send_buffer_times) != n_keys:
                raise ConfigurationException(
                    "The array or arrays of times {} does not have the "
                    "expected length of {}".format(send_buffer_times, n_keys))

        self._send_buffer = BufferedSendingRegion(self._send_buffer_max_space)
        self._send_buffer_times = send_buffer_times

        (ip_address, port, tag, board_address) = target_address
        self._iptags = [IPtagResource(
            ip_address=ip_address, port=port, strip_sdp=True, tag=tag,
            traffic_identifier=TRAFFIC_IDENTIFIER)]
        if board_address is not None:
            self.add_constraint(BoardConstraint(board_address))
        self._send_buffers = {
            self._REGIONS.SEND_BUFFER.value:
            self._send_buffer
        }
    def __init__(
        self,
        n_keys,
        label,
        constraints=None,

        # General input and output parameters
        board_address=None,

        # Live input parameters
        receive_port=None,
        receive_sdp_port=(constants.SDP_PORTS.INPUT_BUFFERING_SDP_PORT.value),
        receive_tag=None,
        receive_rate=10,

        # Key parameters
        virtual_key=None,
        prefix=None,
        prefix_type=None,
        check_keys=False,

        # Send buffer parameters
        send_buffer_times=None,
        send_buffer_partition_id=None,
        send_buffer_max_space=(constants.MAX_SIZE_OF_BUFFERED_REGION_ON_CHIP),
        send_buffer_space_before_notify=640,

        # Buffer notification details
        buffer_notification_ip_address=None,
        buffer_notification_port=None,
        buffer_notification_tag=None,

        # Extra flag for receiving packets without a port
        reserve_reverse_ip_tag=False):
        """

        :param n_keys: The number of keys to be sent via this multicast source
        :param label: The label of this vertex
        :param constraints: Any initial constraints to this vertex
        :param board_address: The IP address of the board on which to place\
                this vertex if receiving data, either buffered or live (by\
                default, any board is chosen)
        :param receive_port: The port on the board that will listen for\
                incoming event packets (default is to disable this feature;\
                set a value to enable it)
        :param receive_sdp_port: The SDP port to listen on for incoming event\
                packets (defaults to 1)
        :param receive_tag: The IP tag to use for receiving live events\
                (uses any by default)
        :param virtual_key: The base multicast key to send received events\
                with (assigned automatically by default)
        :param prefix: The prefix to "or" with generated multicast keys\
                (default is no prefix)
        :param prefix_type: Whether the prefix should apply to the upper or\
                lower half of the multicast keys (default is upper half)
        :param check_keys: True if the keys of received events should be\
                verified before sending (default False)
        :param send_buffer_times: An array of arrays of times at which keys\
                should be sent (one array for each key, default disabled)
        :param send_buffer_max_space: The maximum amount of space to use of\
                the SDRAM on the machine (default is 1MB)
        :param send_buffer_space_before_notify: The amount of space free in\
                the sending buffer before the machine will ask the host for\
                more data (default setting is optimised for most cases)
        :param buffer_notification_ip_address: The IP address of the host\
                that will send new buffers (must be specified if a send buffer\
                is specified)
        :param buffer_notification_port: The port that the host that will\
                send new buffers is listening on (must be specified if a\
                send buffer is specified)
        :param buffer_notification_tag: The IP tag to use to notify the\
                host about space in the buffer (default is to use any tag)
        """
        MachineVertex.__init__(self, label, constraints)
        AbstractReceiveBuffersToHost.__init__(self)

        AbstractProvidesOutgoingPartitionConstraints.__init__(self)

        self._iptags = None
        self._reverse_iptags = None

        # Set up for receiving live packets
        if receive_port is not None or reserve_reverse_ip_tag:
            self._reverse_iptags = [
                ReverseIPtagResource(port=receive_port,
                                     sdp_port=receive_sdp_port,
                                     tag=receive_tag)
            ]
            if board_address is not None:
                self.add_constraint(BoardConstraint(board_address))
        self._receive_rate = receive_rate
        self._receive_sdp_port = receive_sdp_port

        # Work out if buffers are being sent
        self._send_buffer = None
        self._send_buffer_partition_id = send_buffer_partition_id
        if send_buffer_times is None:
            self._send_buffer_times = None
            self._send_buffer_max_space = send_buffer_max_space
            self._send_buffers = None
        else:
            self._send_buffer_max_space = send_buffer_max_space
            self._send_buffer = BufferedSendingRegion(send_buffer_max_space)
            self._send_buffer_times = send_buffer_times

            self._iptags = [
                IPtagResource(ip_address=buffer_notification_ip_address,
                              port=buffer_notification_port,
                              strip_sdp=True,
                              tag=buffer_notification_tag,
                              traffic_identifier=TRAFFIC_IDENTIFIER)
            ]
            if board_address is not None:
                self.add_constraint(BoardConstraint(board_address))
            self._send_buffers = {
                self._REGIONS.SEND_BUFFER.value: self._send_buffer
            }

        # buffered out parameters
        self._send_buffer_space_before_notify = send_buffer_space_before_notify
        if self._send_buffer_space_before_notify > send_buffer_max_space:
            self._send_buffer_space_before_notify = send_buffer_max_space

        # Set up for recording (if requested)
        self._record_buffer_size = 0
        self._buffer_size_before_receive = 0
        self._time_between_triggers = 0
        self._maximum_recording_buffer = 0

        # Set up for buffering
        self._buffer_notification_ip_address = buffer_notification_ip_address
        self._buffer_notification_port = buffer_notification_port
        self._buffer_notification_tag = buffer_notification_tag

        # set flag for checking if in injection mode
        self._in_injection_mode = receive_port is not None

        # Sort out the keys to be used
        self._n_keys = n_keys
        self._virtual_key = virtual_key
        self._mask = None
        self._prefix = prefix
        self._prefix_type = prefix_type
        self._check_keys = check_keys

        # Work out the prefix details
        if self._prefix is not None:
            if self._prefix_type is None:
                self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
            if self._prefix_type == EIEIOPrefix.UPPER_HALF_WORD:
                self._prefix = prefix << 16

        # If the user has specified a virtual key
        if self._virtual_key is not None:

            # check that virtual key is valid
            if self._virtual_key < 0:
                raise ConfigurationException("Virtual keys must be positive")

            # Get a mask and maximum number of keys for the number of keys
            # requested
            self._mask, max_key = self._calculate_mask(n_keys)

            # Check that the number of keys and the virtual key don't interfere
            if n_keys > max_key:
                raise ConfigurationException(
                    "The mask calculated from the number of keys will "
                    "not work with the virtual key specified")

            if self._prefix is not None:

                # Check that the prefix doesn't change the virtual key in the
                # masked area
                masked_key = (self._virtual_key | self._prefix) & self._mask
                if self._virtual_key != masked_key:
                    raise ConfigurationException(
                        "The number of keys, virtual key and key prefix"
                        " settings don't work together")
            else:

                # If no prefix was generated, generate one
                self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
                self._prefix = self._virtual_key
class ReverseIPTagMulticastSourceMachineVertex(
        MachineVertex, AbstractGeneratesDataSpecification,
        AbstractHasAssociatedBinary, AbstractSupportsDatabaseInjection,
        ProvidesProvenanceDataFromMachineImpl,
        AbstractProvidesOutgoingPartitionConstraints,
        SendsBuffersFromHostPreBufferedImpl, AbstractReceiveBuffersToHost,
        AbstractRecordable):
    """ A model which allows events to be injected into spinnaker and\
        converted in to multicast packets
    """

    _REGIONS = Enum(value="_REGIONS",
                    names=[('SYSTEM', 0), ('CONFIGURATION', 1),
                           ('RECORDING', 2), ('SEND_BUFFER', 3),
                           ('PROVENANCE_REGION', 4)])

    _PROVENANCE_ITEMS = Enum(value="_PROVENANCE_ITEMS",
                             names=[("N_RECEIVED_PACKETS", 0),
                                    ("N_SENT_PACKETS", 1),
                                    ("INCORRECT_KEYS", 2),
                                    ("INCORRECT_PACKETS", 3),
                                    ("LATE_PACKETS", 4)])

    # 12 ints (1. has prefix, 2. prefix, 3. prefix type, 4. check key flag,
    #          5. has key, 6. key, 7. mask, 8. buffer space,
    #          9. send buffer flag before notify, 10. tag,
    #          11. tag destination (y, x), 12. receive SDP port)
    _CONFIGURATION_REGION_SIZE = 12 * 4

    def __init__(
        self,
        n_keys,
        label,
        constraints=None,

        # General input and output parameters
        board_address=None,

        # Live input parameters
        receive_port=None,
        receive_sdp_port=(constants.SDP_PORTS.INPUT_BUFFERING_SDP_PORT.value),
        receive_tag=None,
        receive_rate=10,

        # Key parameters
        virtual_key=None,
        prefix=None,
        prefix_type=None,
        check_keys=False,

        # Send buffer parameters
        send_buffer_times=None,
        send_buffer_partition_id=None,
        send_buffer_max_space=(constants.MAX_SIZE_OF_BUFFERED_REGION_ON_CHIP),
        send_buffer_space_before_notify=640,

        # Buffer notification details
        buffer_notification_ip_address=None,
        buffer_notification_port=None,
        buffer_notification_tag=None,

        # Extra flag for receiving packets without a port
        reserve_reverse_ip_tag=False):
        """

        :param n_keys: The number of keys to be sent via this multicast source
        :param label: The label of this vertex
        :param constraints: Any initial constraints to this vertex
        :param board_address: The IP address of the board on which to place\
                this vertex if receiving data, either buffered or live (by\
                default, any board is chosen)
        :param receive_port: The port on the board that will listen for\
                incoming event packets (default is to disable this feature;\
                set a value to enable it)
        :param receive_sdp_port: The SDP port to listen on for incoming event\
                packets (defaults to 1)
        :param receive_tag: The IP tag to use for receiving live events\
                (uses any by default)
        :param virtual_key: The base multicast key to send received events\
                with (assigned automatically by default)
        :param prefix: The prefix to "or" with generated multicast keys\
                (default is no prefix)
        :param prefix_type: Whether the prefix should apply to the upper or\
                lower half of the multicast keys (default is upper half)
        :param check_keys: True if the keys of received events should be\
                verified before sending (default False)
        :param send_buffer_times: An array of arrays of times at which keys\
                should be sent (one array for each key, default disabled)
        :param send_buffer_max_space: The maximum amount of space to use of\
                the SDRAM on the machine (default is 1MB)
        :param send_buffer_space_before_notify: The amount of space free in\
                the sending buffer before the machine will ask the host for\
                more data (default setting is optimised for most cases)
        :param buffer_notification_ip_address: The IP address of the host\
                that will send new buffers (must be specified if a send buffer\
                is specified)
        :param buffer_notification_port: The port that the host that will\
                send new buffers is listening on (must be specified if a\
                send buffer is specified)
        :param buffer_notification_tag: The IP tag to use to notify the\
                host about space in the buffer (default is to use any tag)
        """
        MachineVertex.__init__(self, label, constraints)
        AbstractReceiveBuffersToHost.__init__(self)

        AbstractProvidesOutgoingPartitionConstraints.__init__(self)

        self._iptags = None
        self._reverse_iptags = None

        # Set up for receiving live packets
        if receive_port is not None or reserve_reverse_ip_tag:
            self._reverse_iptags = [
                ReverseIPtagResource(port=receive_port,
                                     sdp_port=receive_sdp_port,
                                     tag=receive_tag)
            ]
            if board_address is not None:
                self.add_constraint(BoardConstraint(board_address))
        self._receive_rate = receive_rate
        self._receive_sdp_port = receive_sdp_port

        # Work out if buffers are being sent
        self._send_buffer = None
        self._send_buffer_partition_id = send_buffer_partition_id
        if send_buffer_times is None:
            self._send_buffer_times = None
            self._send_buffer_max_space = send_buffer_max_space
            self._send_buffers = None
        else:
            self._send_buffer_max_space = send_buffer_max_space
            self._send_buffer = BufferedSendingRegion(send_buffer_max_space)
            self._send_buffer_times = send_buffer_times

            self._iptags = [
                IPtagResource(ip_address=buffer_notification_ip_address,
                              port=buffer_notification_port,
                              strip_sdp=True,
                              tag=buffer_notification_tag,
                              traffic_identifier=TRAFFIC_IDENTIFIER)
            ]
            if board_address is not None:
                self.add_constraint(BoardConstraint(board_address))
            self._send_buffers = {
                self._REGIONS.SEND_BUFFER.value: self._send_buffer
            }

        # buffered out parameters
        self._send_buffer_space_before_notify = send_buffer_space_before_notify
        if self._send_buffer_space_before_notify > send_buffer_max_space:
            self._send_buffer_space_before_notify = send_buffer_max_space

        # Set up for recording (if requested)
        self._record_buffer_size = 0
        self._buffer_size_before_receive = 0
        self._time_between_triggers = 0
        self._maximum_recording_buffer = 0

        # Set up for buffering
        self._buffer_notification_ip_address = buffer_notification_ip_address
        self._buffer_notification_port = buffer_notification_port
        self._buffer_notification_tag = buffer_notification_tag

        # set flag for checking if in injection mode
        self._in_injection_mode = receive_port is not None

        # Sort out the keys to be used
        self._n_keys = n_keys
        self._virtual_key = virtual_key
        self._mask = None
        self._prefix = prefix
        self._prefix_type = prefix_type
        self._check_keys = check_keys

        # Work out the prefix details
        if self._prefix is not None:
            if self._prefix_type is None:
                self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
            if self._prefix_type == EIEIOPrefix.UPPER_HALF_WORD:
                self._prefix = prefix << 16

        # If the user has specified a virtual key
        if self._virtual_key is not None:

            # check that virtual key is valid
            if self._virtual_key < 0:
                raise ConfigurationException("Virtual keys must be positive")

            # Get a mask and maximum number of keys for the number of keys
            # requested
            self._mask, max_key = self._calculate_mask(n_keys)

            # Check that the number of keys and the virtual key don't interfere
            if n_keys > max_key:
                raise ConfigurationException(
                    "The mask calculated from the number of keys will "
                    "not work with the virtual key specified")

            if self._prefix is not None:

                # Check that the prefix doesn't change the virtual key in the
                # masked area
                masked_key = (self._virtual_key | self._prefix) & self._mask
                if self._virtual_key != masked_key:
                    raise ConfigurationException(
                        "The number of keys, virtual key and key prefix"
                        " settings don't work together")
            else:

                # If no prefix was generated, generate one
                self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
                self._prefix = self._virtual_key

    @property
    @overrides(ProvidesProvenanceDataFromMachineImpl._provenance_region_id)
    def _provenance_region_id(self):
        return self._REGIONS.PROVENANCE_REGION.value

    @property
    @overrides(ProvidesProvenanceDataFromMachineImpl._n_additional_data_items)
    def _n_additional_data_items(self):
        return 5

    @property
    @overrides(MachineVertex.resources_required)
    def resources_required(self):
        resources = ResourceContainer(
            dtcm=DTCMResource(self.get_dtcm_usage()),
            sdram=SDRAMResource(
                self.get_sdram_usage(self._send_buffer_times,
                                     self._send_buffer_max_space,
                                     self._record_buffer_size > 0)),
            cpu_cycles=CPUCyclesPerTickResource(self.get_cpu_usage()),
            iptags=self._iptags,
            reverse_iptags=self._reverse_iptags)
        if self._iptags is None:
            resources.extend(
                recording_utilities.get_recording_resources(
                    [self._record_buffer_size],
                    self._buffer_notification_ip_address,
                    self._buffer_notification_port,
                    self._buffer_notification_tag))
        else:
            resources.extend(
                recording_utilities.get_recording_resources(
                    [self._record_buffer_size]))
        return resources

    @staticmethod
    def get_sdram_usage(send_buffer_times, send_buffer_max_space,
                        recording_enabled):
        send_buffer_size = 0
        if send_buffer_times is not None:
            send_buffer_size = send_buffer_max_space

        mallocs = \
            ReverseIPTagMulticastSourceMachineVertex.n_regions_to_allocate(
                send_buffer_times is not None, recording_enabled)
        allocation_size = mallocs * constants.SARK_PER_MALLOC_SDRAM_USAGE

        return (constants.SYSTEM_BYTES_REQUIREMENT +
                (ReverseIPTagMulticastSourceMachineVertex.
                 _CONFIGURATION_REGION_SIZE) + send_buffer_size +
                allocation_size + (ReverseIPTagMulticastSourceMachineVertex.
                                   get_provenance_data_size(0)))

    @staticmethod
    def get_dtcm_usage():
        return 1

    @staticmethod
    def get_cpu_usage():
        return 1

    @staticmethod
    def n_regions_to_allocate(send_buffering, recording):
        """ Get the number of regions that will be allocated
        """
        if recording and send_buffering:
            return 5
        elif recording or send_buffering:
            return 4
        return 3

    @property
    def send_buffer_times(self):
        return self._send_buffer_times

    @send_buffer_times.setter
    def send_buffer_times(self, send_buffer_times):
        self._send_buffer_times = send_buffer_times

    def _is_in_range(self, time_stamp_in_ticks, first_machine_time_step,
                     n_machine_time_steps):
        return ((n_machine_time_steps is None)
                or (first_machine_time_step <= time_stamp_in_ticks <
                    n_machine_time_steps))

    def _fill_send_buffer(self, machine_time_step, first_machine_time_step,
                          n_machine_time_steps):
        """ Fill the send buffer with keys to send
        """

        key_to_send = self._virtual_key
        if self._virtual_key is None:
            key_to_send = 0

        if self._send_buffer is not None:
            self._send_buffer.clear()
        if (self._send_buffer_times is not None
                and len(self._send_buffer_times) != 0):
            if hasattr(self._send_buffer_times[0], "__len__"):

                # Works with a list-of-lists
                for key in range(self._n_keys):
                    for timeStamp in sorted(self._send_buffer_times[key]):
                        time_stamp_in_ticks = int(
                            math.ceil(
                                float(int(timeStamp * 1000.0)) /
                                machine_time_step))
                        if self._is_in_range(time_stamp_in_ticks,
                                             first_machine_time_step,
                                             n_machine_time_steps):
                            self._send_buffer.add_key(time_stamp_in_ticks,
                                                      key_to_send + key)
            else:

                # Work with a single list
                key_list = [key + key_to_send for key in range(self._n_keys)]
                for timeStamp in sorted(self._send_buffer_times):
                    time_stamp_in_ticks = int(
                        math.ceil(
                            float(int(timeStamp * 1000.0)) /
                            machine_time_step))

                    # add to send_buffer collection
                    if self._is_in_range(time_stamp_in_ticks,
                                         first_machine_time_step,
                                         n_machine_time_steps):
                        self._send_buffer.add_keys(time_stamp_in_ticks,
                                                   key_list)

    @staticmethod
    def _generate_prefix(virtual_key, prefix_type):
        if prefix_type == EIEIOPrefix.LOWER_HALF_WORD:
            return virtual_key & 0xFFFF
        return (virtual_key >> 16) & 0xFFFF

    @staticmethod
    def _calculate_mask(n_neurons):
        if n_neurons == 1:
            return 0xFFFFFFFF, 1
        temp_value = int(math.ceil(math.log(n_neurons, 2)))
        max_key = (int(math.pow(2, temp_value)) - 1)
        mask = 0xFFFFFFFF - max_key
        return mask, max_key

    def enable_recording(
            self,
            record_buffer_size=constants.MAX_SIZE_OF_BUFFERED_REGION_ON_CHIP,
            buffer_size_before_receive=(
                constants.DEFAULT_BUFFER_SIZE_BEFORE_RECEIVE),
            time_between_triggers=0):
        """ Enable recording of the keys sent

        :param record_buffer_size:\
            The size of the recording buffer in bytes.  Note that when using\
            automatic pause and resume, this will be used as the minimum size\
            of the buffer
        :type record_buffer_size: int
        :param buffer_size_before_receive:\
            The size that the buffer can grow to before a read request is\
            issued to the host (in bytes)
        :type buffer_size_before_receive: int
        :param time_between_triggers:\
            The minimum time between the sending of read requests
        :type time_between_triggers: int
        """
        self._record_buffer_size = record_buffer_size
        self._buffer_size_before_receive = buffer_size_before_receive
        self._time_between_triggers = time_between_triggers

    def _reserve_regions(self, spec):

        # Reserve system and configuration memory regions:
        spec.reserve_memory_region(region=self._REGIONS.SYSTEM.value,
                                   size=constants.SYSTEM_BYTES_REQUIREMENT,
                                   label='SYSTEM')
        spec.reserve_memory_region(region=self._REGIONS.CONFIGURATION.value,
                                   size=self._CONFIGURATION_REGION_SIZE,
                                   label='CONFIGURATION')

        # Reserve recording buffer regions if required
        spec.reserve_memory_region(
            region=self._REGIONS.RECORDING.value,
            size=recording_utilities.get_recording_header_size(1),
            label="RECORDING")

        # Reserve send buffer region if required
        if self._send_buffer_times is not None:
            max_buffer_size = self.get_max_buffer_size_possible(
                self._REGIONS.SEND_BUFFER.value)
            spec.reserve_memory_region(region=self._REGIONS.SEND_BUFFER.value,
                                       size=max_buffer_size,
                                       label="SEND_BUFFER",
                                       empty=True)

        self.reserve_provenance_data_region(spec)

    def _update_virtual_key(self, routing_info, machine_graph):
        if self._virtual_key is None:
            if self._send_buffer_partition_id is not None:

                rinfo = routing_info.get_routing_info_from_pre_vertex(
                    self, self._send_buffer_partition_id)

                # if no edge leaving this vertex, no key needed
                if rinfo is not None:
                    self._virtual_key = rinfo.first_key
                    self._mask = rinfo.first_mask

            else:
                partitions = machine_graph\
                    .get_outgoing_edge_partitions_starting_at_vertex(self)
                partition = next(iter(partitions), None)

                if partition is not None:
                    rinfo = routing_info.get_routing_info_from_partition(
                        partition)
                    self._virtual_key = rinfo.first_key
                    self._mask = rinfo.first_mask

        if self._virtual_key is not None and self._prefix is None:
            self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
            self._prefix = self._virtual_key

    def _write_configuration(self, spec, ip_tags):
        spec.switch_write_focus(region=self._REGIONS.CONFIGURATION.value)

        # Write apply_prefix and prefix and prefix_type
        if self._prefix is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=self._prefix)
            spec.write_value(data=self._prefix_type.value)

        # Write check
        if self._check_keys:
            spec.write_value(data=1)
        else:
            spec.write_value(data=0)

        # Write if you have a key to transmit write it and the mask,
        # otherwise write flags to fill in space
        if self._virtual_key is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=self._virtual_key)
            spec.write_value(data=self._mask)

        # Write send buffer data
        if self._send_buffer_times is not None:

            this_tag = None

            for tag in ip_tags:
                if tag.traffic_identifier == TRAFFIC_IDENTIFIER:
                    this_tag = tag
                    break
            if this_tag is None:
                raise Exception("Could not find tag for send buffering")

            buffer_space = self.get_max_buffer_size_possible(
                self._REGIONS.SEND_BUFFER.value)

            spec.write_value(data=buffer_space)
            spec.write_value(data=self._send_buffer_space_before_notify)
            spec.write_value(data=this_tag.tag)
            spec.write_value(
                struct.unpack(
                    "<I",
                    struct.pack("<HH", this_tag.destination_y,
                                this_tag.destination_x))[0])
        else:
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)

        # write SDP port to which SDP packets will be received
        spec.write_value(data=self._receive_sdp_port)

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "machine_graph": "MemoryMachineGraph",
        "routing_info": "MemoryRoutingInfos",
        "tags": "MemoryTags",
        "first_machine_time_step": "FirstMachineTimeStep",
        "n_machine_time_steps": "TotalMachineTimeSteps"
    })
    @overrides(AbstractGeneratesDataSpecification.generate_data_specification,
               additional_arguments={
                   "machine_time_step", "time_scale_factor", "machine_graph",
                   "routing_info", "tags", "first_machine_time_step",
                   "n_machine_time_steps"
               })
    def generate_data_specification(self, spec, placement, machine_time_step,
                                    time_scale_factor, machine_graph,
                                    routing_info, tags,
                                    first_machine_time_step,
                                    n_machine_time_steps):

        self._update_virtual_key(routing_info, machine_graph)
        self._fill_send_buffer(machine_time_step, first_machine_time_step,
                               n_machine_time_steps)

        # Reserve regions
        self._reserve_regions(spec)

        # Write the system region
        spec.switch_write_focus(self._REGIONS.SYSTEM.value)
        spec.write_array(
            get_simulation_header_array(self.get_binary_file_name(),
                                        machine_time_step, time_scale_factor))

        # Write the additional recording information
        iptags = tags.get_ip_tags_for_vertex(self)
        spec.switch_write_focus(self._REGIONS.RECORDING.value)
        spec.write_array(
            recording_utilities.get_recording_header_array(
                [self._record_buffer_size], self._time_between_triggers,
                self._buffer_size_before_receive, iptags,
                self._buffer_notification_tag))

        # Write the configuration information
        self._write_configuration(spec, iptags)

        # End spec
        spec.end_specification()

    @overrides(AbstractHasAssociatedBinary.get_binary_file_name)
    def get_binary_file_name(self):
        return "reverse_iptag_multicast_source.aplx"

    @overrides(AbstractHasAssociatedBinary.get_binary_start_type)
    def get_binary_start_type(self):
        return ExecutableStartType.USES_SIMULATION_INTERFACE

    @overrides(AbstractProvidesOutgoingPartitionConstraints.
               get_outgoing_partition_constraints)
    def get_outgoing_partition_constraints(self, partition):
        if self._virtual_key is not None:
            return list([
                FixedKeyAndMaskConstraint(
                    [BaseKeyAndMask(self._virtual_key, self._mask)])
            ])
        return list()

    @property
    def virtual_key(self):
        return self._virtual_key

    @property
    def mask(self):
        return self._mask

    @property
    @overrides(AbstractSupportsDatabaseInjection.is_in_injection_mode)
    def is_in_injection_mode(self):
        return self._in_injection_mode

    @overrides(AbstractRecordable.is_recording)
    def is_recording(self):
        return self._record_buffer_size > 0

    @inject("FirstMachineTimeStep")
    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "n_machine_time_steps": "TotalMachineTimeSteps"
    })
    def update_buffer(self, first_machine_time_step, machine_time_step,
                      n_machine_time_steps):
        self._fill_send_buffer(machine_time_step, first_machine_time_step,
                               n_machine_time_steps)

    @overrides(AbstractReceiveBuffersToHost.get_minimum_buffer_sdram_usage)
    def get_minimum_buffer_sdram_usage(self):
        return self._record_buffer_size

    @overrides(AbstractReceiveBuffersToHost.get_n_timesteps_in_buffer_space)
    def get_n_timesteps_in_buffer_space(self, buffer_space, machine_time_step):

        # If not recording, not an issue
        if self._record_buffer_size == 0:
            return sys.maxint

        # If recording and using pre-defined keys, use the maximum
        if self._send_buffer is not None:
            return recording_utilities.get_n_timesteps_in_buffer_space(
                buffer_space, [self._send_buffer.max_packets_in_timestamp])

        # If recording and not using pre-defined keys, use the specified
        # rate to work it out - add 10% for safety
        keys_per_timestep = math.ceil(
            (self._receive_rate / (machine_time_step * 1000.0)) * 1.1)

        # 4 bytes per key + 2 byte header + 4 byte timestamp
        bytes_per_timestep = (keys_per_timestep * 4) + 6
        return recording_utilities.get_n_timesteps_in_buffer_space(
            buffer_space, [bytes_per_timestep])

    @overrides(AbstractReceiveBuffersToHost.get_recorded_region_ids)
    def get_recorded_region_ids(self):
        return recording_utilities.get_recorded_region_ids(
            [self._record_buffer_size])

    @overrides(AbstractReceiveBuffersToHost.get_recording_region_base_address)
    def get_recording_region_base_address(self, txrx, placement):
        return locate_memory_region_for_placement(
            placement, self._REGIONS.RECORDING.value, txrx)

    @property
    def send_buffers(self):
        return self._send_buffers

    @send_buffers.setter
    def send_buffers(self, value):
        self._send_buffers = value

    @overrides(
        ProvidesProvenanceDataFromMachineImpl.get_provenance_data_from_machine)
    def get_provenance_data_from_machine(self, transceiver, placement):
        provenance_data = self._read_provenance_data(transceiver, placement)
        provenance_items = self._read_basic_provenance_items(
            provenance_data, placement)
        provenance_data = self._get_remaining_provenance_data_items(
            provenance_data)
        _, _, _, _, names = self._get_placement_details(placement)

        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "received_sdp_packets"),
                provenance_data[
                    self._PROVENANCE_ITEMS.N_RECEIVED_PACKETS.value],
                report=(provenance_data[
                    self._PROVENANCE_ITEMS.N_RECEIVED_PACKETS.value] == 0
                        and self._send_buffer_times is None),
                message=
                ("No SDP packets were received by {}.  If you expected packets"
                 " to be injected, this could indicate an error".format(
                     self._label))))
        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "send_multicast_packets"),
                provenance_data[self._PROVENANCE_ITEMS.N_SENT_PACKETS.value],
                report=provenance_data[
                    self._PROVENANCE_ITEMS.N_SENT_PACKETS.value] == 0,
                message=(
                    "No multicast packets were sent by {}.  If you expected"
                    " packets to be sent this could indicate an error".format(
                        self._label))))
        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "incorrect_keys"),
                provenance_data[self._PROVENANCE_ITEMS.INCORRECT_KEYS.value],
                report=provenance_data[
                    self._PROVENANCE_ITEMS.INCORRECT_KEYS.value] > 0,
                message=(
                    "Keys were received by {} that did not match the key {} and"
                    " mask {}".format(self._label, self._virtual_key,
                                      self._mask))))
        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "incorrect_packets"),
                provenance_data[
                    self._PROVENANCE_ITEMS.INCORRECT_PACKETS.value],
                report=provenance_data[
                    self._PROVENANCE_ITEMS.INCORRECT_PACKETS.value] > 0,
                message=(
                    "SDP Packets were received by {} that were not correct".
                    format(self._label))))
        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "late_packets"),
                provenance_data[self._PROVENANCE_ITEMS.LATE_PACKETS.value],
                report=provenance_data[
                    self._PROVENANCE_ITEMS.LATE_PACKETS.value] > 0,
                message=(
                    "SDP Packets were received by {} that were too late to be"
                    " transmitted in the simulation".format(self._label))))

        return provenance_items

    def __repr__(self):
        return self._label
class ReverseIPTagMulticastSourceMachineVertex(
        MachineVertex, AbstractGeneratesDataSpecification,
        AbstractHasAssociatedBinary, AbstractSupportsDatabaseInjection,
        ProvidesProvenanceDataFromMachineImpl,
        AbstractProvidesOutgoingPartitionConstraints,
        SendsBuffersFromHostPreBufferedImpl,
        AbstractReceiveBuffersToHost, AbstractRecordable):
    """ A model which allows events to be injected into SpiNNaker and\
        converted in to multicast packets
    """

    _REGIONS = Enum(
        value="_REGIONS",
        names=[('SYSTEM', 0),
               ('CONFIGURATION', 1),
               ('RECORDING', 2),
               ('SEND_BUFFER', 3),
               ('PROVENANCE_REGION', 4)])

    _PROVENANCE_ITEMS = Enum(
        value="_PROVENANCE_ITEMS",
        names=[("N_RECEIVED_PACKETS", 0),
               ("N_SENT_PACKETS", 1),
               ("INCORRECT_KEYS", 2),
               ("INCORRECT_PACKETS", 3),
               ("LATE_PACKETS", 4)])

    # 12 ints (1. has prefix, 2. prefix, 3. prefix type, 4. check key flag,
    #          5. has key, 6. key, 7. mask, 8. buffer space,
    #          9. send buffer flag before notify, 10. tag,
    #          11. tag destination (y, x), 12. receive SDP port)
    _CONFIGURATION_REGION_SIZE = 12 * 4

    def __init__(
            self, n_keys, label, constraints=None,

            # General input and output parameters
            board_address=None,

            # Live input parameters
            receive_port=None,
            receive_sdp_port=SDP_PORTS.INPUT_BUFFERING_SDP_PORT.value,
            receive_tag=None,
            receive_rate=10,

            # Key parameters
            virtual_key=None, prefix=None,
            prefix_type=None, check_keys=False,

            # Send buffer parameters
            send_buffer_times=None,
            send_buffer_partition_id=None,
            send_buffer_max_space=MAX_SIZE_OF_BUFFERED_REGION_ON_CHIP,
            send_buffer_space_before_notify=640,

            # Buffer notification details
            buffer_notification_ip_address=None,
            buffer_notification_port=None,
            buffer_notification_tag=None,

            # Extra flag for receiving packets without a port
            reserve_reverse_ip_tag=False):
        """
        :param n_keys: The number of keys to be sent via this multicast source
        :param label: The label of this vertex
        :param constraints: Any initial constraints to this vertex
        :param board_address: The IP address of the board on which to place\
            this vertex if receiving data, either buffered or live (by\
            default, any board is chosen)
        :param receive_port: The port on the board that will listen for\
            incoming event packets (default is to disable this feature; set a\
            value to enable it, or set the reserve_reverse_ip_tag parameter\
            to True if a random port is to be used)
        :param receive_sdp_port: The SDP port to listen on for incoming event\
            packets (defaults to 1)
        :param receive_tag: The IP tag to use for receiving live events\
            (uses any by default)
        :param virtual_key: The base multicast key to send received events\
            with (assigned automatically by default)
        :param prefix: The prefix to "or" with generated multicast keys\
            (default is no prefix)
        :param prefix_type: Whether the prefix should apply to the upper or\
            lower half of the multicast keys (default is upper half)
        :param check_keys: True if the keys of received events should be\
            verified before sending (default False)
        :param send_buffer_times: An array of arrays of times at which keys\
            should be sent (one array for each key, default disabled)
        :param send_buffer_max_space: The maximum amount of space to use of\
            the SDRAM on the machine (default is 1MB)
        :param send_buffer_space_before_notify: The amount of space free in\
            the sending buffer before the machine will ask the host for more\
            data (default setting is optimised for most cases)
        :param buffer_notification_ip_address: The IP address of the host that\
            will send new buffers (must be specified if a send buffer is\
            specified)
        :param buffer_notification_port: The port that the host that will\
            send new buffers is listening on (must be specified if a send\
            buffer is specified)
        :param buffer_notification_tag: The IP tag to use to notify the\
            host about space in the buffer (default is to use any tag)
        :param reserve_reverse_ip_tag: True if the source should set up a tag\
            through which it can receive packets; if port is set to None this\
            can be used to enable the reception of packets on a randomly\
            assigned port, which can be read from the database
        """
        # pylint: disable=too-many-arguments, too-many-locals
        super(ReverseIPTagMulticastSourceMachineVertex, self).__init__(
            label, constraints)

        self._iptags = None
        self._reverse_iptags = None

        # Set up for receiving live packets
        if receive_port is not None or reserve_reverse_ip_tag:
            self._reverse_iptags = [ReverseIPtagResource(
                port=receive_port, sdp_port=receive_sdp_port,
                tag=receive_tag)]
            if board_address is not None:
                self.add_constraint(BoardConstraint(board_address))
        self._receive_rate = receive_rate
        self._receive_sdp_port = receive_sdp_port

        # Work out if buffers are being sent
        self._send_buffer = None
        self._send_buffer_partition_id = send_buffer_partition_id
        self._send_buffer_max_space = send_buffer_max_space
        if send_buffer_times is None:
            self._send_buffer_times = None
            self._send_buffers = None
        else:
            self._install_send_buffer(n_keys, send_buffer_times, (
                buffer_notification_ip_address, buffer_notification_port,
                buffer_notification_tag, board_address))

        # buffered out parameters
        self._send_buffer_space_before_notify = max((
            send_buffer_space_before_notify, send_buffer_max_space))

        # Set up for recording (if requested)
        self._record_buffer_size = 0
        self._buffer_size_before_receive = 0
        self._time_between_triggers = 0
        self._maximum_recording_buffer = 0

        # Set up for buffering
        self._buffer_notification_ip_address = buffer_notification_ip_address
        self._buffer_notification_port = buffer_notification_port
        self._buffer_notification_tag = buffer_notification_tag

        # set flag for checking if in injection mode
        self._in_injection_mode = \
            receive_port is not None or reserve_reverse_ip_tag

        # Sort out the keys to be used
        self._n_keys = n_keys
        self._virtual_key = virtual_key
        self._mask = None
        self._prefix = prefix
        self._prefix_type = prefix_type
        self._check_keys = check_keys

        # Work out the prefix details
        if self._prefix is not None:
            if self._prefix_type is None:
                self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
            if self._prefix_type == EIEIOPrefix.UPPER_HALF_WORD:
                self._prefix = prefix << 16

        # If the user has specified a virtual key
        if self._virtual_key is not None:
            self._install_virtual_key(n_keys)

    def _install_send_buffer(self, n_keys, send_buffer_times, target_address):
        if (len(send_buffer_times) and
                hasattr(send_buffer_times[0], "__len__")):
            # Working with a list of lists so check length
            if len(send_buffer_times) != n_keys:
                raise ConfigurationException(
                    "The array or arrays of times {} does not have the "
                    "expected length of {}".format(send_buffer_times, n_keys))

        self._send_buffer = BufferedSendingRegion(self._send_buffer_max_space)
        self._send_buffer_times = send_buffer_times

        (ip_address, port, tag, board_address) = target_address
        self._iptags = [IPtagResource(
            ip_address=ip_address, port=port, strip_sdp=True, tag=tag,
            traffic_identifier=TRAFFIC_IDENTIFIER)]
        if board_address is not None:
            self.add_constraint(BoardConstraint(board_address))
        self._send_buffers = {
            self._REGIONS.SEND_BUFFER.value:
            self._send_buffer
        }

    def _install_virtual_key(self, n_keys):
        # check that virtual key is valid
        if self._virtual_key < 0:
            raise ConfigurationException("Virtual keys must be positive")

        # Get a mask and maximum number of keys for the number of keys
        # requested
        self._mask, max_key = self._calculate_mask(n_keys)

        # Check that the number of keys and the virtual key don't interfere
        if n_keys > max_key:
            raise ConfigurationException(
                "The mask calculated from the number of keys will not work "
                "with the virtual key specified")

        if self._prefix is not None:
            # Check that the prefix doesn't change the virtual key in the
            # masked area
            masked_key = (self._virtual_key | self._prefix) & self._mask
            if self._virtual_key != masked_key:
                raise ConfigurationException(
                    "The number of keys, virtual key and key prefix settings "
                    "don't work together")
        else:
            # If no prefix was generated, generate one
            self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
            self._prefix = self._virtual_key

    @property
    @overrides(ProvidesProvenanceDataFromMachineImpl._provenance_region_id)
    def _provenance_region_id(self):
        return self._REGIONS.PROVENANCE_REGION.value

    @property
    @overrides(ProvidesProvenanceDataFromMachineImpl._n_additional_data_items)
    def _n_additional_data_items(self):
        return 5

    @property
    @overrides(MachineVertex.resources_required)
    def resources_required(self):
        resources = ResourceContainer(
            dtcm=DTCMResource(self.get_dtcm_usage()),
            sdram=SDRAMResource(self.get_sdram_usage(
                self._send_buffer_times, self._send_buffer_max_space,
                self._record_buffer_size > 0)),
            cpu_cycles=CPUCyclesPerTickResource(self.get_cpu_usage()),
            iptags=self._iptags,
            reverse_iptags=self._reverse_iptags)
        if self._iptags is None:
            resources.extend(get_recording_resources(
                [self._record_buffer_size],
                self._buffer_notification_ip_address,
                self._buffer_notification_port, self._buffer_notification_tag))
        else:
            resources.extend(get_recording_resources(
                [self._record_buffer_size]))
        return resources

    @staticmethod
    def get_sdram_usage(
            send_buffer_times, send_buffer_max_space, recording_enabled):
        send_buffer_size = 0
        if send_buffer_times is not None:
            send_buffer_size = send_buffer_max_space

        mallocs = \
            ReverseIPTagMulticastSourceMachineVertex.n_regions_to_allocate(
                send_buffer_times is not None, recording_enabled)
        allocation_size = mallocs * SARK_PER_MALLOC_SDRAM_USAGE

        return (
            SYSTEM_BYTES_REQUIREMENT +
            (ReverseIPTagMulticastSourceMachineVertex.
                _CONFIGURATION_REGION_SIZE) +
            send_buffer_size + allocation_size +
            (ReverseIPTagMulticastSourceMachineVertex.
                get_provenance_data_size(0)))

    @staticmethod
    def get_dtcm_usage():
        return 1

    @staticmethod
    def get_cpu_usage():
        return 1

    @staticmethod
    def n_regions_to_allocate(send_buffering, recording):
        """ Get the number of regions that will be allocated
        """
        if recording and send_buffering:
            return 5
        elif recording or send_buffering:
            return 4
        return 3

    @property
    def send_buffer_times(self):
        return self._send_buffer_times

    @send_buffer_times.setter
    def send_buffer_times(self, send_buffer_times):
        self._send_buffer_times = send_buffer_times

    def _is_in_range(
            self, time_stamp_in_ticks,
            first_machine_time_step, n_machine_time_steps):
        return (n_machine_time_steps is None) or (
            first_machine_time_step <= time_stamp_in_ticks <
            n_machine_time_steps)

    def _fill_send_buffer(
            self, machine_time_step, first_machine_time_step,
            n_machine_time_steps):
        """ Fill the send buffer with keys to send
        """

        key_to_send = self._virtual_key
        if self._virtual_key is None:
            key_to_send = 0

        if self._send_buffer is not None:
            self._send_buffer.clear()
        if (self._send_buffer_times is not None and
                len(self._send_buffer_times)):
            if hasattr(self._send_buffer_times[0], "__len__"):
                # Works with a list-of-lists
                self.__fill_send_buffer_2d(
                    key_to_send, machine_time_step, first_machine_time_step,
                    n_machine_time_steps)
            else:
                # Work with a single list
                self.__fill_send_buffer_1d(
                    key_to_send, machine_time_step, first_machine_time_step,
                    n_machine_time_steps)

    def __fill_send_buffer_2d(
            self, key_base, time_step, first_time_step, n_time_steps):
        for key in range(self._n_keys):
            for time_stamp in sorted(self._send_buffer_times[key]):
                tick = int(math.ceil(
                    float(int(time_stamp * 1000.0)) / time_step))
                if self._is_in_range(tick, first_time_step, n_time_steps):
                    self._send_buffer.add_key(tick, key_base + key)

    def __fill_send_buffer_1d(
            self, key_base, time_step, first_time_step, n_time_steps):
        key_list = [key + key_base for key in xrange(self._n_keys)]
        for time_stamp in sorted(self._send_buffer_times):
            tick = int(math.ceil(
                float(int(time_stamp * 1000.0)) / time_step))
            if self._is_in_range(tick, first_time_step, n_time_steps):
                self._send_buffer.add_keys(tick, key_list)

    @staticmethod
    def _generate_prefix(virtual_key, prefix_type):
        if prefix_type == EIEIOPrefix.LOWER_HALF_WORD:
            return virtual_key & 0xFFFF
        return (virtual_key >> 16) & 0xFFFF

    @staticmethod
    def _calculate_mask(n_neurons):
        if n_neurons == 1:
            return 0xFFFFFFFF, 1
        temp_value = int(math.ceil(math.log(n_neurons, 2)))
        max_key = (int(math.pow(2, temp_value)) - 1)
        mask = 0xFFFFFFFF - max_key
        return mask, max_key

    def enable_recording(
            self,
            record_buffer_size=MAX_SIZE_OF_BUFFERED_REGION_ON_CHIP,
            buffer_size_before_receive=DEFAULT_BUFFER_SIZE_BEFORE_RECEIVE,
            time_between_triggers=0):
        """ Enable recording of the keys sent

        :param record_buffer_size:\
            The size of the recording buffer in bytes.  Note that when using\
            automatic pause and resume, this will be used as the minimum size\
            of the buffer
        :type record_buffer_size: int
        :param buffer_size_before_receive:\
            The size that the buffer can grow to before a read request is\
            issued to the host (in bytes)
        :type buffer_size_before_receive: int
        :param time_between_triggers:\
            The minimum time between the sending of read requests
        :type time_between_triggers: int
        """
        self._record_buffer_size = record_buffer_size
        self._buffer_size_before_receive = buffer_size_before_receive
        self._time_between_triggers = time_between_triggers

    def _reserve_regions(self, spec):

        # Reserve system and configuration memory regions:
        spec.reserve_memory_region(
            region=self._REGIONS.SYSTEM.value,
            size=SYSTEM_BYTES_REQUIREMENT, label='SYSTEM')
        spec.reserve_memory_region(
            region=self._REGIONS.CONFIGURATION.value,
            size=self._CONFIGURATION_REGION_SIZE, label='CONFIGURATION')

        # Reserve recording buffer regions if required
        spec.reserve_memory_region(
            region=self._REGIONS.RECORDING.value,
            size=get_recording_header_size(1),
            label="RECORDING")

        # Reserve send buffer region if required
        if self._send_buffer_times is not None:
            max_buffer_size = self.get_max_buffer_size_possible(
                self._REGIONS.SEND_BUFFER.value)
            spec.reserve_memory_region(
                region=self._REGIONS.SEND_BUFFER.value,
                size=max_buffer_size, label="SEND_BUFFER", empty=True)

        self.reserve_provenance_data_region(spec)

    def _update_virtual_key(self, routing_info, machine_graph):
        if self._virtual_key is None:
            if self._send_buffer_partition_id is not None:

                rinfo = routing_info.get_routing_info_from_pre_vertex(
                    self, self._send_buffer_partition_id)

                # if no edge leaving this vertex, no key needed
                if rinfo is not None:
                    self._virtual_key = rinfo.first_key
                    self._mask = rinfo.first_mask

            else:
                partitions = machine_graph\
                    .get_outgoing_edge_partitions_starting_at_vertex(self)
                partition = next(iter(partitions), None)

                if partition is not None:
                    rinfo = routing_info.get_routing_info_from_partition(
                        partition)
                    self._virtual_key = rinfo.first_key
                    self._mask = rinfo.first_mask

        if self._virtual_key is not None and self._prefix is None:
            self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
            self._prefix = self._virtual_key

    def _write_configuration(self, spec, ip_tags):
        spec.switch_write_focus(region=self._REGIONS.CONFIGURATION.value)

        # Write apply_prefix and prefix and prefix_type
        if self._prefix is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=self._prefix)
            spec.write_value(data=self._prefix_type.value)

        # Write check
        if self._check_keys:
            spec.write_value(data=1)
        else:
            spec.write_value(data=0)

        # Write if you have a key to transmit write it and the mask,
        # otherwise write flags to fill in space
        if self._virtual_key is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=self._virtual_key)
            spec.write_value(data=self._mask)

        # Write send buffer data
        if self._send_buffer_times is not None:

            this_tag = None

            for tag in ip_tags:
                if tag.traffic_identifier == TRAFFIC_IDENTIFIER:
                    this_tag = tag
                    break
            if this_tag is None:
                raise Exception("Could not find tag for send buffering")

            buffer_space = self.get_max_buffer_size_possible(
                self._REGIONS.SEND_BUFFER.value)

            spec.write_value(data=buffer_space)
            spec.write_value(data=self._send_buffer_space_before_notify)
            spec.write_value(data=this_tag.tag)
            spec.write_value(_ONE_WORD.unpack(_TWO_SHORTS.pack(
                this_tag.destination_y, this_tag.destination_x))[0])
        else:
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)

        # write SDP port to which SDP packets will be received
        spec.write_value(data=self._receive_sdp_port)

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "machine_graph": "MemoryMachineGraph",
        "routing_info": "MemoryRoutingInfos",
        "tags": "MemoryTags",
        "first_machine_time_step": "FirstMachineTimeStep",
        "n_machine_time_steps": "TotalMachineTimeSteps"
    })
    @overrides(
        AbstractGeneratesDataSpecification.generate_data_specification,
        additional_arguments={
            "machine_time_step", "time_scale_factor", "machine_graph",
            "routing_info", "tags", "first_machine_time_step",
            "n_machine_time_steps"
        })
    def generate_data_specification(
            self, spec, placement,  # @UnusedVariable
            machine_time_step, time_scale_factor, machine_graph, routing_info,
            tags, first_machine_time_step, n_machine_time_steps):
        # pylint: disable=too-many-arguments, arguments-differ
        self._update_virtual_key(routing_info, machine_graph)
        self._fill_send_buffer(
            machine_time_step, first_machine_time_step, n_machine_time_steps)

        # Reserve regions
        self._reserve_regions(spec)

        # Write the system region
        spec.switch_write_focus(self._REGIONS.SYSTEM.value)
        spec.write_array(get_simulation_header_array(
            self.get_binary_file_name(), machine_time_step,
            time_scale_factor))

        # Write the additional recording information
        iptags = tags.get_ip_tags_for_vertex(self)
        spec.switch_write_focus(self._REGIONS.RECORDING.value)
        spec.write_array(get_recording_header_array(
            [self._record_buffer_size],
            self._time_between_triggers, self._buffer_size_before_receive,
            iptags, self._buffer_notification_tag))

        # Write the configuration information
        self._write_configuration(spec, iptags)

        # End spec
        spec.end_specification()

    @overrides(AbstractHasAssociatedBinary.get_binary_file_name)
    def get_binary_file_name(self):
        return "reverse_iptag_multicast_source.aplx"

    @overrides(AbstractHasAssociatedBinary.get_binary_start_type)
    def get_binary_start_type(self):
        return ExecutableType.USES_SIMULATION_INTERFACE

    @overrides(AbstractProvidesOutgoingPartitionConstraints.
               get_outgoing_partition_constraints)
    def get_outgoing_partition_constraints(self, partition):  # @UnusedVariable
        if self._virtual_key is not None:
            return list([FixedKeyAndMaskConstraint(
                [BaseKeyAndMask(self._virtual_key, self._mask)])])
        return list()

    @property
    def virtual_key(self):
        return self._virtual_key

    @property
    def mask(self):
        return self._mask

    @property
    @overrides(AbstractSupportsDatabaseInjection.is_in_injection_mode)
    def is_in_injection_mode(self):
        return self._in_injection_mode

    @overrides(AbstractRecordable.is_recording)
    def is_recording(self):
        return self._record_buffer_size > 0

    @inject("FirstMachineTimeStep")
    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "n_machine_time_steps": "TotalMachineTimeSteps"
    })
    def update_buffer(
            self, first_machine_time_step, machine_time_step,
            n_machine_time_steps):
        self._fill_send_buffer(
            machine_time_step, first_machine_time_step, n_machine_time_steps)

    @overrides(AbstractReceiveBuffersToHost.get_minimum_buffer_sdram_usage)
    def get_minimum_buffer_sdram_usage(self):
        return self._record_buffer_size

    @overrides(AbstractReceiveBuffersToHost.get_n_timesteps_in_buffer_space)
    def get_n_timesteps_in_buffer_space(self, buffer_space, machine_time_step):

        # If not recording, not an issue
        if self._record_buffer_size == 0:
            return sys.maxsize

        # If recording and using pre-defined keys, use the maximum
        if self._send_buffer is not None:
            return get_n_timesteps_in_buffer_space(
                buffer_space, [self._send_buffer.max_packets_in_timestamp])

        # If recording and not using pre-defined keys, use the specified
        # rate to work it out - add 10% for safety
        keys_per_timestep = math.ceil(
            (self._receive_rate / (machine_time_step * 1000.0)) * 1.1
        )

        # 4 bytes per key + 2 byte header + 4 byte timestamp
        bytes_per_timestep = (keys_per_timestep * 4) + 6
        return get_n_timesteps_in_buffer_space(
            buffer_space, [bytes_per_timestep])

    @overrides(AbstractReceiveBuffersToHost.get_recorded_region_ids)
    def get_recorded_region_ids(self):
        return get_recorded_region_ids([self._record_buffer_size])

    @overrides(AbstractReceiveBuffersToHost.get_recording_region_base_address)
    def get_recording_region_base_address(self, txrx, placement):
        return locate_memory_region_for_placement(
            placement, self._REGIONS.RECORDING.value, txrx)

    @property
    def send_buffers(self):
        return self._send_buffers

    @send_buffers.setter
    def send_buffers(self, value):
        self._send_buffers = value

    @overrides(ProvidesProvenanceDataFromMachineImpl.
               get_provenance_data_from_machine)
    def get_provenance_data_from_machine(self, transceiver, placement):
        provenance_data = self._read_provenance_data(transceiver, placement)
        provenance_items = self._read_basic_provenance_items(
            provenance_data, placement)
        provenance_data = self._get_remaining_provenance_data_items(
            provenance_data)
        _, _, _, _, names = self._get_placement_details(placement)

        provenance_items.append(ProvenanceDataItem(
            self._add_name(names, "received_sdp_packets"),
            provenance_data[self._PROVENANCE_ITEMS.N_RECEIVED_PACKETS.value],
            report=(
                provenance_data[
                    self._PROVENANCE_ITEMS.N_RECEIVED_PACKETS.value] == 0 and
                self._send_buffer_times is None),
            message=(
                "No SDP packets were received by {}.  If you expected packets"
                " to be injected, this could indicate an error".format(
                    self._label))))
        provenance_items.append(ProvenanceDataItem(
            self._add_name(names, "send_multicast_packets"),
            provenance_data[self._PROVENANCE_ITEMS.N_SENT_PACKETS.value],
            report=provenance_data[
                self._PROVENANCE_ITEMS.N_SENT_PACKETS.value] == 0,
            message=(
                "No multicast packets were sent by {}.  If you expected"
                " packets to be sent this could indicate an error".format(
                    self._label))))
        provenance_items.append(ProvenanceDataItem(
            self._add_name(names, "incorrect_keys"),
            provenance_data[self._PROVENANCE_ITEMS.INCORRECT_KEYS.value],
            report=provenance_data[
                self._PROVENANCE_ITEMS.INCORRECT_KEYS.value] > 0,
            message=(
                "Keys were received by {} that did not match the key {} and"
                " mask {}".format(
                    self._label, self._virtual_key, self._mask))))
        provenance_items.append(ProvenanceDataItem(
            self._add_name(names, "incorrect_packets"),
            provenance_data[self._PROVENANCE_ITEMS.INCORRECT_PACKETS.value],
            report=provenance_data[
                self._PROVENANCE_ITEMS.INCORRECT_PACKETS.value] > 0,
            message=(
                "SDP Packets were received by {} that were not correct".format(
                    self._label))))
        provenance_items.append(ProvenanceDataItem(
            self._add_name(names, "late_packets"),
            provenance_data[self._PROVENANCE_ITEMS.LATE_PACKETS.value],
            report=provenance_data[
                self._PROVENANCE_ITEMS.LATE_PACKETS.value] > 0,
            message=(
                "SDP Packets were received by {} that were too late to be"
                " transmitted in the simulation".format(self._label))))

        return provenance_items

    def __repr__(self):
        return self._label
Exemple #7
0
class ReverseIPTagMulticastSourceMachineVertex(
        MachineVertex, AbstractGeneratesDataSpecification,
        AbstractHasAssociatedBinary, AbstractSupportsDatabaseInjection,
        ProvidesProvenanceDataFromMachineImpl,
        AbstractProvidesOutgoingPartitionConstraints,
        SendsBuffersFromHostPreBufferedImpl, AbstractReceiveBuffersToHost,
        AbstractRecordable):
    """ A model which allows events to be injected into SpiNNaker and\
        converted in to multicast packets
    """

    _REGIONS = Enum(value="_REGIONS",
                    names=[('SYSTEM', 0), ('CONFIGURATION', 1),
                           ('RECORDING', 2), ('SEND_BUFFER', 3),
                           ('PROVENANCE_REGION', 4)])

    _PROVENANCE_ITEMS = Enum(value="_PROVENANCE_ITEMS",
                             names=[("N_RECEIVED_PACKETS", 0),
                                    ("N_SENT_PACKETS", 1),
                                    ("INCORRECT_KEYS", 2),
                                    ("INCORRECT_PACKETS", 3),
                                    ("LATE_PACKETS", 4)])

    # 12 ints (1. has prefix, 2. prefix, 3. prefix type, 4. check key flag,
    #          5. has key, 6. key, 7. mask, 8. buffer space,
    #          9. send buffer flag before notify, 10. tag,
    #          11. tag destination (y, x), 12. receive SDP port,
    #          13. timer offset)
    _CONFIGURATION_REGION_SIZE = 13 * 4

    # Counts to do timer offsets
    _n_vertices = 0
    _n_data_specs = 0

    def __init__(
        self,
        n_keys,
        label,
        constraints=None,

        # General input and output parameters
        board_address=None,

        # Live input parameters
        receive_port=None,
        receive_sdp_port=SDP_PORTS.INPUT_BUFFERING_SDP_PORT.value,
        receive_tag=None,
        receive_rate=10,

        # Key parameters
        virtual_key=None,
        prefix=None,
        prefix_type=None,
        check_keys=False,

        # Send buffer parameters
        send_buffer_times=None,
        send_buffer_partition_id=None,

        # Extra flag for receiving packets without a port
        reserve_reverse_ip_tag=False):
        """
        :param n_keys: The number of keys to be sent via this multicast source
        :param label: The label of this vertex
        :param constraints: Any initial constraints to this vertex
        :param board_address: The IP address of the board on which to place\
            this vertex if receiving data, either buffered or live (by\
            default, any board is chosen)
        :param receive_port: The port on the board that will listen for\
            incoming event packets (default is to disable this feature; set a\
            value to enable it, or set the reserve_reverse_ip_tag parameter\
            to True if a random port is to be used)
        :param receive_sdp_port: The SDP port to listen on for incoming event\
            packets (defaults to 1)
        :param receive_tag: The IP tag to use for receiving live events\
            (uses any by default)
        :param virtual_key: The base multicast key to send received events\
            with (assigned automatically by default)
        :param prefix: The prefix to "or" with generated multicast keys\
            (default is no prefix)
        :param prefix_type: Whether the prefix should apply to the upper or\
            lower half of the multicast keys (default is upper half)
        :param check_keys: True if the keys of received events should be\
            verified before sending (default False)
        :param send_buffer_times: An array of arrays of time steps at which\
            keys should be sent (one array for each key, default disabled)
        :param send_buffer_max_space: The maximum amount of space to use of\
            the SDRAM on the machine (default is 1MB)
        :param send_buffer_space_before_notify: The amount of space free in\
            the sending buffer before the machine will ask the host for more\
            data (default setting is optimised for most cases)
        :param buffer_notification_ip_address: The IP address of the host that\
            will send new buffers (must be specified if a send buffer is\
            specified)
        :param buffer_notification_port: The port that the host that will\
            send new buffers is listening on (must be specified if a send\
            buffer is specified)
        :param buffer_notification_tag: The IP tag to use to notify the\
            host about space in the buffer (default is to use any tag)
        :param reserve_reverse_ip_tag: True if the source should set up a tag\
            through which it can receive packets; if port is set to None this\
            can be used to enable the reception of packets on a randomly\
            assigned port, which can be read from the database
        """
        # pylint: disable=too-many-arguments, too-many-locals
        super(ReverseIPTagMulticastSourceMachineVertex,
              self).__init__(label, constraints)

        self._reverse_iptags = None
        self._n_keys = n_keys

        # Set up for receiving live packets
        if receive_port is not None or reserve_reverse_ip_tag:
            self._reverse_iptags = [
                ReverseIPtagResource(port=receive_port,
                                     sdp_port=receive_sdp_port,
                                     tag=receive_tag)
            ]
            if board_address is not None:
                self.add_constraint(BoardConstraint(board_address))
        self._receive_rate = receive_rate
        self._receive_sdp_port = receive_sdp_port

        # Work out if buffers are being sent
        self._send_buffer = None
        self._send_buffer_partition_id = send_buffer_partition_id
        self._send_buffer_size = 0
        if send_buffer_times is None:
            self._send_buffer_times = None
            self._send_buffers = None
        else:
            self._install_send_buffer(send_buffer_times)

        # Set up for recording (if requested)
        self._is_recording = False

        # set flag for checking if in injection mode
        self._in_injection_mode = \
            receive_port is not None or reserve_reverse_ip_tag

        # Sort out the keys to be used
        self._virtual_key = virtual_key
        self._mask = None
        self._prefix = prefix
        self._prefix_type = prefix_type
        self._check_keys = check_keys

        # Work out the prefix details
        if self._prefix is not None:
            if self._prefix_type is None:
                self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
            if self._prefix_type == EIEIOPrefix.UPPER_HALF_WORD:
                self._prefix = prefix << 16

        # If the user has specified a virtual key
        if self._virtual_key is not None:
            self._install_virtual_key(n_keys)

        self._n_vertices += 1

    @staticmethod
    def max_send_buffer_keys_per_timestep(send_buffer_times, n_keys):
        if len(send_buffer_times) and hasattr(send_buffer_times[0], "__len__"):
            counts = numpy.bincount(numpy.concatenate(send_buffer_times))
            if len(counts):
                return max(counts)
            return 0
        if len(send_buffer_times):
            counts = numpy.bincount(send_buffer_times)
            if len(counts):
                return n_keys * max(counts)
            return 0
        return 0

    @staticmethod
    def send_buffer_sdram_per_timestep(send_buffer_times, n_keys):
        # If there is a send buffer, calculate the keys per timestep
        if send_buffer_times is not None:
            sdram = get_n_bytes(
                ReverseIPTagMulticastSourceMachineVertex.
                max_send_buffer_keys_per_timestep(send_buffer_times, n_keys))
            return sdram
        return 0

    @staticmethod
    def recording_sdram_per_timestep(machine_time_step, is_recording,
                                     receive_rate, send_buffer_times, n_keys):

        # If recording live data, use the user provided receive rate
        if is_recording and send_buffer_times is None:
            keys_per_timestep = math.ceil(
                (receive_rate / (machine_time_step * 1000.0)) * 1.1)
            header_size = EIEIODataHeader.get_header_size(EIEIOType.KEY_32_BIT,
                                                          is_payload_base=True)
            # Maximum size is one packet per key
            return ((header_size + EIEIOType.KEY_32_BIT.key_bytes) *
                    keys_per_timestep)

        # If recording send data, the recorded size is the send size
        if is_recording and send_buffer_times is not None:
            return (ReverseIPTagMulticastSourceMachineVertex.
                    send_buffer_sdram_per_timestep(send_buffer_times, n_keys))

        # Not recording no SDRAM needed per timestep
        return 0

    def _install_send_buffer(self, send_buffer_times):
        if len(send_buffer_times) and hasattr(send_buffer_times[0], "__len__"):
            # Working with a list of lists so check length
            if len(send_buffer_times) != self._n_keys:
                raise ConfigurationException(
                    "The array or arrays of times {} does not have the "
                    "expected length of {}".format(send_buffer_times,
                                                   self._n_keys))

        self._send_buffer = BufferedSendingRegion()
        self._send_buffer_times = send_buffer_times
        self._send_buffers = {
            self._REGIONS.SEND_BUFFER.value: self._send_buffer
        }

    def _install_virtual_key(self, n_keys):
        # check that virtual key is valid
        if self._virtual_key < 0:
            raise ConfigurationException("Virtual keys must be positive")

        # Get a mask and maximum number of keys for the number of keys
        # requested
        self._mask = self._calculate_mask(n_keys)

        if self._prefix is not None:
            # Check that the prefix doesn't change the virtual key in the
            # masked area
            masked_key = (self._virtual_key | self._prefix) & self._mask
            if self._virtual_key != masked_key:
                raise ConfigurationException(
                    "The number of keys, virtual key and key prefix settings "
                    "don't work together")
        else:
            # If no prefix was generated, generate one
            self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
            self._prefix = self._virtual_key

    @property
    @overrides(ProvidesProvenanceDataFromMachineImpl._provenance_region_id)
    def _provenance_region_id(self):
        return self._REGIONS.PROVENANCE_REGION.value

    @property
    @overrides(ProvidesProvenanceDataFromMachineImpl._n_additional_data_items)
    def _n_additional_data_items(self):
        return 5

    @property
    @overrides(MachineVertex.resources_required)
    def resources_required(self):
        sim = globals_variables.get_simulator()
        sdram = self.get_sdram_usage(self._send_buffer_times,
                                     self._is_recording, sim.machine_time_step,
                                     self._receive_rate, self._n_keys)

        resources = ResourceContainer(dtcm=DTCMResource(self.get_dtcm_usage()),
                                      sdram=sdram,
                                      cpu_cycles=CPUCyclesPerTickResource(
                                          self.get_cpu_usage()),
                                      reverse_iptags=self._reverse_iptags)
        return resources

    @staticmethod
    def get_sdram_usage(send_buffer_times, recording_enabled,
                        machine_time_step, receive_rate, n_keys):

        static_usage = (SYSTEM_BYTES_REQUIREMENT +
                        (ReverseIPTagMulticastSourceMachineVertex.
                         _CONFIGURATION_REGION_SIZE) +
                        get_recording_header_size(1) +
                        get_recording_data_constant_size(1) +
                        (ReverseIPTagMulticastSourceMachineVertex.
                         get_provenance_data_size(0)))
        per_timestep = (
            ReverseIPTagMulticastSourceMachineVertex.
            send_buffer_sdram_per_timestep(send_buffer_times, n_keys) +
            ReverseIPTagMulticastSourceMachineVertex.
            recording_sdram_per_timestep(machine_time_step, recording_enabled,
                                         receive_rate, send_buffer_times,
                                         n_keys))
        static_usage += per_timestep
        return VariableSDRAM(static_usage, per_timestep)

    @staticmethod
    def get_dtcm_usage():
        return 1

    @staticmethod
    def get_cpu_usage():
        return 1

    @staticmethod
    def n_regions_to_allocate(send_buffering, recording):
        """ Get the number of regions that will be allocated
        """
        if recording and send_buffering:
            return 5
        elif recording or send_buffering:
            return 4
        return 3

    @property
    def send_buffer_times(self):
        return self._send_buffer_times

    @send_buffer_times.setter
    def send_buffer_times(self, send_buffer_times):
        self._install_send_buffer(send_buffer_times)

    def _is_in_range(self, time_stamp_in_ticks, first_machine_time_step,
                     n_machine_time_steps):
        return (n_machine_time_steps is
                None) or (first_machine_time_step <= time_stamp_in_ticks <
                          n_machine_time_steps)

    def _fill_send_buffer(self, first_machine_time_step, run_until_timesteps):
        """ Fill the send buffer with keys to send
        """

        key_to_send = self._virtual_key
        if self._virtual_key is None:
            key_to_send = 0

        if self._send_buffer is not None:
            self._send_buffer.clear()
        if (self._send_buffer_times is not None
                and len(self._send_buffer_times)):
            if hasattr(self._send_buffer_times[0], "__len__"):
                # Works with a list-of-lists
                self.__fill_send_buffer_2d(key_to_send,
                                           first_machine_time_step,
                                           run_until_timesteps)
            else:
                # Work with a single list
                self.__fill_send_buffer_1d(key_to_send,
                                           first_machine_time_step,
                                           run_until_timesteps)

    def __fill_send_buffer_2d(self, key_base, first_time_step, n_time_steps):
        for key in range(self._n_keys):
            for tick in sorted(self._send_buffer_times[key]):
                if self._is_in_range(tick, first_time_step, n_time_steps):
                    self._send_buffer.add_key(tick, key_base + key)

    def __fill_send_buffer_1d(self, key_base, first_time_step, n_time_steps):
        key_list = [key + key_base for key in xrange(self._n_keys)]
        for tick in sorted(self._send_buffer_times):
            if self._is_in_range(tick, first_time_step, n_time_steps):
                self._send_buffer.add_keys(tick, key_list)

    @staticmethod
    def _generate_prefix(virtual_key, prefix_type):
        if prefix_type == EIEIOPrefix.LOWER_HALF_WORD:
            return virtual_key & 0xFFFF
        return (virtual_key >> 16) & 0xFFFF

    @staticmethod
    def _calculate_mask(n_neurons):
        temp_value = n_neurons.bit_length()
        max_key = 2**temp_value - 1
        mask = 0xFFFFFFFF - max_key
        return mask

    def enable_recording(self, new_state=True):
        """ Enable recording of the keys sent
        """
        self._is_recording = new_state

    def _reserve_regions(self, spec, n_machine_time_steps):

        # Reserve system and configuration memory regions:
        spec.reserve_memory_region(region=self._REGIONS.SYSTEM.value,
                                   size=SIMULATION_N_BYTES,
                                   label='SYSTEM')
        spec.reserve_memory_region(region=self._REGIONS.CONFIGURATION.value,
                                   size=self._CONFIGURATION_REGION_SIZE,
                                   label='CONFIGURATION')

        # Reserve recording buffer regions if required
        spec.reserve_memory_region(region=self._REGIONS.RECORDING.value,
                                   size=get_recording_header_size(1),
                                   label="RECORDING")

        # Reserve send buffer region if required
        if self._send_buffer_times is not None:
            self._send_buffer_size = (self.send_buffer_sdram_per_timestep(
                self._send_buffer_times, self._n_keys) * n_machine_time_steps)
            if self._send_buffer_size:
                spec.reserve_memory_region(
                    region=self._REGIONS.SEND_BUFFER.value,
                    size=self._send_buffer_size,
                    label="SEND_BUFFER",
                    empty=True)

        self.reserve_provenance_data_region(spec)

    def _update_virtual_key(self, routing_info, machine_graph):
        if self._virtual_key is None:
            if self._send_buffer_partition_id is not None:

                rinfo = routing_info.get_routing_info_from_pre_vertex(
                    self, self._send_buffer_partition_id)

                # if no edge leaving this vertex, no key needed
                if rinfo is not None:
                    self._virtual_key = rinfo.first_key
                    self._mask = rinfo.first_mask

            else:
                partitions = machine_graph\
                    .get_outgoing_edge_partitions_starting_at_vertex(self)
                partition = next(iter(partitions), None)

                if partition is not None:
                    rinfo = routing_info.get_routing_info_from_partition(
                        partition)
                    self._virtual_key = rinfo.first_key
                    self._mask = rinfo.first_mask

        if self._virtual_key is not None and self._prefix is None:
            self._prefix_type = EIEIOPrefix.UPPER_HALF_WORD
            self._prefix = self._virtual_key

    def _write_configuration(self, spec, machine_time_step, time_scale_factor):
        spec.switch_write_focus(region=self._REGIONS.CONFIGURATION.value)

        # Write apply_prefix and prefix and prefix_type
        if self._prefix is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=self._prefix)
            spec.write_value(data=self._prefix_type.value)

        # Write check
        if self._check_keys:
            spec.write_value(data=1)
        else:
            spec.write_value(data=0)

        # Write if you have a key to transmit write it and the mask,
        # otherwise write flags to fill in space
        if self._virtual_key is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=self._virtual_key)
            spec.write_value(data=self._mask)

        # Write send buffer data
        if self._send_buffer_times is not None:

            spec.write_value(data=self._send_buffer_size)
            # The following disable the buffer notifications
            spec.write_value(data=self._send_buffer_size + 256)
            spec.write_value(data=0)
            spec.write_value(_ONE_WORD.unpack(_TWO_SHORTS.pack(0, 0))[0])
        else:
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)
            spec.write_value(data=0)

        # write SDP port to which SDP packets will be received
        spec.write_value(data=self._receive_sdp_port)

        # write timer offset in microseconds
        max_offset = (machine_time_step *
                      time_scale_factor) // _MAX_OFFSET_DENOMINATOR
        spec.write_value(
            int(math.ceil(max_offset / self._n_vertices)) * self._n_data_specs)
        self._n_data_specs += 1

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "machine_graph": "MemoryMachineGraph",
        "routing_info": "MemoryRoutingInfos",
        "first_machine_time_step": "FirstMachineTimeStep",
        "data_n_time_steps": "DataNTimeSteps",
        "run_until_timesteps": "RunUntilTimeSteps"
    })
    @overrides(AbstractGeneratesDataSpecification.generate_data_specification,
               additional_arguments={
                   "machine_time_step", "time_scale_factor", "machine_graph",
                   "routing_info", "first_machine_time_step",
                   "data_n_time_steps", "run_until_timesteps"
               })
    def generate_data_specification(
            self,
            spec,
            placement,  # @UnusedVariable
            machine_time_step,
            time_scale_factor,
            machine_graph,
            routing_info,
            first_machine_time_step,
            data_n_time_steps,
            run_until_timesteps):
        # pylint: disable=too-many-arguments, arguments-differ
        self._update_virtual_key(routing_info, machine_graph)
        self._fill_send_buffer(first_machine_time_step, run_until_timesteps)

        # Reserve regions
        self._reserve_regions(spec, data_n_time_steps)

        # Write the system region
        spec.switch_write_focus(self._REGIONS.SYSTEM.value)
        spec.write_array(
            get_simulation_header_array(self.get_binary_file_name(),
                                        machine_time_step, time_scale_factor))

        # Write the additional recording information
        spec.switch_write_focus(self._REGIONS.RECORDING.value)
        recording_size = 0
        if self._is_recording:
            per_timestep = self.recording_sdram_per_timestep(
                machine_time_step, self._is_recording, self._receive_rate,
                self._send_buffer_times, self._n_keys)
            recording_size = per_timestep * data_n_time_steps
        spec.write_array(get_recording_header_array([recording_size]))

        # Write the configuration information
        self._write_configuration(spec, machine_time_step, time_scale_factor)

        # End spec
        spec.end_specification()

    @overrides(AbstractHasAssociatedBinary.get_binary_file_name)
    def get_binary_file_name(self):
        return "reverse_iptag_multicast_source.aplx"

    @overrides(AbstractHasAssociatedBinary.get_binary_start_type)
    def get_binary_start_type(self):
        return ExecutableType.USES_SIMULATION_INTERFACE

    @overrides(AbstractProvidesOutgoingPartitionConstraints.
               get_outgoing_partition_constraints)
    def get_outgoing_partition_constraints(self, partition):  # @UnusedVariable
        if self._virtual_key is not None:
            return list([
                FixedKeyAndMaskConstraint(
                    [BaseKeyAndMask(self._virtual_key, self._mask)])
            ])
        return list()

    @property
    def virtual_key(self):
        return self._virtual_key

    @property
    def mask(self):
        return self._mask

    @property
    @overrides(AbstractSupportsDatabaseInjection.is_in_injection_mode)
    def is_in_injection_mode(self):
        return self._in_injection_mode

    @overrides(AbstractRecordable.is_recording)
    def is_recording(self):
        return self._is_recording > 0

    @inject("FirstMachineTimeStep")
    @inject_items({"run_until_timesteps": "RunUntilTimeSteps"})
    def update_buffer(self, first_machine_time_step, run_until_timesteps):
        if self._virtual_key is not None:
            self._fill_send_buffer(first_machine_time_step,
                                   run_until_timesteps)

    @overrides(AbstractReceiveBuffersToHost.get_recorded_region_ids)
    def get_recorded_region_ids(self):
        if not self._is_recording:
            return []
        return [0]

    @overrides(AbstractReceiveBuffersToHost.get_recording_region_base_address)
    def get_recording_region_base_address(self, txrx, placement):
        return locate_memory_region_for_placement(
            placement, self._REGIONS.RECORDING.value, txrx)

    @property
    def send_buffers(self):
        return self._send_buffers

    @send_buffers.setter
    def send_buffers(self, value):
        self._send_buffers = value

    def get_region_buffer_size(self, region):
        if region == self._REGIONS.SEND_BUFFER.value:
            return self._send_buffer_size
        return 0

    @overrides(
        ProvidesProvenanceDataFromMachineImpl.get_provenance_data_from_machine)
    def get_provenance_data_from_machine(self, transceiver, placement):
        provenance_data = self._read_provenance_data(transceiver, placement)
        provenance_items = self._read_basic_provenance_items(
            provenance_data, placement)
        provenance_data = self._get_remaining_provenance_data_items(
            provenance_data)
        _, _, _, _, names = self._get_placement_details(placement)

        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "received_sdp_packets"),
                provenance_data[
                    self._PROVENANCE_ITEMS.N_RECEIVED_PACKETS.value],
                report=(provenance_data[
                    self._PROVENANCE_ITEMS.N_RECEIVED_PACKETS.value] == 0
                        and self._send_buffer_times is None),
                message=
                ("No SDP packets were received by {}.  If you expected packets"
                 " to be injected, this could indicate an error".format(
                     self._label))))
        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "send_multicast_packets"),
                provenance_data[self._PROVENANCE_ITEMS.N_SENT_PACKETS.value],
                report=provenance_data[
                    self._PROVENANCE_ITEMS.N_SENT_PACKETS.value] == 0,
                message=(
                    "No multicast packets were sent by {}.  If you expected"
                    " packets to be sent this could indicate an error".format(
                        self._label))))
        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "incorrect_keys"),
                provenance_data[self._PROVENANCE_ITEMS.INCORRECT_KEYS.value],
                report=provenance_data[
                    self._PROVENANCE_ITEMS.INCORRECT_KEYS.value] > 0,
                message=(
                    "Keys were received by {} that did not match the key {} and"
                    " mask {}".format(self._label, self._virtual_key,
                                      self._mask))))
        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "incorrect_packets"),
                provenance_data[
                    self._PROVENANCE_ITEMS.INCORRECT_PACKETS.value],
                report=provenance_data[
                    self._PROVENANCE_ITEMS.INCORRECT_PACKETS.value] > 0,
                message=(
                    "SDP Packets were received by {} that were not correct".
                    format(self._label))))
        provenance_items.append(
            ProvenanceDataItem(
                self._add_name(names, "late_packets"),
                provenance_data[self._PROVENANCE_ITEMS.LATE_PACKETS.value],
                report=provenance_data[
                    self._PROVENANCE_ITEMS.LATE_PACKETS.value] > 0,
                message=(
                    "SDP Packets were received by {} that were too late to be"
                    " transmitted in the simulation".format(self._label))))

        return provenance_items

    def __repr__(self):
        return self._label