Source code for data_specification.data_specification_executor_functions

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

import io
import struct
from spinn_utilities.overrides import overrides
from .constants import (
    END_SPEC_EXECUTOR, LEN2, LEN3, MAX_MEM_REGIONS, MAX_REGISTERS)
from .exceptions import (
    DataSpecificationSyntaxError, ExecuteBreakInstruction, NoMoreException,
    NoRegionSelectedException, ParameterOutOfBoundsException,
    RegionInUseException, RegionNotAllocatedException,
    RegionUnfilledException, UnknownTypeLengthException)
from data_specification.spi import AbstractExecutorFunctions
from .memory_region_real import MemoryRegionReal
from .memory_region_reference import MemoryRegionReference
from .memory_region_collection import MemoryRegionCollection

_ONE_BYTE = struct.Struct("<B")
_ONE_SHORT = struct.Struct("<H")
_ONE_WORD = struct.Struct("<I")
_TWO_WORDS = struct.Struct("<II")
_ONE_LONG = struct.Struct("<Q")
_ONE_SIGNED_INT = struct.Struct("<i")


[docs]class DataSpecificationExecutorFunctions(AbstractExecutorFunctions): """ This class includes the function related to each of the commands of the data specification file. .. note:: DSG operations not mentioned in this class will cause an error during DSE if used. """ __slots__ = [ "_spec_reader", "_memory_space", "_space_allocated", "_current_region", "_registers", "_mem_regions", "_referenceable_regions", "_references_to_fill", # Decodings of the current command "__cmd_size", "__dest_reg", "__src1_reg", "__src2_reg", "__data_len"] def __init__(self, spec_reader, memory_space): """ :param ~io.RawIOBase spec_reader: The object to read the specification language file from :param int memory_space: Memory space available for the data to be generated *per region* """ #: Where we are reading the data spec from self._spec_reader = spec_reader #: How much space do we have available? Maximum *PER REGION* self._memory_space = memory_space #: How much space has been allocated self._space_allocated = 0 #: What is the current region that we're writing to self._current_region = None #: The model registers, a list of 16 ints self._registers = [0] * MAX_REGISTERS #: The collection of memory regions that can be written to self._mem_regions = MemoryRegionCollection(MAX_MEM_REGIONS) #: The indices of regions that are marked as referenceable self._referenceable_regions = [] #: The indices of regions that are references of others self._references_to_fill = [] #: Decoded from command: size in words self.__cmd_size = None #: Decoded from command: destination register or None self.__dest_reg = None #: Decoded from command: first source register or None self.__src1_reg = None #: Decoded from command: second source register or None self.__src2_reg = None #: Decoded from command: data length self.__data_len = None @property def mem_regions(self): """ The collection of memory regions that can be written to. :rtype: MemoryRegionCollection """ return self._mem_regions def __unpack_cmd(self, cmd): """ Routine to unpack the command read from the data spec file. The parameters of the command are stored in the class data. :param int cmd: The command read form the data spec file """ self.__cmd_size = (cmd >> 28) & 0x3 use_dest_reg = (cmd >> 18) & 0x1 == 0x1 use_src1_reg = (cmd >> 17) & 0x1 == 0x1 use_src2_reg = (cmd >> 16) & 0x1 == 0x1 self.__dest_reg = (cmd >> 12) & 0xF if use_dest_reg else None self.__src1_reg = (cmd >> 8) & 0xF if use_src1_reg else None self.__src2_reg = (cmd >> 4) & 0xF if use_src2_reg else None self.__data_len = (cmd >> 12) & 0x3 @property def _region(self): if self._current_region is None: return None return self._mem_regions[self._current_region]
[docs] @overrides(AbstractExecutorFunctions.execute_break) def execute_break(self, cmd): """ :raise ExecuteBreakInstruction: Raises the exception to break the execution of the DSE """ if isinstance(self._spec_reader, io.FileIO): name = self._spec_reader.name else: name = "<stream>" raise ExecuteBreakInstruction(self._spec_reader.tell(), name)
[docs] @overrides(AbstractExecutorFunctions.execute_reserve) def execute_reserve(self, cmd): """ :raise ParameterOutOfBoundsException: If the requested size of the region is beyond the available memory space """ self.__unpack_cmd(cmd) region = cmd & 0x1F # cmd[4:0] unfilled = (cmd >> 7) & 0x1 == 0x1 referenceable = (cmd >> 6) & 0x1 == 0x1 if not referenceable and self.__cmd_size != LEN2: raise DataSpecificationSyntaxError( "Command RESERVE requires one word as argument (total 2 " f"words), but the current encoding ({cmd:X}) is specified to " f"be {self.__cmd_size:d} words long") if referenceable and self.__cmd_size != LEN3: raise DataSpecificationSyntaxError( "Command RESERVE requires two words as arguments (total 3 " f"words), but the current encoding ({cmd:X}) is specified to " f"be {self.__cmd_size:d} words long") if not self._mem_regions.is_empty(region): raise RegionInUseException(region) if not referenceable: size = _ONE_WORD.unpack(self._spec_reader.read(4))[0] reference = None else: size, reference = _TWO_WORDS.unpack(self._spec_reader.read(8)) if size & 0x3 != 0: size = (size + 4) - (size & 0x3) if not (0 < size <= self._memory_space): raise ParameterOutOfBoundsException( "region size", size, 1, self._memory_space, "RESERVE") self._mem_regions[region] = MemoryRegionReal( unfilled=unfilled, size=size, reference=reference) if referenceable: self._referenceable_regions.append(region) self._space_allocated += size
[docs] @overrides(AbstractExecutorFunctions.execute_reference) def execute_reference(self, cmd): """ :raise ParameterOutOfBoundsException: If the requested size of the region is beyond the available memory space """ self.__unpack_cmd(cmd) region = cmd & 0x1F # cmd[4:0] if self.__cmd_size != LEN2: raise DataSpecificationSyntaxError( "Command REFERENCE requires one word as argument (total 2 " f"words), but the current encoding ({cmd:X}) is specified to " f"be {self.__cmd_size:d} words long") if not self._mem_regions.is_empty(region): raise RegionInUseException(region) ref = _ONE_WORD.unpack(self._spec_reader.read(4))[0] self._mem_regions[region] = MemoryRegionReference(ref) self._references_to_fill.append(region)
[docs] @overrides(AbstractExecutorFunctions.execute_write) def execute_write(self, cmd): """ :raise NoRegionSelectedException: If there is no memory region selected for the write operation :raise RegionNotAllocatedException: If the selected region has not been allocated memory space :raise NoMoreException: If the selected region has not enough available memory to store the required data :raise UnknownTypeLengthException: If the data type size is not 1, 2, 4, or 8 bytes """ self.__unpack_cmd(cmd) if self.__src2_reg is not None: n_repeats = self._registers[self.__src2_reg] else: n_repeats = cmd & 0xFF # Convert data length to bytes data_len = 1 << self.__data_len if self.__src1_reg is not None: value = self._registers[self.__src1_reg] elif self.__cmd_size == LEN2 and data_len != 8: value = _ONE_WORD.unpack(self._spec_reader.read(4))[0] elif self.__cmd_size == LEN3 and data_len == 8: value = _ONE_LONG.unpack(self._spec_reader.read(8))[0] else: raise DataSpecificationSyntaxError( "Command WRITE requires a value as an argument, but the " f"current encoding ({cmd:X}) is specified to be " f"{self.__cmd_size:d} words long and the data length command " f"argument is specified to be {data_len:d} bytes long") # Perform the writes self._write_to_mem(value, data_len, n_repeats, "WRITE")
[docs] @overrides(AbstractExecutorFunctions.execute_write_array) def execute_write_array(self, cmd): # @UnusedVariable """ :raise NoRegionSelectedException: If there is no memory region selected for the write operation :raise RegionNotAllocatedException: If the selected region has not been allocated memory space :raise NoMoreException: If the selected region has not enough available memory to store the required data """ length = _ONE_WORD.unpack(self._spec_reader.read(4))[0] value_encoded = self._spec_reader.read(4 * length) self._write_bytes_to_mem(value_encoded, "WRITE_ARRAY")
[docs] @overrides(AbstractExecutorFunctions.execute_switch_focus) def execute_switch_focus(self, cmd): """ :raise RegionUnfilledException: If the focus is being switched to a region of memory which has been declared to be kept unfilled """ self.__unpack_cmd(cmd) if self.__src1_reg is not None: region = self._registers[self.__src1_reg] else: region = (cmd >> 8) & 0x1F if self._mem_regions.is_empty(region): raise RegionUnfilledException(region, "SWITCH_FOCUS") self._current_region = region
[docs] @overrides(AbstractExecutorFunctions.execute_mv) def execute_mv(self, cmd): self.__unpack_cmd(cmd) if self.__dest_reg is None: raise DataSpecificationSyntaxError( "Destination register not correctly specified") if self.__src1_reg is not None: self._registers[self.__dest_reg] = self._registers[self.__src1_reg] else: self._registers[self.__dest_reg] = \ _ONE_WORD.unpack(self._spec_reader.read(4))[0]
[docs] @overrides(AbstractExecutorFunctions.execute_set_wr_ptr) def execute_set_wr_ptr(self, cmd): """ :raise NoRegionSelectedException: If there is no memory region selected for the set-ptr operation """ self.__unpack_cmd(cmd) if self.__src1_reg is not None: # the data is a register future_address = self._registers[self.__src1_reg] else: # the data is a raw address future_address = _ONE_WORD.unpack(self._spec_reader.read(4))[0] # check that the address is relative or absolute if cmd & 0x1 == 1: # relative to its current write pointer if self._region is None: raise NoRegionSelectedException( "the write pointer for this region is currently undefined") # relative to the base address of the region (obsolete) # noinspection PyTypeChecker address = self._region.write_pointer + future_address else: address = future_address # update write pointer self._region.write_pointer = address
[docs] @overrides(AbstractExecutorFunctions.execute_end_spec) def execute_end_spec(self, cmd): # @UnusedVariable value = _ONE_SIGNED_INT.unpack(self._spec_reader.read(4))[0] if value != -1: raise DataSpecificationSyntaxError( "Command END_SPEC requires an argument equal to -1. The " f"current argument value is {value}") return END_SPEC_EXECUTOR
def _write_to_mem(self, value, n_bytes, repeat, command): """ Write the specified value to data memory the specified amount of times. The selected memory region needs to be already allocated. :param int value: the value to be written in the data memory region :param int n_bytes: number of bytes that represent the value :param int repeat: the number of times the value is to be repeated :param str command: the command which is being executed :raise NoRegionSelectedException: If there is no memory region selected for the write operation :raise RegionNotAllocatedException: If the selected region has not been allocated memory space :raise NoMoreException: f the selected region has not enough available memory to store the required data :raise UnknownTypeLengthException: If the data type size is not 1, 2, 4, or 8 bytes """ if n_bytes == 1: encoder = _ONE_BYTE elif n_bytes == 2: encoder = _ONE_SHORT elif n_bytes == 4: encoder = _ONE_WORD elif n_bytes == 8: encoder = _ONE_LONG else: raise UnknownTypeLengthException(n_bytes, command) self._write_bytes_to_mem(encoder.pack(value) * repeat, command) def _write_bytes_to_mem(self, data, command): """ Write raw bytes to data memory. The selected memory region needs to be already allocated. :param data: the value to be written in the data memory region :type data: bytes or bytearray :param str command: the command which is being executed :raise NoRegionSelectedException: If there is no memory region selected for the write operation :raise RegionNotAllocatedException: If the selected region has not been allocated memory space :raise NoMoreException: If the selected region has not enough available memory to store the required data """ # A region must've been selected if self._current_region is None: raise NoRegionSelectedException(command) # It must be a real region if self._region is None: raise RegionNotAllocatedException(self._current_region, command) self.__write_blob(data) def __write_blob(self, data): """ Does the actual write to the region, enforcing that writes cannot go outside the region. :param data: The data to write :type data: bytes or bytearray :raise NoMoreException: if the selected region has not enough space to store the data """ # It must have enough space region = self._region if region.remaining_space < len(data): raise NoMoreException( region.remaining_space, len(data), self._current_region) # We can safely write write_ptr = region.write_pointer region.region_data[write_ptr:write_ptr + len(data)] = data region.increment_write_pointer(len(data)) @property def referenceable_regions(self): """ The regions that can be referenced by others. :rtype: list(int) """ return self._referenceable_regions @property def references_to_fill(self): """ The references that need to be filled. :rtype: list(int) """ return self._references_to_fill