| """ Assign pin directions to all tile pins. | |
| Tile pins are defined by one of two methods: | |
| - Pins that are part of a direct connection (e.g. edge_with_mux) are assigned | |
| based on the direction relationship between the two tiles, e.g. facing each | |
| other. | |
| - Pins that connect to a routing track face a routing track. | |
| Tile pins may end up with multiple edges if the routing tracks are formed | |
| differently throughout the grid. | |
| No connection database modifications are made in | |
| prjxray_assign_tile_pin_direction. | |
| """ | |
| import argparse | |
| from collections import namedtuple | |
| import prjxray.db | |
| import prjxray.tile | |
| import simplejson as json | |
| from lib.rr_graph import tracks | |
| from lib.connection_database import ( | |
| NodeClassification, yield_logical_wire_info_from_node, get_track_model, | |
| node_to_site_pins, get_pin_name_of_wire | |
| ) | |
| from prjxray_constant_site_pins import yield_ties_to_wire | |
| from lib import progressbar_utils | |
| import datetime | |
| from prjxray_db_cache import DatabaseCache | |
| now = datetime.datetime.now | |
| DirectConnection = namedtuple( | |
| 'DirectConnection', 'from_pin to_pin switch_name x_offset y_offset' | |
| ) | |
| def handle_direction_connections(conn, direct_connections, edge_assignments): | |
| # Edges with mux should have one source tile and one destination_tile. | |
| # The pin from the source_tile should face the destination_tile. | |
| # | |
| # It is expected that all edges_with_mux will lies in a line (e.g. X only or | |
| # Y only). | |
| c = conn.cursor() | |
| for src_wire_pkey, dest_wire_pkey, pip_in_tile_pkey, switch_pkey in \ | |
| progressbar_utils.progressbar( | |
| c.execute(""" | |
| SELECT src_wire_pkey, dest_wire_pkey, pip_in_tile_pkey, switch_pkey FROM edge_with_mux;""" | |
| )): | |
| c2 = conn.cursor() | |
| # Get the node that is attached to the source. | |
| c2.execute( | |
| """ | |
| SELECT node_pkey FROM wire WHERE pkey = ?""", (src_wire_pkey, ) | |
| ) | |
| (src_node_pkey, ) = c2.fetchone() | |
| # Find the wire connected to the source. | |
| src_wire = list(node_to_site_pins(conn, src_node_pkey)) | |
| assert len(src_wire) == 1 | |
| source_wire_pkey, src_tile_pkey, src_wire_in_tile_pkey = src_wire[0] | |
| c2.execute( | |
| """ | |
| SELECT tile_type_pkey, grid_x, grid_y FROM tile WHERE pkey = ?""", | |
| (src_tile_pkey, ) | |
| ) | |
| src_tile_type_pkey, source_loc_grid_x, source_loc_grid_y = c2.fetchone( | |
| ) | |
| c2.execute( | |
| """ | |
| SELECT name FROM tile_type WHERE pkey = ?""", (src_tile_type_pkey, ) | |
| ) | |
| (source_tile_type, ) = c2.fetchone() | |
| source_wire = get_pin_name_of_wire(conn, source_wire_pkey) | |
| # Get the node that is attached to the sink. | |
| c2.execute( | |
| """ | |
| SELECT node_pkey FROM wire WHERE pkey = ?""", (dest_wire_pkey, ) | |
| ) | |
| (dest_node_pkey, ) = c2.fetchone() | |
| # Find the wire connected to the sink. | |
| dest_wire = list(node_to_site_pins(conn, dest_node_pkey)) | |
| assert len(dest_wire) == 1 | |
| destination_wire_pkey, dest_tile_pkey, dest_wire_in_tile_pkey = dest_wire[ | |
| 0] | |
| c2.execute( | |
| """ | |
| SELECT tile_type_pkey, grid_x, grid_y FROM tile WHERE pkey = ?;""", | |
| (dest_tile_pkey, ) | |
| ) | |
| dest_tile_type_pkey, destination_loc_grid_x, destination_loc_grid_y = c2.fetchone( | |
| ) | |
| c2.execute( | |
| """ | |
| SELECT name FROM tile_type WHERE pkey = ?""", (dest_tile_type_pkey, ) | |
| ) | |
| (destination_tile_type, ) = c2.fetchone() | |
| destination_wire = get_pin_name_of_wire(conn, destination_wire_pkey) | |
| c2.execute( | |
| "SELECT name FROM switch WHERE pkey = ?" | |
| "", (switch_pkey, ) | |
| ) | |
| switch_name = c2.fetchone()[0] | |
| direct_connections.add( | |
| DirectConnection( | |
| from_pin='{}.{}'.format(source_tile_type, source_wire), | |
| to_pin='{}.{}'.format(destination_tile_type, destination_wire), | |
| switch_name=switch_name, | |
| x_offset=destination_loc_grid_x - source_loc_grid_x, | |
| y_offset=destination_loc_grid_y - source_loc_grid_y, | |
| ) | |
| ) | |
| if destination_loc_grid_x == source_loc_grid_x: | |
| if destination_loc_grid_y > source_loc_grid_y: | |
| source_dir = tracks.Direction.TOP | |
| destination_dir = tracks.Direction.BOTTOM | |
| else: | |
| source_dir = tracks.Direction.BOTTOM | |
| destination_dir = tracks.Direction.TOP | |
| else: | |
| if destination_loc_grid_x > source_loc_grid_x: | |
| source_dir = tracks.Direction.RIGHT | |
| destination_dir = tracks.Direction.LEFT | |
| else: | |
| source_dir = tracks.Direction.LEFT | |
| destination_dir = tracks.Direction.RIGHT | |
| edge_assignments[(source_tile_type, | |
| source_wire)].append((source_dir, )) | |
| edge_assignments[(destination_tile_type, | |
| destination_wire)].append((destination_dir, )) | |
| def handle_edges_to_channels( | |
| conn, null_tile_wires, edge_assignments, channel_wires_to_tracks | |
| ): | |
| c = conn.cursor() | |
| c.execute( | |
| """ | |
| SELECT vcc_track_pkey, gnd_track_pkey FROM constant_sources; | |
| """ | |
| ) | |
| vcc_track_pkey, gnd_track_pkey = c.fetchone() | |
| const_tracks = { | |
| 0: gnd_track_pkey, | |
| 1: vcc_track_pkey, | |
| } | |
| for node_pkey, classification in progressbar_utils.progressbar(c.execute( | |
| """ | |
| SELECT pkey, classification FROM node WHERE classification != ?; | |
| """, (NodeClassification.CHANNEL.value, ))): | |
| reason = NodeClassification(classification) | |
| if reason == NodeClassification.NULL: | |
| for (tile_type, | |
| wire) in yield_logical_wire_info_from_node(conn, node_pkey): | |
| null_tile_wires.add((tile_type, wire)) | |
| if reason != NodeClassification.EDGES_TO_CHANNEL: | |
| continue | |
| c2 = conn.cursor() | |
| for wire_pkey, phy_tile_pkey, tile_pkey, wire_in_tile_pkey in c2.execute( | |
| """ | |
| SELECT | |
| pkey, phy_tile_pkey, tile_pkey, wire_in_tile_pkey | |
| FROM | |
| wire | |
| WHERE | |
| node_pkey = ?; | |
| """, (node_pkey, )): | |
| c3 = conn.cursor() | |
| c3.execute( | |
| """ | |
| SELECT grid_x, grid_y FROM tile WHERE pkey = ?;""", (tile_pkey, ) | |
| ) | |
| (grid_x, grid_y) = c3.fetchone() | |
| c3.execute( | |
| """ | |
| SELECT | |
| name | |
| FROM | |
| tile_type | |
| WHERE | |
| pkey = ( | |
| SELECT | |
| tile_type_pkey | |
| FROM | |
| tile | |
| WHERE | |
| pkey = ? | |
| ); | |
| """, (tile_pkey, ) | |
| ) | |
| (tile_type, ) = c3.fetchone() | |
| wire = get_pin_name_of_wire(conn, wire_pkey) | |
| if wire is None: | |
| # This node has no site pin, don't need to assign pin direction. | |
| continue | |
| for other_phy_tile_pkey, other_wire_in_tile_pkey, pip_pkey, pip in c3.execute( | |
| """ | |
| WITH wires_from_node(wire_in_tile_pkey, phy_tile_pkey) AS ( | |
| SELECT | |
| wire_in_tile_pkey, | |
| phy_tile_pkey | |
| FROM | |
| wire | |
| WHERE | |
| node_pkey = ? AND phy_tile_pkey IS NOT NULL | |
| ), | |
| other_wires(other_phy_tile_pkey, pip_pkey, other_wire_in_tile_pkey) AS ( | |
| SELECT | |
| wires_from_node.phy_tile_pkey, | |
| undirected_pips.pip_in_tile_pkey, | |
| undirected_pips.other_wire_in_tile_pkey | |
| FROM undirected_pips | |
| INNER JOIN wires_from_node ON | |
| undirected_pips.wire_in_tile_pkey = wires_from_node.wire_in_tile_pkey) | |
| SELECT | |
| other_wires.other_phy_tile_pkey, | |
| other_wires.other_wire_in_tile_pkey, | |
| pip_in_tile.pkey, | |
| pip_in_tile.name | |
| FROM | |
| other_wires | |
| INNER JOIN pip_in_tile | |
| ON pip_in_tile.pkey == other_wires.pip_pkey | |
| WHERE | |
| pip_in_tile.is_directional = 1 AND pip_in_tile.is_pseudo = 0; | |
| """, (node_pkey, )): | |
| # Need to walk from the wire_in_tile table, to the wire table, | |
| # to the node table and get track_pkey. | |
| # other_wire_in_tile_pkey -> wire pkey -> node_pkey -> track_pkey | |
| c4 = conn.cursor() | |
| c4.execute( | |
| """ | |
| SELECT | |
| track_pkey, | |
| classification | |
| FROM | |
| node | |
| WHERE | |
| pkey = ( | |
| SELECT | |
| node_pkey | |
| FROM | |
| wire | |
| WHERE | |
| phy_tile_pkey = ? | |
| AND wire_in_tile_pkey = ? | |
| );""", (other_phy_tile_pkey, other_wire_in_tile_pkey) | |
| ) | |
| result = c4.fetchone() | |
| assert result is not None, ( | |
| wire_pkey, pip_pkey, tile_pkey, wire_in_tile_pkey, | |
| other_wire_in_tile_pkey | |
| ) | |
| (track_pkey, classification) = result | |
| # Some pips do connect to a track at all, e.g. null node | |
| if track_pkey is None: | |
| # TODO: Handle weird connections. | |
| # other_node_class = NodeClassification(classification) | |
| # assert other_node_class == NodeClassification.NULL, ( | |
| # node_pkey, pip_pkey, pip, other_node_class) | |
| continue | |
| tracks_model = channel_wires_to_tracks[track_pkey] | |
| available_pins = set( | |
| tracks_model.get_tracks_for_wire_at_coord( | |
| (grid_x, grid_y) | |
| ).keys() | |
| ) | |
| edge_assignments[(tile_type, wire)].append(available_pins) | |
| for constant in yield_ties_to_wire(wire): | |
| tracks_model = channel_wires_to_tracks[ | |
| const_tracks[constant]] | |
| available_pins = set( | |
| tracks_model.get_tracks_for_wire_at_coord( | |
| (grid_x, grid_y) | |
| ).keys() | |
| ) | |
| edge_assignments[(tile_type, wire)].append(available_pins) | |
| def initialize_edge_assignments(db, conn): | |
| """ Create initial edge_assignments map. """ | |
| c = conn.cursor() | |
| c2 = conn.cursor() | |
| c.execute( | |
| """ | |
| SELECT name, pkey FROM tile_type WHERE pkey IN ( | |
| SELECT DISTINCT tile_type_pkey FROM tile | |
| );""" | |
| ) | |
| tiles = dict(c) | |
| edge_assignments = {} | |
| wires_in_tile_types = set() | |
| # First find out which tile types were split during VPR grid formation. | |
| # These tile types should not get edge assignments directly, instead | |
| # their sites will get edge assignements. | |
| sites_as_tiles = set() | |
| split_tile_types = set() | |
| for site_pkey, tile_type_pkey in c.execute(""" | |
| SELECT site_pkey, tile_type_pkey FROM site_as_tile; | |
| """): | |
| c2.execute( | |
| "SELECT name FROM tile_type WHERE pkey = ?", (tile_type_pkey, ) | |
| ) | |
| split_tile_types.add(c2.fetchone()[0]) | |
| c2.execute( | |
| """ | |
| SELECT name FROM site_type WHERE pkey = ( | |
| SELECT site_type_pkey FROM site WHERE pkey = ? | |
| );""", (site_pkey, ) | |
| ) | |
| site_type_name = c2.fetchone()[0] | |
| sites_as_tiles.add(site_type_name) | |
| # Initialize edge assignments for split tiles | |
| for site_type in sites_as_tiles: | |
| del tiles[site_type] | |
| site_obj = db.get_site_type(site_type) | |
| for site_pin in site_obj.get_site_pins(): | |
| key = (site_type, site_pin) | |
| assert key not in edge_assignments, key | |
| edge_assignments[key] = [] | |
| for tile_type in db.get_tile_types(): | |
| if tile_type not in tiles: | |
| continue | |
| del tiles[tile_type] | |
| # Skip tile types that are split tiles | |
| if tile_type in split_tile_types: | |
| continue | |
| (tile_type_pkey, ) = c.execute( | |
| """ | |
| SELECT pkey | |
| FROM tile_type | |
| WHERE name = ? | |
| """, (tile_type, ) | |
| ).fetchone() | |
| for (wire, ) in c.execute(""" | |
| SELECT name | |
| FROM wire_in_tile | |
| WHERE tile_type_pkey = ?""", (tile_type_pkey, )): | |
| wires_in_tile_types.add((tile_type, wire)) | |
| type_obj = db.get_tile_type(tile_type) | |
| for site in type_obj.get_sites(): | |
| for site_pin in site.site_pins: | |
| if site_pin.wire is None: | |
| continue | |
| # Skip if this wire is not in the database | |
| c.execute( | |
| """ | |
| SELECT pkey | |
| FROM wire_in_tile | |
| WHERE name = ? | |
| """, (site_pin.wire, ) | |
| ) | |
| if not c.fetchone(): | |
| continue | |
| key = (tile_type, site_pin.wire) | |
| assert key not in edge_assignments, key | |
| edge_assignments[key] = [] | |
| for tile_type, tile_pkey in tiles.items(): | |
| assert tile_type not in split_tile_types | |
| for (wire, ) in c.execute(""" | |
| SELECT name | |
| FROM wire_in_tile | |
| WHERE pkey in ( | |
| SELECT DISTINCT wire_in_tile_pkey | |
| FROM wire | |
| WHERE tile_pkey IN ( | |
| SELECT pkey | |
| FROM tile | |
| WHERE tile_type_pkey = ?) | |
| );""", (tile_pkey, )): | |
| wires_in_tile_types.add((tile_type, wire)) | |
| for (wire, ) in c.execute(""" | |
| SELECT DISTINCT name | |
| FROM wire_in_tile | |
| WHERE pkey in ( | |
| SELECT DISTINCT wire_in_tile_pkey | |
| FROM wire | |
| WHERE tile_pkey IN ( | |
| SELECT pkey | |
| FROM tile | |
| WHERE tile_type_pkey = ?) | |
| ) | |
| AND | |
| site_pin_pkey IS NOT NULL""", (tile_pkey, )): | |
| key = (tile_type, wire) | |
| assert key not in edge_assignments, key | |
| edge_assignments[key] = [] | |
| return edge_assignments, wires_in_tile_types | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| '--db_root', help='Project X-Ray Database', required=True | |
| ) | |
| parser.add_argument('--part', help='FPGA part', required=True) | |
| parser.add_argument( | |
| '--connection_database', | |
| help='Database of fabric connectivity', | |
| required=True | |
| ) | |
| parser.add_argument( | |
| '--pin_assignments', | |
| help=""" | |
| Output JSON assigning pins to tile types and direction connections""", | |
| required=True | |
| ) | |
| args = parser.parse_args() | |
| db = prjxray.db.Database(args.db_root, args.part) | |
| edge_assignments = {} | |
| with DatabaseCache(args.connection_database, read_only=True) as conn: | |
| c = conn.cursor() | |
| edge_assignments, wires_in_tile_types = initialize_edge_assignments( | |
| db, conn | |
| ) | |
| direct_connections = set() | |
| print('{} Processing direct connections.'.format(now())) | |
| handle_direction_connections( | |
| conn, direct_connections, edge_assignments | |
| ) | |
| wires_not_in_channels = {} | |
| c = conn.cursor() | |
| print('{} Processing non-channel nodes.'.format(now())) | |
| for node_pkey, classification in progressbar_utils.progressbar( | |
| c.execute(""" | |
| SELECT pkey, classification FROM node WHERE classification != ?; | |
| """, (NodeClassification.CHANNEL.value, ))): | |
| reason = NodeClassification(classification) | |
| for (tile_type, | |
| wire) in yield_logical_wire_info_from_node(conn, node_pkey): | |
| key = (tile_type, wire) | |
| # Sometimes nodes in particular tile instances are disconnected, | |
| # disregard classification changes if this is the case. | |
| if reason != NodeClassification.NULL: | |
| if key not in wires_not_in_channels: | |
| wires_not_in_channels[key] = reason | |
| else: | |
| other_reason = wires_not_in_channels[key] | |
| assert reason == other_reason, ( | |
| tile_type, wire, reason, other_reason | |
| ) | |
| if key in wires_in_tile_types: | |
| wires_in_tile_types.remove(key) | |
| # List of nodes that are channels. | |
| channel_nodes = [] | |
| # Map of (tile, wire) to track. This will be used to find channels for pips | |
| # that come from EDGES_TO_CHANNEL. | |
| channel_wires_to_tracks = {} | |
| # Generate track models and verify that wires are either in a channel | |
| # or not in a channel. | |
| print('{} Creating models from tracks.'.format(now())) | |
| for node_pkey, track_pkey in progressbar_utils.progressbar(c.execute( | |
| """ | |
| SELECT pkey, track_pkey FROM node WHERE classification = ?; | |
| """, (NodeClassification.CHANNEL.value, ))): | |
| assert track_pkey is not None | |
| tracks_model, _ = get_track_model(conn, track_pkey) | |
| channel_nodes.append(tracks_model) | |
| channel_wires_to_tracks[track_pkey] = tracks_model | |
| for (tile_type, | |
| wire) in yield_logical_wire_info_from_node(conn, node_pkey): | |
| key = (tile_type, wire) | |
| # Make sure all wires in channels always are in channels | |
| assert key not in wires_not_in_channels | |
| if key in wires_in_tile_types: | |
| wires_in_tile_types.remove(key) | |
| # Make sure all wires appear to have been assigned. | |
| if len(wires_in_tile_types) > 0: | |
| for tile_type, wire in sorted(wires_in_tile_types): | |
| print(tile_type, wire) | |
| assert len(wires_in_tile_types) == 0 | |
| # Verify that all tracks are sane. | |
| for node in channel_nodes: | |
| node.verify_tracks() | |
| null_tile_wires = set() | |
| # Verify that all nodes that are classified as edges to channels have at | |
| # least one site, and at least one live connection to a channel. | |
| # | |
| # If no live connections from the node are present, this node should've | |
| # been marked as NULL during channel formation. | |
| print('{} Handling edges to channels.'.format(now())) | |
| handle_edges_to_channels( | |
| conn, null_tile_wires, edge_assignments, channel_wires_to_tracks | |
| ) | |
| print('{} Processing edge assignments.'.format(now())) | |
| final_edge_assignments = {} | |
| for key, available_pins in progressbar_utils.progressbar( | |
| edge_assignments.items()): | |
| (tile_type, wire) = key | |
| available_pins = [pins for pins in available_pins if len(pins) > 0] | |
| if len(available_pins) == 0: | |
| if (tile_type, wire) not in null_tile_wires: | |
| # TODO: Figure out what is going on with these wires. Appear to | |
| # tile internal connections sometimes? | |
| print((tile_type, wire)) | |
| final_edge_assignments[key] = [tracks.Direction.RIGHT] | |
| continue | |
| pins = set(available_pins[0]) | |
| for p in available_pins[1:]: | |
| pins &= set(p) | |
| if len(pins) > 0: | |
| final_edge_assignments[key] = [list(pins)[0]] | |
| else: | |
| # More than 2 pins are required, final the minimal number of pins | |
| pins = set() | |
| for p in available_pins: | |
| pins |= set(p) | |
| while len(pins) > 2: | |
| pins = list(pins) | |
| prev_len = len(pins) | |
| for idx in range(len(pins)): | |
| pins_subset = list(pins) | |
| del pins_subset[idx] | |
| pins_subset = set(pins_subset) | |
| bad_subset = False | |
| for p in available_pins: | |
| if len(pins_subset & set(p)) == 0: | |
| bad_subset = True | |
| break | |
| if not bad_subset: | |
| pins = list(pins_subset) | |
| break | |
| # Failed to remove any pins, stop. | |
| if len(pins) == prev_len: | |
| break | |
| final_edge_assignments[key] = pins | |
| for key, available_pins in edge_assignments.items(): | |
| (tile_type, wire) = key | |
| pins = set(final_edge_assignments[key]) | |
| for required_pins in available_pins: | |
| if len(required_pins) == 0: | |
| continue | |
| assert len(pins & set(required_pins)) > 0, ( | |
| tile_type, wire, pins, required_pins, available_pins | |
| ) | |
| pin_directions = {} | |
| for key, pins in progressbar_utils.progressbar( | |
| final_edge_assignments.items()): | |
| (tile_type, wire) = key | |
| if tile_type not in pin_directions: | |
| pin_directions[tile_type] = {} | |
| pin_directions[tile_type][wire] = [pin._name_ for pin in pins] | |
| with open(args.pin_assignments, 'w') as f: | |
| json.dump( | |
| { | |
| 'pin_directions': | |
| pin_directions, | |
| 'direct_connections': | |
| [d._asdict() for d in direct_connections], | |
| }, | |
| f, | |
| indent=2 | |
| ) | |
| print( | |
| '{} Flushing database back to file "{}"'.format( | |
| now(), args.connection_database | |
| ) | |
| ) | |
| if __name__ == '__main__': | |
| main() |