Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chip takes lists of Processors #252

Merged
merged 17 commits into from
Apr 26, 2024
77 changes: 14 additions & 63 deletions spinn_machine/chip.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import (Collection, Iterable, Iterator, Optional, Tuple)
from typing import (Iterable, Iterator, Optional, Tuple)

from spinn_utilities.ordered_set import OrderedSet
from spinn_utilities.typing.coords import XY

from spinn_machine.data import MachineDataView
from .router import Router


Expand All @@ -29,31 +28,31 @@ class Chip(XY):
# tag 0 is reserved for stuff like IO STD
_IPTAG_IDS = OrderedSet(range(1, 8))

def __new__(cls, x: int, y: int, n_processors: int, router: Router,
def __new__(cls, x: int, y: int, scamp_processors: Iterable[int],
placable_processors: Iterable[int], router: Router,
sdram: int, nearest_ethernet_x: int, nearest_ethernet_y: int,
ip_address: Optional[str] = None,
tag_ids: Optional[Iterable[int]] = None,
down_cores: Optional[Collection[int]] = None,
parent_link: Optional[int] = None,
v_to_p_map: Optional[bytes] = None):
parent_link: Optional[int] = None):
return tuple.__new__(cls, (x, y))

# pylint: disable=too-many-arguments, wrong-spelling-in-docstring
# pylint: disable=unused-argument
def __init__(self, x: int, y: int, n_processors: int, router: Router,
def __init__(self, x: int, y: int, scamp_processors: Iterable[int],
placable_processors: Iterable[int], router: Router,
sdram: int, nearest_ethernet_x: int, nearest_ethernet_y: int,
ip_address: Optional[str] = None,
tag_ids: Optional[Iterable[int]] = None,
down_cores: Optional[Collection[int]] = None,
parent_link: Optional[int] = None,
v_to_p_map: Optional[bytes] = None):
parent_link: Optional[int] = None):
"""
:param int x: the x-coordinate of the chip's position in the
two-dimensional grid of chips
:param int y: the y-coordinate of the chip's position in the
two-dimensional grid of chips
:param int n_processors:
the number of processors including monitor processors.
:param iterable(int) scamp_processors:
the ids of scamp processors
:param iterable(int) placable_processors:
the ids of processors excluding scamp processors.
:param ~spinn_machine.Router router: a router for the chip
:param int sdram: an SDRAM for the chip
:param ip_address:
Expand All @@ -67,8 +66,6 @@ def __init__(self, x: int, y: int, n_processors: int, router: Router,
:type nearest_ethernet_x: int or None
:param nearest_ethernet_y: the nearest Ethernet y coordinate
:type nearest_ethernet_y: int or None
:param down_cores: Ids of cores that are down for this Chip
:type down_cores: iterable(int) or None
:param parent_link: The link down which the parent chips is found in
the tree of chips towards the root (boot) chip
:type parent_link: int or None
Expand All @@ -80,10 +77,8 @@ def __init__(self, x: int, y: int, n_processors: int, router: Router,
``processor_id``
"""
# X and Y set by new
self._scamp_processors = tuple(range(
MachineDataView.get_machine_version().n_scamp_cores))
self._placable_processors = self.__generate_processors(
n_processors, down_cores)
self._scamp_processors = tuple(scamp_processors)
self._placable_processors = tuple(placable_processors)
self._router = router
self._sdram = sdram
self._ip_address = ip_address
Expand All @@ -96,24 +91,6 @@ def __init__(self, x: int, y: int, n_processors: int, router: Router,
self._nearest_ethernet_x = nearest_ethernet_x
self._nearest_ethernet_y = nearest_ethernet_y
self._parent_link = parent_link
self._v_to_p_map = v_to_p_map

def __generate_processors(
self, n_processors: int,
down_cores: Optional[Collection[int]]) -> Tuple[int, ...]:
n_monitors = MachineDataView.get_machine_version().n_scamp_cores
if down_cores is None:
return tuple(range(n_monitors, n_processors))
else:
processors = list()
for i in range(n_monitors):
if i in down_cores:
raise NotImplementedError(
f"Declaring monitor core {i} as down is not supported")
for i in range(n_monitors, n_processors):
if i not in down_cores:
processors.append(i)
return tuple(processors)

def is_processor_with_id(self, processor_id: int) -> bool:
"""
Expand Down Expand Up @@ -267,40 +244,14 @@ def parent_link(self) -> Optional[int]:
"""
return self._parent_link

def get_physical_core_id(self, virtual_p: int) -> Optional[int]:
"""
Get the physical core ID from a virtual core ID.

:param int virtual_p: The virtual core ID
:rtype: int or None if core not in map
"""
if (self._v_to_p_map is None or virtual_p >= len(self._v_to_p_map) or
self._v_to_p_map[virtual_p] == 0xFF):
return None
return self._v_to_p_map[virtual_p]

def get_physical_core_string(self, virtual_p: int) -> str:
"""
Get a string that can be appended to a core to show the physical
core, or an empty string if not possible.

:param int virtual_p: The virtual core ID
:rtype: str
"""
physical_p = self.get_physical_core_id(virtual_p)
if physical_p is None:
return ""
return f" (ph: {physical_p})"

def __str__(self) -> str:
if self._ip_address:
ip_info = f"ip_address={self.ip_address} "
else:
ip_info = ""
return (
f"[Chip: x={self[0]}, y={self[1]}, {ip_info}"
f"n_cores={self.n_processors}, "
f"mon={self.get_physical_core_id(0)}]")
f"n_cores={self.n_processors}]")

