• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SpiNNakerManchester / DataSpecification / 4563269470

pending completion
4563269470

push

github

GitHub
Merge pull request #135 from SpiNNakerManchester/doc-polish

342 of 415 branches covered (82.41%)

Branch coverage included in aggregate %.

94 of 94 new or added lines in 5 files covered. (100.0%)

1420 of 1517 relevant lines covered (93.61%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

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

15
from enum import Enum, IntEnum
1✔
16
import io
1✔
17
import logging
1✔
18
import struct
1✔
19
import numpy
1✔
20
from spinn_utilities.log import FormatAdapter
1✔
21
from spinn_machine import sdram
1✔
22
from .constants import (
1✔
23
    MAX_CONSTRUCTORS, MAX_MEM_REGIONS, MAX_RANDOM_DISTS, MAX_REGISTERS,
24
    MAX_RNGS, MAX_STRUCT_ELEMENTS, MAX_STRUCT_SLOTS, LEN1, LEN2, LEN3, LEN4,
25
    NO_REGS, DEST_AND_SRC1, DEST_ONLY, SRC1_ONLY, SRC1_AND_SRC2,
26
    BYTES_PER_WORD)
27
from .exceptions import (
1✔
28
    DataUndefinedWriterException, DuplicateParameterException,
29
    FunctionInUseException, InvalidCommandException,
30
    InvalidOperationException, InvalidSizeException, NotAllocatedException,
31
    NoRegionSelectedException, ParameterOutOfBoundsException,
32
    RandomNumberDistributionInUseException, RegionInUseException,
33
    RegionUnfilledException, RNGInUseException, StructureInUseException,
34
    TypeMismatchException, UnknownConditionException, UnknownTypeException,
35
    UnknownTypeLengthException, WrongParameterNumberException)
36
from .enums import (
1✔
37
    DataType, RandomNumberGenerator, Commands, Condition, LogicOperation,
38
    ArithmeticOperation)
39

40
logger = FormatAdapter(logging.getLogger(__name__))
1✔
41
_ONE_SBYTE = struct.Struct("<b")
1✔
42
_ONE_WORD = struct.Struct("<I")
1✔
43
_ONE_SIGNED_INT = struct.Struct("<i")
1✔
44
_TWO_WORDS = struct.Struct("<II")
1✔
45

46

47
def _bounds(cmd, name, value, low, high):
1✔
48
    """
49
    A simple bounds checker.
50
    """
51
    if value < low or value >= high:
1✔
52
        raise ParameterOutOfBoundsException(
1✔
53
            name, value, low, high - 1, cmd.name)
54

55

56
def _typebounds(cmd, name, value, valuetype):
1✔
57
    """
58
    A simple bounds checker that uses the bounds from a type descriptor.
59
    """
60
    if valuetype not in DataType:
1!
61
        raise UnknownTypeException(valuetype, cmd.name)
×
62
    if value < valuetype.min or value > valuetype.max:
1✔
63
        raise ParameterOutOfBoundsException(
1✔
64
            name, value, valuetype.min, valuetype.max, cmd.name)
65

66

67
class _Field(IntEnum):
1✔
68
    """
69
    Various shifts for fields used with :py:func:`_binencode`.
70
    """
71
    LENGTH = 28
1✔
72
    COMMAND = 20
1✔
73
    SIGNED = 19
1✔
74
    USAGE = 16
1✔
75
    DESTINATION = 12
1✔
76
    FUNCTION = 11
1✔
77
    SOURCE_1 = 8
1✔
78
    EMPTY = 7
1✔
79
    REFERENCEABLE = 6
1✔
80
    SOURCE_2 = 4
1✔
81
    IMMEDIATE = 0
1✔
82

83

84
def _binencode(command, arguments):
1✔
85
    """
86
    Encodes commands as binary words.
87

88
    :param Commands command: The code of the command being encoded.
89
    :param  dict(_Field,int) arguments: How to parameterise the command.
90
    :return: the encoded command
91
    :rtype: bytearray
92
    """
93
    cmd_word = command.value << _Field.COMMAND
1✔
94
    for shift in arguments:
1✔
95
        if shift < 0 or shift > 31:
1!
96
            raise KeyError()
×
97
        val = arguments[shift]
1✔
98
        if isinstance(val, Enum):
1✔
99
            val = val.value
1✔
100
        else:
101
            val = int(val)
1✔
102
        cmd_word |= val << shift
1✔
103
    return bytearray(_ONE_WORD.pack(cmd_word))
1✔
104

105

106
class _MemSlot(object):
1✔
107
    """
108
    Metadata for a memory region.
109
    """
110
    __slots__ = ["label", "size", "empty"]
1✔
111

112
    def __init__(self, label, size, empty):
1✔
113
        #: Optional label for the region; str or None
114
        self.label = label
1✔
115
        # round size to a number of words
116
        if size % BYTES_PER_WORD != 0:
1✔
117
            size = size + (BYTES_PER_WORD - (size % BYTES_PER_WORD))
1✔
118
        #: The size of the region; int
119
        self.size = size
1✔
120
        #: Whether the region is to be left empty; bool
121
        self.empty = empty
1✔
122

123

124
class DataSpecificationGenerator(object):
1✔
125
    """
126
    Used to generate a SpiNNaker data specification language file that
127
    can be executed to produce a memory image.
128
    """
129
    # pylint: disable=too-many-arguments
130

131
    __slots__ = [
1✔
132
        "_spec_writer",
133
        "_report_writer",
134
        "_txt_indent",
135
        "_instruction_counter",
136
        "_mem_slots",
137
        "_function_slots",
138
        "_struct_slots",
139
        "_rng",
140
        "_random_distribution",
141
        "_conditionals",
142
        "_current_region",
143
        "_ongoing_function_definition",
144
        "_ongoing_loop"
145
    ]
146

147
    def __init__(self, spec_writer, report_writer=None):
1✔
148
        """
149
        :param ~io.RawIOBase spec_writer:
150
            The object to write the specification to
151
        :param report_writer:
152
            Determines if a text version of the specification is to be
153
            written and, if so, where. No report is written if this is `None`.
154
        :type report_writer: ~io.TextIOBase or None
155
        :raise IOError: If a write to external storage fails
156
        """
157
        if not isinstance(spec_writer, io.RawIOBase):
1!
158
            raise TypeError("spec_writer must be a RawIOBase")
×
159
        #: The object to write the specification to
160
        self._spec_writer = spec_writer
1✔
161
        if report_writer is not None and not isinstance(
1!
162
                report_writer, io.TextIOBase):
163
            raise TypeError("report_writer must be a TextIOBase or None")
×
164
        #: the writer for the human readable report
165
        self._report_writer = report_writer
1✔
166
        #: current indentation for the report writer
167
        self._txt_indent = 0
1✔
168
        #: instruction counter, for the report writer only
169
        self._instruction_counter = 0
1✔
170
        #: the memory regions; list(
171
        self._mem_slots = [None] * MAX_MEM_REGIONS
1✔
172
        #: the functions
173
        self._function_slots = [None] * MAX_CONSTRUCTORS
1✔
174
        #: the structure definitions
175
        self._struct_slots = [None] * MAX_STRUCT_SLOTS
1✔
176
        #: the random number generators
177
        self._rng = [None] * MAX_RNGS
1✔
178
        #: the random distributions
179
        self._random_distribution = [None] * MAX_RANDOM_DISTS
1✔
180
        #: stack of _conditionals, used for 'else' tracking
181
        self._conditionals = []
1✔
182
        #: the current DSG region we're writing to
183
        self._current_region = None
1✔
184
        #: whether there is a currently-being-made function definition
185
        self._ongoing_function_definition = False
1✔
186
        #: whether there is a currently executing loop
187
        self._ongoing_loop = False
1✔
188

189
    def comment(self, comment):
1✔
190
        """
191
        Write a comment to the text version of the specification.
192

193
        .. note::
194
            This is ignored by the binary file.
195

196
        :param str comment: The comment to write
197
        :raise DataUndefinedWriterException:
198
            If the binary specification file writer has not been initialised
199
        :raise IOError: If a write to external storage fails
200
        """
201
        self._write_command_to_files(
1✔
202
            bytearray(), comment, no_instruction_number=True)
203

204
    def define_break(self):
1✔
205
        """
206
        Insert command to stop execution with an exception (for debugging).
207

208
        :raise DataUndefinedWriterException:
209
            If the binary specification file writer has not been initialised
210
        :raise IOError: If a write to external storage fails
211
        """
212
        cmd_word = _binencode(Commands.BREAK, {
1✔
213
            _Field.LENGTH: LEN1})
214
        cmd_string = Commands.BREAK.name
1✔
215
        self._write_command_to_files(cmd_word, cmd_string)
1✔
216

217
    def no_operation(self):
1✔
218
        """
219
        Insert command to execute nothing.
220

221
        :raise DataUndefinedWriterException:
222
            If the binary specification file writer has not been initialised
223
        :raise IOError: If a write to external storage fails
224
        """
225
        cmd_word = _binencode(Commands.NOP, {
1✔
226
            _Field.LENGTH: LEN1})
227
        cmd_string = Commands.NOP.name
1✔
228
        self._write_command_to_files(cmd_word, cmd_string)
1✔
229
        return
1✔
230

231
    def reserve_memory_region(
1✔
232
            self, region, size, label=None, empty=False, reference=None):
233
        """
234
        Insert command to reserve a memory region.
235

236
        :param int region: The number of the region to reserve, from 0 to 15
237
        :param int size: The size to reserve for the region, in bytes
238
        :param label: An optional label for the region
239
        :type label: str or None
240
        :param bool empty: Specifies if the region will be left empty
241
        :param reference: A globally unique reference for this region
242
        :type reference: int or None
243
        :raise DataUndefinedWriterException:
244
            If the binary specification file writer has not been initialised
245
        :raise IOError: If a write to external storage fails
246
        :raise RegionInUseException: If the ``region`` was already reserved
247
        :raise ParameterOutOfBoundsException:
248
            If the ``region`` requested was out of the allowed range, or the
249
            ``size`` was too big to fit in SDRAM
250
        """
251
        _bounds(Commands.RESERVE, "memory region identifier",
1✔
252
                region, 0, MAX_MEM_REGIONS)
253
        _bounds(Commands.RESERVE, "memory size",
1✔
254
                size, 1, sdram.SDRAM.max_sdram_found)
255
        if self._mem_slots[region] is not None:
1✔
256
            raise RegionInUseException(region, self._mem_slots[region].label)
1✔
257

258
        self._mem_slots[region] = _MemSlot(label, size, empty)
1✔
259

260
        cmd_word = _binencode(Commands.RESERVE, {
1✔
261
            _Field.LENGTH: LEN2 if reference is None else LEN3,
262
            _Field.USAGE: NO_REGS,
263
            _Field.EMPTY: bool(empty),
264
            _Field.REFERENCEABLE: reference is not None,
265
            _Field.IMMEDIATE: region})
266
        encoded_size = _ONE_WORD.pack(size)
1✔
267
        encoded_ref = b""
1✔
268
        if reference is not None:
1✔
269
            encoded_ref = _ONE_WORD.pack(reference)
1✔
270

271
        cmd_string = Commands.RESERVE.name
1✔
272
        cmd_string += f" memRegion={region:d} size={size:d}"
1✔
273
        if label is not None:
1✔
274
            cmd_string += f" label='{label}'"
1✔
275
        if empty:
1✔
276
            cmd_string += " UNFILLED"
1✔
277
        if reference is not None:
1✔
278
            cmd_string += f" REF {reference:d}"
1✔
279

280
        self._write_command_to_files(
1✔
281
            cmd_word + encoded_size + encoded_ref, cmd_string)
282

283
    def reference_memory_region(self, region, ref, label=None):
1✔
284
        """
285
        Insert command to reference another memory region.
286

287
        :param int region: The number of the region to reserve, from 0 to 15
288
        :param int ref: The identifier of the region to reference
289
        :param label: An optional label for the region
290
        :type label: str or None
291
        :raise DataUndefinedWriterException:
292
            If the binary specification file writer has not been initialised
293
        :raise IOError: If a write to external storage fails
294
        :raise RegionInUseException: If the ``region`` was already reserved
295
        :raise ParameterOutOfBoundsException:
296
            If the ``region`` requested was out of the allowed range, or the
297
            ``size`` was too big to fit in SDRAM
298
        """
299
        _bounds(Commands.REFERENCE, "memory region identifier",
1✔
300
                region, 0, MAX_MEM_REGIONS)
301
        if self._mem_slots[region] is not None:
1!
302
            raise RegionInUseException(region, self._mem_slots[region].label)
×
303

304
        self._mem_slots[region] = _MemSlot(label, 0, True)
1✔
305

306
        cmd_word = _binencode(Commands.REFERENCE, {
1✔
307
            _Field.LENGTH: LEN2,
308
            _Field.IMMEDIATE: region})
309
        encoded_args = _ONE_WORD.pack(ref)
1✔
310

311
        cmd_string = Commands.REFERENCE.name
1✔
312
        cmd_string += f" memRegion={region:d} ref={ref:d}"
1✔
313
        if label is not None:
1✔
314
            cmd_string += f" label='{label}'"
1✔
315

316
        self._write_command_to_files(cmd_word + encoded_args, cmd_string)
1✔
317

318
    def free_memory_region(self, region):
1✔
319
        """
320
        Insert command to free a previously reserved memory region.
321

322
        :param int region: The number of the region to free, from 0 to 15
323
        :raise DataUndefinedWriterException:
324
            If the binary specification file writer has not been initialised
325
        :raise IOError: If a write to external storage fails
326
        :raise NotAllocatedException: If the region was not reserved
327
        :raise ParameterOutOfBoundsException:
328
            If the ``region`` requested was out of the allowed range
329
        """
330
        _bounds(Commands.FREE, "memory region identifier",
1✔
331
                region, 0, MAX_MEM_REGIONS)
332
        if self._mem_slots[region] is None:
1✔
333
            raise NotAllocatedException("region", region, Commands.FREE.name)
1✔
334

335
        label = self._mem_slots[region].label
1✔
336
        self._mem_slots[region] = None
1✔
337

338
        cmd_word = _binencode(Commands.FREE, {
1✔
339
            _Field.LENGTH: LEN1,
340
            _Field.USAGE: NO_REGS,
341
            _Field.IMMEDIATE: region})
342
        cmd_string = Commands.FREE.name
1✔
343
        cmd_string += f" memRegion={region:d}"
1✔
344
        if label is not None:
1!
345
            cmd_string += f" ({label})"
×
346
        self._write_command_to_files(cmd_word, cmd_string)
1✔
347

348
    def declare_random_number_generator(self, rng_id, rng_type, seed):
1✔
349
        """
350
        Insert command to declare a random number generator.
351

352
        :param int rng_id: The ID of the random number generator
353
        :param RandomNumberGenerator rng_type:
354
            The type of the random number generator
355
        :param int seed: The seed of the random number generator >= 0
356
        :raise DataUndefinedWriterException:
357
            If the binary specification file writer has not been initialised
358
        :raise IOError: If a write to external storage fails
359
        :raise UnknownTypeException:
360
            If the ``rng_type`` is not one of the allowed values
361
        :raise ParameterOutOfBoundsException:
362
            * If the ``seed`` is too big or too small
363
            * If the ``rng_id`` is not in the allowed range
364
        :raise RNGInUseException:
365
            If the random number generator with the given ID has already been
366
            defined
367
        """
368
        _bounds(Commands.DECLARE_RNG, "random number generator ID",
1✔
369
                rng_id, 0, MAX_RNGS)
370
        if rng_type not in RandomNumberGenerator:
1!
371
            raise UnknownTypeException(
×
372
                rng_type.value, Commands.DECLARE_RNG.name)
373

374
        if self._rng[rng_id] is not None:
1✔
375
            raise RNGInUseException(rng_id)
1✔
376
        _typebounds(Commands.DECLARE_RNG, "seed", seed, DataType.UINT32)
1✔
377

378
        self._rng[rng_id] = [rng_type, seed]
1✔
379

380
        cmd_word = _binencode(Commands.DECLARE_RNG, {
1✔
381
            _Field.LENGTH: LEN2,
382
            _Field.DESTINATION: rng_id,
383
            _Field.SOURCE_1: rng_type})
384
        encoded_seed = _ONE_WORD.pack(seed)
1✔
385

386
        cmd_string = Commands.DECLARE_RNG.name
1✔
387
        cmd_string += (
1✔
388
            f" ID={rng_id:d}, source={rng_type.value:d}, seed={seed:d}")
389
        self._write_command_to_files(cmd_word + encoded_seed, cmd_string)
1✔
390

391
    def declare_uniform_random_distribution(
1✔
392
            self, distribution_id, structure_id, rng_id, min_value, max_value):
393
        """
394
        Insert commands to declare a uniform random distribution.
395

396
        :param int distribution_id: ID of the distribution being set up
397
        :param int structure_id: ID of an empty structure slot to fill with
398
            the uniform random distribution data
399
        :param int rng_id:
400
            The ID of the random number generator, between 0 and 15
401
        :param float min_value: The minimum value that should be returned from
402
            the distribution between -32768.0 and max_value
403
        :param float max_value: The maximum value that should be returned from
404
            the distribution between min_value and 32767.9999847
405
        :raise DataUndefinedWriterException:
406
            If the binary specification file writer has not been initialised
407
        :raise IOError: If a write to external storage fails
408
        :raise NoMoreException:
409
            If there is no more space for a new random distribution
410
        :raise NotAllocatedException:
411
            If the requested ``rng_id`` has not been allocated
412
        :raise ParameterOutOfBoundsException:
413
            If ``rng_id``, ``structure_id``, ``min_value`` or ``max_value`` is
414
            out of range
415
        :raise StructureInUseException:
416
            If structure ``structure_id`` is already defined
417
        """
418
        _bounds(Commands.DECLARE_RANDOM_DIST, "distribution ID",
1✔
419
                distribution_id, 0, MAX_RANDOM_DISTS)
420
        _bounds(Commands.DECLARE_RANDOM_DIST, "rng ID",
1✔
421
                rng_id, 0, MAX_RNGS)
422
        _typebounds(Commands.DECLARE_RANDOM_DIST, "min_value",
1✔
423
                    min_value, DataType.S1615)
424
        _typebounds(Commands.DECLARE_RANDOM_DIST, "max_value",
1✔
425
                    max_value, DataType.S1615)
426
        _bounds(Commands.DECLARE_RANDOM_DIST, "structure ID",
1✔
427
                structure_id, 0, MAX_STRUCT_SLOTS)
428

429
        if self._rng[rng_id] is None:
1✔
430
            raise NotAllocatedException(
1✔
431
                "RNG", rng_id, Commands.DECLARE_RANDOM_DIST.name)
432
        if self._random_distribution[distribution_id] is not None:
1✔
433
            raise RandomNumberDistributionInUseException(distribution_id)
1✔
434

435
        parameters = [("distType", DataType.UINT32, 0),
1✔
436
                      ("rngID", DataType.UINT32, rng_id),
437
                      ("param1", DataType.S1615, min_value),
438
                      ("param2", DataType.S1615, max_value)]
439

440
        self._random_distribution[distribution_id] = parameters
1✔
441
        self.define_structure(structure_id, parameters)
1✔
442

443
        cmd_word = _binencode(Commands.DECLARE_RANDOM_DIST, {
1✔
444
            _Field.LENGTH: LEN1,
445
            _Field.SOURCE_1: distribution_id,
446
            _Field.IMMEDIATE: structure_id})
447

448
        cmd_string = Commands.DECLARE_RANDOM_DIST.name
1✔
449
        cmd_string += (
1✔
450
            f" distribution_id={distribution_id:d}"
451
            f" structure_id={structure_id:d}")
452
        self._write_command_to_files(cmd_word, cmd_string)
1✔
453

454
    def call_random_distribution(self, distribution_id, register_id):
1✔
455
        """
456
        Insert command to get the next random number from a random
457
        distribution, placing the result in a register to be used in a
458
        future call.
459

460
        :param int distribution_id:
461
            The ID of the random distribution to call between 0 and 63
462
        :param int register_id:
463
            The ID of the register to store the result in between 0 and 15
464
        :raise DataUndefinedWriterException:
465
            If the binary specification file writer has not been initialised
466
        :raise IOError: If a write to external storage fails
467
        :raise NotAllocatedException:
468
            If the random distribution ID was not previously declared
469
        :raise ParameterOutOfBoundsException:
470
            If the ``distribution_id`` or ``register_id`` specified was out
471
            of range
472
        """
473
        _bounds(Commands.GET_RANDOM_NUMBER, "register_id",
1✔
474
                register_id, 0, MAX_REGISTERS)
475
        _bounds(Commands.GET_RANDOM_NUMBER, "distribution_id",
1✔
476
                distribution_id, 0, MAX_RANDOM_DISTS)
477
        if self._random_distribution[distribution_id] is None:
1✔
478
            raise NotAllocatedException(
1✔
479
                "random number distribution", distribution_id,
480
                Commands.GET_RANDOM_NUMBER.name)
481

482
        cmd_word = _binencode(Commands.GET_RANDOM_NUMBER, {
1✔
483
            _Field.LENGTH: LEN1,
484
            _Field.USAGE: 0x4,
485
            _Field.DESTINATION: register_id,
486
            _Field.IMMEDIATE: distribution_id})
487
        cmd_string = Commands.GET_RANDOM_NUMBER.name
1✔
488
        cmd_string += (
1✔
489
            f" distribution={distribution_id:d} dest=reg[{register_id:d}]")
490
        self._write_command_to_files(cmd_word, cmd_string)
1✔
491

492
    def define_structure(self, structure_id, parameters):
1✔
493
        """
494
        Insert commands to define a data structure.
495

496
        :param int structure_id:
497
            the ID of the structure to create, between 0 and 15
498
        :param parameters: A list of between 1 and 255 tuples of
499
            `(label, data_type, value)` where:
500

501
            * `label` is the label of the element (for debugging)
502
            * `data_type` is the data type of the element
503
            * `value` is the value of the element, or `None` if no value is to
504
               be assigned
505
        :type parameters: list(tuple(str, DataType, float))
506
        :raise DataUndefinedWriterException:
507
            If the binary specification file writer has not been initialised
508
        :raise IOError: If a write to external storage fails
509
        :raise NoMoreException:
510
            If there are no more spaces for new structures
511
        :raise ParameterOutOfBoundsException:
512
            * If there are an incorrect number of parameters
513
            * If the size of one of the tuples is incorrect
514
            * If one of the values to be assigned has an integer ``data_type``
515
              but has a fractional part
516
            * If one of the values to be assigned would overflow its
517
              ``data_type``
518
        :raise UnknownTypeException:
519
            If one of the data types in the structure is unknown
520
        """
521
        # start of struct
522
        _bounds(Commands.START_STRUCT, "structure ID",
1✔
523
                structure_id, 0, MAX_STRUCT_SLOTS)
524
        _bounds(Commands.START_STRUCT, "structure elements",
1✔
525
                len(parameters), 1, MAX_STRUCT_ELEMENTS)
526
        if self._struct_slots[structure_id] is not None:
1✔
527
            raise StructureInUseException(structure_id)
1✔
528

529
        self._struct_slots[structure_id] = parameters
1✔
530
        cmd_word = _binencode(Commands.START_STRUCT, {
1✔
531
            _Field.LENGTH: LEN1,
532
            _Field.IMMEDIATE: structure_id})
533
        cmd_string = Commands.START_STRUCT.name
1✔
534
        cmd_string += f" ID={structure_id:d}"
1✔
535
        self._write_command_to_files(cmd_word, cmd_string)
1✔
536

537
        # elements of the struct
538
        for elem_index, i in enumerate(parameters):
1✔
539
            label, data_type, value = i
1✔
540
            if data_type not in DataType:
1!
541
                raise UnknownTypeException(
×
542
                    data_type.value, Commands.WRITE_PARAM.name)
543

544
            cmd_string = Commands.WRITE_PARAM.name
1✔
545
            cmd_string += (
1✔
546
                f" element_id={elem_index:d}, element_type={data_type.name}")
547
            if value is not None:
1✔
548
                _typebounds(Commands.WRITE_PARAM, "value", value, data_type)
1✔
549
                if data_type.size <= 4:
1✔
550
                    cmd_word = _binencode(Commands.STRUCT_ELEM, {
1✔
551
                        _Field.LENGTH: LEN2,
552
                        _Field.IMMEDIATE: data_type})
553
                elif data_type.size == 8:
1!
554
                    cmd_word = _binencode(Commands.STRUCT_ELEM, {
1✔
555
                        _Field.LENGTH: LEN3,
556
                        _Field.IMMEDIATE: data_type})
557
                else:
558
                    raise InvalidSizeException(
×
559
                        data_type.name, data_type.size,
560
                        Commands.STRUCT_ELEM.name)
561

562
                value_bytes = data_type.encode(value)
1✔
563
                if len(label) == 0:
1!
564
                    cmd_string += f", value={value:d}"
×
565
                else:
566
                    cmd_string += f", value={value:f}, label={label}"
1✔
567
                self._write_command_to_files(
1✔
568
                    cmd_word + value_bytes, cmd_string)
569
            else:
570
                cmd_word = _binencode(Commands.STRUCT_ELEM, {
1✔
571
                    _Field.LENGTH: LEN1,
572
                    _Field.IMMEDIATE: data_type})
573
                if len(label):
1!
574
                    cmd_string += f", label={label}"
1✔
575
                self._write_command_to_files(cmd_word, cmd_string)
1✔
576

577
        # end of struct
578
        cmd_word = _binencode(Commands.END_STRUCT, {
1✔
579
            _Field.LENGTH: LEN1})
580
        cmd_string = Commands.END_STRUCT.name
1✔
581
        cmd_string += f" ID={structure_id:d}"
1✔
582
        self._write_command_to_files(cmd_word, cmd_string)
1✔
583

584
    def get_structure_value(
1✔
585
            self, destination_id, structure_id, parameter_index,
586
            parameter_index_is_register=False):
587
        """
588
        Insert command to get a value from a structure.
589
        The value is copied in a register.
590

591
        :param int destination_id: The ID of the destination register
592
        :param int structure_id: The ID of the source structure
593
        :param int parameter_index: The ID of the parameter/element to copy
594
        :param bool parameter_index_is_register:
595
            True if ``parameter_index`` is a register ID containing the ID of
596
            the parameter/element to copy
597
        :raise DataUndefinedWriterException:
598
            If the binary specification file writer has not been initialised
599
        :raise IOError: If a write to external storage fails
600
        :raise ParameterOutOfBoundsException:
601
            * If ``structure_id`` is not in the allowed range
602
            * If ``parameter_index`` is larger than the number of parameters
603
              declared in the original structure
604
            * If ``destination_id`` is not the ID of a valid register
605
            * If ``parameter_index_is_register`` is True and
606
              ``parameter_index`` is not a valid register ID
607
        :raise NotAllocatedException:
608
            If the structure requested has not been declared
609
        """
610
        _bounds(Commands.READ_PARAM, "structure_id",
1✔
611
                structure_id, 0, MAX_STRUCT_SLOTS)
612
        _bounds(Commands.READ_PARAM, "destination_id",
1✔
613
                destination_id, 0, MAX_REGISTERS)
614
        if self._struct_slots[structure_id] is None:
1✔
615
            raise NotAllocatedException(
1✔
616
                "structure", structure_id, Commands.READ_PARAM.name)
617

618
        cmd_string = Commands.READ_PARAM.name
1✔
619
        cmd_string += f" structure_id={structure_id:d}, "
1✔
620

621
        if parameter_index_is_register is True:
1✔
622
            _bounds(Commands.READ_PARAM, "parameter_index",
1✔
623
                    parameter_index, 0, MAX_REGISTERS)
624
            cmd_word = _binencode(Commands.READ_PARAM, {
1✔
625
                _Field.LENGTH: LEN1,
626
                _Field.USAGE: DEST_AND_SRC1,
627
                _Field.DESTINATION: destination_id,
628
                _Field.SOURCE_1: parameter_index,
629
                _Field.IMMEDIATE: structure_id})
630
            cmd_string += (
1✔
631
                f"element_id_from_register={parameter_index:d}, "
632
                f"destination_register={destination_id:d}")
633
        else:
634
            _bounds(Commands.READ_PARAM, "parameter_index",
1✔
635
                    parameter_index, 0, MAX_STRUCT_ELEMENTS)
636
            if len(self._struct_slots[structure_id]) <= parameter_index:
1✔
637
                raise NotAllocatedException(
1✔
638
                    f"structure {structure_id:d} parameter",
639
                    parameter_index, Commands.READ_PARAM.name)
640
            cmd_word = _binencode(Commands.READ_PARAM, {
1✔
641
                _Field.LENGTH: LEN1,
642
                _Field.USAGE: DEST_ONLY,
643
                _Field.DESTINATION: destination_id,
644
                _Field.SOURCE_2: parameter_index,
645
                _Field.IMMEDIATE: structure_id})
646
            cmd_string += (
1✔
647
                f"element_id={parameter_index:d}, "
648
                f"destination_register={destination_id:d}")
649

650
        self._write_command_to_files(cmd_word, cmd_string)
1✔
651

652
    def set_structure_value(self, structure_id, parameter_index, value,
1✔
653
                            data_type, value_is_register=False):
654
        """
655
        Insert command to set a value in a structure.
656

657
        :param int structure_id:
658
            * If called outside of a function, the ID of the structure,
659
              between 0 and 15
660
            * If called within a function, the ID of the structure
661
              argument, between 0 and 4
662
        :param int parameter_index: The index of the value to assign in the
663
            structure, between 0 and the number of parameters declared in the
664
            structure
665
        :param float value:
666
            * If ``value_is_register`` is False, the value to assign at the
667
              selected position as a float or int
668
            * If ``value_is_register`` is True, the ID of the register
669
              containing the value to assign to the position, between 0 and 15
670
        :param DataType data_type: type of the data to be stored in the
671
            structure. If parameter ``value_is_register`` is set to true,
672
            this variable is disregarded
673
        :param bool value_is_register:
674
            Identifies if value identifies a register
675
        :raise DataUndefinedWriterException:
676
            If the binary specification file writer has not been initialised
677
        :raise IOError: If a write to external storage fails
678
        :raise ParameterOutOfBoundsException:
679
            * If ``structure_id`` is not in the allowed range
680
            * If ``parameter_index`` is larger than the number of parameters
681
              declared in the original structure
682
            * If ``value_is_register`` is False, and the data type of the
683
              structure value is an integer, and ``value`` has a fractional
684
              part
685
            * If ``value_is_register`` is False, and ``value`` would overflow
686
              the position in the structure
687
            * If ``value_is_register`` is True, and ``value`` is not a valid
688
              register ID
689
        :raise NotAllocatedException:
690
            If the structure requested has not been declared
691
        :raise UnknownTypeException: If the data type is unknown
692
        """
693
        _bounds(Commands.WRITE_PARAM, "structure_id",
1✔
694
                structure_id, 0, MAX_STRUCT_SLOTS)
695
        _bounds(Commands.WRITE_PARAM, "parameter_index",
1✔
696
                parameter_index, 0, MAX_STRUCT_ELEMENTS)
697
        if self._struct_slots[structure_id] is None:
1✔
698
            raise NotAllocatedException(
1✔
699
                "structure", structure_id, Commands.WRITE_PARAM.name)
700
        if len(self._struct_slots[structure_id]) <= parameter_index:
1✔
701
            raise NotAllocatedException(
1✔
702
                f"structure {structure_id} parameter",
703
                parameter_index, Commands.WRITE_PARAM.name)
704

705
        if self._struct_slots[
1✔
706
                structure_id][parameter_index][1] is not data_type:
707
            raise TypeMismatchException(Commands.WRITE_PARAM.name)
1✔
708

709
        cmd_string = Commands.WRITE_PARAM.name
1✔
710
        cmd_string += (
1✔
711
            f" structure_id={structure_id:d}, "
712
            f"element_id={parameter_index:d}, ")
713

714
        if value_is_register:
1✔
715
            _bounds(Commands.WRITE_PARAM, "value", value, 0, MAX_REGISTERS)
1✔
716
            cmd_word = _binencode(Commands.WRITE_PARAM, {
1✔
717
                _Field.LENGTH: LEN1,
718
                _Field.USAGE: SRC1_ONLY,
719
                _Field.DESTINATION: structure_id,
720
                _Field.SOURCE_1: value,
721
                _Field.IMMEDIATE: parameter_index})
722
            cmd_string += f"value=reg[{value:d}]"
1✔
723
            self._write_command_to_files(cmd_word, cmd_string)
1✔
724
        else:
725
            _typebounds(Commands.WRITE_PARAM, "value", value, data_type)
1✔
726
            if data_type.size > 4 and data_type.size != 8:
1!
727
                raise InvalidSizeException(
×
728
                    data_type.name, data_type.size, Commands.WRITE_PARAM.name)
729
            cmd_word = _binencode(Commands.WRITE_PARAM, {
1✔
730
                _Field.LENGTH: LEN2 if data_type.size <= 4 else LEN3,
731
                _Field.USAGE: NO_REGS,
732
                _Field.DESTINATION: structure_id,
733
                _Field.IMMEDIATE: parameter_index})
734
            value_encoded = data_type.encode(value)
1✔
735
            cmd_string += f"value={value:d}"
1✔
736
            self._write_command_to_files(cmd_word + value_encoded, cmd_string)
1✔
737

738
    def write_structure(
1✔
739
            self, structure_id, repeats=1, repeats_is_register=False):
740
        """
741
        Insert command to write a structure to the current write pointer,
742
        causing the current write pointer to move on by the number of bytes
743
        needed to represent the structure.
744

745
        :param int structure_id:
746
            * If called within a function, the ID of the structure to write,
747
              between 0 and 15
748
            * If called outside of a function, the ID of the structure
749
              argument, between 0 and 5
750
        :param int repeats:
751
            * If ``repeats_is_register`` is True, the ID of the register
752
              containing the number of repeats, between 0 and 15
753
            * If ``repeats_is_register`` is False, the number of repeats to
754
              write, between 0 and 255
755
        :param bool repeats_is_register:
756
            Whether ``repeats`` identifies a register
757
        :raise DataUndefinedWriterException:
758
            If the binary specification file writer has not been initialised
759
        :raise IOError: If a write to external storage fails
760
        :raise ParameterOutOfBoundsException:
761
            * If ``structure_id`` is not a valid structure ID
762
            * If ``repeats_is_register`` is False and ``repeats`` is not in
763
              range
764
            * If ``repeats_is_register`` is True and ``repeats`` is not a
765
              valid register ID
766
        :raise NoRegionSelectedException: If no region has been selected
767
        :raise RegionExhaustedException:
768
            If the selected region has no more space
769
        """
770
        _bounds(Commands.WRITE_STRUCT, "structure_id",
1✔
771
                structure_id, 0, MAX_STRUCT_SLOTS)
772
        if self._struct_slots[structure_id] is None:
1✔
773
            raise NotAllocatedException(
1✔
774
                "structure", structure_id, Commands.WRITE_STRUCT.name)
775

776
        cmd_string = Commands.WRITE_STRUCT.name
1✔
777
        cmd_string += f" structure_id={structure_id:d}, "
1✔
778
        if repeats_is_register:
1✔
779
            _bounds(Commands.WRITE_STRUCT, "repeats",
1✔
780
                    repeats, 0, MAX_REGISTERS)
781
            cmd_word = _binencode(Commands.WRITE_STRUCT, {
1✔
782
                _Field.LENGTH: LEN1,
783
                _Field.USAGE: SRC1_ONLY,
784
                _Field.SOURCE_1: repeats,
785
                _Field.IMMEDIATE: structure_id})
786
            cmd_string += f"repeats=reg[{repeats:d}]"
1✔
787
        else:
788
            _bounds(Commands.WRITE_STRUCT, "repeats",
1✔
789
                    repeats, 0, MAX_STRUCT_SLOTS)
790
            cmd_word = _binencode(Commands.WRITE_STRUCT, {
1✔
791
                _Field.LENGTH: LEN1,
792
                _Field.SOURCE_1: repeats,
793
                _Field.IMMEDIATE: structure_id})
794
            cmd_string += f"repeats={repeats:d}"
1✔
795
        self._write_command_to_files(cmd_word, cmd_string)
1✔
796

797
    def start_function(self, function_id, argument_by_value):
1✔
798
        """
799
        Insert command to start a function definition, with up to 5 arguments,
800
        which are the IDs of structures to be used within the function, each
801
        of which can be passed by reference or by value. In the commands
802
        following this command up to the :meth:`end_function` command,
803
        structures can only be referenced using the numbers 1 to 5 which
804
        address the arguments, rather than the original structure IDs.
805

806
        :param int function_id: The ID of the function currently defined.
807
        :param list(bool) argument_by_value: A list of up to 5 booleans
808
            indicating if the structure to be passed as an argument is to be
809
            passed by reference (i.e., changes made within the function are
810
            persisted) or by value (i.e., changes made within the function
811
            are lost when the function exits. The number of arguments is
812
            determined by the length of this list.
813
        :raise ParameterOutOfBoundsException:
814
            If there are too many items in the list of arguments
815
        :raise InvalidCommandException:
816
            If there is already a function being defined at this point
817
        :raise FunctionInUseException: If the function is already defined
818
        """
819
        if self._ongoing_function_definition:
1✔
820
            raise InvalidCommandException(Commands.START_CONSTRUCTOR.name)
1✔
821
        _bounds(Commands.START_CONSTRUCTOR, "number of arguments",
1✔
822
                len(argument_by_value), 0, 6)
823
        _bounds(Commands.START_CONSTRUCTOR, "function_id",
1✔
824
                function_id, 0, MAX_CONSTRUCTORS)
825
        if self._function_slots[function_id] is not None:
1✔
826
            raise FunctionInUseException(function_id)
1✔
827

828
        self._function_slots[function_id] = argument_by_value
1✔
829

830
        cmd_string = Commands.START_CONSTRUCTOR.name
1✔
831
        cmd_string += (
1✔
832
            f" ID={function_id:d} number_of_args={len(argument_by_value):d}")
833

834
        self._ongoing_function_definition = True
1✔
835

836
        read_only_flags = 0
1✔
837
        for i, abv in enumerate(argument_by_value):
1✔
838
            cmd_string += f" arg[{i + 1:d}]="
1✔
839
            if abv:
1✔
840
                read_only_flags |= 1 << i
1✔
841
                cmd_string += "read-only"
1✔
842
            else:
843
                cmd_string += "read-write"
1✔
844

845
        cmd_word = _binencode(Commands.START_CONSTRUCTOR, {
1✔
846
            _Field.LENGTH: LEN1,
847
            _Field.FUNCTION: function_id,
848
            _Field.SOURCE_1: len(argument_by_value),
849
            _Field.IMMEDIATE: read_only_flags})
850
        self._write_command_to_files(cmd_word, cmd_string, indent=True)
1✔
851

852
    def end_function(self):
1✔
853
        """
854
        Insert command to mark the end of a function definition.
855

856
        :raise InvalidCommandException:
857
            If there is no function being defined at this point
858
        """
859
        if not self._ongoing_function_definition:
1✔
860
            raise InvalidCommandException(Commands.END_CONSTRUCTOR.name)
1✔
861

862
        self._ongoing_function_definition = False
1✔
863

864
        cmd_word = _binencode(Commands.END_CONSTRUCTOR, {
1✔
865
            _Field.LENGTH: LEN1})
866
        cmd_string = Commands.END_CONSTRUCTOR.name
1✔
867
        self._write_command_to_files(cmd_word, cmd_string, outdent=True)
1✔
868

869
    def call_function(self, function_id, structure_ids):
1✔
870
        """
871
        Insert command to call a function.
872

873
        :param int function_id:
874
            The ID of a previously defined function, between 0 and 31
875
        :param structure_ids: A list of structure IDs that will be passed into
876
            the function, each between 0 and 15
877
        :type structure_ids: list(int)
878
        :raise DataUndefinedWriterException:
879
            If the binary specification file writer has not been initialised
880
        :raise IOError: If a write to external storage fails
881
        :raise ParameterOutOfBoundsException:
882
            * If the function ID is not valid
883
            * If any of the structure IDs are not valid
884
        :raise NotAllocatedException:
885
            * If a function has not been defined with the given ID
886
            * If no structure has been defined with one of the IDs in
887
              ``structure_ids``
888
        :raise WrongParameterNumberException:
889
            If a function is called with a wrong number of parameters
890
        :raise DuplicateParameterException:
891
            If a function is called with duplicate parameters
892
        """
893
        _bounds(Commands.CONSTRUCT, "function",
1✔
894
                function_id, 0, MAX_CONSTRUCTORS)
895
        if self._function_slots[function_id] is None:
1✔
896
            raise NotAllocatedException(
1✔
897
                "function", function_id, Commands.CONSTRUCT.name)
898
        if len(structure_ids) != len(self._function_slots[function_id]):
1✔
899
            raise WrongParameterNumberException(
1✔
900
                function_id, len(self._function_slots[function_id]),
901
                structure_ids)
902
        if len(structure_ids) != len(set(structure_ids)):
1✔
903
            raise DuplicateParameterException(
1✔
904
                f"{Commands.CONSTRUCT.name} {function_id}",
905
                structure_ids)
906

907
        cmd_string = Commands.CONSTRUCT.name
1✔
908
        cmd_string += f" function_id={function_id:d}"
1✔
909

910
        param_word_encoded = bytearray()
1✔
911
        cmd_word_length = LEN1
1✔
912
        if structure_ids:
1✔
913
            param_word = 0
1✔
914
            for i, struct_id in enumerate(structure_ids):
1✔
915
                _bounds(Commands.CONSTRUCT,
1✔
916
                        f"structure argument {i}",
917
                        structure_ids[i], 0, MAX_STRUCT_SLOTS)
918
                if self._struct_slots[struct_id] is None:
1✔
919
                    raise NotAllocatedException(
1✔
920
                        f"structure argument {i}",
921
                        struct_id, Commands.CONSTRUCT.name)
922

923
                param_word |= struct_id << (6 * i)
1✔
924
                cmd_string += f" arg[{i:d}]=struct[{struct_id:d}]"
1✔
925

926
            cmd_word_length = LEN2
1✔
927
            param_word_encoded += _ONE_WORD.pack(param_word)
1✔
928

929
        cmd_word = _binencode(Commands.CONSTRUCT, {
1✔
930
            _Field.LENGTH: cmd_word_length,
931
            _Field.SOURCE_1: function_id})
932
        self._write_command_to_files(cmd_word + param_word_encoded, cmd_string)
1✔
933

934
    def read_value(self, dest_id, data_type):
1✔
935
        """
936
        Insert command to read a value from the current write pointer, causing
937
        the write pointer to move by the number of bytes read. The data is
938
        stored in a register passed as argument.
939

940
        :param int dest_id: The ID of the destination register.
941
        :param DataType data_type: The type of the data to be read.
942
        :raise ParameterOutOfBoundsException:
943
            If ``dest_id`` is out of range for a register ID
944
        """
945
        if data_type not in DataType:
1!
946
            raise UnknownTypeException(
×
947
                data_type.value, Commands.READ.name)
948
        _bounds(Commands.READ, "register", dest_id, 0, MAX_REGISTERS)
1✔
949

950
        cmd_word = _binencode(Commands.READ, {
1✔
951
            _Field.LENGTH: LEN1,
952
            _Field.USAGE: DEST_ONLY,
953
            _Field.DESTINATION: dest_id,
954
            _Field.IMMEDIATE: data_type.size})
955
        cmd_string = Commands.READ.name
1✔
956
        cmd_string += f" {data_type.size:d} bytes in register {dest_id:d}"
1✔
957
        self._write_command_to_files(cmd_word, cmd_string)
1✔
958

959
    def create_cmd(self, data, data_type=DataType.UINT32):
1✔
960
        """
961
        Creates command to write a value to the current write pointer, causing
962
        the write pointer to move on by the number of bytes required to
963
        represent the data type. The data is passed as a parameter to this
964
        function.
965

966
        .. note::
967
            This does not actually insert the ``WRITE`` command in the spec;
968
            that is done by :py:meth:`write_cmd`.
969

970
        :param data: the data to write.
971
        :type data: int or float
972
        :param DataType data_type: the type to convert ``data`` to
973
        :return: ``cmd_word_list`` (binary data to be added to the binary data
974
            specification file), and ``cmd_string`` (string describing the
975
            command to be added to the report for the data specification file)
976
        :rtype: tuple(bytearray, str)
977
        :raise ParameterOutOfBoundsException:
978
            * If ``data_type`` is an integer type, and ``data`` has a
979
              fractional part
980
            * If ``data`` would overflow the data type
981
        :raise UnknownTypeException: If the data type is not known
982
        :raise InvalidSizeException: If the data size is invalid
983
        """
984
        _typebounds(Commands.WRITE, "data", data, data_type)
1✔
985

986
        data_size = data_type.size
1✔
987
        if data_size == 1:
1!
988
            cmd_data_len = LEN2
×
989
            data_len = 0
×
990
        elif data_size == 2:
1!
991
            cmd_data_len = LEN2
×
992
            data_len = 1
×
993
        elif data_size == 4:
1✔
994
            cmd_data_len = LEN2
1✔
995
            data_len = 2
1✔
996
        elif data_size == 8:
1!
997
            cmd_data_len = LEN3
1✔
998
            data_len = 3
1✔
999
        else:
1000
            raise InvalidSizeException(
×
1001
                data_type.name, data_size, Commands.WRITE.name)
1002

1003
        cmd_string = None
1✔
1004
        if self._report_writer is not None:
1✔
1005
            cmd_string = Commands.WRITE.name
1✔
1006
            cmd_string += f" data={data}"
1✔
1007

1008
        repeat_reg_usage = NO_REGS
1✔
1009
        cmd_word = _binencode(Commands.WRITE, {
1✔
1010
            _Field.LENGTH: cmd_data_len,
1011
            _Field.USAGE: repeat_reg_usage,
1012
            _Field.DESTINATION: data_len,
1013
            _Field.IMMEDIATE: 1})
1014
        # 1 is based on parameters = 0, repeats = 1 and parameters |= repeats
1015

1016
        cmd_word_list = cmd_word + data_type.encode(data)
1✔
1017
        if self._report_writer is not None:
1✔
1018
            cmd_string += f", dataType={data_type.name}"
1✔
1019
        return (cmd_word_list, cmd_string)
1✔
1020

1021
    def write_value(self, data, data_type=DataType.UINT32):
1✔
1022
        """
1023
        Insert command to write a value (once) to the current write pointer,
1024
        causing the write pointer to move on by the number of bytes required
1025
        to represent the data type. The data is passed as a parameter to this
1026
        function
1027

1028
        .. note::
1029
            This method used to have two extra parameters ``repeats`` and
1030
            ``repeats_is_register``. They have been removed here. If you need
1031
            them, use :meth:`write_repeated_value`
1032

1033
        :param data: the data to write as a float.
1034
        :type data: int or float
1035
        :param DataType data_type: the type to convert ``data`` to
1036
        :raise DataUndefinedWriterException:
1037
            If the binary specification file writer has not been initialised
1038
        :raise IOError: If a write to external storage fails
1039
        :raise ParameterOutOfBoundsException:
1040
            * If ``data_type`` is an integer type, and ``data`` has a
1041
              fractional part
1042
            * If ``data`` would overflow the data type
1043
        :raise UnknownTypeException: If the data type is not known
1044
        :raise InvalidSizeException: If the data size is invalid
1045
        :raise NoRegionSelectedException: If no region has been selected
1046
        """
1047
        if self._current_region is None:
1✔
1048
            raise NoRegionSelectedException(Commands.WRITE.name)
1✔
1049
        cmd_word_list, cmd_string = self.create_cmd(data, data_type)
1✔
1050
        self._write_command_to_files(cmd_word_list, cmd_string)
1✔
1051

1052
    def write_cmd(self, cmd_word_list, cmd_string):
1✔
1053
        """
1054
        Applies write commands created previously created (and cached).
1055

1056
        .. note::
1057
            See :meth:`create_cmd` for how to create the arguments to
1058
            this method.
1059

1060
        :param bytearray cmd_word_list: list of binary words to be added to
1061
            the binary data specification file
1062
        :param str cmd_string: string describing the command to be added to
1063
            the report for the data specification file
1064
        :raise IOError: If a write to external storage fails
1065
        :raise NoRegionSelectedException: If no region has been selected
1066
        """
1067
        if self._current_region is None:
×
1068
            raise NoRegionSelectedException(Commands.WRITE.name)
×
1069
        self._write_command_to_files(cmd_word_list, cmd_string)
×
1070

1071
    def write_repeated_value(
1✔
1072
            self, data, repeats=1, repeats_is_register=False,
1073
            data_type=DataType.UINT32):
1074
        """
1075
        Insert command to write a value one or more times to the current write
1076
        pointer, causing the write pointer to move on by the number of bytes
1077
        required to represent the data type. The data is passed as a parameter
1078
        to this function
1079

1080
        :param data: the data to write as a float.
1081
        :type data: float or int
1082
        :param int repeats:
1083
            * If ``repeats_is_register`` is False, this parameter identifies
1084
              the number of times to repeat the data, between 1 and 255
1085
              (default 1)
1086
            * If ``repeats_is_register`` is True, this parameter identifies
1087
              the register that contains the number of repeats.
1088
        :param bool repeats_is_register:
1089
            Indicates if the parameter ``repeats`` identifies the register
1090
            containing the number of repeats of the value to write
1091
        :param DataType data_type: the type to convert data to
1092
        :raise DataUndefinedWriterException:
1093
            If the binary specification file writer has not been initialised
1094
        :raise IOError: If a write to external storage fails
1095
        :raise ParameterOutOfBoundsException:
1096
            * If ``repeats_is_register`` is False, and ``repeats`` is out of
1097
              range
1098
            * If ``repeats_is_register`` is True, and ``repeats`` is not a
1099
              valid register ID
1100
            * If ``data_type`` is an integer type, and ``data`` has a
1101
              fractional part
1102
            * If ``data`` would overflow the data type
1103
        :raise UnknownTypeException: If the data type is not known
1104
        :raise InvalidSizeException: If the data size is invalid
1105
        :raise NoRegionSelectedException: If no region has been selected
1106
        """
1107
        if self._current_region is None:
1!
1108
            raise NoRegionSelectedException(Commands.WRITE.name)
×
1109
        _typebounds(Commands.WRITE, "data", data, data_type)
1✔
1110

1111
        data_size = data_type.size
1✔
1112
        if data_size == 1:
1✔
1113
            cmd_data_len = LEN2
1✔
1114
            data_len = 0
1✔
1115
        elif data_size == 2:
1✔
1116
            cmd_data_len = LEN2
1✔
1117
            data_len = 1
1✔
1118
        elif data_size == 4:
1✔
1119
            cmd_data_len = LEN2
1✔
1120
            data_len = 2
1✔
1121
        elif data_size == 8:
1!
1122
            cmd_data_len = LEN3
1✔
1123
            data_len = 3
1✔
1124
        else:
1125
            raise InvalidSizeException(
×
1126
                data_type.name, data_size, Commands.WRITE.name)
1127

1128
        if repeats_is_register is False:
1✔
1129
            _bounds(Commands.WRITE, "repeats", repeats, 1, 256)
1✔
1130
        else:
1131
            _bounds(Commands.WRITE, "repeats", repeats, 0, MAX_REGISTERS)
1✔
1132

1133
        parameters = 0
1✔
1134
        cmd_string = Commands.WRITE.name
1✔
1135
        cmd_string += f" data={data}"
1✔
1136

1137
        if repeats_is_register:
1✔
1138
            repeat_reg_usage = 1
1✔
1139
            parameters |= (repeats << 4)
1✔
1140
            cmd_string += f", repeats=reg[{repeats:d}]"
1✔
1141
        else:
1142
            repeat_reg_usage = NO_REGS
1✔
1143
            parameters |= repeats & 0xFF
1✔
1144
            cmd_string += f", repeats={repeats:d}"
1✔
1145

1146
        cmd_word = _binencode(Commands.WRITE, {
1✔
1147
            _Field.LENGTH: cmd_data_len,
1148
            _Field.USAGE: repeat_reg_usage,
1149
            _Field.DESTINATION: data_len,
1150
            _Field.IMMEDIATE: parameters})
1151
        data_word = data_type.encode(data)
1✔
1152
        cmd_string += f", dataType={data_type.name}"
1✔
1153
        self._write_command_to_files(cmd_word + data_word, cmd_string)
1✔
1154

1155
    def write_value_from_register(
1✔
1156
            self, data_register, repeats=1, repeats_is_register=False,
1157
            data_type=DataType.UINT32):
1158
        """
1159
        Insert command to write a value one or more times at the write pointer
1160
        of the current memory region, causing it to move. The data is contained
1161
        in a register whose ID is passed to the function.
1162

1163
        :param int data_register:
1164
            Identifies the register in which the data is stored.
1165
        :param int repeats:
1166
            * If ``repeats_is_register`` is `None`, this parameter identifies
1167
              the number of times to repeat the data, between 1 and 255
1168
              (default 1)
1169
            * If ``repeats_is_register`` is not `None` (i.e. has an integer
1170
              value), the content of this parameter is disregarded
1171
        :param bool repeats_is_register: Identifies if ``repeats`` is the
1172
            register containing the number of repeats of the value to write
1173
        :param DataType data_type: the type of the data held in the register
1174
        :raise DataUndefinedWriterException:
1175
            If the binary specification file writer has not been initialised
1176
        :raise IOError: If a write to external storage fails
1177
        :raise ParameterOutOfBoundsException:
1178
            * If ``repeats_is_register`` is False, and ``repeats`` is out of
1179
              range
1180
            * If ``repeats_is_register`` is True, and ``repeats`` is not a
1181
              valid register ID
1182
            * If ``data_register`` is not a valid register ID
1183
        :raise UnknownTypeException: If the data type is not known
1184
        :raise NoRegionSelectedException: If no region has been selected
1185
        :raise RegionExhaustedException:
1186
            If the selected region has no more space
1187
        """
1188
        if data_type not in DataType:
1!
1189
            raise UnknownTypeException(
×
1190
                data_type.value, Commands.WRITE.name)
1191
        if self._current_region is None:
1✔
1192
            raise NoRegionSelectedException(Commands.WRITE.name)
1✔
1193

1194
        data_size = data_type.size
1✔
1195
        if data_size == 1:
1✔
1196
            cmd_data_len = 0
1✔
1197
        elif data_size == 2:
1✔
1198
            cmd_data_len = 1
1✔
1199
        elif data_size == 4:
1!
1200
            cmd_data_len = 2
1✔
1201
        elif data_size == 8:
×
1202
            cmd_data_len = 3
×
1203
        else:
1204
            raise InvalidSizeException(
×
1205
                data_type.name, data_size, Commands.WRITE.name)
1206

1207
        if repeats_is_register is False:
1✔
1208
            _bounds(Commands.WRITE, "repeats", repeats, 1, 256)
1✔
1209
        else:
1210
            _bounds(Commands.WRITE, "repeats", repeats, 0, MAX_REGISTERS)
1✔
1211
        _bounds(Commands.WRITE, "data_register",
1✔
1212
                data_register, 0, MAX_REGISTERS)
1213

1214
        cmd_string = Commands.WRITE.name
1✔
1215
        cmd_string += f" data=reg[{data_register:d}]"
1✔
1216
        if repeats_is_register:
1✔
1217
            reg_usage = SRC1_AND_SRC2
1✔
1218
            parameters = repeats << 4
1✔
1219
            cmd_string += f", repeats=reg[{repeats:d}]"
1✔
1220
        else:
1221
            reg_usage = SRC1_ONLY
1✔
1222
            parameters = repeats & 0xFF
1✔
1223
            cmd_string += f", repeats={repeats:d}"
1✔
1224

1225
        cmd_word = _binencode(Commands.WRITE, {
1✔
1226
            _Field.LENGTH: LEN1,
1227
            _Field.USAGE: reg_usage,
1228
            _Field.DESTINATION: cmd_data_len,
1229
            _Field.SOURCE_1: data_register,
1230
            _Field.IMMEDIATE: parameters})
1231
        cmd_string += f", dataType={data_type.name}"
1✔
1232
        self._write_command_to_files(cmd_word, cmd_string)
1✔
1233

1234
    def write_array(self, array_values, data_type=DataType.UINT32):
1✔
1235
        """
1236
        Insert command to write an array, causing the write pointer
1237
        to move on by (data type size * the array size), in bytes.
1238

1239
        :param array_values: An array of words to be written
1240
        :type array_values: list(int) or list(float) or ~numpy.ndarray
1241
        :param DataType data_type: Type of data contained in the array
1242
        :raise DataUndefinedWriterException:
1243
            If the binary specification file writer has not been initialised
1244
        :raise IOError: If a write to external storage fails
1245
        :raise NoRegionSelectedException: If no region has been selected
1246
        """
1247
        if data_type.numpy_typename is None:
1!
1248
            raise TypeMismatchException(Commands.WRITE_ARRAY.name)
×
1249
        if self._current_region is None:
1✔
1250
            raise NoRegionSelectedException(Commands.WRITE_ARRAY.name)
1✔
1251

1252
        data = numpy.array(array_values, dtype=data_type.numpy_typename)
1✔
1253
        size = data.size * data_type.size
1✔
1254

1255
        if size % 4 != 0:
1!
1256
            raise UnknownTypeLengthException(size, Commands.WRITE_ARRAY.name)
×
1257

1258
        cmd_word = _binencode(Commands.WRITE_ARRAY, {
1✔
1259
            _Field.LENGTH: LEN2,
1260
            _Field.IMMEDIATE: data_type.size})
1261
        cmd_string = Commands.WRITE_ARRAY.name
1✔
1262
        cmd_string += f" {size // 4:d} elements\n"
1✔
1263
        cmd_string += str(list(array_values))
1✔
1264
        arg_word = _ONE_WORD.pack(size // 4)
1✔
1265
        self._write_command_to_files(cmd_word + arg_word, cmd_string)
1✔
1266
        self._spec_writer.write(data.tostring())
1✔
1267

1268
    def switch_write_focus(self, region):
1✔
1269
        """
1270
        Insert command to switch the region being written to.
1271

1272
        :param int region: The ID of the region to switch to, between 0 and 15
1273
        :raise DataUndefinedWriterException:
1274
            If the binary specification file writer has not been initialised
1275
        :raise IOError: If a write to external storage fails
1276
        :raise ParameterOutOfBoundsException:
1277
            If the region identifier is not valid
1278
        :raise NotAllocatedException: If the region has not been allocated
1279
        :raise RegionUnfilledException:
1280
            If the selected region should not be filled
1281
        """
1282
        _bounds(Commands.SWITCH_FOCUS, "region", region, 0, MAX_MEM_REGIONS)
1✔
1283
        if self._mem_slots[region] is None:
1✔
1284
            raise NotAllocatedException(
1✔
1285
                "region", region, Commands.SWITCH_FOCUS.name)
1286
        if self._mem_slots[region].empty:
1✔
1287
            raise RegionUnfilledException(region, Commands.SWITCH_FOCUS.name)
1✔
1288

1289
        self._current_region = region
1✔
1290

1291
        cmd_string = Commands.SWITCH_FOCUS.name
1✔
1292
        cmd_string += f" memRegion = {region:d}"
1✔
1293
        # Write command to switch focus:
1294
        cmd_word = _binencode(Commands.SWITCH_FOCUS, {
1✔
1295
            _Field.LENGTH: LEN1,
1296
            _Field.USAGE: 0x0,
1297
            _Field.SOURCE_1: region})
1298
        self._write_command_to_files(cmd_word, cmd_string)
1✔
1299

1300
    def start_loop(self, counter_register_id, start, end, increment=1,
1✔
1301
                   start_is_register=False, end_is_register=False,
1302
                   increment_is_register=False):
1303
        """
1304
        Insert command to start a loop.
1305

1306
        :param int counter_register_id: The ID of the register to use as the
1307
            loop counter, between 0 and 15
1308
        :param int start:
1309
            * If ``start_is_register`` is False, the number at which to start
1310
              the loop counter, >= 0
1311
            * If ``start_is_register`` is True, the ID of the register
1312
              containing the number at which to start the loop counter,
1313
              between 0 and 15
1314
        :param int end:
1315
            * If ``end_is_register`` is False, the number which the loop
1316
              counter must reach to stop the loop i.e. the loop will run while
1317
              the contents of ``counter_register`` < ``end``, >= 0
1318
            * If ``end_is_register`` is True, the ID of the register
1319
              containing the number at which to stop the loop, between 0 and 15
1320
        :param int increment:
1321
            * If ``increment_is_register`` is False, the amount by which to
1322
              increment the loop counter on each run of the loop, >= 0
1323
            * If ``increment_is_register`` is True, the ID of the register
1324
              containing the amount by which to increment the loop counter on
1325
              each run of the loop, between 0 and 15
1326
        :param bool start_is_register: Indicates if ``start`` is a register ID
1327
        :param bool end_is_register: Indicates if ``end`` is a register ID
1328
        :param bool increment_is_register:
1329
            Indicates if ``increment`` is a register ID
1330
        :raise DataUndefinedWriterException:
1331
            If the binary specification file writer has not been initialised
1332
        :raise IOError: If a write to external storage fails
1333
        :raise ParameterOutOfBoundsException:
1334
            * If ``counter_register_id`` is not a valid register ID
1335
            * If ``start_is_register`` is True and ``start`` is not a valid
1336
              register ID
1337
            * If ``end_is_register`` is True and ``end`` is not a valid
1338
              register ID
1339
            * If ``increment_is_register`` is True, and ``increment`` is not a
1340
              valid register ID
1341
            * If ``start_is_register is False and ``start`` is not in the
1342
              allowed range
1343
            * If ``end_is_register is False and ``end`` is not in the allowed
1344
              range
1345
            * If ``increment_is_register`` is False and ``increment`` is not
1346
              in the allowed range
1347
        """
1348
        _bounds(Commands.LOOP, "counter_register_id",
1✔
1349
                counter_register_id, 0, MAX_REGISTERS)
1350

1351
        bit_field = 0
1✔
1352
        length = LEN1
1✔
1353
        encoded_values = bytearray()
1✔
1354
        cmd_string = Commands.LOOP.name
1✔
1355
        cmd_string += f" counter_register_id=reg[{counter_register_id:d}],"
1✔
1356
        r1 = r2 = r3 = 0
1✔
1357

1358
        if start_is_register:
1✔
1359
            _bounds(Commands.LOOP, "start", start, 0, MAX_REGISTERS)
1✔
1360
            bit_field |= 0x4
1✔
1361
            r1 = start
1✔
1362
            cmd_string += f" start=reg[{start:d}],"
1✔
1363
        else:
1364
            _typebounds(Commands.LOOP, "start", start, DataType.INT32)
1✔
1365
            length += 1
1✔
1366
            encoded_values += _ONE_SIGNED_INT.pack(start)
1✔
1367
            cmd_string += f" start={start:d},"
1✔
1368

1369
        if end_is_register:
1✔
1370
            _bounds(Commands.LOOP, "end", end, 0, MAX_REGISTERS)
1✔
1371
            bit_field |= 0x2
1✔
1372
            r2 = end
1✔
1373
            cmd_string += f" end=reg[{end:d}],"
1✔
1374
        else:
1375
            _typebounds(Commands.LOOP, "end", end, DataType.INT32)
1✔
1376
            length += 1
1✔
1377
            encoded_values += _ONE_SIGNED_INT.pack(end)
1✔
1378
            cmd_string += f" end={end:d},"
1✔
1379

1380
        if increment_is_register:
1✔
1381
            _bounds(Commands.LOOP, "increment", increment, 0, MAX_REGISTERS)
1✔
1382
            bit_field |= 0x1
1✔
1383
            r3 = increment
1✔
1384
            cmd_string += f" increment=reg[{increment:d}],"
1✔
1385
        else:
1386
            _typebounds(Commands.LOOP, "increment", increment, DataType.INT32)
1✔
1387
            length += 1
1✔
1388
            encoded_values += _ONE_SIGNED_INT.pack(increment)
1✔
1389
            cmd_string += f" increment={increment:d},"
1✔
1390

1391
        self._ongoing_loop = True
1✔
1392

1393
        cmd_word = _binencode(Commands.LOOP, {
1✔
1394
            _Field.LENGTH: length,
1395
            _Field.USAGE: bit_field,
1396
            _Field.DESTINATION: r1,   # non-standard usage
1397
            _Field.SOURCE_1: r2,      # non-standard usage
1398
            _Field.SOURCE_2: r3,      # non-standard usage
1399
            _Field.IMMEDIATE: counter_register_id})
1400
        self._write_command_to_files(cmd_word + encoded_values, cmd_string)
1✔
1401

1402
    def break_loop(self):
1✔
1403
        """
1404
        Insert command to break out of a loop before it has completed.
1405

1406
        :raise DataUndefinedWriterException:
1407
            If the binary specification file writer has not been initialised
1408
        :raise IOError: If a write to external storage fails
1409
        :raise InvalidCommandException:
1410
            If there is no loop in operation at this point
1411
        """
1412
        if not self._ongoing_loop:
1✔
1413
            raise InvalidCommandException(Commands.BREAK_LOOP.name)
1✔
1414

1415
        cmd_word = _binencode(Commands.BREAK_LOOP, {
1✔
1416
            _Field.LENGTH: LEN1})
1417
        cmd_string = Commands.BREAK_LOOP.name
1✔
1418
        self._write_command_to_files(cmd_word, cmd_string)
1✔
1419

1420
    def end_loop(self):
1✔
1421
        """
1422
        Insert command to indicate that this is the end of the loop.
1423
        Commands between the start of the loop and this command will be
1424
        repeated.
1425

1426
        :raise DataUndefinedWriterException:
1427
            If the binary specification file writer has not been initialised
1428
        :raise IOError: If a write to external storage fails
1429
        :raise InvalidCommandException:
1430
            If there is no loop in operation at this point
1431
        """
1432
        if not self._ongoing_loop:
1✔
1433
            raise InvalidCommandException(Commands.END_LOOP.name)
1✔
1434
        cmd_word = _binencode(Commands.END_LOOP, {
1✔
1435
            _Field.LENGTH: LEN1})
1436
        cmd_string = Commands.END_LOOP.name
1✔
1437
        self._ongoing_loop = False
1✔
1438
        self._write_command_to_files(cmd_word, cmd_string)
1✔
1439

1440
    def start_conditional(self, register_id, condition, value,
1✔
1441
                          value_is_register=False):
1442
        """
1443
        Insert command to start a conditional if...then...else construct. If
1444
        the condition evaluates to true, the statement is executed up to the
1445
        next else statement, or the end of the conditional, whichever comes
1446
        first.
1447

1448
        :param int register_id: The ID of a register to compare the value of
1449
        :param Condition condition:
1450
            The condition which must be true to execute the following commands
1451
        :param int value:
1452
            * If ``value_is_register`` is False, the value to compare to the
1453
              value in the register
1454
            * If ``value_is_register`` is True, the ID of the register
1455
              containing the value to compare, between 0 and 15
1456
        :param bool value_is_register: Indicates if ``value`` is a register ID
1457
        :raise DataUndefinedWriterException:
1458
            If the binary specification file writer has not been initialised
1459
        :raise IOError: If a write to external storage fails
1460
        :raise ParameterOutOfBoundsException:
1461
            * If ``register_id`` is not a valid register ID
1462
            * if ``value_is_register`` is True and ``value`` is not a valid
1463
              register ID
1464
        :raise UnknownConditionException:
1465
            If ``condition`` is not a valid condition
1466
        """
1467
        _bounds(Commands.IF, "register_id", register_id, 0, MAX_REGISTERS)
1✔
1468
        if condition not in Condition:
1!
1469
            raise UnknownConditionException(condition, Commands.IF.name)
×
1470

1471
        data_encoded = bytearray()
1✔
1472
        cmd_string = Commands.IF.name
1✔
1473
        if value_is_register:
1✔
1474
            _bounds(Commands.IF, "value", value, 0, MAX_REGISTERS)
1✔
1475
            cmd_word = _binencode(Commands.IF, {
1✔
1476
                _Field.LENGTH: LEN1,
1477
                _Field.USAGE: 0x3,
1478
                _Field.SOURCE_1: register_id,
1479
                _Field.SOURCE_2: value,
1480
                _Field.IMMEDIATE: condition})
1481
            cmd_string += (
1✔
1482
                f" reg[{register_id:d}] {condition.operator} reg[{value:d}]")
1483
        else:
1484
            _typebounds(Commands.IF, "value", value, DataType.INT32)
1✔
1485
            cmd_word = _binencode(Commands.IF, {
1✔
1486
                _Field.LENGTH: LEN2,
1487
                _Field.USAGE: 0x2,
1488
                _Field.SOURCE_1: register_id,
1489
                _Field.IMMEDIATE: condition})
1490
            data_encoded += _ONE_SIGNED_INT.pack(value)
1✔
1491
            cmd_string += (
1✔
1492
                f" reg[{register_id:d}] {condition.operator} {value:d}")
1493

1494
        self._conditionals.append(False)
1✔
1495
        cmd_word_list = cmd_word + data_encoded
1✔
1496
        self._write_command_to_files(cmd_word_list, cmd_string, indent=True)
1✔
1497

1498
    def else_conditional(self):
1✔
1499
        """
1500
        Insert command for the else of an if...then...else construct. If the
1501
        condition of the conditional evaluates to false, the statements up
1502
        between the conditional and the insertion of this "else" are skipped,
1503
        and the statements from this point until the end of the conditional
1504
        are executed.
1505

1506
        :raise DataUndefinedWriterException:
1507
            If the binary specification file writer has not been initialised
1508
        :raise IOError: If a write to external storage fails
1509
        :raise InvalidCommandException:
1510
            If there is no conditional in operation at this point
1511
        """
1512

1513
        if not self._conditionals or \
1✔
1514
                self._conditionals[len(self._conditionals) - 1] is True:
1515
            raise InvalidCommandException(Commands.ELSE.name)
1✔
1516

1517
        self._conditionals[len(self._conditionals) - 1] = True
1✔
1518
        cmd_word = _binencode(Commands.ELSE, {
1✔
1519
            _Field.LENGTH: LEN1})
1520
        cmd_string = Commands.ELSE.name
1✔
1521
        self._write_command_to_files(
1✔
1522
            cmd_word, cmd_string, indent=True, outdent=True)
1523

1524
    def end_conditional(self):
1✔
1525
        """
1526
        Insert command to mark the end of an if...then...else construct
1527

1528
        :raise DataUndefinedWriterException:
1529
            If the binary specification file writer has not been initialised
1530
        :raise IOError: If a write to external storage fails
1531
        :raise InvalidCommandException:
1532
            If there is no conditional in operation at this point
1533
        """
1534
        if not self._conditionals:
1✔
1535
            raise InvalidCommandException(Commands.END_IF.name)
1✔
1536

1537
        self._conditionals.pop()
1✔
1538
        cmd_word = _binencode(Commands.END_IF, {
1✔
1539
            _Field.LENGTH: LEN1})
1540
        cmd_string = Commands.END_IF.name
1✔
1541
        self._write_command_to_files(cmd_word, cmd_string, outdent=True)
1✔
1542

1543
    def set_register_value(self, register_id, data, data_is_register=False,
1✔
1544
                           data_type=DataType.UINT32):
1545
        """
1546
        Insert command to set the value of a register.
1547

1548
        :param int register_id:
1549
            The ID of the register to assign, between 0 and 15
1550
        :param data:
1551
            * If ``data_is_register`` is True, the register ID containing
1552
              the data to set, between 0 and 15
1553
            * If ``data_is_register`` is False, the data is a value of the
1554
              type given by ``data_type``
1555
        :type data: int or float
1556
        :param bool data_is_register: Indicates if ``data`` is a register ID
1557
        :param DataType data_type: The type of the data to be assigned
1558
        :raise DataUndefinedWriterException:
1559
            If the binary specification file writer has not been initialised
1560
        :raise IOError: If a write to external storage fails
1561
        :raise ParameterOutOfBoundsException:
1562
            * If ``register_id`` is not a valid register ID
1563
            * If ``data_is_register`` is True, and ``data`` is not a valid
1564
              register ID
1565
            * If ``data_is_register`` is False, ``data_type`` is an integer
1566
              type and ``data`` has a fractional part
1567
            * If ``data_is_register`` if False, and ``data`` would overflow
1568
              the data type
1569
        :raise UnknownTypeException: If the data type is not known
1570
        """
1571
        _bounds(Commands.MV, "register_id", register_id, 0, MAX_REGISTERS)
1✔
1572

1573
        if data_is_register:
1✔
1574
            # Build command to move between registers:
1575
            _bounds(Commands.MV, "data", data, 0, MAX_REGISTERS)
1✔
1576
            if data == register_id:
1✔
1577
                raise DuplicateParameterException(
1✔
1578
                    Commands.MV.name, [register_id, data])
1579

1580
            cmd_word = _binencode(Commands.MV, {
1✔
1581
                _Field.LENGTH: LEN1,
1582
                _Field.USAGE: DEST_AND_SRC1,
1583
                _Field.DESTINATION: register_id,
1584
                _Field.SOURCE_1: data})
1585
            encoded_data = bytearray()
1✔
1586
            cmd_string = f"reg[{register_id:d}] = reg[{data:d}]"
1✔
1587
        else:
1588
            # Build command to assign from an immediate:
1589
            # command has a second word (the immediate)
1590
            _typebounds(Commands.MV, "data", data, data_type)
1✔
1591

1592
            cmd_word = _binencode(Commands.MV, {
1✔
1593
                _Field.LENGTH: LEN3 if data_type.size > 4 else LEN2,
1594
                _Field.USAGE: DEST_ONLY,
1595
                _Field.DESTINATION: register_id})
1596
            encoded_data = data_type.encode(data)
1✔
1597
            cmd_string = f"reg[{register_id:d}] = {data:d} (0x{data:X})"
1✔
1598

1599
        self._write_command_to_files(cmd_word + encoded_data, cmd_string)
1✔
1600

1601
    def save_write_pointer(self, register_id):
1✔
1602
        """
1603
        Insert command to save the write pointer to a register.
1604

1605
        :param int register_id:
1606
            The ID of the register to assign, between 0 and 15
1607
        :raise DataUndefinedWriterException:
1608
            If the binary specification file writer has not been initialised
1609
        :raise IOError: If a write to external storage fails
1610
        :raise ParameterOutOfBoundsException:
1611
            If the ``register_id`` is not a valid register ID
1612
        :raise NoRegionSelectedException: If no region has been selected
1613
        """
1614
        _bounds(Commands.GET_WR_PTR, "register_id",
1✔
1615
                register_id, 0, MAX_REGISTERS)
1616
        if self._current_region is None:
1✔
1617
            raise NoRegionSelectedException(Commands.GET_WR_PTR.name)
1✔
1618

1619
        cmd_word = _binencode(Commands.GET_WR_PTR, {
1✔
1620
            _Field.LENGTH: LEN1,
1621
            _Field.USAGE: 0x4,
1622
            _Field.DESTINATION: register_id})
1623
        cmd_string = Commands.GET_WR_PTR.name
1✔
1624
        cmd_string += f" reg[{register_id:d}]"
1✔
1625
        self._write_command_to_files(cmd_word, cmd_string)
1✔
1626

1627
    def set_write_pointer(self, address, address_is_register=False,
1✔
1628
                          relative_to_current=False):
1629
        """
1630
        Insert command to set the position of the write pointer within the
1631
        current region.
1632

1633
        :param int address:
1634
            * If ``address_is_register`` is True, the ID of the register
1635
              containing the address to move to
1636
            * If ``address_is_register`` is False, the address to move the
1637
              write pointer to
1638
        :param bool address_is_register:
1639
            Indicates if ``address`` is a register ID
1640
        :param bool relative_to_current:
1641
            Indicates if ``address`` (or the value read from that register
1642
            when ``address_is_register`` is True) is to be added to the
1643
            current address, or used as an absolute address from the start
1644
            of the current region
1645
        :raise DataUndefinedWriterException:
1646
            If the binary specification file writer has not been initialised
1647
        :raise IOError: If a write to external storage fails
1648
        :raise ParameterOutOfBoundsException:
1649
            If the ``address_is_register`` is True and ``address`` is not a
1650
            valid register ID
1651
        :raise NoRegionSelectedException: If no region has been selected
1652
        """
1653
        if self._current_region is None:
1✔
1654
            raise NoRegionSelectedException(Commands.SET_WR_PTR.name)
1✔
1655
        relative = bool(relative_to_current)
1✔
1656
        relative_string = "RELATIVE" if relative else "ABSOLUTE"
1✔
1657

1658
        data_encoded = bytearray()
1✔
1659
        cmd_string = Commands.SET_WR_PTR.name
1✔
1660
        if address_is_register:
1✔
1661
            _bounds(Commands.SET_WR_PTR, "address", address, 0, MAX_REGISTERS)
1✔
1662
            cmd_word = _binencode(Commands.SET_WR_PTR, {
1✔
1663
                _Field.LENGTH: LEN1,
1664
                _Field.USAGE: SRC1_ONLY,
1665
                _Field.SOURCE_1: address,
1666
                _Field.IMMEDIATE: relative})
1667
            cmd_string += f" reg[{address:d}] {relative_string}"
1✔
1668
        else:
1669
            if not relative_to_current:
1✔
1670
                _typebounds(Commands.SET_WR_PTR, "address",
1✔
1671
                            address, DataType.UINT32)
1672
                data_encoded += _ONE_WORD.pack(address)
1✔
1673
            else:
1674
                _typebounds(Commands.SET_WR_PTR, "address",
1✔
1675
                            address, DataType.INT32)
1676
                data_encoded += _ONE_SIGNED_INT.pack(address)
1✔
1677

1678
            cmd_word = _binencode(Commands.SET_WR_PTR, {
1✔
1679
                _Field.LENGTH: LEN2,
1680
                _Field.USAGE: NO_REGS,
1681
                _Field.IMMEDIATE: relative})
1682
            cmd_string += f" {address:d} {relative_string}"
1✔
1683

1684
        self._write_command_to_files(cmd_word + data_encoded, cmd_string)
1✔
1685

1686
    def align_write_pointer(self, log_block_size,
1✔
1687
                            log_block_size_is_register=False,
1688
                            return_register_id=None):
1689
        """
1690
        Insert command to align the write pointer against a power-of-2
1691
        block size in bytes.  Zeros are inserted in the intervening space
1692

1693
        :param int log_block_size:
1694
            * If ``log_block_size_is_register`` is False, log to base 2 of
1695
              the block size (e.g. The write pointer will be moved so that
1696
              it is a multiple of 2\\ :sup:`log_block_size`), between 0 and 32
1697
            * If ``log_block_size_is_register`` is True, the ID of the
1698
              register containing log to the base 2 of the block size,
1699
              between 0 and 15
1700
        :param bool log_block_size_is_register:
1701
            Indicates if ``log_block_size`` is a register ID
1702
        :param return_register_id: The ID of a register where the write
1703
            pointer will be written to once it has been updated, between
1704
            0 and 15, or `None` if no such writing is to be done
1705
        :type return_register_id: int or None
1706
        :raise DataUndefinedWriterException:
1707
            If the binary specification file writer has not been initialised
1708
        :raise IOError: If a write to external storage fails
1709
        :raise ParameterOutOfBoundsException:
1710
            * If ``log_block_size_is_register`` is False, and
1711
              ``log_block_size`` is not within the allowed range
1712
            * If ``log_block_size_is_register`` is True and
1713
              ``log_block_size`` is not a valid register ID
1714
        :raise RegionOutOfBoundsException:
1715
            If the move of the pointer would put it outside of the current
1716
            region
1717
        :raise NoRegionSelectedException: If no region has been selected
1718
        """
1719
        if self._current_region is None:
1✔
1720
            raise NoRegionSelectedException(Commands.ALIGN_WR_PTR.name)
1✔
1721

1722
        bit_field = 0
1✔
1723
        imm_value = 0
1✔
1724
        return_register_value = 0
1✔
1725
        block_size_reg = 0
1✔
1726
        cmd_string = Commands.ALIGN_WR_PTR.name
1✔
1727

1728
        if return_register_id is not None:
1✔
1729
            _bounds(Commands.ALIGN_WR_PTR, "return_register_id",
1✔
1730
                    return_register_id, 0, MAX_REGISTERS)
1731
            bit_field |= 0x4
1✔
1732
            return_register_value = return_register_id
1✔
1733
            cmd_string = f" reg[{return_register_value:d}] ="
1✔
1734

1735
        if log_block_size_is_register:
1✔
1736
            _bounds(Commands.ALIGN_WR_PTR, "log_block_size",
1✔
1737
                    log_block_size, 0, MAX_REGISTERS)
1738
            bit_field |= 0x2
1✔
1739
            block_size_reg = log_block_size
1✔
1740
            cmd_string += f" align(reg[{block_size_reg:d}])"
1✔
1741
        else:
1742
            _bounds(Commands.ALIGN_WR_PTR, "log_block_size",
1✔
1743
                    log_block_size, 0, 32)
1744
            imm_value = log_block_size
1✔
1745
            cmd_string += f" align({imm_value:d})"
1✔
1746

1747
        cmd_word = _binencode(Commands.ALIGN_WR_PTR, {
1✔
1748
            _Field.LENGTH: LEN1,
1749
            _Field.USAGE: bit_field,
1750
            _Field.DESTINATION: return_register_value,
1751
            _Field.SOURCE_1: block_size_reg,
1752
            _Field.IMMEDIATE: imm_value})
1753
        self._write_command_to_files(cmd_word, cmd_string)
1✔
1754

1755
    def call_arithmetic_operation(self, register_id, operand_1, operation,
1✔
1756
                                  operand_2, signed,
1757
                                  operand_1_is_register=False,
1758
                                  operand_2_is_register=False):
1759
        """
1760
        Insert command to perform an arithmetic operation on two signed or
1761
        unsigned values and store the result in a register
1762

1763
        :param int register_id: The ID of the register to store the result in
1764
        :param int operand_1:
1765
            * If ``operand_1_is_register`` is True, the ID of a register where
1766
              the first operand can be found, between 0 and 15
1767
            * If ``operand_1_is_register`` is False, a 32-bit value
1768
        :param ArithmeticOperation operation: The operation to perform
1769
        :param int operand_2:
1770
            * If ``operand_2_is_register`` is True, the ID of a register where
1771
              the second operand can be found, between 0 and 15
1772
            * If ``operand_2_is_register`` is False, a 32-bit value
1773
        :param bool signed: Indicates if the values should be considered signed
1774
        :param bool operand_1_is_register:
1775
            Indicates if ``operand_1`` is a register ID
1776
        :param bool operand_2_is_register:
1777
            Indicates if ``operand_2`` is a register ID
1778
        :raise DataUndefinedWriterException:
1779
            If the binary specification file writer has not been initialised
1780
        :raise IOError: If a write to external storage fails
1781
        :raise ParameterOutOfBoundsException:
1782
            * If ``register_id`` is not a valid register ID
1783
            * If ``operand_1_is_register`` is True and ``operand_1`` is not a
1784
              valid register ID
1785
            * If ``operand_2_is_register`` is True and ``operand_2`` is not a
1786
              valid register ID
1787
        :raise InvalidOperationException:
1788
            If ``operation`` is not a known operation
1789
        """
1790
        _bounds(Commands.ARITH_OP, "register_id",
1✔
1791
                register_id, 0, MAX_REGISTERS)
1792
        if operation not in ArithmeticOperation:
1✔
1793
            raise InvalidOperationException(
1✔
1794
                "arithmetic", operation.value, Commands.ARITH_OP.name)
1795

1796
        cmd_length = 0
1✔
1797
        bit_field = 0x4
1✔
1798
        register_op_1 = 0
1✔
1799
        register_op_2 = 0
1✔
1800
        encoded_operands = bytearray()
1✔
1801

1802
        cmd_string = Commands.ARITH_OP.name
1✔
1803
        cmd_string += (
1✔
1804
            f' {"SIGNED" if signed else "UNSIGNED"} reg[{register_id:d}] =')
1805

1806
        if operand_1_is_register:
1✔
1807
            _bounds(Commands.ARITH_OP, "operand_1",
1✔
1808
                    operand_1, 0, MAX_REGISTERS)
1809
            bit_field |= 2
1✔
1810
            register_op_1 = operand_1
1✔
1811
            cmd_string += f" reg[{register_op_1:d}]"
1✔
1812
        elif signed:
1✔
1813
            _typebounds(Commands.ARITH_OP, "operand_1",
1✔
1814
                        operand_1, DataType.INT32)
1815
            cmd_length += 1
1✔
1816
            encoded_operands += _ONE_SIGNED_INT.pack(operand_1)
1✔
1817
            cmd_string += f" {operand_1:d}"
1✔
1818
        else:
1819
            _typebounds(Commands.ARITH_OP, "operand_1",
1✔
1820
                        operand_1, DataType.UINT32)
1821
            cmd_length += 1
1✔
1822
            encoded_operands += _ONE_WORD.pack(operand_1)
1✔
1823
            cmd_string += f" {operand_1:d}"
1✔
1824

1825
        cmd_string += f" {operation.operator}"
1✔
1826

1827
        if operand_2_is_register:
1✔
1828
            _bounds(Commands.ARITH_OP, "operand_2",
1✔
1829
                    operand_2, 0, MAX_REGISTERS)
1830
            bit_field |= 1
1✔
1831
            register_op_2 = operand_2
1✔
1832
            cmd_string += f" reg[{register_op_2:d}]"
1✔
1833
        elif signed:
1✔
1834
            _typebounds(Commands.ARITH_OP, "operand_2",
1✔
1835
                        operand_2, DataType.INT32)
1836
            cmd_length += 1
1✔
1837
            encoded_operands += _ONE_SIGNED_INT.pack(operand_2)
1✔
1838
            cmd_string += f" {operand_2:d}"
1✔
1839
        else:
1840
            _typebounds(Commands.ARITH_OP, "operand_2",
1✔
1841
                        operand_2, DataType.UINT32)
1842
            cmd_length += 1
1✔
1843
            encoded_operands += _ONE_WORD.pack(operand_2)
1✔
1844
            cmd_string += f" {operand_2:d}"
1✔
1845

1846
        cmd_word = _binencode(Commands.ARITH_OP, {
1✔
1847
            _Field.LENGTH: cmd_length,
1848
            _Field.SIGNED: bool(signed),
1849
            _Field.USAGE: bit_field,
1850
            _Field.DESTINATION: register_id,
1851
            _Field.SOURCE_1: register_op_1,
1852
            _Field.SOURCE_2: register_op_2,
1853
            _Field.IMMEDIATE: operation})
1854
        self._write_command_to_files(cmd_word + encoded_operands, cmd_string)
1✔
1855

1856
    def logical_and(self, register_id, operand_1, operand_2,
1✔
1857
                    operand_1_is_register=False, operand_2_is_register=False):
1858
        """
1859
        Insert command to perform a logical AND operation.
1860

1861
        :param int register_id: The ID of the register to store the result in
1862
        :param int operand_1:
1863
            * If ``operand_1_is_register`` is True, the ID of a register
1864
              where the first operand can be found, between 0 and 15
1865
            * If ``operand_1_is_register`` is False, a 32-bit value
1866
        :param int operand_2:
1867
            * If ``operand_2_is_register`` is True, the ID of a register
1868
              where the second operand can be found. between 0 and 15
1869
            * If ``operand_2_is_register`` is False, a 32-bit value
1870
        :param bool operand_1_is_register:
1871
            Indicates if ``operand_1`` is a register ID
1872
        :param bool operand_2_is_register:
1873
            Indicates if ``operand_2`` is a register ID
1874
        :raise DataUndefinedWriterException:
1875
            If the binary specification file writer has not been initialised
1876
        :raise IOError: If a write to external storage fails
1877
        :raise ParameterOutOfBoundsException:
1878
            * If ``register_id`` is not a valid register ID
1879
            * If ``operand_1_is_register`` is True and ``operand_1`` is not a
1880
              valid register ID
1881
            * If ``operand_2_is_register`` is True and ``operand_2`` is not a
1882
              valid register ID
1883
        """
1884
        self._call_logic_operation(
1✔
1885
            register_id, operand_1, LogicOperation.AND, operand_2,
1886
            operand_1_is_register, operand_2_is_register)
1887

1888
    def logical_or(self, register_id, operand_1, operand_2,
1✔
1889
                   operand_1_is_register=False, operand_2_is_register=False):
1890
        """
1891
        Insert command to perform a logical OR operation.
1892

1893
        :param int register_id: The ID of the register to store the result in
1894
        :param int operand_1:
1895
            * If ``operand_1_is_register`` is True, the ID of a register
1896
              where the first operand can be found, between 0 and 15
1897
            * If ``operand_1_is_register`` is False, a 32-bit value
1898
        :param int operand_2:
1899
            * If ``operand_2_is_register`` is True, the ID of a register
1900
              where the second operand can be found. between 0 and 15
1901
            * If ``operand_2_is_register`` is False, a 32-bit value
1902
        :param bool operand_1_is_register:
1903
            Indicates if ``operand_1`` is a register ID
1904
        :param bool operand_2_is_register:
1905
            Indicates if ``operand_2`` is a register ID
1906
        :raise DataUndefinedWriterException:
1907
            If the binary specification file writer has not been initialised
1908
        :raise IOError: If a write to external storage fails
1909
        :raise ParameterOutOfBoundsException:
1910
            * If ``register_id`` is not a valid register ID
1911
            * If ``operand_1_is_register`` is True and ``operand_1`` is not a
1912
              valid register ID
1913
            * If ``operand_2_is_register`` is True and ``operand_2`` is not a
1914
              valid register ID
1915
        """
1916
        self._call_logic_operation(
1✔
1917
            register_id, operand_1, LogicOperation.OR, operand_2,
1918
            operand_1_is_register, operand_2_is_register)
1919

1920
    def logical_left_shift(self, register_id, operand_1, operand_2,
1✔
1921
                           operand_1_is_register=False,
1922
                           operand_2_is_register=False):
1923
        """
1924
        Insert command to perform a logical left shift operation.
1925

1926
        :param int register_id: The ID of the register to store the result in
1927
        :param int operand_1:
1928
            * If ``operand_1_is_register`` is True, the ID of a register
1929
              where the first operand can be found, between 0 and 15
1930
            * If ``operand_1_is_register`` is False, a 32-bit value
1931
        :param int operand_2:
1932
            * If ``operand_2_is_register`` is True, the ID of a register
1933
              where the second operand can be found. between 0 and 15
1934
            * If ``operand_2_is_register`` is False, a 32-bit value
1935
        :param bool operand_1_is_register:
1936
            Indicates if ``operand_1`` is a register ID
1937
        :param bool operand_2_is_register:
1938
            Indicates if ``operand_2`` is a register ID
1939
        :raise DataUndefinedWriterException:
1940
            If the binary specification file writer has not been initialised
1941
        :raise IOError: If a write to external storage fails
1942
        :raise ParameterOutOfBoundsException:
1943
            * If ``register_id`` is not a valid register ID
1944
            * If ``operand_1_is_register`` is True and ``operand_1`` is not a
1945
              valid register ID
1946
            * If ``operand_2_is_register`` is True and ``operand_2`` is not a
1947
              valid register ID
1948
        """
1949
        self._call_logic_operation(
1✔
1950
            register_id, operand_1, LogicOperation.LEFT_SHIFT, operand_2,
1951
            operand_1_is_register, operand_2_is_register)
1952

1953
    def logical_right_shift(self, register_id, operand_1, operand_2,
1✔
1954
                            operand_1_is_register=False,
1955
                            operand_2_is_register=False):
1956
        """
1957
        Insert command to perform a logical right shift operation.
1958

1959
        :param int register_id: The ID of the register to store the result in
1960
        :param int operand_1:
1961
            * If ``operand_1_is_register`` is True, the ID of a register
1962
              where the first operand can be found, between 0 and 15
1963
            * If ``operand_1_is_register`` is False, a 32-bit value
1964
        :param int operand_2:
1965
            * If ``operand_2_is_register`` is True, the ID of a register
1966
              where the second operand can be found. between 0 and 15
1967
            * If ``operand_2_is_register`` is False, a 32-bit value
1968
        :param bool operand_1_is_register:
1969
            Indicates if ``operand_1`` is a register ID
1970
        :param bool operand_2_is_register:
1971
            Indicates if ``operand_2`` is a register ID
1972
        :raise DataUndefinedWriterException:
1973
            If the binary specification file writer has not been initialised
1974
        :raise IOError: If a write to external storage fails
1975
        :raise ParameterOutOfBoundsException:
1976
            * If ``register_id`` is not a valid register ID
1977
            * If ``operand_1_is_register`` is True and ``operand_1`` is not a
1978
              valid register ID
1979
            * If ``operand_2_is_register`` is True and ``operand_2`` is not a
1980
              valid register ID
1981
        """
1982
        self._call_logic_operation(
1✔
1983
            register_id, operand_1, LogicOperation.RIGHT_SHIFT, operand_2,
1984
            operand_1_is_register, operand_2_is_register)
1985

1986
    def logical_xor(self, register_id, operand_1, operand_2,
1✔
1987
                    operand_1_is_register=False, operand_2_is_register=False):
1988
        """
1989
        Insert command to perform a logical XOR operation.
1990

1991
        :param int register_id: The ID of the register to store the result in
1992
        :param int operand_1:
1993
            * If ``operand_1_is_register`` is True, the ID of a register
1994
              where the first operand can be found, between 0 and 15
1995
            * If ``operand_1_is_register`` is False, a 32-bit value
1996
        :param int operand_2:
1997
            * If ``operand_2_is_register`` is True, the ID of a register
1998
              where the second operand can be found. between 0 and 15
1999
            * If ``operand_2_is_register`` is False, a 32-bit value
2000
        :param bool operand_1_is_register:
2001
            Indicates if ``operand_1`` is a register ID
2002
        :param bool operand_2_is_register:
2003
            Indicates if ``operand_2`` is a register ID
2004
        :raise DataUndefinedWriterException:
2005
            If the binary specification file writer has not been initialised
2006
        :raise IOError: If a write to external storage fails
2007
        :raise ParameterOutOfBoundsException:
2008
            * If ``register_id`` is not a valid register ID
2009
            * If ``operand_1_is_register`` is True and ``operand_1`` is not a
2010
              valid register ID
2011
            * If ``operand_2_is_register`` is True and ``operand_2`` is not a
2012
              valid register ID
2013
        """
2014
        self._call_logic_operation(
1✔
2015
            register_id, operand_1, LogicOperation.XOR, operand_2,
2016
            operand_1_is_register, operand_2_is_register)
2017

2018
    def logical_not(self, register_id, operand, operand_is_register=False):
1✔
2019
        """
2020
        Insert command to perform a logical NOT operation.
2021

2022
        :param int register_id: The ID of the register to store the result in
2023
        :param int operand:
2024
            * If ``operand_is_register`` is True, the ID of a register where
2025
              the first operand can be found, between 0 and 15
2026
            * If ``operand_is_register`` is False, a 32-bit value
2027
        :param bool operand_is_register:
2028
            Indicates if ``operand`` is a register ID
2029
        :raise DataUndefinedWriterException:
2030
            If the binary specification file writer has not been initialised
2031
        :raise IOError: If a write to external storage fails
2032
        :raise ParameterOutOfBoundsException:
2033
            * If ``register_id`` is not a valid register ID
2034
            * If ``operand_is_register`` is True and ``operand`` is not a
2035
              valid register ID
2036
        """
2037
        self._call_logic_operation(
1✔
2038
            register_id, operand, LogicOperation.NOT, 0,
2039
            operand_is_register, False)
2040

2041
    def _call_logic_operation(self, register_id, operand_1, operation,
1✔
2042
                              operand_2, operand_1_is_register=False,
2043
                              operand_2_is_register=False):
2044
        """
2045
        Insert command to perform a logic operation on two signed or
2046
        unsigned values and store the result in a register.
2047

2048
        :param int register_id: The ID of the register to store the result in
2049
        :param int operand_1:
2050
            * If ``operand_1_is_register`` is True, the ID of a register
2051
              where the first operand can be found, between 0 and 15
2052
            * If ``operand_1_is_register`` is False, a 32-bit value
2053
        :param LogicOperation operation: The operation to perform
2054
        :param int operand_2:
2055
            * If ``operand_2_is_register`` is True, the ID of a register
2056
              where the second operand can be found. between 0 and 15
2057
            * If ``operand_2_is_register`` is False, a 32-bit value
2058
        :param bool operand_1_is_register:
2059
            Indicates if ``operand_1`` is a register ID
2060
        :param bool operand_2_is_register:
2061
            Indicates if ``operand_2`` is a register ID
2062
        :raise DataUndefinedWriterException:
2063
            If the binary specification file writer has not been initialised
2064
        :raise IOError: If a write to external storage fails
2065
        :raise ParameterOutOfBoundsException:
2066
            * If ``register_id`` is not a valid register ID
2067
            * If ``operand_1_is_register`` is True and ``operand_1`` is not a
2068
              valid register ID
2069
            * If ``operand_2_is_register`` is True and ``operand_2`` is not a
2070
              valid register ID
2071
        :raise InvalidOperationException:
2072
            If ``operation`` is not a known operation
2073
        """
2074
        _bounds(Commands.LOGIC_OP, "register_id",
1✔
2075
                register_id, 0, MAX_REGISTERS)
2076
        if operation not in LogicOperation:
1!
2077
            raise InvalidOperationException(
×
2078
                "logic", operation.value, Commands.LOGIC_OP.name)
2079

2080
        cmd_length = 0
1✔
2081
        bit_field = 0x4
1✔
2082
        register_op_1 = 0
1✔
2083
        register_op_2 = 0
1✔
2084
        encoded_operands = bytearray()
1✔
2085

2086
        cmd_string = Commands.LOGIC_OP.name
1✔
2087
        cmd_string += f" reg[{register_id:d}] ="
1✔
2088
        if operation.value == LogicOperation.NOT.value:
1✔
2089
            cmd_string += f" {operation.operator}"
1✔
2090

2091
        if operand_1_is_register:
1✔
2092
            _bounds(Commands.LOGIC_OP, "operand_1",
1✔
2093
                    operand_1, 0, MAX_REGISTERS)
2094
            bit_field |= 2
1✔
2095
            register_op_1 = operand_1
1✔
2096
            cmd_string += f" reg[{register_op_1:d}]"
1✔
2097
        else:
2098
            cmd_length += 1
1✔
2099
            _typebounds(Commands.LOGIC_OP, "operand_1",
1✔
2100
                        operand_1, DataType.UINT32)
2101
            encoded_operands += _ONE_WORD.pack(operand_1)
1✔
2102
            cmd_string += f" {operand_1}"
1✔
2103

2104
        if operation.value != LogicOperation.NOT.value:
1✔
2105
            cmd_string += f" {operation.operator}"
1✔
2106
            if operand_2_is_register:
1✔
2107
                _bounds(Commands.LOGIC_OP, "operand_2",
1✔
2108
                        operand_2, 0, MAX_REGISTERS)
2109
                bit_field |= 1
1✔
2110
                register_op_2 = operand_2
1✔
2111
                cmd_string += f" reg[{register_op_2:d}]"
1✔
2112
            else:
2113
                cmd_length += 1
1✔
2114
                _typebounds(Commands.LOGIC_OP, "operand_2",
1✔
2115
                            operand_2, DataType.UINT32)
2116
                encoded_operands += _ONE_WORD.pack(operand_2)
1✔
2117
                cmd_string += f" {operand_2}"
1✔
2118

2119
        cmd_word = _binencode(Commands.LOGIC_OP, {
1✔
2120
            _Field.LENGTH: cmd_length,
2121
            _Field.USAGE: bit_field,
2122
            _Field.DESTINATION: register_id,
2123
            _Field.SOURCE_1: register_op_1,
2124
            _Field.SOURCE_2: register_op_2,
2125
            _Field.IMMEDIATE: operation})
2126
        self._write_command_to_files(cmd_word + encoded_operands, cmd_string)
1✔
2127

2128
    def copy_structure(self, source_structure_id, destination_structure_id,
1✔
2129
                       source_id_is_register=False,
2130
                       destination_id_is_register=False):
2131
        """
2132
        Insert command to copy a structure, possibly overwriting another
2133
        structure.
2134

2135
        :param int source_structure_id:
2136
            * If ``source_id_is_register`` is True, the ID of the register
2137
              holding the source structure ID, between 0 and 15
2138
            * Otherwise, the ID of the source structure, between 0 and 15
2139
        :param int destination_structure_id:
2140
            * If ``destination_id_is_register`` is True, the ID of the
2141
              register holding the destination structure ID, between 0 and 15
2142
            * If ``destination_id_is_register`` is False, the ID of the
2143
              destination structure, between 0 and 15
2144
        :param bool source_id_is_register:
2145
            Indicates if ``source_structure_id`` is a register ID
2146
        :param bool destination_id_is_register:
2147
            Indicates if ``destination_structure_id`` is a register ID
2148
        :raise DataUndefinedWriterException:
2149
            If the binary specification file writer has not been initialised
2150
        :raise IOError: If a write to external storage fails
2151
        :raise ParameterOutOfBoundsException:
2152
            * If ``source_id_is_register`` is True and
2153
              ``source_structure_id`` is not a valid register ID
2154
            * If ``destination_id_is_register`` is True and
2155
              ``destination_structure_id`` is not a valid register ID
2156
            * If ``source_id_is_register`` is False and
2157
              ``source_structure_id`` is not a valid structure ID
2158
            * If ``destination_id_is_register`` is False and
2159
              ``destination_structure_id`` is not a valid structure ID
2160
        :raise NotAllocatedException:
2161
            * If no structure with ID ``source_structure_id`` has been
2162
              allocated
2163
        """
2164
        if source_structure_id == destination_structure_id and \
1✔
2165
                destination_id_is_register == source_id_is_register:
2166
            raise DuplicateParameterException(
1✔
2167
                Commands.COPY_STRUCT.name,
2168
                [source_structure_id, destination_structure_id])
2169

2170
        bit_field = 0
1✔
2171
        cmd_string = Commands.COPY_STRUCT.name
1✔
2172

2173
        if source_id_is_register:
1✔
2174
            _bounds(Commands.COPY_STRUCT, "source_structure_id",
1✔
2175
                    source_structure_id, 0, MAX_REGISTERS)
2176
            bit_field |= SRC1_ONLY
1✔
2177
            cmd_string += f" source_struct=reg[{source_structure_id:d}]"
1✔
2178
        else:
2179
            _bounds(Commands.COPY_STRUCT, "source_structure_id",
1✔
2180
                    source_structure_id, 0, MAX_STRUCT_SLOTS)
2181
            if self._struct_slots[source_structure_id] is None:
1✔
2182
                raise NotAllocatedException(
1✔
2183
                    "struct", source_structure_id, Commands.COPY_STRUCT.name)
2184
            cmd_string += f" source_struct={source_structure_id:d}"
1✔
2185

2186
        if destination_id_is_register:
1✔
2187
            _bounds(Commands.COPY_STRUCT, "destination_structure_id",
1✔
2188
                    destination_structure_id, 0, MAX_REGISTERS)
2189
            bit_field |= DEST_ONLY
1✔
2190
            cmd_string += (
1✔
2191
                f" destination_struct=reg[{destination_structure_id:d}]")
2192
        else:
2193
            _bounds(Commands.COPY_STRUCT, "destination_structure_id",
1✔
2194
                    destination_structure_id, 0, MAX_STRUCT_SLOTS)
2195
            cmd_string += f" destination_struct={destination_structure_id:d}"
1✔
2196

2197
        cmd_word = _binencode(Commands.COPY_STRUCT, {
1✔
2198
            _Field.LENGTH: LEN1,
2199
            _Field.USAGE: bit_field,
2200
            _Field.DESTINATION: destination_structure_id,
2201
            _Field.SOURCE_1: source_structure_id})
2202
        self._write_command_to_files(cmd_word, cmd_string)
1✔
2203

2204
    def copy_structure_parameter(
1✔
2205
            self, source_structure_id, source_parameter_index,
2206
            destination_id, destination_parameter_index=None,
2207
            destination_is_register=False):
2208
        """
2209
        Insert command to copy the value of a parameter from one
2210
        structure to another.
2211

2212
        :param int source_structure_id:
2213
            The ID of the source structure, between 0 and 15
2214
        :param int source_parameter_index:
2215
            The index of the parameter in the source structure
2216
        :param int destination_id: The ID of the destination structure, or
2217
            the ID of the destination register, between 0 and 15
2218
        :param int destination_parameter_index: The index of the parameter in
2219
            the destination structure. Ignored when writing to a register.
2220
        :param bool destination_is_register:
2221
            Indicates whether the destination is a structure or a register.
2222
        :raise DataUndefinedWriterException:
2223
            If the binary specification file writer has not been initialised
2224
        :raise IOError: If a write to external storage fails
2225
        :raise ParameterOutOfBoundsException:
2226
            * If ``source_structure_id`` is not a valid structure ID
2227
            * If ``destination_id`` is not a valid structure ID
2228
            * If ``source_parameter_index`` is not a valid parameter index
2229
              in the source structure
2230
            * If ``destination_parameter_index`` is not a valid parameter
2231
              index in the destination structure
2232
        :raise NotAllocatedException:
2233
            * If no structure with ID ``destination_id`` has been allocated
2234
            * If no structure with ID ``source_structure_id`` has been
2235
              allocated
2236
        """
2237
        _bounds(Commands.COPY_PARAM, "source_structure_id",
1✔
2238
                source_structure_id, 0, MAX_STRUCT_SLOTS)
2239
        _bounds(Commands.COPY_PARAM, "source_parameter_index",
1✔
2240
                source_parameter_index, 0, MAX_STRUCT_ELEMENTS)
2241
        if self._struct_slots[source_structure_id] is None:
1✔
2242
            raise NotAllocatedException(
1✔
2243
                "structure", source_structure_id, Commands.COPY_PARAM.name)
2244
        if (len(self._struct_slots[source_structure_id]) <=
1✔
2245
                source_parameter_index):
2246
            raise NotAllocatedException(
1✔
2247
                "parameter", source_parameter_index, Commands.COPY_PARAM.name)
2248

2249
        cmd_string = Commands.COPY_PARAM.name
1✔
2250
        if not destination_is_register:
1✔
2251
            _bounds(Commands.COPY_PARAM, "destination_parameter_index",
1✔
2252
                    destination_parameter_index, 0, MAX_STRUCT_ELEMENTS)
2253
            _bounds(Commands.COPY_PARAM, "destination_structure_id",
1✔
2254
                    destination_id, 0, MAX_STRUCT_SLOTS)
2255
            if self._struct_slots[destination_id] is None:
1✔
2256
                raise NotAllocatedException(
1✔
2257
                    "structure", destination_id, Commands.COPY_PARAM.name)
2258
            if (len(self._struct_slots[source_structure_id]) <=
1!
2259
                    source_parameter_index):
2260
                raise NotAllocatedException(
×
2261
                    "parameter", destination_parameter_index,
2262
                    Commands.COPY_PARAM.name)
2263
            if (len(self._struct_slots[destination_id]) <=
1✔
2264
                    destination_parameter_index):
2265
                raise NotAllocatedException(
1✔
2266
                    "parameter", destination_parameter_index,
2267
                    Commands.COPY_PARAM.name)
2268
            if (self._struct_slots[source_structure_id]
1✔
2269
                    [source_parameter_index][1] !=
2270
                    self._struct_slots[destination_id]
2271
                    [destination_parameter_index][1]):
2272
                raise TypeMismatchException(Commands.COPY_PARAM.name)
1✔
2273
            if (source_structure_id == destination_id and
1✔
2274
                    destination_parameter_index == source_parameter_index):
2275
                raise DuplicateParameterException(
1✔
2276
                    Commands.COPY_PARAM.name, [
2277
                        source_structure_id, source_parameter_index,
2278
                        destination_id, destination_parameter_index])
2279

2280
            cmd_word_1 = _binencode(Commands.COPY_PARAM, {
1✔
2281
                _Field.LENGTH: LEN2,
2282
                _Field.USAGE: NO_REGS,
2283
                _Field.DESTINATION: destination_id,
2284
                _Field.SOURCE_1: source_structure_id})
2285
            param_word = ((destination_parameter_index << 8) |
1✔
2286
                          source_parameter_index)
2287
            cmd_string += (
1✔
2288
                f" source_structure_id = {source_structure_id:d}, "
2289
                f"source_parameter_id = {source_parameter_index:d}, "
2290
                f"destination_structure_id = {destination_id:d}, "
2291
                f"destination_parameter_id = {destination_parameter_index:d}")
2292
        else:
2293
            _bounds(Commands.COPY_PARAM, "destination_register_id",
1✔
2294
                    destination_id, 0, MAX_REGISTERS)
2295

2296
            cmd_word_1 = _binencode(Commands.COPY_PARAM, {
1✔
2297
                _Field.LENGTH: LEN2,
2298
                _Field.USAGE: DEST_ONLY,
2299
                _Field.DESTINATION: destination_id,
2300
                _Field.SOURCE_1: source_structure_id})
2301
            param_word = source_parameter_index
1✔
2302
            cmd_string += (
1✔
2303
                f" source_structure_id = {source_structure_id:d}, "
2304
                f"source_parameter_id = {source_parameter_index:d}, "
2305
                f"destination_register_id = {destination_id:d}")
2306

2307
        cmd_word_list = cmd_word_1 + _ONE_WORD.pack(param_word)
1✔
2308
        self._write_command_to_files(cmd_word_list, cmd_string)
1✔
2309

2310
    def print_value(self, value, value_is_register=False,
1✔
2311
                    data_type=DataType.UINT32):
2312
        """
2313
        Insert command to print out a value (for debugging).
2314

2315
        :param value:
2316
            * If ``value_is_register`` is True, the ID of the register
2317
              containing the value to print
2318
            * If ``value_is_register`` is False, the value to print as a
2319
              value of type given by ``data_type``
2320
        :type value: float or int
2321
        :param bool value_is_register: Indicates if ``value`` is a register
2322
        :param DataType data_type: The type of the data to be printed
2323
        :raise DataUndefinedWriterException:
2324
            If the binary specification file writer has not been initialised
2325
        :raise IOError: If a write to external storage fails
2326
        :raise ParameterOutOfBoundsException:
2327
            * If ``value_is_register`` is True and ``value`` is not a valid
2328
              register ID
2329
            * If ``value_is_register`` is False, the ``data_type`` is an
2330
              integer type and ``value`` has a fractional part
2331
            * If ``value_is_register`` is False and the ``value`` would
2332
              overflow the data type
2333
        :raise UnknownTypeException: If ``data_type`` is not a valid data type
2334
        """
2335
        source_register_id = 0
1✔
2336
        bit_field = 0
1✔
2337
        data_encoded = bytearray()
1✔
2338
        cmd_string = Commands.PRINT_VAL.name
1✔
2339

2340
        if value_is_register:
1✔
2341
            _bounds(Commands.PRINT_VAL, "value", value, 0, MAX_REGISTERS)
1✔
2342
            cmd_word_length = LEN1
1✔
2343
            bit_field |= 2
1✔
2344
            source_register_id = value
1✔
2345
            cmd_string += f" reg[{source_register_id:d}]"
1✔
2346
        else:
2347
            _typebounds(Commands.PRINT_VAL, "value", value, data_type)
1✔
2348
            cmd_word_length = LEN2 if data_type.size <= 4 else LEN3
1✔
2349
            data_encoded += data_type.encode(value)
1✔
2350
            cmd_string += f" {value:d}"
1✔
2351

2352
        cmd_word = _binencode(Commands.PRINT_VAL, {
1✔
2353
            _Field.LENGTH: cmd_word_length,
2354
            _Field.USAGE: bit_field,
2355
            _Field.SOURCE_1: source_register_id,
2356
            _Field.IMMEDIATE: data_type})
2357
        self._write_command_to_files(cmd_word + data_encoded, cmd_string)
1✔
2358

2359
    def print_text(self, text, encoding="ASCII"):
1✔
2360
        """
2361
        Insert command to print some text (for debugging).
2362

2363
        :param str text: The text to write (max 12 *bytes* once encoded)
2364
        :param str encoding:
2365
            The character encoding to use for the string. Defaults to ASCII.
2366
        :raise DataUndefinedWriterException:
2367
            If the binary specification file writer has not been initialised
2368
        :raise IOError: If a write to external storage fails
2369
        """
2370
        text_encoded = bytearray(text.encode(encoding=encoding))
1✔
2371
        text_len = len(text_encoded)
1✔
2372
        _bounds(Commands.PRINT_TXT, "len(text)", text_len, 1, 13)
1✔
2373

2374
        if text_len <= 4:
1✔
2375
            cmd_word_len = LEN2
1✔
2376
        elif text_len <= 8:
1✔
2377
            cmd_word_len = LEN3
1✔
2378
        else:
2379
            cmd_word_len = LEN4
1✔
2380

2381
        # add final padding to the encoded text
2382
        if text_len % 4 != 0:
1✔
2383
            text_encoded += bytearray(4 - text_len % 4)
1✔
2384

2385
        cmd_string = Commands.PRINT_TXT.name
1✔
2386
        cmd_string += f' "{text}"'
1✔
2387
        cmd_word = _binencode(Commands.PRINT_TXT, {
1✔
2388
            _Field.LENGTH: cmd_word_len,
2389
            _Field.IMMEDIATE: text_len - 1})
2390
        self._write_command_to_files(cmd_word + text_encoded, cmd_string)
1✔
2391

2392
    def print_struct(self, structure_id, structure_id_is_register=False):
1✔
2393
        """
2394
        Insert command to print out a structure (for debugging).
2395

2396
        :param int structure_id:
2397
            * If ``structure_id_is_register`` is True, the ID of the register
2398
              containing the ID of the structure to print, between 0 and 15
2399
            * If ``structure_id_is_register`` is False, the ID of the
2400
              structure to print, between 0 and 15
2401
        :param bool structure_id_is_register:
2402
            Indicates if the ``structure_id`` is a register
2403
        :raise DataUndefinedWriterException:
2404
            If the binary specification file writer has not been initialised
2405
        :raise IOError: If a write to external storage fails
2406
        :raise ParameterOutOfBoundsException:
2407
            * If ``structure_id_is_register`` is True and ``structure_id`` is
2408
              not a valid register ID
2409
            * If ``structure_id_is_register`` is False and ``structure_id``
2410
              is not a valid structure ID
2411
        :raise NotAllocatedException:
2412
            If ``structure_id_is_register`` is False and ``structure_id`` is
2413
            the ID of a structure that has not been allocated
2414
        """
2415
        cmd_string = Commands.PRINT_STRUCT.name
1✔
2416
        if structure_id_is_register:
1✔
2417
            _bounds(Commands.PRINT_STRUCT, "structure_id",
1✔
2418
                    structure_id, 0, MAX_REGISTERS)
2419
            struct_register = structure_id
1✔
2420
            structure_id = 0
1✔
2421
            bit_field = 0x2
1✔
2422
            cmd_string += f" struct(reg[{struct_register:d}])"
1✔
2423
        else:
2424
            _bounds(Commands.PRINT_STRUCT, "structure_id",
1✔
2425
                    structure_id, 0, MAX_STRUCT_SLOTS)
2426
            if self._struct_slots[structure_id] is None:
1✔
2427
                raise NotAllocatedException(
1✔
2428
                    "structure", structure_id, Commands.PRINT_STRUCT.name)
2429
            struct_register = 0
1✔
2430
            bit_field = 0
1✔
2431
            cmd_string += f" struct({structure_id:d})"
1✔
2432

2433
        cmd_word = _binencode(Commands.PRINT_STRUCT, {
1✔
2434
            _Field.LENGTH: LEN1,
2435
            _Field.USAGE: bit_field,
2436
            _Field.SOURCE_1: struct_register,
2437
            _Field.IMMEDIATE: structure_id})
2438
        self._write_command_to_files(cmd_word, cmd_string)
1✔
2439

2440
    def end_specification(self, close_writer=True):
1✔
2441
        """
2442
        Insert a command to indicate that the specification has finished
2443
        and finish writing.
2444

2445
        :param bool close_writer:
2446
            Indicates whether to close the underlying writer(s)
2447
        :raise DataUndefinedWriterException:
2448
            If the binary specification file writer has not been initialised
2449
        :raise IOError: If a write to external storage fails
2450
        """
2451
        self.comment("\nEnd of specification:")
1✔
2452

2453
        cmd_word = _binencode(Commands.END_SPEC, {
1✔
2454
            _Field.LENGTH: LEN1})
2455
        encoded_parameter = _ONE_SIGNED_INT.pack(-1)
1✔
2456
        cmd_string = Commands.END_SPEC.name
1✔
2457
        self._write_command_to_files(cmd_word + encoded_parameter, cmd_string)
1✔
2458

2459
        if close_writer:
1✔
2460
            self._spec_writer.close()
1✔
2461
            self._spec_writer = None
1✔
2462
            if self._report_writer is not None:
1✔
2463
                self._report_writer.close()
1✔
2464
                self._report_writer = None
1✔
2465

2466
    def _write_command_to_files(self, cmd_word_list, cmd_string, indent=False,
1✔
2467
                                outdent=False, no_instruction_number=False):
2468
        """
2469
        Writes the binary command to the binary output file and, if the
2470
        user has requested a text output for debug purposes, also write
2471
        the text version to the text file.
2472

2473
        Setting the optional parameter ``indent`` to ``True`` causes subsequent
2474
        commands to be indented by two spaces relative to this one. Similarly,
2475
        setting ``outdent`` to ``True`` reverses this spacing.
2476

2477
        :param bytearray cmd_word_list: list of binary words to be added to
2478
            the binary data specification file
2479
        :param str cmd_string: string describing the command to be added to
2480
            the report for the data specification file
2481
        :param bool indent: if the following lines need to be indented
2482
        :param bool outdent: if the following lines need to be out-dented
2483
        :param bool no_instruction_number: if each report line should include
2484
            also the address of the command in the file
2485
        :raise DataUndefinedWriterException:
2486
            If the binary specification file writer has not been initialised
2487
        :raise IOError: If a write to external storage fails
2488
        """
2489
        if self._spec_writer is None:
1!
2490
            raise DataUndefinedWriterException(
×
2491
                "The spec file writer has not been initialised")
2492
        elif cmd_word_list:
1✔
2493
            self._spec_writer.write(cmd_word_list)
1✔
2494

2495
        if self._report_writer is not None:
1✔
2496
            if outdent is True:
1✔
2497
                self._txt_indent = min(self._txt_indent - 1, 0)
1✔
2498
            indent_string = "   " * self._txt_indent
1✔
2499
            if no_instruction_number:
1✔
2500
                formatted_cmd_string = f"{indent_string}{cmd_string}\n"
1✔
2501
            else:
2502
                formatted_cmd_string = (
1✔
2503
                    f"{self._instruction_counter:08X}. "
2504
                    f"{indent_string}{cmd_string}\n")
2505
                self._instruction_counter += len(cmd_word_list)
1✔
2506
            self._report_writer.write(str(formatted_cmd_string))
1✔
2507
            if indent is True:
1✔
2508
                self._txt_indent += 1
1✔
2509
        return
1✔
2510

2511
    @property
1✔
2512
    def region_sizes(self):
1✔
2513
        """
2514
        A list of sizes of each region that has been reserved.
2515

2516
        .. note::
2517
            The list will include ``0`` for each non-reserved region.
2518

2519
        :rtype: list(int)
2520
        """
2521
        return [0 if slot is None else slot.size for slot in self._mem_slots]
×
2522

2523
    @property
1✔
2524
    def current_region(self):
1✔
2525
        """
2526
        The ID of the current DSG region we're writing to.
2527
        If not yet writing to any region, ``None``.
2528

2529
        :rtype: int or None
2530
        """
2531
        return self._current_region
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc