def test_fully_connect_chips_pair_of_chips_no_wrap_around(self): """ Test that a model.fully_connect_chips connects (and leaves disconnected) the correct links when a pair of touching chips are created. """ # Test with the neighbouring chip being on every possible edge. for direction in [ topology.EAST , topology.NORTH_EAST , topology.NORTH , topology.WEST , topology.SOUTH_WEST , topology.SOUTH ]: other_chip_position = topology.to_xy(topology.add_direction((0,0,0), direction)) chips = { (0,0) : model.make_chip((0,0)) , other_chip_position : model.make_chip(other_chip_position) } model.fully_connect_chips(chips) # Only the touching ports are connected for port in model.Router.EXTERNAL_PORTS: if port == direction: # Touching port self.assertEqual( chips[(0,0)].router.connections[port] , chips[other_chip_position].router ) self.assertEqual( chips[other_chip_position].router.connections[topology.opposite(port)] , chips[(0,0)].router ) else: # Non-touching port self.assertIsNone(chips[(0,0)].router.connections[port]) self.assertIsNone(chips[other_chip_position].router.connections[topology.opposite(port)])
def follow_packet(self, in_wire_side, packet_direction): """ Follow the path of a packet which entered in to the board via the wire in_wire_side following packet_direction through the chips in the board. Returns a tuple (next_in_wire_side, next_board). We only need to know the side on which the incoming link is on (not the exact chip) because for any incoming side there is a fixed outgoing side when travelling in a fixed direction. """ # Mapping of {(in_wire_side, packet_direction) : out_wire_side,...} out_sides = { (topology.SOUTH_WEST, topology.EAST) : topology.EAST, (topology.WEST, topology.EAST) : topology.NORTH_EAST, (topology.SOUTH_WEST, topology.NORTH_EAST) : topology.NORTH, (topology.SOUTH, topology.NORTH_EAST) : topology.NORTH_EAST, (topology.SOUTH, topology.NORTH) : topology.WEST, (topology.EAST, topology.NORTH) : topology.NORTH, } # Opposite cases are simply inverted versions of the above... for (iws, pd), ows in out_sides.items(): out_sides[( topology.opposite(iws) , topology.opposite(pd) )] = topology.opposite(ows) out_wire_side = out_sides[(in_wire_side, packet_direction)] return (topology.opposite(out_wire_side), self.follow_wire(out_wire_side))
def test_fully_connect_chips_pair_of_chips_no_wrap_around(self): """ Test that a model.fully_connect_chips connects (and leaves disconnected) the correct links when a pair of touching chips are created. """ # Test with the neighbouring chip being on every possible edge. for direction in [ topology.EAST , topology.NORTH_EAST , topology.NORTH , topology.WEST , topology.SOUTH_WEST , topology.SOUTH ]: other_chip_position = topology.to_xy(topology.add_direction((0,0,0), direction)) chips = { (0,0) : model.make_chip((0,0)) , other_chip_position : model.make_chip(other_chip_position) } model.fully_connect_chips(chips) # Only the touching ports are connected for port in model.Router.EXTERNAL_PORTS: if port == direction: # Touching port self.assertEqual( chips[(0,0)][0].connections[port] , chips[other_chip_position][0] ) self.assertEqual( chips[other_chip_position][0].connections[topology.opposite(port)] , chips[(0,0)][0] ) else: # Non-touching port self.assertIsNone(chips[(0,0)][0].connections[port]) self.assertIsNone(chips[other_chip_position][0].connections[topology.opposite(port)])
def follow_packet(self, in_wire_side, packet_direction): """ Follow the path of a packet which entered in to the board via the wire in_wire_side following packet_direction through the chips in the board. Returns a tuple (next_in_wire_side, next_board). We only need to know the side on which the incoming link is on (not the exact chip) because for any incoming side there is a fixed outgoing side when travelling in a fixed direction. """ # Mapping of {(in_wire_side, packet_direction) : out_wire_side,...} out_sides = { (topology.SOUTH_WEST, topology.EAST): topology.EAST, (topology.WEST, topology.EAST): topology.NORTH_EAST, (topology.SOUTH_WEST, topology.NORTH_EAST): topology.NORTH, (topology.SOUTH, topology.NORTH_EAST): topology.NORTH_EAST, (topology.SOUTH, topology.NORTH): topology.WEST, (topology.EAST, topology.NORTH): topology.NORTH, } # Opposite cases are simply inverted versions of the above... for (iws, pd), ows in out_sides.items(): out_sides[(topology.opposite(iws), topology.opposite(pd))] = topology.opposite(ows) out_wire_side = out_sides[(in_wire_side, packet_direction)] return (topology.opposite(out_wire_side), self.follow_wire(out_wire_side))
def connect_wire(self, other, direction): """ Connect a wire between this board and another for the given direction. """ # Ensure it isn't already connected assert(self.follow_wire(direction) is None) assert(other.follow_wire(topology.opposite(direction)) is None) self.connection[direction] = other other.connection[topology.opposite(direction)] = self
def connect_wire(self, other, direction): """ Connect a wire between this board and another for the given direction. """ # Ensure it isn't already connected assert (self.follow_wire(direction) is None) assert (other.follow_wire(topology.opposite(direction)) is None) self.connection[direction] = other other.connection[topology.opposite(direction)] = self
def table_gen(router): """ Generate a routing table description file for a given router based on the format used for loading by ybug. """ # A list of (route_bits, key, mask) tuples corresponding to the router entries # required. table_entries = [] # Work out what routing entries are required for route, (incoming_port, outgoing_ports) in router.routes.iteritems(): # Routes which simply forward packets without changing their # direction/forking are default routed and do not require a table entry. if not ( incoming_port in model.Router.EXTERNAL_PORTS \ and len(outgoing_ports) == 1 \ and topology.opposite(incoming_port) in outgoing_ports ): route_bits = sum(LINK_BITS[port] for port in outgoing_ports) key = route.key mask = 0xFFFFFFFF table_entries.append((route_bits, key, mask)) # Generate the binary formatted table entries out = "" for entry_num, (route_bits, key, mask) in enumerate(table_entries): out += rtr_entry_t.pack(entry_num, len(table_entries), route_bits, key, mask) # Terminate with an empty all-ones entry out += rtr_entry_t.pack(0xFFFF, 0xFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF) return out
def test_connections(self): # Try with several sizes for torus_size in SpiNNakerTorusTests.TORUS_SIZES: self.generate_torus(*torus_size) # Get a dictionary of chips to their locations chips = {} for board in self.torus.boards.itervalues(): for chip in board.chips.itervalues(): chips[chip.get_mesh_position()] = chip # Check each chip is connected with their neighbours N, NE, W for pos, chip in chips.iteritems(): for direction in [topology.NORTH, topology.NORTH_EAST, topology.WEST]: other_pos = topology.to_xy( topology.add_direction(topology.zero_pad(pos), direction)) other_chip = chips[( other_pos[0]%(self.torus.width*12) , other_pos[1]%(self.torus.height*12) , )] other_direction = topology.opposite(direction) # Make sure we share links self.assertEqual(chip.get_in_link(direction), other_chip.get_out_link(other_direction)) self.assertEqual(chip.get_out_link(direction), other_chip.get_in_link(other_direction))
def test_threeboard_packets(self): # Exhaustively check that packets travelling in each direction take the # correct number of hops to wrap back according to Simon Davidson's model. for testcase in BoardTests.TEST_CASES: w, h = testcase boards = board.create_torus(w, h) # Try starting from every board for start_board, start_coord in boards: # Try going in every possible direction for direction in [ topology.EAST , topology.NORTH_EAST , topology.NORTH , topology.WEST , topology.SOUTH_WEST , topology.SOUTH ]: # Packets can enter when travelling in direction from the side with the # opposite label and one counter-clockwise from that. for entry_point in [topology.opposite(direction) , topology.next_ccw(topology.opposite(direction)) ]: num_boards = len(list(board.follow_packet_loop(start_board, entry_point, direction))) # For every threeboard traversed, the number of chips traversed is 3*l # where l is the number of rings in the hexagon. Travelling in one # direction we pass through a threeboard every two boards traversed so # the number of nodes traversed is num_nodes*l where num_hops is given # as below. num_nodes = (num_boards/2) * 3 # The principal axis is south to north, i.e. along the height in # threeboards. This should have 3*l*h nodes along its length. if direction in (topology.NORTH, topology.SOUTH): self.assertEqual(num_nodes, h*3) # The major axis is east to west, i.e. along the width in # threeboards. This should have 3*l*w nodes along its length. if direction in (topology.EAST, topology.WEST): self.assertEqual(num_nodes, w*3) # The minor axis is norht-east to south-west, i.e. diagonally across # the mesh of threeboards. This should have 3*l*lcm(w,h) nodes along # its length. if direction in (topology.NORTH_EAST, topology.SOUTH_WEST): self.assertEqual(num_nodes, self.lcm(w,h)*3)
def wire_length(boards, board, direction, wire_offsets={}): """ Returns the length of a wire leaving the specified board in a given direction. boards is a list [(board, coord),...)] where all coords support subtraction and magnitude() (such as those from the coordinates module). board is a board in that list direction is a wire direction to measure wire_offsets is an (optional) dict {direction:offset,...} where the offset supplied for each direction. """ b2c = dict(boards) source = b2c[board] target = b2c[board.connection[direction]] if direction in wire_offsets: source += wire_offsets[direction] if topology.opposite(direction) in wire_offsets: target += wire_offsets[topology.opposite(direction)] return (source - target).magnitude()
def get_router_entries(router): """ Given a router, returns a list of (route_bits, key, mask) tuples. """ # A list of (route_bits, key, mask) tuples corresponding to the router entries # required. table_entries = [] # Work out what routing entries are required for route, (incoming_port, outgoing_ports) in router.routes.iteritems(): # Routes which simply forward packets without changing their # direction/forking are default routed and do not require a table entry. if not ( incoming_port in model.Router.EXTERNAL_PORTS \ and len(outgoing_ports) == 1 \ and topology.opposite(incoming_port) in outgoing_ports ): route_bits = sum(LINK_BITS[port] for port in outgoing_ports) key = route.key mask = 0xFFFFFFFF table_entries.append((route_bits, key, mask)) return table_entries
def fully_connect_chips(chips, wrap_around = False): """ Given a set of chips (i.e. (router, cores) tuples), fully interconnects the chips optionally including wrap-around links. """ # Calculate the bounds of the system's size (in case wrap_around is used) width = max(x for (x,y) in chips.iterkeys()) + 1 height = max(y for (x,y) in chips.iterkeys()) + 1 # Connect up the nodes to their neighbours for position, (router, cores) in chips.iteritems(): for direction in [topology.EAST, topology.NORTH_EAST, topology.NORTH]: next_x, next_y = topology.to_xy( topology.add_direction(topology.to_xyz(position), direction)) if wrap_around: next_x %= width next_y %= height # Only connect up to nodes which actually exist... if (next_x, next_y) in chips: router.connect(direction, chips[(next_x, next_y)][0], topology.opposite(direction))
def test_chips(self): # A board should have chips in the correct locations and no repeats self.assertEqual(len(self.board.chips), 48) self.assertEqual(set(self.board.chips.iterkeys()), set(topology.hexagon(4))) # For all inputs & outputs, if there is a chip in that direction it must be # connected via a SilistixLink and if not it should be attached to a # DeadLink. for src_pos, src_chip in self.board.chips.iteritems(): for direction in ( topology.EAST , topology.NORTH_EAST , topology.NORTH , topology.WEST , topology.SOUTH_WEST , topology.SOUTH ): in_link = src_chip.get_in_link(direction) out_link = src_chip.get_out_link(direction) dst_pos = topology.to_xy(topology.add_direction(topology.zero_pad(src_pos), direction)) if dst_pos in self.board.chips: # There is a chip opposite this connection dst_chip = self.board.chips[dst_pos] direction = topology.opposite(direction) # Check that they have the same link (connected to opposite ports) self.assertEqual(out_link, dst_chip.get_in_link(direction)) self.assertEqual(in_link, dst_chip.get_out_link(direction)) # And that they're SilistixLinks self.assertEqual(type(in_link), SilistixLink) self.assertEqual(type(out_link), SilistixLink) else: # No adjacent chip so should be DeadLinks self.assertEqual(type(in_link), DeadLink) self.assertEqual(type(out_link), DeadLink)
def fully_connect_chips(chips, wrap_around=False): """ Given a set of chips (i.e. (router, cores) tuples), fully interconnects the chips optionally including wrap-around links. """ # Calculate the bounds of the system's size (in case wrap_around is used) width = max(x for (x, y) in chips.iterkeys()) + 1 height = max(y for (x, y) in chips.iterkeys()) + 1 # Connect up the nodes to their neighbours for position, (router, cores) in chips.iteritems(): for direction in [topology.EAST, topology.NORTH_EAST, topology.NORTH]: next_x, next_y = topology.to_xy( topology.add_direction(topology.to_xyz(position), direction)) if wrap_around: next_x %= width next_y %= height # Only connect up to nodes which actually exist... if (next_x, next_y) in chips: router.connect(direction, chips[(next_x, next_y)][0], topology.opposite(direction))
def test_add_route(self): """ Test that model.add_route successfully works for a simple multicast route (and that defining the route twice has no ill-effects). """ chips = model.make_rectangular_board(2,2) # Make a path travelling round the system (do it twice to make sure nothing # gets duplicated) route = model.Route(0) for _ in range(2): model.add_route( route , [ chips[(0,0)].cores[0] , chips[(0,0)].router , chips[(0,1)].router , chips[(1,1)].router , chips[(1,0)].router , chips[(1,0)].cores[17] ] ) model.add_route( route , [ chips[(0,0)].cores[0] , chips[(0,0)].router , chips[(0,1)].router , chips[(1,1)].router , chips[(1,1)].cores[17] ] ) # Check that the route was added in the appropriate sink/source and nowhere # else for (position, core) in sum(( list((router.position, core) for core in cores.itervalues()) for (router,cores) in chips.itervalues() ), []): # Source should be in chip (0,0)'s 0th core if position == (0,0) and core.core_id == 0: self.assertEqual(core.sources, set([route])) else: self.assertEqual(core.sources, set()) # Sink should be in chips (1,0)'s and (1,1)'s 17th core if position in ((1,0), (1,1)) and core.core_id == 17: self.assertEqual(core.sinks, set([route])) else: self.assertEqual(core.sinks, set()) # Check that all connecting edges between routers are valid (i.e. face in # opposite directions and make sense) for router, cores in chips.itervalues(): for route, (input_port, output_ports) in router.routes.iteritems(): # Test the input has a corresponding output in the router/core if input_port in model.Router.INTERNAL_PORTS: # If a route is from a core, make sure the core knows about it core = router.connections[input_port] self.assertIn(route, core.sources) else: # Check the corresponding router has an output for this route pointing # at this router. other_router = router.connections[input_port] self.assertIn( topology.opposite(input_port) , other_router.routes[route][1] ) # Test all outputs have a coresponding input in another router/core for output_port in output_ports: if output_port in model.Router.INTERNAL_PORTS: # If a route is to a core, make sure the core knows about it core = router.connections[output_port] self.assertIn(route, core.sinks) else: # Check the corresponding router has an input for this route pointing # from this router. other_router = router.connections[output_port] self.assertEqual( topology.opposite(output_port) , other_router.routes[route][0] )
def test_add_route(self): """ Test that model.add_route successfully works for a simple multicast route (and that defining the route twice has no ill-effects). """ chips = model.make_rectangular_board(2,2) # Make a path travelling round the system (do it twice to make sure nothing # gets duplicated) route = model.Route(0) for _ in range(2): model.add_route( route , [ chips[(0,0)][1][0] , chips[(0,0)][0] , chips[(0,1)][0] , chips[(1,1)][0] , chips[(1,0)][0] , chips[(1,0)][1][17] ] ) model.add_route( route , [ chips[(0,0)][1][0] , chips[(0,0)][0] , chips[(0,1)][0] , chips[(1,1)][0] , chips[(1,1)][1][17] ] ) # Check that the route was added in the appropriate sink/source and nowhere # else for (position, core) in sum(( list((router.position, core) for core in cores) for (router,cores) in chips.itervalues() ), []): # Source should be in chip (0,0)'s 0th core if position == (0,0) and core.core_id == 0: self.assertEqual(core.sources, set([route])) else: self.assertEqual(core.sources, set()) # Sink should be in chips (1,0)'s and (1,1)'s 17th core if position in ((1,0), (1,1)) and core.core_id == 17: self.assertEqual(core.sinks, set([route])) else: self.assertEqual(core.sinks, set()) # Check that all connecting edges between routers are valid (i.e. face in # opposite directions and make sense) for router, cores in chips.itervalues(): for route, (input_port, output_ports) in router.routes.iteritems(): # Test the input has a corresponding output in the router/core if input_port in model.Router.INTERNAL_PORTS: # If a route is from a core, make sure the core knows about it core = router.connections[input_port] self.assertIn(route, core.sources) else: # Check the corresponding router has an output for this route pointing # at this router. other_router = router.connections[input_port] self.assertIn( topology.opposite(input_port) , other_router.routes[route][1] ) # Test all outputs have a coresponding input in another router/core for output_port in output_ports: if output_port in model.Router.INTERNAL_PORTS: # If a route is to a core, make sure the core knows about it core = router.connections[output_port] self.assertIn(route, core.sinks) else: # Check the corresponding router has an input for this route pointing # from this router. other_router = router.connections[output_port] self.assertEqual( topology.opposite(output_port) , other_router.routes[route][0] )
def __init__( self , scheduler , system , link_send_cycles # SilistixLink , link_ack_cycles # SilistixLink , injection_buffer_length # SpiNNaker101 , router_period # SpiNNakerRouter , wait_before_emergency # SpiNNakerRouter , wait_before_drop # SpiNNakerRouter , core_period # SpiNNakerTrafficGenerator , packet_prob # SpiNNakerTrafficGenerator , distance_std = None # SpiNNakerTrafficGenerator ): """ link_send_cycles see SilistixLink link_ack_cycles see SilistixLink injection_buffer_length see SpiNNaker101 router_period see SpiNNakerRouter wait_before_emergency see SpiNNakerRouter wait_before_drop see SpiNNakerRouter core_period see SpiNNakerTrafficGenerator packet_prob see SpiNNakerTrafficGenerator distance_std see SpiNNakerTrafficGenerator """ self.scheduler = scheduler self.system = system # A dictionary { (x,y): SpiNNaker101, ... } of all contained chips. The # coordinates are relative to the central chip (created first in the # hexagon). self.chips = { } # Utility function to add a new chip to the chips dict. def add_chip(position): "Add a chip at the specified position" self.chips[position] = SpiNNaker101( self.scheduler , self.system , injection_buffer_length , router_period , wait_before_emergency , wait_before_drop , core_period , packet_prob , distance_std ) # Create the chips in a hexagonal pattern for position in topology.hexagon(4): add_chip(position) # Put SilistixLinks between them for src_pos, src_chip in self.chips.iteritems(): # Try and link this chip to all other neighbours which are towards the # top/right of the chip for direction in (topology.NORTH, topology.NORTH_EAST, topology.EAST): dst_pos = topology.to_xy( topology.add_direction(topology.zero_pad(src_pos), direction)) # If the chip exists, put links in this direction if dst_pos in self.chips: dst_chip = self.chips[dst_pos] in_link = SilistixLink(self.scheduler, link_send_cycles, link_ack_cycles) out_link = SilistixLink(self.scheduler, link_send_cycles, link_ack_cycles) src_chip.set_out_link(direction, out_link) src_chip.set_in_link(direction, in_link) dst_chip.set_in_link(topology.opposite(direction), out_link) dst_chip.set_out_link(topology.opposite(direction), in_link)
def __init__( self , scheduler , system , width , height , use_sata_links , sata_accept_period # SATALink , sata_buffer_length # SATALink , sata_latency # SATALink , silistix_send_cycles # SilistixLink , silistix_ack_cycles # SilistixLink , injection_buffer_length # SpiNNaker101 , router_period # SpiNNakerRouter , wait_before_emergency # SpiNNakerRouter , wait_before_drop # SpiNNakerRouter , core_period # SpiNNakerTrafficGenerator , packet_prob # SpiNNakerTrafficGenerator , distance_std = None # SpiNNakerTrafficGenerator ): """ width is the number of three-board board-sets wide the system will be. height is the number of three-board board-sets tall the system will be. use_sata_links is a decision whether or not to use sata links to connect boards. If false, regular links are used. sata_accept_period see SATALink sata_buffer_length see SATALink sata_latency see SATALink silistix_send_cycles see SilistixLink silistix_ack_cycles see SilistixLink injection_buffer_length see SpiNNaker101 router_period see SpiNNakerRouter wait_before_emergency see SpiNNakerRouter wait_before_drop see SpiNNakerRouter core_period see SpiNNakerTrafficGenerator packet_prob see SpiNNakerTrafficGenerator distance_std see SpiNNakerTrafficGenerator """ self.scheduler = scheduler self.system = system self.width = width self.height = height # A dictionary { (x,y): SpiNNaker103, ... } of all contained boards. The # coordinates are relative to the bottom-leftmost board (which is not # wrapped around). A full board is two units wide and two units tall in this # coordinate system. self.boards = { } # The size of the mesh of chips: twelve chips per board set mesh_dimensions = (self.width * 12, self.height * 12) # Initially create all the boards for y in range(self.height): for x in range(self.width): # z is the index of the board within the set. 0 is the bottom left, 1 is # the top, 2 is the right for z in range(3): # Odd-rows start offset by one unit right, odd columns start offset one # unit up. board = SpiNNaker103( scheduler , system , silistix_send_cycles , silistix_ack_cycles , injection_buffer_length , router_period , wait_before_emergency , wait_before_drop , core_period , packet_prob , distance_std ) # The coordinates of a board within the set of boards x_coord = x*3 + z y_coord = y*3 + (3-z)%3 self.boards[(x_coord, y_coord)] = board # Set the board's position in terms of the whole system board.set_mesh_dimensions(*mesh_dimensions) x_mesh_coord = x_coord*4 y_mesh_coord = y_coord*4 board.set_mesh_position(x_mesh_coord, y_mesh_coord) # If the board is on a right/top edge, the right/top half of its chips are # actually on the left-hand-side/bottom of the system if x_coord == (self.width*3)-1: board.set_mesh_position_right(0, y_mesh_coord) if y_coord == (self.height*3)-1: # Plus one is due to the bottom edge of the hexagons being longer than # the left-edge board.set_mesh_position_top(x_mesh_coord+1, 0) # Now link every board with all those above and to the right for board_coords, board in self.boards.iteritems(): top_board_coords = ( (board_coords[0]+1) % (self.width*3) , (board_coords[1]+2) % (self.height*3) ) top_right_board_coords = ( (board_coords[0]+2) % (self.width*3) , (board_coords[1]+1) % (self.height*3) ) btm_right_board_coords = ( (board_coords[0]+1) % (self.width*3) , (board_coords[1]-1) % (self.height*3) ) # Create the links for these edges for other_coords, edge in ( (top_board_coords, topology.EDGE_TOP) , (top_right_board_coords, topology.EDGE_TOP_RIGHT) , (btm_right_board_coords, topology.EDGE_BOTTOM_RIGHT) ): other_board = self.boards[other_coords] if use_sata_links: # From board to other_board in_link = SATALink( self.scheduler , 8 # num_channels , sata_accept_period , sata_buffer_length , sata_latency , silistix_send_cycles , silistix_ack_cycles ) # From other_board to board out_link = SATALink( self.scheduler , 8 # num_channels , sata_accept_period , sata_buffer_length , sata_latency , silistix_send_cycles , silistix_ack_cycles ) # Link up each of the channels on this edge in both directions for channel in range(8): in_channel = in_link.get_channel_link(channel) out_channel = out_link.get_channel_link(channel) board.set_in_link(edge, channel, in_channel) board.set_out_link(edge, channel, out_channel) other_board.set_out_link(topology.opposite(edge), channel, in_channel) other_board.set_in_link(topology.opposite(edge), channel, out_channel) else: # Link up each of the channels on this edge in both directions with # SilistixLinks for channel in range(8): in_link = SilistixLink( self.scheduler , silistix_send_cycles , silistix_ack_cycles ) out_link = SilistixLink( self.scheduler , silistix_send_cycles , silistix_ack_cycles ) board.set_in_link(edge, channel, in_link) board.set_out_link(edge, channel, out_link) other_board.set_out_link(topology.opposite(edge), channel, in_link) other_board.set_in_link(topology.opposite(edge), channel, out_link)