# 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.
from enum import Enum, IntEnum
import io
import logging
import struct
import numpy
from spinn_utilities.log import FormatAdapter
from spinn_machine import sdram
from .constants import (
MAX_CONSTRUCTORS, MAX_MEM_REGIONS, MAX_RANDOM_DISTS, MAX_REGISTERS,
MAX_RNGS, MAX_STRUCT_ELEMENTS, MAX_STRUCT_SLOTS, LEN1, LEN2, LEN3, LEN4,
NO_REGS, DEST_AND_SRC1, DEST_ONLY, SRC1_ONLY, SRC1_AND_SRC2,
BYTES_PER_WORD)
from .exceptions import (
DataUndefinedWriterException, DuplicateParameterException,
FunctionInUseException, InvalidCommandException,
InvalidOperationException, InvalidSizeException, NotAllocatedException,
NoRegionSelectedException, ParameterOutOfBoundsException,
RandomNumberDistributionInUseException, RegionInUseException,
RegionUnfilledException, RNGInUseException, StructureInUseException,
TypeMismatchException, UnknownConditionException, UnknownTypeException,
UnknownTypeLengthException, WrongParameterNumberException)
from .enums import (
DataType, RandomNumberGenerator, Commands, Condition, LogicOperation,
ArithmeticOperation)
logger = FormatAdapter(logging.getLogger(__name__))
_ONE_SBYTE = struct.Struct("<b")
_ONE_WORD = struct.Struct("<I")
_ONE_SIGNED_INT = struct.Struct("<i")
_TWO_WORDS = struct.Struct("<II")
def _bounds(cmd, name, value, low, high):
"""
A simple bounds checker.
"""
if value < low or value >= high:
raise ParameterOutOfBoundsException(
name, value, low, high - 1, cmd.name)
def _typebounds(cmd, name, value, valuetype):
"""
A simple bounds checker that uses the bounds from a type descriptor.
"""
if valuetype not in DataType:
raise UnknownTypeException(valuetype, cmd.name)
if value < valuetype.min or value > valuetype.max:
raise ParameterOutOfBoundsException(
name, value, valuetype.min, valuetype.max, cmd.name)
class _Field(IntEnum):
"""
Various shifts for fields used with :py:func:`_binencode`.
"""
LENGTH = 28
COMMAND = 20
SIGNED = 19
USAGE = 16
DESTINATION = 12
FUNCTION = 11
SOURCE_1 = 8
EMPTY = 7
REFERENCEABLE = 6
SOURCE_2 = 4
IMMEDIATE = 0
def _binencode(command, arguments):
"""
Encodes commands as binary words.
:param Commands command: The code of the command being encoded.
:param dict(_Field,int) arguments: How to parameterise the command.
:return: the encoded command
:rtype: bytearray
"""
cmd_word = command.value << _Field.COMMAND
for shift in arguments:
if shift < 0 or shift > 31:
raise KeyError()
val = arguments[shift]
if isinstance(val, Enum):
val = val.value
else:
val = int(val)
cmd_word |= val << shift
return bytearray(_ONE_WORD.pack(cmd_word))
class _MemSlot(object):
"""
Metadata for a memory region.
"""
__slots__ = ["label", "size", "empty"]
def __init__(self, label, size, empty):
#: Optional label for the region; str or None
self.label = label
# round size to a number of words
if size % BYTES_PER_WORD != 0:
size = size + (BYTES_PER_WORD - (size % BYTES_PER_WORD))
#: The size of the region; int
self.size = size
#: Whether the region is to be left empty; bool
self.empty = empty
[docs]class DataSpecificationGenerator(object):
"""
Used to generate a SpiNNaker data specification language file that
can be executed to produce a memory image.
"""
# pylint: disable=too-many-arguments
__slots__ = [
"_spec_writer",
"_report_writer",
"_txt_indent",
"_instruction_counter",
"_mem_slots",
"_function_slots",
"_struct_slots",
"_rng",
"_random_distribution",
"_conditionals",
"_current_region",
"_ongoing_function_definition",
"_ongoing_loop"
]
def __init__(self, spec_writer, report_writer=None):
"""
:param ~io.RawIOBase spec_writer:
The object to write the specification to
:param report_writer:
Determines if a text version of the specification is to be
written and, if so, where. No report is written if this is `None`.
:type report_writer: ~io.TextIOBase or None
:raise IOError: If a write to external storage fails
"""
if not isinstance(spec_writer, io.RawIOBase):
raise TypeError("spec_writer must be a RawIOBase")
#: The object to write the specification to
self._spec_writer = spec_writer
if report_writer is not None and not isinstance(
report_writer, io.TextIOBase):
raise TypeError("report_writer must be a TextIOBase or None")
#: the writer for the human readable report
self._report_writer = report_writer
#: current indentation for the report writer
self._txt_indent = 0
#: instruction counter, for the report writer only
self._instruction_counter = 0
#: the memory regions; list(
self._mem_slots = [None] * MAX_MEM_REGIONS
#: the functions
self._function_slots = [None] * MAX_CONSTRUCTORS
#: the structure definitions
self._struct_slots = [None] * MAX_STRUCT_SLOTS
#: the random number generators
self._rng = [None] * MAX_RNGS
#: the random distributions
self._random_distribution = [None] * MAX_RANDOM_DISTS
#: stack of _conditionals, used for 'else' tracking
self._conditionals = []
#: the current DSG region we're writing to
self._current_region = None
#: whether there is a currently-being-made function definition
self._ongoing_function_definition = False
#: whether there is a currently executing loop
self._ongoing_loop = False
[docs] def define_break(self):
"""
Insert command to stop execution with an exception (for debugging).
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
"""
cmd_word = _binencode(Commands.BREAK, {
_Field.LENGTH: LEN1})
cmd_string = Commands.BREAK.name
self._write_command_to_files(cmd_word, cmd_string)
[docs] def no_operation(self):
"""
Insert command to execute nothing.
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
"""
cmd_word = _binencode(Commands.NOP, {
_Field.LENGTH: LEN1})
cmd_string = Commands.NOP.name
self._write_command_to_files(cmd_word, cmd_string)
return
[docs] def reserve_memory_region(
self, region, size, label=None, empty=False, reference=None):
"""
Insert command to reserve a memory region.
:param int region: The number of the region to reserve, from 0 to 15
:param int size: The size to reserve for the region, in bytes
:param label: An optional label for the region
:type label: str or None
:param bool empty: Specifies if the region will be left empty
:param reference: A globally unique reference for this region
:type reference: int or None
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise RegionInUseException: If the ``region`` was already reserved
:raise ParameterOutOfBoundsException:
If the ``region`` requested was out of the allowed range, or the
``size`` was too big to fit in SDRAM
"""
_bounds(Commands.RESERVE, "memory region identifier",
region, 0, MAX_MEM_REGIONS)
_bounds(Commands.RESERVE, "memory size",
size, 1, sdram.SDRAM.max_sdram_found)
if self._mem_slots[region] is not None:
raise RegionInUseException(region, self._mem_slots[region].label)
self._mem_slots[region] = _MemSlot(label, size, empty)
cmd_word = _binencode(Commands.RESERVE, {
_Field.LENGTH: LEN2 if reference is None else LEN3,
_Field.USAGE: NO_REGS,
_Field.EMPTY: bool(empty),
_Field.REFERENCEABLE: reference is not None,
_Field.IMMEDIATE: region})
encoded_size = _ONE_WORD.pack(size)
encoded_ref = b""
if reference is not None:
encoded_ref = _ONE_WORD.pack(reference)
cmd_string = Commands.RESERVE.name
cmd_string += f" memRegion={region:d} size={size:d}"
if label is not None:
cmd_string += f" label='{label}'"
if empty:
cmd_string += " UNFILLED"
if reference is not None:
cmd_string += f" REF {reference:d}"
self._write_command_to_files(
cmd_word + encoded_size + encoded_ref, cmd_string)
[docs] def reference_memory_region(self, region, ref, label=None):
"""
Insert command to reference another memory region.
:param int region: The number of the region to reserve, from 0 to 15
:param int ref: The identifier of the region to reference
:param label: An optional label for the region
:type label: str or None
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise RegionInUseException: If the ``region`` was already reserved
:raise ParameterOutOfBoundsException:
If the ``region`` requested was out of the allowed range, or the
``size`` was too big to fit in SDRAM
"""
_bounds(Commands.REFERENCE, "memory region identifier",
region, 0, MAX_MEM_REGIONS)
if self._mem_slots[region] is not None:
raise RegionInUseException(region, self._mem_slots[region].label)
self._mem_slots[region] = _MemSlot(label, 0, True)
cmd_word = _binencode(Commands.REFERENCE, {
_Field.LENGTH: LEN2,
_Field.IMMEDIATE: region})
encoded_args = _ONE_WORD.pack(ref)
cmd_string = Commands.REFERENCE.name
cmd_string += f" memRegion={region:d} ref={ref:d}"
if label is not None:
cmd_string += f" label='{label}'"
self._write_command_to_files(cmd_word + encoded_args, cmd_string)
[docs] def free_memory_region(self, region):
"""
Insert command to free a previously reserved memory region.
:param int region: The number of the region to free, from 0 to 15
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise NotAllocatedException: If the region was not reserved
:raise ParameterOutOfBoundsException:
If the ``region`` requested was out of the allowed range
"""
_bounds(Commands.FREE, "memory region identifier",
region, 0, MAX_MEM_REGIONS)
if self._mem_slots[region] is None:
raise NotAllocatedException("region", region, Commands.FREE.name)
label = self._mem_slots[region].label
self._mem_slots[region] = None
cmd_word = _binencode(Commands.FREE, {
_Field.LENGTH: LEN1,
_Field.USAGE: NO_REGS,
_Field.IMMEDIATE: region})
cmd_string = Commands.FREE.name
cmd_string += f" memRegion={region:d}"
if label is not None:
cmd_string += f" ({label})"
self._write_command_to_files(cmd_word, cmd_string)
[docs] def declare_random_number_generator(self, rng_id, rng_type, seed):
"""
Insert command to declare a random number generator.
:param int rng_id: The ID of the random number generator
:param RandomNumberGenerator rng_type:
The type of the random number generator
:param int seed: The seed of the random number generator >= 0
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise UnknownTypeException:
If the ``rng_type`` is not one of the allowed values
:raise ParameterOutOfBoundsException:
* If the ``seed`` is too big or too small
* If the ``rng_id`` is not in the allowed range
:raise RNGInUseException:
If the random number generator with the given ID has already been
defined
"""
_bounds(Commands.DECLARE_RNG, "random number generator ID",
rng_id, 0, MAX_RNGS)
if rng_type not in RandomNumberGenerator:
raise UnknownTypeException(
rng_type.value, Commands.DECLARE_RNG.name)
if self._rng[rng_id] is not None:
raise RNGInUseException(rng_id)
_typebounds(Commands.DECLARE_RNG, "seed", seed, DataType.UINT32)
self._rng[rng_id] = [rng_type, seed]
cmd_word = _binencode(Commands.DECLARE_RNG, {
_Field.LENGTH: LEN2,
_Field.DESTINATION: rng_id,
_Field.SOURCE_1: rng_type})
encoded_seed = _ONE_WORD.pack(seed)
cmd_string = Commands.DECLARE_RNG.name
cmd_string += (
f" ID={rng_id:d}, source={rng_type.value:d}, seed={seed:d}")
self._write_command_to_files(cmd_word + encoded_seed, cmd_string)
[docs] def call_random_distribution(self, distribution_id, register_id):
"""
Insert command to get the next random number from a random
distribution, placing the result in a register to be used in a
future call.
:param int distribution_id:
The ID of the random distribution to call between 0 and 63
:param int register_id:
The ID of the register to store the result in between 0 and 15
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise NotAllocatedException:
If the random distribution ID was not previously declared
:raise ParameterOutOfBoundsException:
If the ``distribution_id`` or ``register_id`` specified was out
of range
"""
_bounds(Commands.GET_RANDOM_NUMBER, "register_id",
register_id, 0, MAX_REGISTERS)
_bounds(Commands.GET_RANDOM_NUMBER, "distribution_id",
distribution_id, 0, MAX_RANDOM_DISTS)
if self._random_distribution[distribution_id] is None:
raise NotAllocatedException(
"random number distribution", distribution_id,
Commands.GET_RANDOM_NUMBER.name)
cmd_word = _binencode(Commands.GET_RANDOM_NUMBER, {
_Field.LENGTH: LEN1,
_Field.USAGE: 0x4,
_Field.DESTINATION: register_id,
_Field.IMMEDIATE: distribution_id})
cmd_string = Commands.GET_RANDOM_NUMBER.name
cmd_string += (
f" distribution={distribution_id:d} dest=reg[{register_id:d}]")
self._write_command_to_files(cmd_word, cmd_string)
[docs] def define_structure(self, structure_id, parameters):
"""
Insert commands to define a data structure.
:param int structure_id:
the ID of the structure to create, between 0 and 15
:param parameters: A list of between 1 and 255 tuples of
`(label, data_type, value)` where:
* `label` is the label of the element (for debugging)
* `data_type` is the data type of the element
* `value` is the value of the element, or `None` if no value is to
be assigned
:type parameters: list(tuple(str, DataType, float))
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise NoMoreException:
If there are no more spaces for new structures
:raise ParameterOutOfBoundsException:
* If there are an incorrect number of parameters
* If the size of one of the tuples is incorrect
* If one of the values to be assigned has an integer ``data_type``
but has a fractional part
* If one of the values to be assigned would overflow its
``data_type``
:raise UnknownTypeException:
If one of the data types in the structure is unknown
"""
# start of struct
_bounds(Commands.START_STRUCT, "structure ID",
structure_id, 0, MAX_STRUCT_SLOTS)
_bounds(Commands.START_STRUCT, "structure elements",
len(parameters), 1, MAX_STRUCT_ELEMENTS)
if self._struct_slots[structure_id] is not None:
raise StructureInUseException(structure_id)
self._struct_slots[structure_id] = parameters
cmd_word = _binencode(Commands.START_STRUCT, {
_Field.LENGTH: LEN1,
_Field.IMMEDIATE: structure_id})
cmd_string = Commands.START_STRUCT.name
cmd_string += f" ID={structure_id:d}"
self._write_command_to_files(cmd_word, cmd_string)
# elements of the struct
for elem_index, i in enumerate(parameters):
label, data_type, value = i
if data_type not in DataType:
raise UnknownTypeException(
data_type.value, Commands.WRITE_PARAM.name)
cmd_string = Commands.WRITE_PARAM.name
cmd_string += (
f" element_id={elem_index:d}, element_type={data_type.name}")
if value is not None:
_typebounds(Commands.WRITE_PARAM, "value", value, data_type)
if data_type.size <= 4:
cmd_word = _binencode(Commands.STRUCT_ELEM, {
_Field.LENGTH: LEN2,
_Field.IMMEDIATE: data_type})
elif data_type.size == 8:
cmd_word = _binencode(Commands.STRUCT_ELEM, {
_Field.LENGTH: LEN3,
_Field.IMMEDIATE: data_type})
else:
raise InvalidSizeException(
data_type.name, data_type.size,
Commands.STRUCT_ELEM.name)
value_bytes = data_type.encode(value)
if len(label) == 0:
cmd_string += f", value={value:d}"
else:
cmd_string += f", value={value:f}, label={label}"
self._write_command_to_files(
cmd_word + value_bytes, cmd_string)
else:
cmd_word = _binencode(Commands.STRUCT_ELEM, {
_Field.LENGTH: LEN1,
_Field.IMMEDIATE: data_type})
if len(label):
cmd_string += f", label={label}"
self._write_command_to_files(cmd_word, cmd_string)
# end of struct
cmd_word = _binencode(Commands.END_STRUCT, {
_Field.LENGTH: LEN1})
cmd_string = Commands.END_STRUCT.name
cmd_string += f" ID={structure_id:d}"
self._write_command_to_files(cmd_word, cmd_string)
[docs] def get_structure_value(
self, destination_id, structure_id, parameter_index,
parameter_index_is_register=False):
"""
Insert command to get a value from a structure.
The value is copied in a register.
:param int destination_id: The ID of the destination register
:param int structure_id: The ID of the source structure
:param int parameter_index: The ID of the parameter/element to copy
:param bool parameter_index_is_register:
True if ``parameter_index`` is a register ID containing the ID of
the parameter/element to copy
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``structure_id`` is not in the allowed range
* If ``parameter_index`` is larger than the number of parameters
declared in the original structure
* If ``destination_id`` is not the ID of a valid register
* If ``parameter_index_is_register`` is True and
``parameter_index`` is not a valid register ID
:raise NotAllocatedException:
If the structure requested has not been declared
"""
_bounds(Commands.READ_PARAM, "structure_id",
structure_id, 0, MAX_STRUCT_SLOTS)
_bounds(Commands.READ_PARAM, "destination_id",
destination_id, 0, MAX_REGISTERS)
if self._struct_slots[structure_id] is None:
raise NotAllocatedException(
"structure", structure_id, Commands.READ_PARAM.name)
cmd_string = Commands.READ_PARAM.name
cmd_string += f" structure_id={structure_id:d}, "
if parameter_index_is_register is True:
_bounds(Commands.READ_PARAM, "parameter_index",
parameter_index, 0, MAX_REGISTERS)
cmd_word = _binencode(Commands.READ_PARAM, {
_Field.LENGTH: LEN1,
_Field.USAGE: DEST_AND_SRC1,
_Field.DESTINATION: destination_id,
_Field.SOURCE_1: parameter_index,
_Field.IMMEDIATE: structure_id})
cmd_string += (
f"element_id_from_register={parameter_index:d}, "
f"destination_register={destination_id:d}")
else:
_bounds(Commands.READ_PARAM, "parameter_index",
parameter_index, 0, MAX_STRUCT_ELEMENTS)
if len(self._struct_slots[structure_id]) <= parameter_index:
raise NotAllocatedException(
f"structure {structure_id:d} parameter",
parameter_index, Commands.READ_PARAM.name)
cmd_word = _binencode(Commands.READ_PARAM, {
_Field.LENGTH: LEN1,
_Field.USAGE: DEST_ONLY,
_Field.DESTINATION: destination_id,
_Field.SOURCE_2: parameter_index,
_Field.IMMEDIATE: structure_id})
cmd_string += (
f"element_id={parameter_index:d}, "
f"destination_register={destination_id:d}")
self._write_command_to_files(cmd_word, cmd_string)
[docs] def set_structure_value(self, structure_id, parameter_index, value,
data_type, value_is_register=False):
"""
Insert command to set a value in a structure.
:param int structure_id:
* If called outside of a function, the ID of the structure,
between 0 and 15
* If called within a function, the ID of the structure
argument, between 0 and 4
:param int parameter_index: The index of the value to assign in the
structure, between 0 and the number of parameters declared in the
structure
:param float value:
* If ``value_is_register`` is False, the value to assign at the
selected position as a float or int
* If ``value_is_register`` is True, the ID of the register
containing the value to assign to the position, between 0 and 15
:param DataType data_type: type of the data to be stored in the
structure. If parameter ``value_is_register`` is set to true,
this variable is disregarded
:param bool value_is_register:
Identifies if value identifies a register
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``structure_id`` is not in the allowed range
* If ``parameter_index`` is larger than the number of parameters
declared in the original structure
* If ``value_is_register`` is False, and the data type of the
structure value is an integer, and ``value`` has a fractional
part
* If ``value_is_register`` is False, and ``value`` would overflow
the position in the structure
* If ``value_is_register`` is True, and ``value`` is not a valid
register ID
:raise NotAllocatedException:
If the structure requested has not been declared
:raise UnknownTypeException: If the data type is unknown
"""
_bounds(Commands.WRITE_PARAM, "structure_id",
structure_id, 0, MAX_STRUCT_SLOTS)
_bounds(Commands.WRITE_PARAM, "parameter_index",
parameter_index, 0, MAX_STRUCT_ELEMENTS)
if self._struct_slots[structure_id] is None:
raise NotAllocatedException(
"structure", structure_id, Commands.WRITE_PARAM.name)
if len(self._struct_slots[structure_id]) <= parameter_index:
raise NotAllocatedException(
f"structure {structure_id} parameter",
parameter_index, Commands.WRITE_PARAM.name)
if self._struct_slots[
structure_id][parameter_index][1] is not data_type:
raise TypeMismatchException(Commands.WRITE_PARAM.name)
cmd_string = Commands.WRITE_PARAM.name
cmd_string += (
f" structure_id={structure_id:d}, "
f"element_id={parameter_index:d}, ")
if value_is_register:
_bounds(Commands.WRITE_PARAM, "value", value, 0, MAX_REGISTERS)
cmd_word = _binencode(Commands.WRITE_PARAM, {
_Field.LENGTH: LEN1,
_Field.USAGE: SRC1_ONLY,
_Field.DESTINATION: structure_id,
_Field.SOURCE_1: value,
_Field.IMMEDIATE: parameter_index})
cmd_string += f"value=reg[{value:d}]"
self._write_command_to_files(cmd_word, cmd_string)
else:
_typebounds(Commands.WRITE_PARAM, "value", value, data_type)
if data_type.size > 4 and data_type.size != 8:
raise InvalidSizeException(
data_type.name, data_type.size, Commands.WRITE_PARAM.name)
cmd_word = _binencode(Commands.WRITE_PARAM, {
_Field.LENGTH: LEN2 if data_type.size <= 4 else LEN3,
_Field.USAGE: NO_REGS,
_Field.DESTINATION: structure_id,
_Field.IMMEDIATE: parameter_index})
value_encoded = data_type.encode(value)
cmd_string += f"value={value:d}"
self._write_command_to_files(cmd_word + value_encoded, cmd_string)
[docs] def write_structure(
self, structure_id, repeats=1, repeats_is_register=False):
"""
Insert command to write a structure to the current write pointer,
causing the current write pointer to move on by the number of bytes
needed to represent the structure.
:param int structure_id:
* If called within a function, the ID of the structure to write,
between 0 and 15
* If called outside of a function, the ID of the structure
argument, between 0 and 5
:param int repeats:
* If ``repeats_is_register`` is True, the ID of the register
containing the number of repeats, between 0 and 15
* If ``repeats_is_register`` is False, the number of repeats to
write, between 0 and 255
:param bool repeats_is_register:
Whether ``repeats`` identifies a register
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``structure_id`` is not a valid structure ID
* If ``repeats_is_register`` is False and ``repeats`` is not in
range
* If ``repeats_is_register`` is True and ``repeats`` is not a
valid register ID
:raise NoRegionSelectedException: If no region has been selected
:raise RegionExhaustedException:
If the selected region has no more space
"""
_bounds(Commands.WRITE_STRUCT, "structure_id",
structure_id, 0, MAX_STRUCT_SLOTS)
if self._struct_slots[structure_id] is None:
raise NotAllocatedException(
"structure", structure_id, Commands.WRITE_STRUCT.name)
cmd_string = Commands.WRITE_STRUCT.name
cmd_string += f" structure_id={structure_id:d}, "
if repeats_is_register:
_bounds(Commands.WRITE_STRUCT, "repeats",
repeats, 0, MAX_REGISTERS)
cmd_word = _binencode(Commands.WRITE_STRUCT, {
_Field.LENGTH: LEN1,
_Field.USAGE: SRC1_ONLY,
_Field.SOURCE_1: repeats,
_Field.IMMEDIATE: structure_id})
cmd_string += f"repeats=reg[{repeats:d}]"
else:
_bounds(Commands.WRITE_STRUCT, "repeats",
repeats, 0, MAX_STRUCT_SLOTS)
cmd_word = _binencode(Commands.WRITE_STRUCT, {
_Field.LENGTH: LEN1,
_Field.SOURCE_1: repeats,
_Field.IMMEDIATE: structure_id})
cmd_string += f"repeats={repeats:d}"
self._write_command_to_files(cmd_word, cmd_string)
[docs] def start_function(self, function_id, argument_by_value):
"""
Insert command to start a function definition, with up to 5 arguments,
which are the IDs of structures to be used within the function, each
of which can be passed by reference or by value. In the commands
following this command up to the :meth:`end_function` command,
structures can only be referenced using the numbers 1 to 5 which
address the arguments, rather than the original structure IDs.
:param int function_id: The ID of the function currently defined.
:param list(bool) argument_by_value: A list of up to 5 booleans
indicating if the structure to be passed as an argument is to be
passed by reference (i.e., changes made within the function are
persisted) or by value (i.e., changes made within the function
are lost when the function exits. The number of arguments is
determined by the length of this list.
:raise ParameterOutOfBoundsException:
If there are too many items in the list of arguments
:raise InvalidCommandException:
If there is already a function being defined at this point
:raise FunctionInUseException: If the function is already defined
"""
if self._ongoing_function_definition:
raise InvalidCommandException(Commands.START_CONSTRUCTOR.name)
_bounds(Commands.START_CONSTRUCTOR, "number of arguments",
len(argument_by_value), 0, 6)
_bounds(Commands.START_CONSTRUCTOR, "function_id",
function_id, 0, MAX_CONSTRUCTORS)
if self._function_slots[function_id] is not None:
raise FunctionInUseException(function_id)
self._function_slots[function_id] = argument_by_value
cmd_string = Commands.START_CONSTRUCTOR.name
cmd_string += (
f" ID={function_id:d} number_of_args={len(argument_by_value):d}")
self._ongoing_function_definition = True
read_only_flags = 0
for i, abv in enumerate(argument_by_value):
cmd_string += f" arg[{i + 1:d}]="
if abv:
read_only_flags |= 1 << i
cmd_string += "read-only"
else:
cmd_string += "read-write"
cmd_word = _binencode(Commands.START_CONSTRUCTOR, {
_Field.LENGTH: LEN1,
_Field.FUNCTION: function_id,
_Field.SOURCE_1: len(argument_by_value),
_Field.IMMEDIATE: read_only_flags})
self._write_command_to_files(cmd_word, cmd_string, indent=True)
[docs] def end_function(self):
"""
Insert command to mark the end of a function definition.
:raise InvalidCommandException:
If there is no function being defined at this point
"""
if not self._ongoing_function_definition:
raise InvalidCommandException(Commands.END_CONSTRUCTOR.name)
self._ongoing_function_definition = False
cmd_word = _binencode(Commands.END_CONSTRUCTOR, {
_Field.LENGTH: LEN1})
cmd_string = Commands.END_CONSTRUCTOR.name
self._write_command_to_files(cmd_word, cmd_string, outdent=True)
[docs] def call_function(self, function_id, structure_ids):
"""
Insert command to call a function.
:param int function_id:
The ID of a previously defined function, between 0 and 31
:param structure_ids: A list of structure IDs that will be passed into
the function, each between 0 and 15
:type structure_ids: list(int)
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If the function ID is not valid
* If any of the structure IDs are not valid
:raise NotAllocatedException:
* If a function has not been defined with the given ID
* If no structure has been defined with one of the IDs in
``structure_ids``
:raise WrongParameterNumberException:
If a function is called with a wrong number of parameters
:raise DuplicateParameterException:
If a function is called with duplicate parameters
"""
_bounds(Commands.CONSTRUCT, "function",
function_id, 0, MAX_CONSTRUCTORS)
if self._function_slots[function_id] is None:
raise NotAllocatedException(
"function", function_id, Commands.CONSTRUCT.name)
if len(structure_ids) != len(self._function_slots[function_id]):
raise WrongParameterNumberException(
function_id, len(self._function_slots[function_id]),
structure_ids)
if len(structure_ids) != len(set(structure_ids)):
raise DuplicateParameterException(
f"{Commands.CONSTRUCT.name} {function_id}",
structure_ids)
cmd_string = Commands.CONSTRUCT.name
cmd_string += f" function_id={function_id:d}"
param_word_encoded = bytearray()
cmd_word_length = LEN1
if structure_ids:
param_word = 0
for i, struct_id in enumerate(structure_ids):
_bounds(Commands.CONSTRUCT,
f"structure argument {i}",
structure_ids[i], 0, MAX_STRUCT_SLOTS)
if self._struct_slots[struct_id] is None:
raise NotAllocatedException(
f"structure argument {i}",
struct_id, Commands.CONSTRUCT.name)
param_word |= struct_id << (6 * i)
cmd_string += f" arg[{i:d}]=struct[{struct_id:d}]"
cmd_word_length = LEN2
param_word_encoded += _ONE_WORD.pack(param_word)
cmd_word = _binencode(Commands.CONSTRUCT, {
_Field.LENGTH: cmd_word_length,
_Field.SOURCE_1: function_id})
self._write_command_to_files(cmd_word + param_word_encoded, cmd_string)
[docs] def read_value(self, dest_id, data_type):
"""
Insert command to read a value from the current write pointer, causing
the write pointer to move by the number of bytes read. The data is
stored in a register passed as argument.
:param int dest_id: The ID of the destination register.
:param DataType data_type: The type of the data to be read.
:raise ParameterOutOfBoundsException:
If ``dest_id`` is out of range for a register ID
"""
if data_type not in DataType:
raise UnknownTypeException(
data_type.value, Commands.READ.name)
_bounds(Commands.READ, "register", dest_id, 0, MAX_REGISTERS)
cmd_word = _binencode(Commands.READ, {
_Field.LENGTH: LEN1,
_Field.USAGE: DEST_ONLY,
_Field.DESTINATION: dest_id,
_Field.IMMEDIATE: data_type.size})
cmd_string = Commands.READ.name
cmd_string += f" {data_type.size:d} bytes in register {dest_id:d}"
self._write_command_to_files(cmd_word, cmd_string)
[docs] def create_cmd(self, data, data_type=DataType.UINT32):
"""
Creates command to write a value to the current write pointer, causing
the write pointer to move on by the number of bytes required to
represent the data type. The data is passed as a parameter to this
function.
.. note::
This does not actually insert the ``WRITE`` command in the spec;
that is done by :py:meth:`write_cmd`.
:param data: the data to write.
:type data: int or float
:param DataType data_type: the type to convert ``data`` to
:return: ``cmd_word_list`` (binary data to be added to the binary data
specification file), and ``cmd_string`` (string describing the
command to be added to the report for the data specification file)
:rtype: tuple(bytearray, str)
:raise ParameterOutOfBoundsException:
* If ``data_type`` is an integer type, and ``data`` has a
fractional part
* If ``data`` would overflow the data type
:raise UnknownTypeException: If the data type is not known
:raise InvalidSizeException: If the data size is invalid
"""
_typebounds(Commands.WRITE, "data", data, data_type)
data_size = data_type.size
if data_size == 1:
cmd_data_len = LEN2
data_len = 0
elif data_size == 2:
cmd_data_len = LEN2
data_len = 1
elif data_size == 4:
cmd_data_len = LEN2
data_len = 2
elif data_size == 8:
cmd_data_len = LEN3
data_len = 3
else:
raise InvalidSizeException(
data_type.name, data_size, Commands.WRITE.name)
cmd_string = None
if self._report_writer is not None:
cmd_string = Commands.WRITE.name
cmd_string += f" data={data}"
repeat_reg_usage = NO_REGS
cmd_word = _binencode(Commands.WRITE, {
_Field.LENGTH: cmd_data_len,
_Field.USAGE: repeat_reg_usage,
_Field.DESTINATION: data_len,
_Field.IMMEDIATE: 1})
# 1 is based on parameters = 0, repeats = 1 and parameters |= repeats
cmd_word_list = cmd_word + data_type.encode(data)
if self._report_writer is not None:
cmd_string += f", dataType={data_type.name}"
return (cmd_word_list, cmd_string)
[docs] def write_value(self, data, data_type=DataType.UINT32):
"""
Insert command to write a value (once) to the current write pointer,
causing the write pointer to move on by the number of bytes required
to represent the data type. The data is passed as a parameter to this
function
.. note::
This method used to have two extra parameters ``repeats`` and
``repeats_is_register``. They have been removed here. If you need
them, use :meth:`write_repeated_value`
:param data: the data to write as a float.
:type data: int or float
:param DataType data_type: the type to convert ``data`` to
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``data_type`` is an integer type, and ``data`` has a
fractional part
* If ``data`` would overflow the data type
:raise UnknownTypeException: If the data type is not known
:raise InvalidSizeException: If the data size is invalid
:raise NoRegionSelectedException: If no region has been selected
"""
if self._current_region is None:
raise NoRegionSelectedException(Commands.WRITE.name)
cmd_word_list, cmd_string = self.create_cmd(data, data_type)
self._write_command_to_files(cmd_word_list, cmd_string)
[docs] def write_cmd(self, cmd_word_list, cmd_string):
"""
Applies write commands created previously created (and cached).
.. note::
See :meth:`create_cmd` for how to create the arguments to
this method.
:param bytearray cmd_word_list: list of binary words to be added to
the binary data specification file
:param str cmd_string: string describing the command to be added to
the report for the data specification file
:raise IOError: If a write to external storage fails
:raise NoRegionSelectedException: If no region has been selected
"""
if self._current_region is None:
raise NoRegionSelectedException(Commands.WRITE.name)
self._write_command_to_files(cmd_word_list, cmd_string)
[docs] def write_repeated_value(
self, data, repeats=1, repeats_is_register=False,
data_type=DataType.UINT32):
"""
Insert command to write a value one or more times to the current write
pointer, causing the write pointer to move on by the number of bytes
required to represent the data type. The data is passed as a parameter
to this function
:param data: the data to write as a float.
:type data: float or int
:param int repeats:
* If ``repeats_is_register`` is False, this parameter identifies
the number of times to repeat the data, between 1 and 255
(default 1)
* If ``repeats_is_register`` is True, this parameter identifies
the register that contains the number of repeats.
:param bool repeats_is_register:
Indicates if the parameter ``repeats`` identifies the register
containing the number of repeats of the value to write
:param DataType data_type: the type to convert data to
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``repeats_is_register`` is False, and ``repeats`` is out of
range
* If ``repeats_is_register`` is True, and ``repeats`` is not a
valid register ID
* If ``data_type`` is an integer type, and ``data`` has a
fractional part
* If ``data`` would overflow the data type
:raise UnknownTypeException: If the data type is not known
:raise InvalidSizeException: If the data size is invalid
:raise NoRegionSelectedException: If no region has been selected
"""
if self._current_region is None:
raise NoRegionSelectedException(Commands.WRITE.name)
_typebounds(Commands.WRITE, "data", data, data_type)
data_size = data_type.size
if data_size == 1:
cmd_data_len = LEN2
data_len = 0
elif data_size == 2:
cmd_data_len = LEN2
data_len = 1
elif data_size == 4:
cmd_data_len = LEN2
data_len = 2
elif data_size == 8:
cmd_data_len = LEN3
data_len = 3
else:
raise InvalidSizeException(
data_type.name, data_size, Commands.WRITE.name)
if repeats_is_register is False:
_bounds(Commands.WRITE, "repeats", repeats, 1, 256)
else:
_bounds(Commands.WRITE, "repeats", repeats, 0, MAX_REGISTERS)
parameters = 0
cmd_string = Commands.WRITE.name
cmd_string += f" data={data}"
if repeats_is_register:
repeat_reg_usage = 1
parameters |= (repeats << 4)
cmd_string += f", repeats=reg[{repeats:d}]"
else:
repeat_reg_usage = NO_REGS
parameters |= repeats & 0xFF
cmd_string += f", repeats={repeats:d}"
cmd_word = _binencode(Commands.WRITE, {
_Field.LENGTH: cmd_data_len,
_Field.USAGE: repeat_reg_usage,
_Field.DESTINATION: data_len,
_Field.IMMEDIATE: parameters})
data_word = data_type.encode(data)
cmd_string += f", dataType={data_type.name}"
self._write_command_to_files(cmd_word + data_word, cmd_string)
[docs] def write_value_from_register(
self, data_register, repeats=1, repeats_is_register=False,
data_type=DataType.UINT32):
"""
Insert command to write a value one or more times at the write pointer
of the current memory region, causing it to move. The data is contained
in a register whose ID is passed to the function.
:param int data_register:
Identifies the register in which the data is stored.
:param int repeats:
* If ``repeats_is_register`` is `None`, this parameter identifies
the number of times to repeat the data, between 1 and 255
(default 1)
* If ``repeats_is_register`` is not `None` (i.e. has an integer
value), the content of this parameter is disregarded
:param bool repeats_is_register: Identifies if ``repeats`` is the
register containing the number of repeats of the value to write
:param DataType data_type: the type of the data held in the register
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``repeats_is_register`` is False, and ``repeats`` is out of
range
* If ``repeats_is_register`` is True, and ``repeats`` is not a
valid register ID
* If ``data_register`` is not a valid register ID
:raise UnknownTypeException: If the data type is not known
:raise NoRegionSelectedException: If no region has been selected
:raise RegionExhaustedException:
If the selected region has no more space
"""
if data_type not in DataType:
raise UnknownTypeException(
data_type.value, Commands.WRITE.name)
if self._current_region is None:
raise NoRegionSelectedException(Commands.WRITE.name)
data_size = data_type.size
if data_size == 1:
cmd_data_len = 0
elif data_size == 2:
cmd_data_len = 1
elif data_size == 4:
cmd_data_len = 2
elif data_size == 8:
cmd_data_len = 3
else:
raise InvalidSizeException(
data_type.name, data_size, Commands.WRITE.name)
if repeats_is_register is False:
_bounds(Commands.WRITE, "repeats", repeats, 1, 256)
else:
_bounds(Commands.WRITE, "repeats", repeats, 0, MAX_REGISTERS)
_bounds(Commands.WRITE, "data_register",
data_register, 0, MAX_REGISTERS)
cmd_string = Commands.WRITE.name
cmd_string += f" data=reg[{data_register:d}]"
if repeats_is_register:
reg_usage = SRC1_AND_SRC2
parameters = repeats << 4
cmd_string += f", repeats=reg[{repeats:d}]"
else:
reg_usage = SRC1_ONLY
parameters = repeats & 0xFF
cmd_string += f", repeats={repeats:d}"
cmd_word = _binencode(Commands.WRITE, {
_Field.LENGTH: LEN1,
_Field.USAGE: reg_usage,
_Field.DESTINATION: cmd_data_len,
_Field.SOURCE_1: data_register,
_Field.IMMEDIATE: parameters})
cmd_string += f", dataType={data_type.name}"
self._write_command_to_files(cmd_word, cmd_string)
[docs] def write_array(self, array_values, data_type=DataType.UINT32):
"""
Insert command to write an array, causing the write pointer
to move on by (data type size * the array size), in bytes.
:param array_values: An array of words to be written
:type array_values: list(int) or list(float) or ~numpy.ndarray
:param DataType data_type: Type of data contained in the array
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise NoRegionSelectedException: If no region has been selected
"""
if data_type.numpy_typename is None:
raise TypeMismatchException(Commands.WRITE_ARRAY.name)
if self._current_region is None:
raise NoRegionSelectedException(Commands.WRITE_ARRAY.name)
data = numpy.array(array_values, dtype=data_type.numpy_typename)
size = data.size * data_type.size
if size % 4 != 0:
raise UnknownTypeLengthException(size, Commands.WRITE_ARRAY.name)
cmd_word = _binencode(Commands.WRITE_ARRAY, {
_Field.LENGTH: LEN2,
_Field.IMMEDIATE: data_type.size})
cmd_string = Commands.WRITE_ARRAY.name
cmd_string += f" {size // 4:d} elements\n"
cmd_string += str(list(array_values))
arg_word = _ONE_WORD.pack(size // 4)
self._write_command_to_files(cmd_word + arg_word, cmd_string)
self._spec_writer.write(data.tostring())
[docs] def switch_write_focus(self, region):
"""
Insert command to switch the region being written to.
:param int region: The ID of the region to switch to, between 0 and 15
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
If the region identifier is not valid
:raise NotAllocatedException: If the region has not been allocated
:raise RegionUnfilledException:
If the selected region should not be filled
"""
_bounds(Commands.SWITCH_FOCUS, "region", region, 0, MAX_MEM_REGIONS)
if self._mem_slots[region] is None:
raise NotAllocatedException(
"region", region, Commands.SWITCH_FOCUS.name)
if self._mem_slots[region].empty:
raise RegionUnfilledException(region, Commands.SWITCH_FOCUS.name)
self._current_region = region
cmd_string = Commands.SWITCH_FOCUS.name
cmd_string += f" memRegion = {region:d}"
# Write command to switch focus:
cmd_word = _binencode(Commands.SWITCH_FOCUS, {
_Field.LENGTH: LEN1,
_Field.USAGE: 0x0,
_Field.SOURCE_1: region})
self._write_command_to_files(cmd_word, cmd_string)
[docs] def start_loop(self, counter_register_id, start, end, increment=1,
start_is_register=False, end_is_register=False,
increment_is_register=False):
"""
Insert command to start a loop.
:param int counter_register_id: The ID of the register to use as the
loop counter, between 0 and 15
:param int start:
* If ``start_is_register`` is False, the number at which to start
the loop counter, >= 0
* If ``start_is_register`` is True, the ID of the register
containing the number at which to start the loop counter,
between 0 and 15
:param int end:
* If ``end_is_register`` is False, the number which the loop
counter must reach to stop the loop i.e. the loop will run while
the contents of ``counter_register`` < ``end``, >= 0
* If ``end_is_register`` is True, the ID of the register
containing the number at which to stop the loop, between 0 and 15
:param int increment:
* If ``increment_is_register`` is False, the amount by which to
increment the loop counter on each run of the loop, >= 0
* If ``increment_is_register`` is True, the ID of the register
containing the amount by which to increment the loop counter on
each run of the loop, between 0 and 15
:param bool start_is_register: Indicates if ``start`` is a register ID
:param bool end_is_register: Indicates if ``end`` is a register ID
:param bool increment_is_register:
Indicates if ``increment`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``counter_register_id`` is not a valid register ID
* If ``start_is_register`` is True and ``start`` is not a valid
register ID
* If ``end_is_register`` is True and ``end`` is not a valid
register ID
* If ``increment_is_register`` is True, and ``increment`` is not a
valid register ID
* If ``start_is_register is False and ``start`` is not in the
allowed range
* If ``end_is_register is False and ``end`` is not in the allowed
range
* If ``increment_is_register`` is False and ``increment`` is not
in the allowed range
"""
_bounds(Commands.LOOP, "counter_register_id",
counter_register_id, 0, MAX_REGISTERS)
bit_field = 0
length = LEN1
encoded_values = bytearray()
cmd_string = Commands.LOOP.name
cmd_string += f" counter_register_id=reg[{counter_register_id:d}],"
r1 = r2 = r3 = 0
if start_is_register:
_bounds(Commands.LOOP, "start", start, 0, MAX_REGISTERS)
bit_field |= 0x4
r1 = start
cmd_string += f" start=reg[{start:d}],"
else:
_typebounds(Commands.LOOP, "start", start, DataType.INT32)
length += 1
encoded_values += _ONE_SIGNED_INT.pack(start)
cmd_string += f" start={start:d},"
if end_is_register:
_bounds(Commands.LOOP, "end", end, 0, MAX_REGISTERS)
bit_field |= 0x2
r2 = end
cmd_string += f" end=reg[{end:d}],"
else:
_typebounds(Commands.LOOP, "end", end, DataType.INT32)
length += 1
encoded_values += _ONE_SIGNED_INT.pack(end)
cmd_string += f" end={end:d},"
if increment_is_register:
_bounds(Commands.LOOP, "increment", increment, 0, MAX_REGISTERS)
bit_field |= 0x1
r3 = increment
cmd_string += f" increment=reg[{increment:d}],"
else:
_typebounds(Commands.LOOP, "increment", increment, DataType.INT32)
length += 1
encoded_values += _ONE_SIGNED_INT.pack(increment)
cmd_string += f" increment={increment:d},"
self._ongoing_loop = True
cmd_word = _binencode(Commands.LOOP, {
_Field.LENGTH: length,
_Field.USAGE: bit_field,
_Field.DESTINATION: r1, # non-standard usage
_Field.SOURCE_1: r2, # non-standard usage
_Field.SOURCE_2: r3, # non-standard usage
_Field.IMMEDIATE: counter_register_id})
self._write_command_to_files(cmd_word + encoded_values, cmd_string)
[docs] def break_loop(self):
"""
Insert command to break out of a loop before it has completed.
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise InvalidCommandException:
If there is no loop in operation at this point
"""
if not self._ongoing_loop:
raise InvalidCommandException(Commands.BREAK_LOOP.name)
cmd_word = _binencode(Commands.BREAK_LOOP, {
_Field.LENGTH: LEN1})
cmd_string = Commands.BREAK_LOOP.name
self._write_command_to_files(cmd_word, cmd_string)
[docs] def end_loop(self):
"""
Insert command to indicate that this is the end of the loop.
Commands between the start of the loop and this command will be
repeated.
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise InvalidCommandException:
If there is no loop in operation at this point
"""
if not self._ongoing_loop:
raise InvalidCommandException(Commands.END_LOOP.name)
cmd_word = _binencode(Commands.END_LOOP, {
_Field.LENGTH: LEN1})
cmd_string = Commands.END_LOOP.name
self._ongoing_loop = False
self._write_command_to_files(cmd_word, cmd_string)
[docs] def start_conditional(self, register_id, condition, value,
value_is_register=False):
"""
Insert command to start a conditional if...then...else construct. If
the condition evaluates to true, the statement is executed up to the
next else statement, or the end of the conditional, whichever comes
first.
:param int register_id: The ID of a register to compare the value of
:param Condition condition:
The condition which must be true to execute the following commands
:param int value:
* If ``value_is_register`` is False, the value to compare to the
value in the register
* If ``value_is_register`` is True, the ID of the register
containing the value to compare, between 0 and 15
:param bool value_is_register: Indicates if ``value`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* if ``value_is_register`` is True and ``value`` is not a valid
register ID
:raise UnknownConditionException:
If ``condition`` is not a valid condition
"""
_bounds(Commands.IF, "register_id", register_id, 0, MAX_REGISTERS)
if condition not in Condition:
raise UnknownConditionException(condition, Commands.IF.name)
data_encoded = bytearray()
cmd_string = Commands.IF.name
if value_is_register:
_bounds(Commands.IF, "value", value, 0, MAX_REGISTERS)
cmd_word = _binencode(Commands.IF, {
_Field.LENGTH: LEN1,
_Field.USAGE: 0x3,
_Field.SOURCE_1: register_id,
_Field.SOURCE_2: value,
_Field.IMMEDIATE: condition})
cmd_string += (
f" reg[{register_id:d}] {condition.operator} reg[{value:d}]")
else:
_typebounds(Commands.IF, "value", value, DataType.INT32)
cmd_word = _binencode(Commands.IF, {
_Field.LENGTH: LEN2,
_Field.USAGE: 0x2,
_Field.SOURCE_1: register_id,
_Field.IMMEDIATE: condition})
data_encoded += _ONE_SIGNED_INT.pack(value)
cmd_string += (
f" reg[{register_id:d}] {condition.operator} {value:d}")
self._conditionals.append(False)
cmd_word_list = cmd_word + data_encoded
self._write_command_to_files(cmd_word_list, cmd_string, indent=True)
[docs] def else_conditional(self):
"""
Insert command for the else of an if...then...else construct. If the
condition of the conditional evaluates to false, the statements up
between the conditional and the insertion of this "else" are skipped,
and the statements from this point until the end of the conditional
are executed.
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise InvalidCommandException:
If there is no conditional in operation at this point
"""
if not self._conditionals or \
self._conditionals[len(self._conditionals) - 1] is True:
raise InvalidCommandException(Commands.ELSE.name)
self._conditionals[len(self._conditionals) - 1] = True
cmd_word = _binencode(Commands.ELSE, {
_Field.LENGTH: LEN1})
cmd_string = Commands.ELSE.name
self._write_command_to_files(
cmd_word, cmd_string, indent=True, outdent=True)
[docs] def end_conditional(self):
"""
Insert command to mark the end of an if...then...else construct
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise InvalidCommandException:
If there is no conditional in operation at this point
"""
if not self._conditionals:
raise InvalidCommandException(Commands.END_IF.name)
self._conditionals.pop()
cmd_word = _binencode(Commands.END_IF, {
_Field.LENGTH: LEN1})
cmd_string = Commands.END_IF.name
self._write_command_to_files(cmd_word, cmd_string, outdent=True)
[docs] def set_register_value(self, register_id, data, data_is_register=False,
data_type=DataType.UINT32):
"""
Insert command to set the value of a register.
:param int register_id:
The ID of the register to assign, between 0 and 15
:param data:
* If ``data_is_register`` is True, the register ID containing
the data to set, between 0 and 15
* If ``data_is_register`` is False, the data is a value of the
type given by ``data_type``
:type data: int or float
:param bool data_is_register: Indicates if ``data`` is a register ID
:param DataType data_type: The type of the data to be assigned
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* If ``data_is_register`` is True, and ``data`` is not a valid
register ID
* If ``data_is_register`` is False, ``data_type`` is an integer
type and ``data`` has a fractional part
* If ``data_is_register`` if False, and ``data`` would overflow
the data type
:raise UnknownTypeException: If the data type is not known
"""
_bounds(Commands.MV, "register_id", register_id, 0, MAX_REGISTERS)
if data_is_register:
# Build command to move between registers:
_bounds(Commands.MV, "data", data, 0, MAX_REGISTERS)
if data == register_id:
raise DuplicateParameterException(
Commands.MV.name, [register_id, data])
cmd_word = _binencode(Commands.MV, {
_Field.LENGTH: LEN1,
_Field.USAGE: DEST_AND_SRC1,
_Field.DESTINATION: register_id,
_Field.SOURCE_1: data})
encoded_data = bytearray()
cmd_string = f"reg[{register_id:d}] = reg[{data:d}]"
else:
# Build command to assign from an immediate:
# command has a second word (the immediate)
_typebounds(Commands.MV, "data", data, data_type)
cmd_word = _binencode(Commands.MV, {
_Field.LENGTH: LEN3 if data_type.size > 4 else LEN2,
_Field.USAGE: DEST_ONLY,
_Field.DESTINATION: register_id})
encoded_data = data_type.encode(data)
cmd_string = f"reg[{register_id:d}] = {data:d} (0x{data:X})"
self._write_command_to_files(cmd_word + encoded_data, cmd_string)
[docs] def save_write_pointer(self, register_id):
"""
Insert command to save the write pointer to a register.
:param int register_id:
The ID of the register to assign, between 0 and 15
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
If the ``register_id`` is not a valid register ID
:raise NoRegionSelectedException: If no region has been selected
"""
_bounds(Commands.GET_WR_PTR, "register_id",
register_id, 0, MAX_REGISTERS)
if self._current_region is None:
raise NoRegionSelectedException(Commands.GET_WR_PTR.name)
cmd_word = _binencode(Commands.GET_WR_PTR, {
_Field.LENGTH: LEN1,
_Field.USAGE: 0x4,
_Field.DESTINATION: register_id})
cmd_string = Commands.GET_WR_PTR.name
cmd_string += f" reg[{register_id:d}]"
self._write_command_to_files(cmd_word, cmd_string)
[docs] def set_write_pointer(self, address, address_is_register=False,
relative_to_current=False):
"""
Insert command to set the position of the write pointer within the
current region.
:param int address:
* If ``address_is_register`` is True, the ID of the register
containing the address to move to
* If ``address_is_register`` is False, the address to move the
write pointer to
:param bool address_is_register:
Indicates if ``address`` is a register ID
:param bool relative_to_current:
Indicates if ``address`` (or the value read from that register
when ``address_is_register`` is True) is to be added to the
current address, or used as an absolute address from the start
of the current region
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
If the ``address_is_register`` is True and ``address`` is not a
valid register ID
:raise NoRegionSelectedException: If no region has been selected
"""
if self._current_region is None:
raise NoRegionSelectedException(Commands.SET_WR_PTR.name)
relative = bool(relative_to_current)
relative_string = "RELATIVE" if relative else "ABSOLUTE"
data_encoded = bytearray()
cmd_string = Commands.SET_WR_PTR.name
if address_is_register:
_bounds(Commands.SET_WR_PTR, "address", address, 0, MAX_REGISTERS)
cmd_word = _binencode(Commands.SET_WR_PTR, {
_Field.LENGTH: LEN1,
_Field.USAGE: SRC1_ONLY,
_Field.SOURCE_1: address,
_Field.IMMEDIATE: relative})
cmd_string += f" reg[{address:d}] {relative_string}"
else:
if not relative_to_current:
_typebounds(Commands.SET_WR_PTR, "address",
address, DataType.UINT32)
data_encoded += _ONE_WORD.pack(address)
else:
_typebounds(Commands.SET_WR_PTR, "address",
address, DataType.INT32)
data_encoded += _ONE_SIGNED_INT.pack(address)
cmd_word = _binencode(Commands.SET_WR_PTR, {
_Field.LENGTH: LEN2,
_Field.USAGE: NO_REGS,
_Field.IMMEDIATE: relative})
cmd_string += f" {address:d} {relative_string}"
self._write_command_to_files(cmd_word + data_encoded, cmd_string)
[docs] def align_write_pointer(self, log_block_size,
log_block_size_is_register=False,
return_register_id=None):
"""
Insert command to align the write pointer against a power-of-2
block size in bytes. Zeros are inserted in the intervening space
:param int log_block_size:
* If ``log_block_size_is_register`` is False, log to base 2 of
the block size (e.g. The write pointer will be moved so that
it is a multiple of 2\\ :sup:`log_block_size`), between 0 and 32
* If ``log_block_size_is_register`` is True, the ID of the
register containing log to the base 2 of the block size,
between 0 and 15
:param bool log_block_size_is_register:
Indicates if ``log_block_size`` is a register ID
:param return_register_id: The ID of a register where the write
pointer will be written to once it has been updated, between
0 and 15, or `None` if no such writing is to be done
:type return_register_id: int or None
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``log_block_size_is_register`` is False, and
``log_block_size`` is not within the allowed range
* If ``log_block_size_is_register`` is True and
``log_block_size`` is not a valid register ID
:raise RegionOutOfBoundsException:
If the move of the pointer would put it outside of the current
region
:raise NoRegionSelectedException: If no region has been selected
"""
if self._current_region is None:
raise NoRegionSelectedException(Commands.ALIGN_WR_PTR.name)
bit_field = 0
imm_value = 0
return_register_value = 0
block_size_reg = 0
cmd_string = Commands.ALIGN_WR_PTR.name
if return_register_id is not None:
_bounds(Commands.ALIGN_WR_PTR, "return_register_id",
return_register_id, 0, MAX_REGISTERS)
bit_field |= 0x4
return_register_value = return_register_id
cmd_string = f" reg[{return_register_value:d}] ="
if log_block_size_is_register:
_bounds(Commands.ALIGN_WR_PTR, "log_block_size",
log_block_size, 0, MAX_REGISTERS)
bit_field |= 0x2
block_size_reg = log_block_size
cmd_string += f" align(reg[{block_size_reg:d}])"
else:
_bounds(Commands.ALIGN_WR_PTR, "log_block_size",
log_block_size, 0, 32)
imm_value = log_block_size
cmd_string += f" align({imm_value:d})"
cmd_word = _binencode(Commands.ALIGN_WR_PTR, {
_Field.LENGTH: LEN1,
_Field.USAGE: bit_field,
_Field.DESTINATION: return_register_value,
_Field.SOURCE_1: block_size_reg,
_Field.IMMEDIATE: imm_value})
self._write_command_to_files(cmd_word, cmd_string)
[docs] def call_arithmetic_operation(self, register_id, operand_1, operation,
operand_2, signed,
operand_1_is_register=False,
operand_2_is_register=False):
"""
Insert command to perform an arithmetic operation on two signed or
unsigned values and store the result in a register
:param int register_id: The ID of the register to store the result in
:param int operand_1:
* If ``operand_1_is_register`` is True, the ID of a register where
the first operand can be found, between 0 and 15
* If ``operand_1_is_register`` is False, a 32-bit value
:param ArithmeticOperation operation: The operation to perform
:param int operand_2:
* If ``operand_2_is_register`` is True, the ID of a register where
the second operand can be found, between 0 and 15
* If ``operand_2_is_register`` is False, a 32-bit value
:param bool signed: Indicates if the values should be considered signed
:param bool operand_1_is_register:
Indicates if ``operand_1`` is a register ID
:param bool operand_2_is_register:
Indicates if ``operand_2`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* If ``operand_1_is_register`` is True and ``operand_1`` is not a
valid register ID
* If ``operand_2_is_register`` is True and ``operand_2`` is not a
valid register ID
:raise InvalidOperationException:
If ``operation`` is not a known operation
"""
_bounds(Commands.ARITH_OP, "register_id",
register_id, 0, MAX_REGISTERS)
if operation not in ArithmeticOperation:
raise InvalidOperationException(
"arithmetic", operation.value, Commands.ARITH_OP.name)
cmd_length = 0
bit_field = 0x4
register_op_1 = 0
register_op_2 = 0
encoded_operands = bytearray()
cmd_string = Commands.ARITH_OP.name
cmd_string += (
f' {"SIGNED" if signed else "UNSIGNED"} reg[{register_id:d}] =')
if operand_1_is_register:
_bounds(Commands.ARITH_OP, "operand_1",
operand_1, 0, MAX_REGISTERS)
bit_field |= 2
register_op_1 = operand_1
cmd_string += f" reg[{register_op_1:d}]"
elif signed:
_typebounds(Commands.ARITH_OP, "operand_1",
operand_1, DataType.INT32)
cmd_length += 1
encoded_operands += _ONE_SIGNED_INT.pack(operand_1)
cmd_string += f" {operand_1:d}"
else:
_typebounds(Commands.ARITH_OP, "operand_1",
operand_1, DataType.UINT32)
cmd_length += 1
encoded_operands += _ONE_WORD.pack(operand_1)
cmd_string += f" {operand_1:d}"
cmd_string += f" {operation.operator}"
if operand_2_is_register:
_bounds(Commands.ARITH_OP, "operand_2",
operand_2, 0, MAX_REGISTERS)
bit_field |= 1
register_op_2 = operand_2
cmd_string += f" reg[{register_op_2:d}]"
elif signed:
_typebounds(Commands.ARITH_OP, "operand_2",
operand_2, DataType.INT32)
cmd_length += 1
encoded_operands += _ONE_SIGNED_INT.pack(operand_2)
cmd_string += f" {operand_2:d}"
else:
_typebounds(Commands.ARITH_OP, "operand_2",
operand_2, DataType.UINT32)
cmd_length += 1
encoded_operands += _ONE_WORD.pack(operand_2)
cmd_string += f" {operand_2:d}"
cmd_word = _binencode(Commands.ARITH_OP, {
_Field.LENGTH: cmd_length,
_Field.SIGNED: bool(signed),
_Field.USAGE: bit_field,
_Field.DESTINATION: register_id,
_Field.SOURCE_1: register_op_1,
_Field.SOURCE_2: register_op_2,
_Field.IMMEDIATE: operation})
self._write_command_to_files(cmd_word + encoded_operands, cmd_string)
[docs] def logical_and(self, register_id, operand_1, operand_2,
operand_1_is_register=False, operand_2_is_register=False):
"""
Insert command to perform a logical AND operation.
:param int register_id: The ID of the register to store the result in
:param int operand_1:
* If ``operand_1_is_register`` is True, the ID of a register
where the first operand can be found, between 0 and 15
* If ``operand_1_is_register`` is False, a 32-bit value
:param int operand_2:
* If ``operand_2_is_register`` is True, the ID of a register
where the second operand can be found. between 0 and 15
* If ``operand_2_is_register`` is False, a 32-bit value
:param bool operand_1_is_register:
Indicates if ``operand_1`` is a register ID
:param bool operand_2_is_register:
Indicates if ``operand_2`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* If ``operand_1_is_register`` is True and ``operand_1`` is not a
valid register ID
* If ``operand_2_is_register`` is True and ``operand_2`` is not a
valid register ID
"""
self._call_logic_operation(
register_id, operand_1, LogicOperation.AND, operand_2,
operand_1_is_register, operand_2_is_register)
[docs] def logical_or(self, register_id, operand_1, operand_2,
operand_1_is_register=False, operand_2_is_register=False):
"""
Insert command to perform a logical OR operation.
:param int register_id: The ID of the register to store the result in
:param int operand_1:
* If ``operand_1_is_register`` is True, the ID of a register
where the first operand can be found, between 0 and 15
* If ``operand_1_is_register`` is False, a 32-bit value
:param int operand_2:
* If ``operand_2_is_register`` is True, the ID of a register
where the second operand can be found. between 0 and 15
* If ``operand_2_is_register`` is False, a 32-bit value
:param bool operand_1_is_register:
Indicates if ``operand_1`` is a register ID
:param bool operand_2_is_register:
Indicates if ``operand_2`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* If ``operand_1_is_register`` is True and ``operand_1`` is not a
valid register ID
* If ``operand_2_is_register`` is True and ``operand_2`` is not a
valid register ID
"""
self._call_logic_operation(
register_id, operand_1, LogicOperation.OR, operand_2,
operand_1_is_register, operand_2_is_register)
[docs] def logical_left_shift(self, register_id, operand_1, operand_2,
operand_1_is_register=False,
operand_2_is_register=False):
"""
Insert command to perform a logical left shift operation.
:param int register_id: The ID of the register to store the result in
:param int operand_1:
* If ``operand_1_is_register`` is True, the ID of a register
where the first operand can be found, between 0 and 15
* If ``operand_1_is_register`` is False, a 32-bit value
:param int operand_2:
* If ``operand_2_is_register`` is True, the ID of a register
where the second operand can be found. between 0 and 15
* If ``operand_2_is_register`` is False, a 32-bit value
:param bool operand_1_is_register:
Indicates if ``operand_1`` is a register ID
:param bool operand_2_is_register:
Indicates if ``operand_2`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* If ``operand_1_is_register`` is True and ``operand_1`` is not a
valid register ID
* If ``operand_2_is_register`` is True and ``operand_2`` is not a
valid register ID
"""
self._call_logic_operation(
register_id, operand_1, LogicOperation.LEFT_SHIFT, operand_2,
operand_1_is_register, operand_2_is_register)
[docs] def logical_right_shift(self, register_id, operand_1, operand_2,
operand_1_is_register=False,
operand_2_is_register=False):
"""
Insert command to perform a logical right shift operation.
:param int register_id: The ID of the register to store the result in
:param int operand_1:
* If ``operand_1_is_register`` is True, the ID of a register
where the first operand can be found, between 0 and 15
* If ``operand_1_is_register`` is False, a 32-bit value
:param int operand_2:
* If ``operand_2_is_register`` is True, the ID of a register
where the second operand can be found. between 0 and 15
* If ``operand_2_is_register`` is False, a 32-bit value
:param bool operand_1_is_register:
Indicates if ``operand_1`` is a register ID
:param bool operand_2_is_register:
Indicates if ``operand_2`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* If ``operand_1_is_register`` is True and ``operand_1`` is not a
valid register ID
* If ``operand_2_is_register`` is True and ``operand_2`` is not a
valid register ID
"""
self._call_logic_operation(
register_id, operand_1, LogicOperation.RIGHT_SHIFT, operand_2,
operand_1_is_register, operand_2_is_register)
[docs] def logical_xor(self, register_id, operand_1, operand_2,
operand_1_is_register=False, operand_2_is_register=False):
"""
Insert command to perform a logical XOR operation.
:param int register_id: The ID of the register to store the result in
:param int operand_1:
* If ``operand_1_is_register`` is True, the ID of a register
where the first operand can be found, between 0 and 15
* If ``operand_1_is_register`` is False, a 32-bit value
:param int operand_2:
* If ``operand_2_is_register`` is True, the ID of a register
where the second operand can be found. between 0 and 15
* If ``operand_2_is_register`` is False, a 32-bit value
:param bool operand_1_is_register:
Indicates if ``operand_1`` is a register ID
:param bool operand_2_is_register:
Indicates if ``operand_2`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* If ``operand_1_is_register`` is True and ``operand_1`` is not a
valid register ID
* If ``operand_2_is_register`` is True and ``operand_2`` is not a
valid register ID
"""
self._call_logic_operation(
register_id, operand_1, LogicOperation.XOR, operand_2,
operand_1_is_register, operand_2_is_register)
[docs] def logical_not(self, register_id, operand, operand_is_register=False):
"""
Insert command to perform a logical NOT operation.
:param int register_id: The ID of the register to store the result in
:param int operand:
* If ``operand_is_register`` is True, the ID of a register where
the first operand can be found, between 0 and 15
* If ``operand_is_register`` is False, a 32-bit value
:param bool operand_is_register:
Indicates if ``operand`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* If ``operand_is_register`` is True and ``operand`` is not a
valid register ID
"""
self._call_logic_operation(
register_id, operand, LogicOperation.NOT, 0,
operand_is_register, False)
def _call_logic_operation(self, register_id, operand_1, operation,
operand_2, operand_1_is_register=False,
operand_2_is_register=False):
"""
Insert command to perform a logic operation on two signed or
unsigned values and store the result in a register.
:param int register_id: The ID of the register to store the result in
:param int operand_1:
* If ``operand_1_is_register`` is True, the ID of a register
where the first operand can be found, between 0 and 15
* If ``operand_1_is_register`` is False, a 32-bit value
:param LogicOperation operation: The operation to perform
:param int operand_2:
* If ``operand_2_is_register`` is True, the ID of a register
where the second operand can be found. between 0 and 15
* If ``operand_2_is_register`` is False, a 32-bit value
:param bool operand_1_is_register:
Indicates if ``operand_1`` is a register ID
:param bool operand_2_is_register:
Indicates if ``operand_2`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``register_id`` is not a valid register ID
* If ``operand_1_is_register`` is True and ``operand_1`` is not a
valid register ID
* If ``operand_2_is_register`` is True and ``operand_2`` is not a
valid register ID
:raise InvalidOperationException:
If ``operation`` is not a known operation
"""
_bounds(Commands.LOGIC_OP, "register_id",
register_id, 0, MAX_REGISTERS)
if operation not in LogicOperation:
raise InvalidOperationException(
"logic", operation.value, Commands.LOGIC_OP.name)
cmd_length = 0
bit_field = 0x4
register_op_1 = 0
register_op_2 = 0
encoded_operands = bytearray()
cmd_string = Commands.LOGIC_OP.name
cmd_string += f" reg[{register_id:d}] ="
if operation.value == LogicOperation.NOT.value:
cmd_string += f" {operation.operator}"
if operand_1_is_register:
_bounds(Commands.LOGIC_OP, "operand_1",
operand_1, 0, MAX_REGISTERS)
bit_field |= 2
register_op_1 = operand_1
cmd_string += f" reg[{register_op_1:d}]"
else:
cmd_length += 1
_typebounds(Commands.LOGIC_OP, "operand_1",
operand_1, DataType.UINT32)
encoded_operands += _ONE_WORD.pack(operand_1)
cmd_string += f" {operand_1}"
if operation.value != LogicOperation.NOT.value:
cmd_string += f" {operation.operator}"
if operand_2_is_register:
_bounds(Commands.LOGIC_OP, "operand_2",
operand_2, 0, MAX_REGISTERS)
bit_field |= 1
register_op_2 = operand_2
cmd_string += f" reg[{register_op_2:d}]"
else:
cmd_length += 1
_typebounds(Commands.LOGIC_OP, "operand_2",
operand_2, DataType.UINT32)
encoded_operands += _ONE_WORD.pack(operand_2)
cmd_string += f" {operand_2}"
cmd_word = _binencode(Commands.LOGIC_OP, {
_Field.LENGTH: cmd_length,
_Field.USAGE: bit_field,
_Field.DESTINATION: register_id,
_Field.SOURCE_1: register_op_1,
_Field.SOURCE_2: register_op_2,
_Field.IMMEDIATE: operation})
self._write_command_to_files(cmd_word + encoded_operands, cmd_string)
[docs] def copy_structure(self, source_structure_id, destination_structure_id,
source_id_is_register=False,
destination_id_is_register=False):
"""
Insert command to copy a structure, possibly overwriting another
structure.
:param int source_structure_id:
* If ``source_id_is_register`` is True, the ID of the register
holding the source structure ID, between 0 and 15
* Otherwise, the ID of the source structure, between 0 and 15
:param int destination_structure_id:
* If ``destination_id_is_register`` is True, the ID of the
register holding the destination structure ID, between 0 and 15
* If ``destination_id_is_register`` is False, the ID of the
destination structure, between 0 and 15
:param bool source_id_is_register:
Indicates if ``source_structure_id`` is a register ID
:param bool destination_id_is_register:
Indicates if ``destination_structure_id`` is a register ID
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``source_id_is_register`` is True and
``source_structure_id`` is not a valid register ID
* If ``destination_id_is_register`` is True and
``destination_structure_id`` is not a valid register ID
* If ``source_id_is_register`` is False and
``source_structure_id`` is not a valid structure ID
* If ``destination_id_is_register`` is False and
``destination_structure_id`` is not a valid structure ID
:raise NotAllocatedException:
* If no structure with ID ``source_structure_id`` has been
allocated
"""
if source_structure_id == destination_structure_id and \
destination_id_is_register == source_id_is_register:
raise DuplicateParameterException(
Commands.COPY_STRUCT.name,
[source_structure_id, destination_structure_id])
bit_field = 0
cmd_string = Commands.COPY_STRUCT.name
if source_id_is_register:
_bounds(Commands.COPY_STRUCT, "source_structure_id",
source_structure_id, 0, MAX_REGISTERS)
bit_field |= SRC1_ONLY
cmd_string += f" source_struct=reg[{source_structure_id:d}]"
else:
_bounds(Commands.COPY_STRUCT, "source_structure_id",
source_structure_id, 0, MAX_STRUCT_SLOTS)
if self._struct_slots[source_structure_id] is None:
raise NotAllocatedException(
"struct", source_structure_id, Commands.COPY_STRUCT.name)
cmd_string += f" source_struct={source_structure_id:d}"
if destination_id_is_register:
_bounds(Commands.COPY_STRUCT, "destination_structure_id",
destination_structure_id, 0, MAX_REGISTERS)
bit_field |= DEST_ONLY
cmd_string += (
f" destination_struct=reg[{destination_structure_id:d}]")
else:
_bounds(Commands.COPY_STRUCT, "destination_structure_id",
destination_structure_id, 0, MAX_STRUCT_SLOTS)
cmd_string += f" destination_struct={destination_structure_id:d}"
cmd_word = _binencode(Commands.COPY_STRUCT, {
_Field.LENGTH: LEN1,
_Field.USAGE: bit_field,
_Field.DESTINATION: destination_structure_id,
_Field.SOURCE_1: source_structure_id})
self._write_command_to_files(cmd_word, cmd_string)
[docs] def copy_structure_parameter(
self, source_structure_id, source_parameter_index,
destination_id, destination_parameter_index=None,
destination_is_register=False):
"""
Insert command to copy the value of a parameter from one
structure to another.
:param int source_structure_id:
The ID of the source structure, between 0 and 15
:param int source_parameter_index:
The index of the parameter in the source structure
:param int destination_id: The ID of the destination structure, or
the ID of the destination register, between 0 and 15
:param int destination_parameter_index: The index of the parameter in
the destination structure. Ignored when writing to a register.
:param bool destination_is_register:
Indicates whether the destination is a structure or a register.
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``source_structure_id`` is not a valid structure ID
* If ``destination_id`` is not a valid structure ID
* If ``source_parameter_index`` is not a valid parameter index
in the source structure
* If ``destination_parameter_index`` is not a valid parameter
index in the destination structure
:raise NotAllocatedException:
* If no structure with ID ``destination_id`` has been allocated
* If no structure with ID ``source_structure_id`` has been
allocated
"""
_bounds(Commands.COPY_PARAM, "source_structure_id",
source_structure_id, 0, MAX_STRUCT_SLOTS)
_bounds(Commands.COPY_PARAM, "source_parameter_index",
source_parameter_index, 0, MAX_STRUCT_ELEMENTS)
if self._struct_slots[source_structure_id] is None:
raise NotAllocatedException(
"structure", source_structure_id, Commands.COPY_PARAM.name)
if (len(self._struct_slots[source_structure_id]) <=
source_parameter_index):
raise NotAllocatedException(
"parameter", source_parameter_index, Commands.COPY_PARAM.name)
cmd_string = Commands.COPY_PARAM.name
if not destination_is_register:
_bounds(Commands.COPY_PARAM, "destination_parameter_index",
destination_parameter_index, 0, MAX_STRUCT_ELEMENTS)
_bounds(Commands.COPY_PARAM, "destination_structure_id",
destination_id, 0, MAX_STRUCT_SLOTS)
if self._struct_slots[destination_id] is None:
raise NotAllocatedException(
"structure", destination_id, Commands.COPY_PARAM.name)
if (len(self._struct_slots[source_structure_id]) <=
source_parameter_index):
raise NotAllocatedException(
"parameter", destination_parameter_index,
Commands.COPY_PARAM.name)
if (len(self._struct_slots[destination_id]) <=
destination_parameter_index):
raise NotAllocatedException(
"parameter", destination_parameter_index,
Commands.COPY_PARAM.name)
if (self._struct_slots[source_structure_id]
[source_parameter_index][1] !=
self._struct_slots[destination_id]
[destination_parameter_index][1]):
raise TypeMismatchException(Commands.COPY_PARAM.name)
if (source_structure_id == destination_id and
destination_parameter_index == source_parameter_index):
raise DuplicateParameterException(
Commands.COPY_PARAM.name, [
source_structure_id, source_parameter_index,
destination_id, destination_parameter_index])
cmd_word_1 = _binencode(Commands.COPY_PARAM, {
_Field.LENGTH: LEN2,
_Field.USAGE: NO_REGS,
_Field.DESTINATION: destination_id,
_Field.SOURCE_1: source_structure_id})
param_word = ((destination_parameter_index << 8) |
source_parameter_index)
cmd_string += (
f" source_structure_id = {source_structure_id:d}, "
f"source_parameter_id = {source_parameter_index:d}, "
f"destination_structure_id = {destination_id:d}, "
f"destination_parameter_id = {destination_parameter_index:d}")
else:
_bounds(Commands.COPY_PARAM, "destination_register_id",
destination_id, 0, MAX_REGISTERS)
cmd_word_1 = _binencode(Commands.COPY_PARAM, {
_Field.LENGTH: LEN2,
_Field.USAGE: DEST_ONLY,
_Field.DESTINATION: destination_id,
_Field.SOURCE_1: source_structure_id})
param_word = source_parameter_index
cmd_string += (
f" source_structure_id = {source_structure_id:d}, "
f"source_parameter_id = {source_parameter_index:d}, "
f"destination_register_id = {destination_id:d}")
cmd_word_list = cmd_word_1 + _ONE_WORD.pack(param_word)
self._write_command_to_files(cmd_word_list, cmd_string)
[docs] def print_value(self, value, value_is_register=False,
data_type=DataType.UINT32):
"""
Insert command to print out a value (for debugging).
:param value:
* If ``value_is_register`` is True, the ID of the register
containing the value to print
* If ``value_is_register`` is False, the value to print as a
value of type given by ``data_type``
:type value: float or int
:param bool value_is_register: Indicates if ``value`` is a register
:param DataType data_type: The type of the data to be printed
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``value_is_register`` is True and ``value`` is not a valid
register ID
* If ``value_is_register`` is False, the ``data_type`` is an
integer type and ``value`` has a fractional part
* If ``value_is_register`` is False and the ``value`` would
overflow the data type
:raise UnknownTypeException: If ``data_type`` is not a valid data type
"""
source_register_id = 0
bit_field = 0
data_encoded = bytearray()
cmd_string = Commands.PRINT_VAL.name
if value_is_register:
_bounds(Commands.PRINT_VAL, "value", value, 0, MAX_REGISTERS)
cmd_word_length = LEN1
bit_field |= 2
source_register_id = value
cmd_string += f" reg[{source_register_id:d}]"
else:
_typebounds(Commands.PRINT_VAL, "value", value, data_type)
cmd_word_length = LEN2 if data_type.size <= 4 else LEN3
data_encoded += data_type.encode(value)
cmd_string += f" {value:d}"
cmd_word = _binencode(Commands.PRINT_VAL, {
_Field.LENGTH: cmd_word_length,
_Field.USAGE: bit_field,
_Field.SOURCE_1: source_register_id,
_Field.IMMEDIATE: data_type})
self._write_command_to_files(cmd_word + data_encoded, cmd_string)
[docs] def print_text(self, text, encoding="ASCII"):
"""
Insert command to print some text (for debugging).
:param str text: The text to write (max 12 *bytes* once encoded)
:param str encoding:
The character encoding to use for the string. Defaults to ASCII.
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
"""
text_encoded = bytearray(text.encode(encoding=encoding))
text_len = len(text_encoded)
_bounds(Commands.PRINT_TXT, "len(text)", text_len, 1, 13)
if text_len <= 4:
cmd_word_len = LEN2
elif text_len <= 8:
cmd_word_len = LEN3
else:
cmd_word_len = LEN4
# add final padding to the encoded text
if text_len % 4 != 0:
text_encoded += bytearray(4 - text_len % 4)
cmd_string = Commands.PRINT_TXT.name
cmd_string += f' "{text}"'
cmd_word = _binencode(Commands.PRINT_TXT, {
_Field.LENGTH: cmd_word_len,
_Field.IMMEDIATE: text_len - 1})
self._write_command_to_files(cmd_word + text_encoded, cmd_string)
[docs] def print_struct(self, structure_id, structure_id_is_register=False):
"""
Insert command to print out a structure (for debugging).
:param int structure_id:
* If ``structure_id_is_register`` is True, the ID of the register
containing the ID of the structure to print, between 0 and 15
* If ``structure_id_is_register`` is False, the ID of the
structure to print, between 0 and 15
:param bool structure_id_is_register:
Indicates if the ``structure_id`` is a register
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
:raise ParameterOutOfBoundsException:
* If ``structure_id_is_register`` is True and ``structure_id`` is
not a valid register ID
* If ``structure_id_is_register`` is False and ``structure_id``
is not a valid structure ID
:raise NotAllocatedException:
If ``structure_id_is_register`` is False and ``structure_id`` is
the ID of a structure that has not been allocated
"""
cmd_string = Commands.PRINT_STRUCT.name
if structure_id_is_register:
_bounds(Commands.PRINT_STRUCT, "structure_id",
structure_id, 0, MAX_REGISTERS)
struct_register = structure_id
structure_id = 0
bit_field = 0x2
cmd_string += f" struct(reg[{struct_register:d}])"
else:
_bounds(Commands.PRINT_STRUCT, "structure_id",
structure_id, 0, MAX_STRUCT_SLOTS)
if self._struct_slots[structure_id] is None:
raise NotAllocatedException(
"structure", structure_id, Commands.PRINT_STRUCT.name)
struct_register = 0
bit_field = 0
cmd_string += f" struct({structure_id:d})"
cmd_word = _binencode(Commands.PRINT_STRUCT, {
_Field.LENGTH: LEN1,
_Field.USAGE: bit_field,
_Field.SOURCE_1: struct_register,
_Field.IMMEDIATE: structure_id})
self._write_command_to_files(cmd_word, cmd_string)
[docs] def end_specification(self, close_writer=True):
"""
Insert a command to indicate that the specification has finished
and finish writing.
:param bool close_writer:
Indicates whether to close the underlying writer(s)
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
"""
self.comment("\nEnd of specification:")
cmd_word = _binencode(Commands.END_SPEC, {
_Field.LENGTH: LEN1})
encoded_parameter = _ONE_SIGNED_INT.pack(-1)
cmd_string = Commands.END_SPEC.name
self._write_command_to_files(cmd_word + encoded_parameter, cmd_string)
if close_writer:
self._spec_writer.close()
self._spec_writer = None
if self._report_writer is not None:
self._report_writer.close()
self._report_writer = None
def _write_command_to_files(self, cmd_word_list, cmd_string, indent=False,
outdent=False, no_instruction_number=False):
"""
Writes the binary command to the binary output file and, if the
user has requested a text output for debug purposes, also write
the text version to the text file.
Setting the optional parameter ``indent`` to ``True`` causes subsequent
commands to be indented by two spaces relative to this one. Similarly,
setting ``outdent`` to ``True`` reverses this spacing.
:param bytearray cmd_word_list: list of binary words to be added to
the binary data specification file
:param str cmd_string: string describing the command to be added to
the report for the data specification file
:param bool indent: if the following lines need to be indented
:param bool outdent: if the following lines need to be out-dented
:param bool no_instruction_number: if each report line should include
also the address of the command in the file
:raise DataUndefinedWriterException:
If the binary specification file writer has not been initialised
:raise IOError: If a write to external storage fails
"""
if self._spec_writer is None:
raise DataUndefinedWriterException(
"The spec file writer has not been initialised")
elif cmd_word_list:
self._spec_writer.write(cmd_word_list)
if self._report_writer is not None:
if outdent is True:
self._txt_indent = min(self._txt_indent - 1, 0)
indent_string = " " * self._txt_indent
if no_instruction_number:
formatted_cmd_string = f"{indent_string}{cmd_string}\n"
else:
formatted_cmd_string = (
f"{self._instruction_counter:08X}. "
f"{indent_string}{cmd_string}\n")
self._instruction_counter += len(cmd_word_list)
self._report_writer.write(str(formatted_cmd_string))
if indent is True:
self._txt_indent += 1
return
@property
def region_sizes(self):
"""
A list of sizes of each region that has been reserved.
.. note::
The list will include ``0`` for each non-reserved region.
:rtype: list(int)
"""
return [0 if slot is None else slot.size for slot in self._mem_slots]
@property
def current_region(self):
"""
The ID of the current DSG region we're writing to.
If not yet writing to any region, ``None``.
:rtype: int or None
"""
return self._current_region