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

SpiNNakerManchester / SpiNNMachine / 8749769988

19 Apr 2024 07:08AM UTC coverage: 92.582% (-0.3%) from 92.928%
8749769988

push

github

web-flow
Merge pull request #257 from SpiNNakerManchester/version2

Version and testing for Spin2

1408 of 1541 branches covered (91.37%)

Branch coverage included in aggregate %.

251 of 263 new or added lines in 14 files covered. (95.44%)

17 existing lines in 4 files now uncovered.

2311 of 2476 relevant lines covered (93.34%)

0.93 hits per line

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

98.58
/spinn_machine/virtual_machine.py
1
# Copyright (c) 2016 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
import math
1✔
15
from collections import defaultdict
1✔
16
import logging
1✔
17
from typing import Dict, List, Optional, Set, Tuple
1✔
18
from spinn_utilities.config_holder import get_config_str_or_none
1✔
19
from spinn_utilities.log import FormatAdapter
1✔
20
from spinn_utilities.typing.coords import XY
1✔
21
from spinn_machine.data import MachineDataView
1✔
22
from spinn_machine.ignores import IgnoreChip, IgnoreCore, IgnoreLink
1✔
23
from .chip import Chip
1✔
24
from .router import Router
1✔
25
from .link import Link
1✔
26
from .machine import Machine
1✔
27

28
logger = FormatAdapter(logging.getLogger(__name__))
1✔
29

30

31
def virtual_machine(width: int, height: int, validate: bool = True):
1✔
32
    """
33
    Create a virtual SpiNNaker machine, used for planning execution.
34

35
    :param int width: the width of the virtual machine in chips
36
    :param int height: the height of the virtual machine in chips
37
    :param bool validate: if True will call the machine validate function
38

39
    :returns: a virtual machine (that cannot execute code)
40
    :rtype: ~spinn_machine.Machine
41
    """
42
    factory = _VirtualMachine(width, height, validate)
1✔
43
    return factory.machine
1✔
44

45

46
def virtual_machine_by_min_size(
1✔
47
        width: int, height: int, validate: bool = True):
48
    """
49
    Create a virtual SpiNNaker machine, used for planning execution.
50

51
    :param int width: the minimum width of the virtual machine in chips
52
    :param int height: the minimum height of the virtual machine in chips
53
    :param bool validate: if True will call the machine validate function
54

55
    :returns: a virtual machine (that cannot execute code)
56
    :rtype: ~spinn_machine.Machine
57
    """
58
    version = MachineDataView.get_machine_version()
1✔
59
    w_board, h_board = version.board_shape
1✔
60
    # check for edge case
61
    if width <= w_board and height > h_board:
1✔
62
        width = w_board * 2
1✔
63
    if height <= h_board and width > w_board:
1!
NEW
64
        height = h_board * 2
×
65
    width = w_board * math.ceil(width / w_board)
1✔
66
    height = h_board * math.ceil(height / h_board)
1✔
67
    return virtual_machine(width, height, validate)
1✔
68

69

70
def virtual_machine_by_cores(n_cores: int, validate: bool = True):
1✔
71
    """
72
    Create a virtual SpiNNaker machine, used for planning execution.
73

74
    Semantic sugar for
75

76
    MachineDataView.get_machine_version()
77

78
    width, height = version.size_from_n_cores(n_cores)
79

80
    return virtual_machine(width, height, validate)
81

82
    :param n_cores: Minimum number of user cores
83
    :param bool validate: if True will call the machine validate function
84

85
    :returns: a virtual machine (that cannot execute code)
86
    :rtype: ~spinn_machine.Machine
87
    :raises SpinnMachineException:
88
        If multiple boards are needed but not supported
89
    """
90
    version = MachineDataView.get_machine_version()
1✔
91
    width, height = version.size_from_n_cores(n_cores)
1✔
92
    return virtual_machine(width, height, validate)
1✔
93

94

95
def virtual_machine_by_chips(n_chips: int, validate: bool = True):
1✔
96
    """
97
    Create a virtual SpiNNaker machine, used for planning execution.
98

99
    Semantic sugar for
100

101
    MachineDataView.get_machine_version()
102

103
    width, height = version.size_from_n_cchips(n_cores)
104

105
    return virtual_machine(width, height, validate)
106

107
    :param n_chips: Minimum number of chips
108
    :param bool validate: if True will call the machine validate function
109

110
    :returns: a virtual machine (that cannot execute code)
111
    :rtype: ~spinn_machine.Machine
112
    :raises SpinnMachineException:
113
        If multiple boards are needed but not supported
114
    """
115
    version = MachineDataView.get_machine_version()
1✔
116
    width, height = version.size_from_n_chips(n_chips)
1✔
117
    return virtual_machine(width, height, validate)
1✔
118

119

120
def virtual_machine_by_boards(n_boards: int, validate: bool = True):
1✔
121
    """
122
    Create a virtual SpiNNaker machine, used for planning execution.
123

124
    semantic sugar for:
125

126
    version = MachineDataView.get_machine_version()
127

128
    width, height = version.size_from_n_boards(n_boards)
129

130
    return virtual_machine(width, height, validate)
131

132
    :param n_boards: Minimum number of boards
133
    :param bool validate: if True will call the machine validate function
134

135
    :returns: a virtual machine (that cannot execute code)
136
    :rtype: ~spinn_machine.Machine
137
    :raises SpinnMachineException:
138
        If multiple boards are needed but not supported
139
    """
140
    version = MachineDataView.get_machine_version()
1✔
141
    width, height = version.size_from_n_boards(n_boards)