def __repr__(self) -> str:
return self.__str__()
68 changes: 66 additions & 2 deletions spinn_machine/data/machine_data_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import Callable, Optional, TYPE_CHECKING
from typing import Callable, Dict, Optional, TYPE_CHECKING
from spinn_utilities.typing.coords import XY
from spinn_utilities.data import UtilsDataView
from spinn_machine.exceptions import SpinnMachineException
Expand Down Expand Up @@ -48,7 +48,8 @@ class _MachineDataModel(object):
"_machine",
"_machine_generator",
"_machine_version",
"_user_accessed_machine"
"_user_accessed_machine",
"_v_to_p_map"
]

def __new__(cls) -> '_MachineDataModel':
Expand Down Expand Up @@ -78,6 +79,7 @@ def _hard_reset(self) -> None:
self._all_monitor_cores: int = 0
self._ethernet_monitor_cores: int = 0
self._machine: Optional[Machine] = None
self._v_to_p_map: Optional[Dict[XY, bytes]] = None
self._user_accessed_machine = False

def _soft_reset(self) -> None:
Expand Down Expand Up @@ -268,6 +270,68 @@ def get_machine_version(cls) -> AbstractVersion:
cls.__data._machine_version = version_factory()
return cls.__data._machine_version

@classmethod
def set_v_to_p_map(cls, v_to_p_map: Dict[XY, bytes]):
"""
Registers the mapping from Virtual to int physical core ids

Note: Only expected to be used in Version 1

:param dict((int, int), bytes) v_to_p_map:
"""
if cls.__data._v_to_p_map is None:
cls.__data._v_to_p_map = v_to_p_map
else:
raise SpinnMachineException(
"Unexpected second call to set_v_to_p_map")

@classmethod
def get_physical_core_id(cls, xy: XY, virtual_p: int) -> Optional[int]:
"""
Get the physical core ID from a virtual core ID.

Note: This call only works for Version 1

:param (int, int) xy: The Chip or its XY coordinates
:param int virtual_p: The virtual core ID
:rtype: int or None if core not in map
"""
if cls.__data._v_to_p_map is None:
version = cls.get_machine_version()
# delayed import to avoid circular reference
# pylint: disable=import-outside-toplevel
from spinn_machine.version.version_spin1 import VersionSpin1
if isinstance(version, VersionSpin1):
return None
else:
# TODO Spin2
raise SpinnMachineException(
f"This call is not supported when using Version {version}")
if xy in cls.__data._v_to_p_map:
v_to_p_map = cls.__data._v_to_p_map[xy]
else:
return None
if (virtual_p >= len(v_to_p_map) or v_to_p_map[virtual_p] == 0xFF):
return None
return v_to_p_map[virtual_p]

