| """ 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() |