1✔
142
    return virtual_machine(width, height, validate)
1✔
143

144

145
class _VirtualMachine(object):
1✔
146
    """
147
    A Virtual SpiNNaker machine factory
148
    """
149

150
    __slots__ = (
1✔
151
        "_unused_cores",
152
        "_unused_links",
153
        "_machine",
154
        "_with_monitors",
155
        "_n_router_entries"
156
    )
157

158
    _4_chip_down_links = {
1✔
159
        (0, 0, 3), (0, 0, 4), (0, 1, 3), (0, 1, 4),
160
        (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)
161
    }
162

163
    ORIGIN = "Virtual"
1✔
164

165
    def __init__(self, width: int, height: int, validate: bool = True):
1✔
166
        version = MachineDataView.get_machine_version()
1✔
167
        version.verify_size(width, height)
1✔
168
        max_cores = version.max_cores_per_chip
1✔
169
        self._n_router_entries = version.n_router_entries
1✔
170
        self._machine = version.create_machine(
1✔
171
            width, height, origin=self.ORIGIN)
172

173
        # Store the down items
174
        unused_chips = []
1✔
175
        for down_chip in IgnoreChip.parse_string(get_config_str_or_none(
1✔
176
                "Machine", "down_chips")):
177
            if down_chip.ip_address is None:
1✔
178
                unused_chips.append((down_chip.x, down_chip.y))
1✔
179

180
        self._unused_cores: Dict[XY, Set[int]] = defaultdict(set)
1✔
181
        for down_core in IgnoreCore.parse_string(get_config_str_or_none(
1✔
182
                "Machine", "down_cores")):
183
            if down_core.ip_address is None:
1✔
184
                self._unused_cores[down_core.x, down_core.y].add(
1✔
185
                    down_core.virtual_p)
186

187
        self._unused_links: Set[Tuple[int, int, int]] = set()
1✔
188
        for down_link in IgnoreLink.parse_string(get_config_str_or_none(
1✔
189
                "Machine", "down_links")):
190
            if down_link.ip_address is None:
1✔
191
                self._unused_links.add(
1✔
192
                    (down_link.x, down_link.y, down_link.link))
193

194
        if width == 2:  # Already checked height is now also 2
1✔
195
            self._unused_links.update(_VirtualMachine._4_chip_down_links)
1✔
196

197
        ethernet_chips = version.get_potential_ethernet_chips(width, height)
1✔
198

199
        # Compute list of chips that are possible based on configuration
200
        # If there are no wrap arounds, and the the size is not 2 * 2,
201
        # the possible chips depend on the 48 chip board's gaps
202
        configured_chips: Dict[XY, Tuple[XY, int]] = dict()
1✔
203
        for eth in ethernet_chips:
1✔
204
            for (xy, n_cores) in self._machine.get_xy_cores_by_ethernet(
1✔
205
                    *eth):
206
                if xy not in unused_chips:
1✔
207
                    configured_chips[xy] = (eth, min(n_cores, max_cores))
1✔
208

209
        # for chip in self._unreachable_outgoing_chips:
210
        #    configured_chips.remove(chip)
211
        # for chip in self._unreachable_incoming_chips:
212
        #    configured_chips.remove(chip)
213

214
        for xy in configured_chips:
1✔
215
            if xy in ethernet_chips:
1✔
216
                x, y = xy
1✔
217
                new_chip = self._create_chip(
1✔
218
                    xy, configured_chips, f"127.0.{x}.{y}")
219
            else:
220
                new_chip = self._create_chip(xy, configured_chips)
1✔
221
            self._machine.add_chip(new_chip)
1✔
222

223
        self._machine.add_spinnaker_links()
1✔
224
        self._machine.add_fpga_links()
1✔
225
        if validate:
1✔
226
            self._machine.validate()
1✔
227

228
    @property
1✔
229
    def machine(self) -> Machine:
1✔
230
        """
231
        The Machine object created by this Factory
232

233
        :rtype: Machine
234
        """
235
        return self._machine
1✔
236

237
    def _create_chip(self, xy: XY, configured_chips: Dict[XY, Tuple[XY, int]],
1✔
238
                     ip_address: Optional[str] = None) -> Chip:
239
        chip_links = self._calculate_links(xy, configured_chips)
1✔
240
        chip_router = Router(chip_links, self._n_router_entries)
1✔
241

242
        ((eth_x, eth_y), n_cores) = configured_chips[xy]
1✔
243

244
        down_cores = self._unused_cores.get(xy, None)
1✔
245
        x, y = xy
1✔
246
        sdram = MachineDataView.get_machine_version().max_sdram_per_chip
1✔
247
        return Chip(
1✔
248
            x, y, n_cores, chip_router, sdram, eth_x, eth_y,
249
            ip_address, down_cores=down_cores)
250

251
    def _calculate_links(
1✔
252
            self, xy: XY, configured_chips: Dict[XY, Tuple[XY, int]]
253
            ) -> List[Link]:
254
        """
255
        Calculate the links needed for a machine structure
256
        """
257
        x, y = xy
1✔
258
        links = list()
1✔
259
        for link_id in range(6):
1✔
260
            if (x, y, link_id) not in self._unused_links:
1✔
261
                link_x_y = self._machine.xy_over_link(x, y, link_id)
1✔
262
                if link_x_y in configured_chips:
1✔
263
                    links.append(
1✔
264
                        Link(source_x=x, source_y=y,
265
                             destination_x=link_x_y[0],
266
                             destination_y=link_x_y[1],
267
                             source_link_id=link_id))
268
        return links
1✔
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