Skip to content

Commit

Permalink
Merge pull request #257 from SpiNNakerManchester/version2
Browse files Browse the repository at this point in the history
Version and testing for Spin2
  • Loading branch information
rowleya authored Apr 19, 2024
2 parents 00dfae5 + 008acdc commit 3c53f25
Show file tree
Hide file tree
Showing 32 changed files with 2,252 additions and 777 deletions.
1 change: 0 additions & 1 deletion spinn_machine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@
from .spinnaker_triad_geometry import SpiNNakerTriadGeometry
from .virtual_machine import virtual_machine


__all__ = ["Chip", "CoreSubset", "CoreSubsets",
"FrozenCoreSubsets", "Link", "Machine", "MulticastRoutingEntry",
"Router", "RoutingEntry", "SpiNNakerTriadGeometry",
Expand Down
33 changes: 33 additions & 0 deletions spinn_machine/data/machine_data_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class _MachineDataModel(object):

__slots__ = [
# Data values cached
"_all_monitor_cores",
"_ethernet_monitor_cores",
"_machine",
"_machine_generator",
"_machine_version",
Expand Down Expand Up @@ -73,6 +75,8 @@ def _hard_reset(self) -> None:
This does NOT clear the machine as it may have been asked for before
"""
self._soft_reset()
self._all_monitor_cores: int = 0
self._ethernet_monitor_cores: int = 0
self._machine: Optional[Machine] = None
self._user_accessed_machine = False

Expand Down Expand Up @@ -263,3 +267,32 @@ def get_machine_version(cls) -> AbstractVersion:
if cls.__data._machine_version is None:
cls.__data._machine_version = version_factory()
return cls.__data._machine_version

@classmethod
def get_all_monitor_cores(cls) -> int:
"""
The number of cores on every chip reported to be used by \
monitor vertices.
Ethernet-enabled chips may have more.
Does not include the system core reserved by the machine/ scamp.
:rtype: int
"""
return cls.__data._all_monitor_cores

@classmethod
def get_ethernet_monitor_cores(cls) -> int:
"""
The number of cores on every Ethernet chip reported to be used by \
monitor vertices.
This includes the one returned by get_all_monitor_cores unless for
some reason these are not on Ethernet chips.
Does not include the system core reserved by the machine/ scamp.
:rtype: int
"""
return cls.__data._ethernet_monitor_cores
29 changes: 22 additions & 7 deletions spinn_machine/data/machine_data_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from spinn_utilities.data.utils_data_writer import UtilsDataWriter
from spinn_utilities.overrides import overrides
from spinn_utilities.log import FormatAdapter
from spinn_machine import Machine, virtual_machine
from spinn_machine import Machine
from spinn_machine.virtual_machine import virtual_machine_by_boards
from .machine_data_view import MachineDataView, _MachineDataModel
logger = FormatAdapter(logging.getLogger(__name__))
__temp_dir = None
Expand Down Expand Up @@ -46,12 +47,7 @@ def _mock_machine(self) -> None:
"""
Method to create a virtual machine in mock mode.
"""
if self.get_machine_version().number == 3:
self.set_machine(virtual_machine(width=2, height=2))
elif self.get_machine_version().number == 5:
self.set_machine(virtual_machine(width=8, height=8))
else:
raise NotImplementedError("Please set machine version")
self.set_machine(virtual_machine_by_boards(1))

@overrides(UtilsDataWriter._setup)
def _setup(self) -> None:
Expand Down Expand Up @@ -109,3 +105,22 @@ def set_machine_generator(self, machine_generator: Callable[[], None]):
if not callable(machine_generator):
raise TypeError("machine_generator must be callable")
self.__data._machine_generator = machine_generator

def add_monitor_core(self, all_chips: bool):
"""
Accepts a simple of the monitor cores to be added.
Called by PacmanDataWriter add_sample_monitor_vertex.
Only affect is to change the numbers reported by the
get_all/ethernet_monitor methods.
:param bool all_chips:
If True assumes that this Vertex will be placed on all chips
including Ethernet ones.
If False assumes that this Vertex type will only be placed on
Ethernet Vertices
"""
self.__data._ethernet_monitor_cores += 1
if all_chips:
self.__data._all_monitor_cores += 1
4 changes: 0 additions & 4 deletions spinn_machine/full_wrap_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ class FullWrapMachine(Machine):
This class provides the more complex maths to deal with wraps.
"""
@overrides(Machine.multiple_48_chip_boards)
def multiple_48_chip_boards(self) -> bool:
return self._width % 12 == 0 and self._height % 12 == 0

@overrides(Machine.get_xys_by_ethernet)
def get_xys_by_ethernet(
self, ethernet_x: int, ethernet_y: int) -> Iterable[XY]:
Expand Down
4 changes: 0 additions & 4 deletions spinn_machine/horizontal_wrap_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ class HorizontalWrapMachine(Machine):
This class provides the more complex maths to deal with wraps.
"""

@overrides(Machine.multiple_48_chip_boards)
def multiple_48_chip_boards(self) -> bool:
return self._width % 12 == 0 and (self._height - 4) % 12 == 0

@overrides(Machine.get_xys_by_ethernet)
def get_xys_by_ethernet(
self, ethernet_x: int, ethernet_y: int) -> Iterable[XY]:
Expand Down
32 changes: 17 additions & 15 deletions spinn_machine/json_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,21 +236,7 @@ def to_json() -> JsonObject:
:rtype: dict
"""
machine = MachineDataView.get_machine()
# Find the standard values for any non-Ethernet chip to use by default
std = None
for chip in machine.chips:
if chip.ip_address is None:
std = _Desc(
monitors=chip.n_processors - chip.n_placable_processors,
router_entries=_int_value(
chip.router.n_available_multicast_entries),
sdram=chip.sdram,
tags=list(chip.tag_ids))
break
else:
raise ValueError("could not compute standard resources")

# find the nth values to use for Ethernet chips
# find the standard values to use for Ethernet chips
chip = machine.boot_chip
eth = _Desc(
monitors=chip.n_processors - chip.n_placable_processors,
Expand All @@ -259,6 +245,22 @@ def to_json() -> JsonObject:
sdram=chip.sdram,
tags=list(chip.tag_ids))

# Find the standard values for any non-Ethernet chip to use by default
if machine.n_chips > 1:
for chip in machine.chips:
if chip.ip_address is None:
std = _Desc(
monitors=chip.n_processors - chip.n_placable_processors,
router_entries=_int_value(
chip.router.n_available_multicast_entries),
sdram=chip.sdram,
tags=list(chip.tag_ids))
break
else:
raise ValueError("could not compute standard resources")
else:
std = eth

# write basic stuff
return {
"height": machine.height,
Expand Down
8 changes: 8 additions & 0 deletions spinn_machine/link_data_objects/spinnaker_link_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,11 @@ def __hash__(self) -> int:
return hash((self._spinnaker_link_id,
self.connected_chip_x, self.connected_chip_y,
self.connected_link, self.board_address))

def __str__(self):
return (f"id:{self._spinnaker_link_id} x:{self.connected_chip_x} "
f"y:{self.connected_chip_y} link:{self._spinnaker_link_id} "
f"{self.board_address}")

def __repr__(self) -> str:
return self.__str__()
129 changes: 35 additions & 94 deletions spinn_machine/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,6 @@ def __init__(self, width: int, height: int, chip_core_map: Dict[XY, int],
self._n_router_entries_counter: Counter[int] = Counter()
self._sdram_counter: Counter[int] = Counter()

@abstractmethod
def multiple_48_chip_boards(self) -> bool:
"""
Checks that the width and height correspond to the expected size for a
multi-board machine made up of more than one 48 chip board.
The assumption is that any size machine can be supported but that
only ones with an expected 48 chip board size can have more than one
Ethernet-enabled chip.
:return: True if this machine can have multiple 48 chip boards
:rtype: bool
"""
raise NotImplementedError

@abstractmethod
def get_xys_by_ethernet(
self, ethernet_x: int, ethernet_y: int) -> Iterable[XY]:
Expand Down Expand Up @@ -501,15 +486,15 @@ def validate(self) -> None:
if self._boot_ethernet_address is None:
raise SpinnMachineException(
"no ethernet chip at 0, 0 found")
version = MachineDataView.get_machine_version()
if len(self._ethernet_connected_chips) > 1:
if not self.multiple_48_chip_boards():
if not version.supports_multiple_boards:
raise SpinnMachineException(
f"A {self.wrap} machine of size {self._width}, "
f"{self._height} can not handle multiple ethernet chips")
# The fact that self._boot_ethernet_address is set means there is an
# Ethernet chip and it is at 0,0 so no need to check that

version = MachineDataView.get_machine_version()
for chip in self.chips:
if chip.x < 0:
raise SpinnMachineException(
Expand Down Expand Up @@ -853,22 +838,18 @@ def add_spinnaker_links(self) -> None:
Add SpiNNaker links that are on a given machine depending on the
version of the board.
"""
if self._width == self._height == 2:
chip_0_0 = self[0, 0]
ip_0_0 = chip_0_0.ip_address
assert ip_0_0 is not None
if not chip_0_0.router.is_link(3):
self._add_spinnaker_link(0, 0, 0, 3, ip_0_0)
chip = self.get_chip_at(1, 0)
if chip is not None and not chip.router.is_link(0):
self._add_spinnaker_link(1, 1, 0, 0, ip_0_0)
elif (self._width == self._height == 8) or \
self.multiple_48_chip_boards():
for chip in self._ethernet_connected_chips:
if not chip.router.is_link(4):
ip = chip.ip_address
assert ip is not None
self._add_spinnaker_link(0, chip.x, chip.y, 4, ip)
version = MachineDataView.get_machine_version()
for ethernet in self._ethernet_connected_chips:
ip = ethernet.ip_address
assert ip is not None
for (s_id, (local_x, local_y, link)) in enumerate(
version.spinnaker_links()):
global_x, global_y = self.get_global_xy(
local_x, local_y, ethernet.x, ethernet.y)
chip = self.get_chip_at(global_x, global_y)
if chip is not None and not chip.router.is_link(link):
self._add_spinnaker_link(
s_id, global_x, global_y, link, ip)

def _add_spinnaker_link(
self, spinnaker_link_id: int, x: int, y: int, link: int,
Expand All @@ -883,72 +864,32 @@ def add_fpga_links(self) -> None:
Add FPGA links that are on a given machine depending on the
version of the board.
"""
if self._width == self._height == 8 or self.multiple_48_chip_boards():

for ethernet_connected_chip in self._ethernet_connected_chips:

# the sides of the hexagonal shape of the board are as follows
#
#
# Top
# ####
# #####
# Top Left ###### Right
# #######
# ########
# #######
# Left ###### Bottom Right
# #####
# Bottom
#

# handle the first chip
(ex, ey) = ethernet_connected_chip
ip = ethernet_connected_chip.ip_address
assert ip is not None

# List of start x, start y, first link, second link,
# change in x to next, change in y to next
chip_links = [(7, 3, 0, 5, -1, -1), # Bottom Right
(4, 0, 4, 5, -1, 0), # Bottom
(0, 0, 4, 3, 0, 1), # Left
(0, 3, 2, 3, 1, 1), # Top Left
(4, 7, 2, 1, 1, 0), # Top
(7, 7, 0, 1, 0, -1)] # Right

f = 0
lk = 0
for i, (x, y, l1, l2, dx, dy) in enumerate(chip_links):
for _ in range(4):
fx = (x + ex) % (self._width)
fy = (y + ey) % (self._height)
self._add_fpga_link(f, lk, fx, fy, l1, ip, ex, ey)
f, lk = self._next_fpga_link(f, lk)
if i % 2 == 1:
x += dx
y += dy
fx = (x + ex) % (self._width)
fy = (y + ey) % (self._height)
self._add_fpga_link(f, lk, fx, fy, l2, ip, ex, ey)
f, lk = self._next_fpga_link(f, lk)
if i % 2 == 0:
x += dx
y += dy
version = MachineDataView.get_machine_version()
for ethernet in self._ethernet_connected_chips:
ip = ethernet.ip_address
assert ip is not None
for (local_x, local_y, link, fpga_id, fpga_link) in \
version.fpga_links():
global_x, global_y = self.get_global_xy(
local_x, local_y, ethernet.x, ethernet.y)
chip = self.get_chip_at(global_x, global_y)
if chip is not None:
self._add_fpga_link(fpga_id, fpga_link, chip.x, chip.y,
link, ip, ethernet.x, ethernet.y)

def _add_fpga_link(
self, fpga_id: int, fpga_link: int, x: int, y: int, link: int,
board_address: str, ex: int, ey: int):
# pylint: disable=too-many-arguments
if self.is_chip_at(x, y):
link_data = FPGALinkData(
fpga_link_id=fpga_link, fpga_id=fpga_id,
connected_chip_x=x, connected_chip_y=y,
connected_link=link, board_address=board_address)
self._fpga_links[board_address, fpga_id, fpga_link] = link_data
# Add for the exact chip coordinates
self._fpga_links[(x, y), fpga_id, fpga_link] = link_data
# Add for the Ethernet chip coordinates to allow this to work too
self._fpga_links[(ex, ey), fpga_id, fpga_link] = link_data
link_data = FPGALinkData(
fpga_link_id=fpga_link, fpga_id=fpga_id,
connected_chip_x=x, connected_chip_y=y,
connected_link=link, board_address=board_address)
self._fpga_links[board_address, fpga_id, fpga_link] = link_data
# Add for the exact chip coordinates
self._fpga_links[(x, y), fpga_id, fpga_link] = link_data
# Add for the Ethernet chip coordinates to allow this to work too
self._fpga_links[(ex, ey), fpga_id, fpga_link] = link_data

@staticmethod
def _next_fpga_link(fpga_id: int, fpga_link: int) -> Tuple[int, int]:
Expand Down
4 changes: 0 additions & 4 deletions spinn_machine/no_wrap_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ class NoWrapMachine(Machine):
This class provides the simpler maths that do not deal with wraps.
"""

@overrides(Machine.multiple_48_chip_boards)
def multiple_48_chip_boards(self) -> bool:
return (self._width - 4) % 12 == 0 and (self._height - 4) % 12 == 0

@overrides(Machine.get_xys_by_ethernet)
def get_xys_by_ethernet(
self, ethernet_x: int, ethernet_y: int) -> Iterable[XY]:
Expand Down
2 changes: 2 additions & 0 deletions spinn_machine/spinn_machine.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

[Machine]
version = None
# Used Instead of version if multiple versions should be tested!
versions = None
width = None
height = None
# Note: if json_path is set all other configs for virtual boards are ignored
Expand Down
4 changes: 4 additions & 0 deletions spinn_machine/version/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@
# 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 .version_factory import FIVE, SPIN2_1CHIP, THREE, version_factory

__all__ = ["FIVE", "SPIN2_1CHIP", "THREE", "version_factory"]
Loading

0 comments on commit 3c53f25

Please sign in to comment.