@classmethod
def get_physical_core_string(cls, xy: XY, virtual_p: int) -> str:
"""
Returns a String representing the physical core

:param (int, int) xy: The Chip or its XY coordinates
:param virtual_p: The virtual (python) id for the core
:rtype: str
"""
if cls.__data._v_to_p_map is not None:
physical_p = cls.get_physical_core_id(xy, virtual_p)
if physical_p is None:
return ""
return f" (ph: {physical_p})"
else:
return ""

@classmethod
def get_all_monitor_cores(cls) -> int:
"""
Expand Down
3 changes: 2 additions & 1 deletion spinn_machine/json_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ def machine_from_json(j_machine: Union[JsonObject, str]) -> Machine:
router = Router(links, router_entries)

# Create and add a chip with this router
n_cores = _int(details["cores"])
chip = Chip(
source_x, source_y, _int(details["cores"]), router, sdram,
source_x, source_y, [0], range(1, n_cores), router, sdram,
_int(board_x), _int(board_y), ip_address, [
_int(tag) for tag in tag_ids])
machine.add_chip(chip)
Expand Down
3 changes: 2 additions & 1 deletion spinn_machine/machine_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ def _machine_ignore(
links.append(link)
router = Router(links, chip.router.n_available_multicast_entries)
chip = Chip(
chip.x, chip.y, chip.n_processors, router, chip.sdram,
chip.x, chip.y, chip.scamp_processors_ids,
chip.placable_processors_ids, router, chip.sdram,
chip.nearest_ethernet_x, chip.nearest_ethernet_y,
chip.ip_address, chip.tag_ids)
new_machine.add_chip(chip)
Expand Down
8 changes: 5 additions & 3 deletions spinn_machine/virtual_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,14 @@ def _create_chip(self, xy: XY, configured_chips: Dict[XY, Tuple[XY, int]],

((eth_x, eth_y), n_cores) = configured_chips[xy]

down_cores = self._unused_cores.get(xy, None)
x, y = xy
sdram = MachineDataView.get_machine_version().max_sdram_per_chip
cores = list(range(1, n_cores))
for down_core in self._unused_cores.get(xy, []):
if down_core in cores:
cores.remove(down_core)
return Chip(
x, y, n_cores, chip_router, sdram, eth_x, eth_y,
ip_address, down_cores=down_cores)
x, y, [0], cores, chip_router, sdram, eth_x, eth_y, ip_address)

def _calculate_links(
self, xy: XY, configured_chips: Dict[XY, Tuple[XY, int]]
Expand Down
24 changes: 24 additions & 0 deletions unittests/data/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,30 @@ def test_where_is_setup(self):
MachineDataView.where_is_chip(None)
)

def test_v_to_p(self):
writer = MachineDataWriter.setup()
# TODO SPIN2
set_config("Machine", "version", FIVE)
# Before setting
self.assertEqual(None, writer.get_physical_core_id((1, 2), 3))
self.assertEqual("", writer.get_physical_core_string((1, 2), 3))

# Set a v_to_p
v_to_p = dict()
v_to_p[(1, 2)] = bytes([10, 11, 12, 13, 14])
writer.set_v_to_p_map(v_to_p)
# XY that exists
self.assertEqual(13, writer.get_physical_core_id((1, 2), 3))
self.assertEqual(" (ph: 13)",
writer.get_physical_core_string((1, 2), 3))
# Xy that does not exist
self.assertEqual(None, writer.get_physical_core_id((1, 4), 3))
self.assertEqual("",
writer.get_physical_core_string((1, 4), 3))
self.assertEqual(None, writer.get_physical_core_id((1, 2), 19))
self.assertEqual("",
writer.get_physical_core_string((1, 2), 19))

def test_mock_any(self):
# Should work with any version
set_config("Machine", "versions", VersionStrings.ANY.text)
Expand Down
14 changes: 6 additions & 8 deletions unittests/test_chip.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def setUp(self):
self._ip = "192.162.240.253"

def _create_chip(self, x, y, processors, r, sdram, ip):
return Chip(x, y, processors, r, sdram, 0, 0, ip)
return Chip(x, y, [0], range(1, processors), r, sdram, 0, 0, ip)

def test_create_chip(self):
new_chip = self._create_chip(self._x, self._y, self.n_processors,
Expand All @@ -59,21 +59,19 @@ def test_create_chip(self):
self.assertEqual(new_chip.n_placable_processors, self.n_processors - 1)
print(new_chip.__repr__())
self.assertEqual(
"[Chip: x=0, y=1, ip_address=192.162.240.253 "
"n_cores=18, mon=None]",
"[Chip: x=0, y=1, ip_address=192.162.240.253 n_cores=18]",
new_chip.__repr__(),)
self.assertEqual(new_chip.tag_ids, OrderedSet([1, 2, 3, 4, 5, 6, 7]))
self.assertTrue(new_chip.is_processor_with_id(3))

def test_0_down(self):
# Chip where 0 the monitor is down
with self.assertRaises(NotImplementedError):
Chip(1, 1, self.n_processors, self._router, self._sdram, 0, 0,
self._ip, down_cores=[0])
Chip(1, 1, [1], range(3, self.n_processors), self._router,
self._sdram, 0, 0, self._ip)

def test_1_chip(self):
# Chip with just 1 processor
new_chip = Chip(1, 1, 1, self._router, self._sdram, 0, 0, self._ip)
new_chip = Chip(1, 1, [0], [], self._router, self._sdram, 0, 0,
self._ip)
with self.assertRaises(Exception):
new_chip.get_first_none_monitor_processor()

Expand Down
8 changes: 4 additions & 4 deletions unittests/test_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ def setUp(self):
def _create_chip(self, x, y):
n_cores = MachineDataView.get_machine_version().max_cores_per_chip
if x == y == 0:
return Chip(x, y, n_cores, self._router, self._sdram,
self._nearest_ethernet_chip[0],
return Chip(x, y, [0], range(1, n_cores), self._router,
self._sdram, self._nearest_ethernet_chip[0],
self._nearest_ethernet_chip[1], self._ip)
return Chip(x, y, n_cores, self._router, self._sdram,
return Chip(x, y, [0], range(1, n_cores), self._router, self._sdram,
self._nearest_ethernet_chip[0],
self._nearest_ethernet_chip[1], None)

Expand Down Expand Up @@ -154,7 +154,7 @@ def test_chip_already_exists(self):
machine = virtual_machine_by_boards(1)
with self.assertRaises(SpinnMachineAlreadyExistsException):
machine.add_chip(Chip(
0, 0, 18, self._router, self._sdram,
0, 0, [0], range(1, 18), self._router, self._sdram,
self._nearest_ethernet_chip[0],
self._nearest_ethernet_chip[1], self._ip))

Expand Down
4 changes: 2 additions & 2 deletions unittests/test_virtual_machine201.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ def _create_chip(self, x, y):
_ip = "192.162.240.253"

if (x == y == 0):
return Chip(x, y, n_processors, _router, _sdram,
return Chip(x, y, [0], range(1, n_processors), _router, _sdram,
nearest_ethernet_chip[0],
nearest_ethernet_chip[1], _ip)
else:
return Chip(x, y, n_processors, _router, _sdram,
return Chip(x, y, [0], range(1, n_processors), _router, _sdram,
nearest_ethernet_chip[0],
nearest_ethernet_chip[1], None)

Expand Down
4 changes: 2 additions & 2 deletions unittests/test_virtual_machine3.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ def _create_chip(self, x, y):
_ip = "192.162.240.253"

if (x == y == 0):
return Chip(x, y, n_processors, _router, _sdram,
return Chip(x, y, [0], range(1, n_processors), _router, _sdram,
nearest_ethernet_chip[0],
nearest_ethernet_chip[1], _ip)
else:
return Chip(x, y, n_processors, _router, _sdram,
return Chip(x, y, [0], range(1, n_processors), _router, _sdram,
nearest_ethernet_chip[0],
nearest_ethernet_chip[1], None)

Expand Down