# Copyright (c) 2017-2019 The University of Manchester
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections import defaultdict
import logging
import functools
from os.path import join
from spinn_utilities.log import FormatAdapter
from spinn_machine import (
Router, Chip, SDRAM, Link, machine_from_size)
from spinn_machine import Machine
from spinn_machine.machine_factory import machine_repair
from spinnman.constants import (
ROUTER_REGISTER_P2P_ADDRESS, SYSTEM_VARIABLE_BASE_ADDRESS)
from spinnman.messages.spinnaker_boot import (
SystemVariableDefinition)
from spinnman.exceptions import SpinnmanUnexpectedResponseCodeException
from spinnman.messages.scp.impl import ReadMemory, ReadLink, GetChipInfo
from spinnman.model import P2PTable
from spinnman.model.enums import CPUState
from .abstract_multi_connection_process import AbstractMultiConnectionProcess
logger = FormatAdapter(logging.getLogger(__name__))
REPORT_FILE = "Ignores_report.rpt"
[docs]class GetMachineProcess(AbstractMultiConnectionProcess):
""" A process for getting the machine details over a set of connections.
"""
__slots__ = [
"_chip_info",
# Used if there are any ignores with ip addresses
# Holds a mapping from ip to board root (x,y)
"_ethernets",
"_ignore_chips",
"_ignore_cores",
# Holds a map from x,y to a set of virtual cores to ignores
"_ignore_cores_map",
"_ignore_links",
"_max_sdram_size",
"_p2p_column_data",
# Used if there are any ignore core requests
# Holds a mapping from (x,y) to a mapping of phsyical to virtual core
"_virtual_map",
# Directory to put the ingore report if required
"_default_report_directory",
# Ignore report file path for ignre report.
# Kept as None until first write
"_report_file"]
def __init__(self, connection_selector, ignore_chips, ignore_cores,
ignore_links, max_sdram_size=None,
default_report_directory=None):
# pylint: disable=too-many-arguments
super(GetMachineProcess, self).__init__(connection_selector)
self._ignore_chips = ignore_chips if ignore_chips is not None else {}
self._ignore_cores = ignore_cores if ignore_cores is not None else {}
self._ignore_cores_map = defaultdict(set)
self._ignore_links = ignore_links if ignore_links is not None else {}
self._max_sdram_size = max_sdram_size
self._p2p_column_data = list()
# A dictionary of (x, y) -> ChipInfo
self._chip_info = dict()
# Set ethernets to None meaning not computed yet
self._ethernets = None
self._virtual_map = {}
self._default_report_directory = default_report_directory
self._report_file = None
def _make_chip(self, chip_info, machine):
""" Creates a chip from a ChipSummaryInfo structure.
:param chip_info: \
The ChipSummaryInfo structure to create the chip from
:type chip_info: \
:py:class:`spinnman.model.ChipSummaryInfo`
:return: The created chip
:rtype: :py:class:`spinn_machine.Chip`
"""
# Create the down cores set if any
n_cores = min(chip_info.n_cores, Machine.max_cores_per_chip())
core_states = chip_info.core_states
down_cores = self._ignore_cores_map.get(
(chip_info.x, chip_info.y), None)
for i in range(1, n_cores):
if core_states[i] != CPUState.IDLE:
self._report_ignore(
"Not using core {}, {}, {} in state {}",
chip_info.x, chip_info.y, i, core_states[i])
if down_cores is None:
down_cores = set()
down_cores.add(i)
# Create the router
router = self._make_router(chip_info, machine)
# Create the chip's SDRAM object
sdram_size = chip_info.largest_free_sdram_block
if (self._max_sdram_size is not None and
sdram_size > self._max_sdram_size):
sdram_size = self._max_sdram_size
sdram = SDRAM(size=sdram_size)
# Create the chip
return Chip(
x=chip_info.x, y=chip_info.y, n_processors=n_cores,
router=router, sdram=sdram,
ip_address=chip_info.ethernet_ip_address,
nearest_ethernet_x=chip_info.nearest_ethernet_x,
nearest_ethernet_y=chip_info.nearest_ethernet_y,
down_cores=down_cores)
def _make_router(self, chip_info, machine):
links = list()
for link in chip_info.working_links:
dest_xy = machine.xy_over_link(chip_info.x, chip_info.y, link)
if dest_xy in self._chip_info:
links.append(Link(
chip_info.x, chip_info.y, link, dest_xy[0], dest_xy[1]))
else:
logger.warning(
"Link {}{}{} points to Chip {} but that is not included ",
chip_info.x, chip_info.y, link, dest_xy)
return Router(
links=links,
n_available_multicast_entries=(
chip_info.n_free_multicast_routing_entries))
def _receive_p2p_data(self, column, scp_read_response):
self._p2p_column_data[column] = (
scp_read_response.data, scp_read_response.offset)
def _receive_chip_info(self, scp_read_chip_info_response):
chip_info = scp_read_chip_info_response.chip_info
self._chip_info[chip_info.x, chip_info.y] = chip_info
def _receive_error(self, request, exception, tb):
# If we get an ReadLink with a
# SpinnmanUnexpectedResponseCodeException, this is a failed link
# and so can be ignored
if isinstance(request, ReadLink):
if isinstance(exception, SpinnmanUnexpectedResponseCodeException):
return
super(GetMachineProcess, self)._receive_error(request, exception, tb)
[docs] def get_machine_details(self, boot_x, boot_y, width, height,
repair_machine, ignore_bad_ethernets):
# Get the P2P table - 8 entries are packed into each 32-bit word
p2p_column_bytes = P2PTable.get_n_column_bytes(height)
self._p2p_column_data = [None] * width
for column in range(width):
offset = P2PTable.get_column_offset(column)
self._send_request(
ReadMemory(
x=boot_x, y=boot_y,
base_address=(ROUTER_REGISTER_P2P_ADDRESS + offset),
size=p2p_column_bytes),
functools.partial(self._receive_p2p_data, column))
self._finish()
self.check_for_error()
p2p_table = P2PTable(width, height, self._p2p_column_data)
# Get the chip information for each chip
for (x, y) in p2p_table.iterchips():
self._send_request(GetChipInfo(x, y), self._receive_chip_info)
self._finish()
try:
self.check_for_error()
except Exception: # pylint: disable=broad-except
# Ignore errors so far, as any error here just means that a chip
# is down that wasn't marked as down
pass
# Warn about unexpected missing chips
for (x, y) in p2p_table.iterchips():
if (x, y) not in self._chip_info:
logger.warning(
"Chip {}, {} was expected but didn't reply", x, y)
machine = machine_from_size(width, height)
self._preprocess_ignore_chips(machine)
self._process_ignore_links(machine)
self._preprocess_ignore_cores(machine)
return self._fill_machine(
machine, repair_machine, ignore_bad_ethernets)
def _fill_machine(
self, machine, repair_machine, ignore_bad_ethernets):
for chip_info in sorted(
self._chip_info.values(), key=lambda chip: (chip.x, chip.y)):
if (chip_info.ethernet_ip_address is not None and
(chip_info.x != chip_info.nearest_ethernet_x
or chip_info.y != chip_info.nearest_ethernet_y)):
if ignore_bad_ethernets:
logger.warning(
"Chip {}:{} claimed it has ip address: {}. "
"This ip will not be used.",
chip_info.x, chip_info.y,
chip_info.ethernet_ip_address)
chip_info.ethernet_ip_address = None
else:
logger.warning(
"Not using chip {}:{} as it has an unexpected "
"ip address: {}", chip_info.x, chip_info.y,
chip_info.ethernet_ip_address)
continue
else:
machine.add_chip(self._make_chip(chip_info, machine))
machine.validate()
return machine_repair(machine, repair_machine)
# Stuff below here is purely for dealing with ignores
def _process_ignore_links(self, machine):
"""
Processes the collection of ignore links to remove then from chipinfo
Converts any local x, y ipaddress to global xy
Discards any ignores which are known to have no\
affect based on the already read chip_info
Also removes any inverse links
Logs all actions except for ignores with unused ip addresses
:param machine: An empty machine to handle wraparrounds
"""
if len(self._ignore_links) == 0:
return
for ignore in self._ignore_links:
global_xy = self._ignores_local_to_global(
ignore.x, ignore.y, ignore.ip_address, machine)
if global_xy is None:
continue
chip_info = self._chip_info.get(global_xy, None)
if chip_info is None:
self._report_ignore(
"Discarding ignore link on chip {} as it is not/ no longer"
" in info", global_xy)
continue
link = ignore.link
if link in chip_info.working_links:
chip_info.working_links.remove(link)
self._report_ignore(
"On chip {} ignoring link:{}", global_xy, link)
# ignore the inverse link too
inv_xy = machine.xy_over_link(global_xy[0], global_xy[1], link)
if inv_xy in self._chip_info:
inv_chip_info = self._chip_info[inv_xy]
inv_link = (link + 3) % 6
if inv_link in inv_chip_info.working_links:
inv_chip_info.working_links.remove(inv_link)
self._report_ignore(
"On chip {} ignoring link {} as it is the inverse "
"of link {} on chip {}", inv_xy, inv_link, link,
global_xy)
else:
self._report_ignore(
"Discarding ignore link {} on chip {} as it is not/"
"no longer in info", link, global_xy)
def _preprocess_ignore_cores(self, machine):
"""
Converts the collection of ignore cores into a map of ignore by xy
Converts any local x, y ipaddress to global xy
Discards (with log messages) any ignores which are known to have no\
affect based on the already read chip_info
Converts any physical cores to virtual ones.\
Core numbers <= 0 are assumed to be 0 - physical_id
:param machine: An empty machine to handle wraparrounds
"""
if len(self._ignore_cores) == 0:
return
# Convert by ip to global
for ignore in self._ignore_cores:
global_xy = self._ignores_local_to_global(
ignore.x, ignore.y, ignore.ip_address, machine)
if global_xy is None:
continue
p = self._get_virtual_p(global_xy, ignore.p)
if p is not None:
self._ignore_cores_map[global_xy].add(p)
def _preprocess_ignore_chips(self, machine):
"""
Processes the collection of ignore chips and discards their chipinfo
Converts any local x, y ipaddress to global xy
Discards any ignores which are known to have no\
affect based on the already read chip_info
Logs all actions except for ignores with unused ip addresses
:param machine: An empty machine to handle wraparrounds
"""
for ignore in self._ignore_chips:
# Convert by ip to global
global_xy = self._ignores_local_to_global(
ignore.x, ignore.y, ignore.ip_address, machine)
if global_xy is None:
continue # Never on this machine
chip_info = self._chip_info.pop(global_xy, None)
if chip_info is None:
continue # Already ignored maybe by a dead chip list
self._report_ignore("Chip {} will be ignored", global_xy)
for link in chip_info.working_links:
# ignore the inverse link
inv_xy = machine.xy_over_link(global_xy[0], global_xy[1], link)
if inv_xy in self._chip_info:
inv_chip_info = self._chip_info[inv_xy]
inv_link = (link + 3) % 6
if inv_link in inv_chip_info.working_links:
inv_chip_info.working_links.remove(inv_link)
self._report_ignore(
"On chip {} ignoring link {} as it points to "
"ignored chip chip {}",
inv_xy, inv_link, global_xy)
def _ignores_local_to_global(self, local_x, local_y, ip_address, machine):
if ip_address is None:
global_xy = (local_x, local_y)
else:
ethernet = self._ethernet_by_ipaddress(ip_address)
if ethernet is None:
self._report_ignore(
"Ignore with ip:{} will be discarded as no board with "
"that ip in this machine", ip_address)
return None
global_xy = machine.get_global_xy(
local_x, local_y, ethernet[0], ethernet[1])
self._report_ignore(
"Ignores for local x:{} y:{} and ip:{} map to global {} with "
"root {}", local_x, local_y, ip_address, global_xy,
self._chip_info[(0, 0)].ethernet_ip_address)
if global_xy in self._chip_info:
return global_xy
else:
self._report_ignore(
"Ignore for global chip {} will be discarded as no such chip "
"in this machine", global_xy)
return None
def _ethernet_by_ipaddress(self, ip_address):
if self._ethernets is None:
self._ethernets = dict()
for chip_info in self._chip_info.values():
if chip_info.ethernet_ip_address is not None:
self._ethernets[chip_info.ethernet_ip_address] = \
(chip_info.x, chip_info.y)
return self._ethernets.get(ip_address, None)
def _get_virtual_p(self, xy, p):
if xy not in self._virtual_map:
if xy not in self._chip_info:
# Chip not part of board so ignore
return None
p_to_v = SystemVariableDefinition.physical_to_virtual_core_map
ba = SYSTEM_VARIABLE_BASE_ADDRESS + p_to_v.offset
self._send_request(
ReadMemory(
x=xy[0], y=xy[1],
base_address=ba,
size=p_to_v.array_size),
functools.partial(
self._receive_physical_to_virtual_core_map, xy))
self._finish()
if p > 0:
self._report_ignore("On chip {} ignoring core {}", xy, p)
return p
else:
virtual_map = self._virtual_map[xy]
if 0-p not in virtual_map:
self._report_ignore(
"On chip {} physical core {} was not used "
"so ignore is being discarded.".format(xy, -p))
return None
virtual_p = virtual_map[0-p]
if virtual_p == 0:
self._report_ignore(
"On chip {} physical core {} was used as the monitor "
"so will NOT be ignored".format(xy, -p))
return None
self._report_ignore(
"On chip {} ignoring core {} as it maps to physical "
"core {}", xy, 0-p, virtual_p)
return virtual_p
def _receive_physical_to_virtual_core_map(self, xy, scp_read_response):
chipinfo = self._chip_info[xy]
ip_address = self._chip_info[(0, 0)].ethernet_ip_address
self._report_ignore(
"Received physical_to_virtual_core_map for chip {} on {}",
xy, ip_address)
p_to_v_map = {}
for i in range(
scp_read_response.offset,
scp_read_response.offset + chipinfo.n_cores):
p_to_v_map[i-scp_read_response.offset] = \
int(scp_read_response.data[i])
# report_ignore (like logger) formats so dict as a param
self._report_ignore("{}", p_to_v_map)
self._virtual_map[xy] = p_to_v_map
def _verify__virtual_to_physical_core_map(self, xy):
""" Add this method to _get_virtual_p to verify the mappings"""
v_to_p = SystemVariableDefinition.virtual_to_physical_core_map
self._send_request(
ReadMemory(
x=xy[0], y=xy[1],
base_address=SYSTEM_VARIABLE_BASE_ADDRESS + v_to_p.offset,
size=v_to_p.array_size),
functools.partial(
self._receive_virtual_to_physical_core_map, xy))
self._finish()
def _receive_virtual_to_physical_core_map(self, xy, scp_read_response):
p_to_v_map = self._virtual_map[xy]
chipinfo = self._chip_info[xy]
for i in range(
scp_read_response.offset,
scp_read_response.offset + chipinfo.n_cores):
assert p_to_v_map[int(scp_read_response.data[i])] == \
i-scp_read_response.offset
self._report_ignore(
"Virtual_to_physical_core_map checks for chip {}", xy)
def _report_ignore(self, message, *args):
"""
Writes the ignore message by either creating or appending the report
The implemenation choice to reopen the file every time is not the\
fastest but is the cleanest and safest for code that in default\
conditions is never run.
"""
full_message = message.format(*args) + "\n"
if self._report_file is None:
if self._default_report_directory is None:
self._report_file = REPORT_FILE
else:
self._report_file = join(
self._default_report_directory, REPORT_FILE)
with open(self._report_file, "w") as r_file:
r_file.write(full_message)
else:
with open(self._report_file, "a") as r_file:
r_file.write(full_message)