def create_tracks(graph, grid_width, grid_height, rcw, verbose=False):
    print("Creating tracks, channel width: %d" % rcw)

    # Not strictly required, but VPR wants this I think?
    assert rcw % 2 == 0

    def alt_pos(begin, end, swap):
        if swap:
            return end, begin, graph2.NodeDirection.DEC_DIR
        else:
            return begin, end, graph2.NodeDirection.INC_DIR

    # chanx going entire width
    for y in range(0, grid_height - 1):
        for tracki in range(rcw):
            begin, end, direction = alt_pos(
                (1, y), (grid_width - 2, y), tracki % 2 == 1
            )

            graph.add_track(
                track=tracks.Track(
                    direction='X',
                    x_low=begin[0],
                    x_high=end[0],
                    y_low=begin[1],
                    y_high=end[1],
                ),
                segment_id=graph.segments[0].id,
                capacity=1,
                direction=direction,
                name="CHANX{:04d}@{:04d}".format(y, tracki),
            )

    # chany going entire height
    for x in range(0, grid_width - 1):
        for tracki in range(rcw):
            begin, end, direction = alt_pos(
                (x, 1), (x, grid_height - 2), tracki % 2 == 1
            )

            graph.add_track(
                track=tracks.Track(
                    direction='Y',
                    x_low=begin[0],
                    x_high=end[0],
                    y_low=begin[1],
                    y_high=end[1],
                ),
                segment_id=graph.segments[0].id,
                capacity=1,
                direction=direction,
                name="CHANY{:04d}@{:04d}".format(x, tracki),
            )
Example #2
0
def add_track_chain(graph, direction, u, v0, v1, segment_id, switch_id):
    """
    Adds a chain of tracks that span the grid in the given direction.
    Returns the first and last node of the chain along with a map of
    coordinates to nodes.
    """
    node_by_v = {}
    prev_node = None

    # Make range generator
    if v0 > v1:
        coords = range(v0, v1 - 1, -1)
    else:
        coords = range(v0, v1 + 1)

    # Add track chain
    for v in coords:

        # Add track (node)
        if direction == "X":
            track = tracks.Track(
                direction=direction,
                x_low=v,
                x_high=v,
                y_low=u,
                y_high=u,
            )
        elif direction == "Y":
            track = tracks.Track(
                direction=direction,
                x_low=u,
                x_high=u,
                y_low=v,
                y_high=v,
            )
        else:
            assert False, direction

        curr_node = add_track(graph, track, segment_id)

        # Add edge from the previous one
        if prev_node is not None:
            add_edge(graph, prev_node.id, curr_node.id, switch_id)

        # No previous one, this is the first one
        else:
            start_node = curr_node

        node_by_v[v] = curr_node
        prev_node = curr_node

    return start_node, curr_node, node_by_v
Example #3
0
def create_track_for_hop_connection(graph, connection):
    """
    Creates a HOP wire track for the given connection
    """

    # Determine whether the wire goes horizontally or vertically.
    if connection.src.loc.y == connection.dst.loc.y:
        direction = "X"
    elif connection.src.loc.x == connection.dst.loc.x:
        direction = "Y"
    else:
        assert False, connection

    assert connection.src.loc != connection.dst.loc, connection

    # Determine the connection length
    length = max(abs(connection.src.loc.x - connection.dst.loc.x),
                 abs(connection.src.loc.y - connection.dst.loc.y))

    segment_name = "hop{}".format(length)

    # Add the track to the graph
    track = tracks.Track(
        direction=direction,
        x_low=min(connection.src.loc.x, connection.dst.loc.x),
        x_high=max(connection.src.loc.x, connection.dst.loc.x),
        y_low=min(connection.src.loc.y, connection.dst.loc.y),
        y_high=max(connection.src.loc.y, connection.dst.loc.y),
    )

    node = add_track(graph, track,
                     graph.get_segment_id_from_name(segment_name))

    return node
Example #4
0
def import_tracks(conn, alive_tracks, node_mapping, graph, segment_id):
    c2 = conn.cursor()
    for (graph_node_pkey, track_pkey, graph_node_type, x_low, x_high, y_low,
         y_high, ptc) in c2.execute("""
    SELECT pkey, track_pkey, graph_node_type, x_low, x_high, y_low, y_high, ptc FROM
        graph_node WHERE track_pkey IS NOT NULL;"""):
        if track_pkey not in alive_tracks:
            continue

        node_type = graph2.NodeType(graph_node_type)

        if node_type == graph2.NodeType.CHANX:
            direction = 'X'
            x_low = max(x_low, 1)
        elif node_type == graph2.NodeType.CHANY:
            direction = 'Y'
            y_low = max(y_low, 1)
        else:
            assert False, node_type

        track = tracks.Track(
            direction=direction,
            x_low=x_low,
            x_high=x_high,
            y_low=y_low,
            y_high=y_high,
        )
        assert graph_node_pkey not in node_mapping
        node_mapping[graph_node_pkey] = graph.add_track(track=track,
                                                        segment_id=segment_id,
                                                        ptc=ptc)
def add_node(graph, loc, direction, segment_id):
    """
    Adds a track of length 1 to the graph. Returns the node object
    """

    return add_track(
        graph,
        tracks.Track(
            direction=direction,
            x_low=loc.x,
            x_high=loc.x,
            y_low=loc.y,
            y_high=loc.y,
        ), segment_id)
def get_track_model(conn, track_pkey):
    assert track_pkey is not None

    track_list = []
    track_nodes = []
    c2 = conn.cursor()
    graph_node_pkey = {}
    for idx, (pkey, graph_node_type, x_low, x_high, y_low,
              y_high) in enumerate(
                  c2.execute(
                      """
    SELECT pkey, graph_node_type, x_low, x_high, y_low, y_high
        FROM graph_node WHERE track_pkey = ?""", (track_pkey, ))):
        node_type = graph2.NodeType(graph_node_type)
        if node_type == graph2.NodeType.CHANX:
            direction = 'X'
        elif node_type == graph2.NodeType.CHANY:
            direction = 'Y'

        graph_node_pkey[pkey] = idx
        track_nodes.append(pkey)
        track_list.append(
            tracks.Track(direction=direction,
                         x_low=x_low,
                         x_high=x_high,
                         y_low=y_low,
                         y_high=y_high))

    track_connections = set()
    for src_graph_node_pkey, dest_graph_node_pkey in c2.execute(
            """
    SELECT src_graph_node_pkey, dest_graph_node_pkey
        FROM graph_edge WHERE track_pkey = ?""", (track_pkey, )):

        src_idx = graph_node_pkey[src_graph_node_pkey]
        dest_idx = graph_node_pkey[dest_graph_node_pkey]

        track_connections.add(tuple(sorted((src_idx, dest_idx))))

    tracks_model = tracks.Tracks(track_list, list(track_connections))

    return tracks_model, track_nodes
Example #7
0
def import_dummy_tracks(conn, graph, segment_id):
    cur = conn.cursor()

    num_dummy = 0
    for (graph_node_pkey, track_pkey, graph_node_type, x_low, x_high, y_low,
         y_high, ptc) in cur.execute(
             """
    SELECT pkey, track_pkey, graph_node_type, x_low, x_high, y_low, y_high, ptc FROM
        graph_node WHERE (graph_node_type = ? or graph_node_type = ?) and capacity = 0;""",
             (graph2.NodeType.CHANX.value, graph2.NodeType.CHANY.value)):

        node_type = graph2.NodeType(graph_node_type)

        if node_type == graph2.NodeType.CHANX:
            direction = 'X'
            x_low = x_low
        elif node_type == graph2.NodeType.CHANY:
            direction = 'Y'
            y_low = y_low
        else:
            assert False, node_type

        track = tracks.Track(
            direction=direction,
            x_low=x_low,
            x_high=x_high,
            y_low=y_low,
            y_high=y_high,
        )

        graph.add_track(track=track,
                        segment_id=segment_id,
                        capacity=0,
                        ptc=ptc)
        num_dummy += 1

    return num_dummy
Example #8
0
def import_tracks(conn, alive_tracks, node_mapping, graph, default_segment_id):
    cur = conn.cursor()
    cur2 = conn.cursor()
    for (graph_node_pkey, track_pkey, graph_node_type, x_low, x_high, y_low,
         y_high, ptc, capacitance, resistance) in cur.execute("""
SELECT
    pkey,
    track_pkey,
    graph_node_type,
    x_low,
    x_high,
    y_low,
    y_high,
    ptc,
    capacitance,
    resistance
FROM
    graph_node WHERE track_pkey IS NOT NULL;"""):
        if track_pkey not in alive_tracks:
            continue

        cur2.execute(
            """
SELECT name FROM segment WHERE pkey = (
    SELECT segment_pkey FROM track WHERE pkey = ?
)""", (track_pkey, ))
        result = cur2.fetchone()
        if result is not None:
            segment_name = result[0]
            segment_id = graph.get_segment_id_from_name(segment_name)
        else:
            segment_id = default_segment_id

        node_type = graph2.NodeType(graph_node_type)

        if node_type == graph2.NodeType.CHANX:
            direction = 'X'
            x_low = max(x_low, 1)
        elif node_type == graph2.NodeType.CHANY:
            direction = 'Y'
            y_low = max(y_low, 1)
        else:
            assert False, node_type

        canonical_loc = None
        cur2.execute(
            """
SELECT grid_x, grid_y FROM phy_tile WHERE pkey = (
    SELECT canon_phy_tile_pkey FROM track WHERE pkey = ?
    )""", (track_pkey, ))
        result = cur2.fetchone()
        if result:
            canonical_loc = graph2.CanonicalLoc(x=result[0], y=result[1])

        track = tracks.Track(
            direction=direction,
            x_low=x_low,
            x_high=x_high,
            y_low=y_low,
            y_high=y_high,
        )
        assert graph_node_pkey not in node_mapping
        node_mapping[graph_node_pkey] = graph.add_track(
            track=track,
            segment_id=segment_id,
            ptc=ptc,
            timing=graph2.NodeTiming(
                r=resistance,
                c=capacitance,
            ),
            canonical_loc=canonical_loc)
Example #9
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
            '--db_root', required=True, help='Project X-Ray Database')
    parser.add_argument(
            '--read_rr_graph', required=True, help='Input rr_graph file')
    parser.add_argument(
            '--write_rr_graph', required=True, help='Output rr_graph file')
    parser.add_argument(
            '--channels', required=True, help='Channel definitions from prjxray_form_channels')
    parser.add_argument(
            '--synth_tiles', help='If using an ROI, synthetic tile defintion from prjxray-arch-import')

    args = parser.parse_args()

    db = prjxray.db.Database(args.db_root)
    grid = db.grid()

    if args.synth_tiles:
        use_roi = True
        with open(args.synth_tiles) as f:
            synth_tiles = json.load(f)

        roi = Roi(
                db=db,
                x1=synth_tiles['info']['GRID_X_MIN'],
                y1=synth_tiles['info']['GRID_Y_MIN'],
                x2=synth_tiles['info']['GRID_X_MAX'],
                y2=synth_tiles['info']['GRID_Y_MAX'],
                )

        print('{} generating routing graph for ROI.'.format(now()))
    else:
        use_roi = False

    # Convert input rr graph into graph2.Graph object.
    input_rr_graph = read_xml_file(args.read_rr_graph)

    xml_graph = xml_graph2.Graph(
            input_rr_graph,
            progressbar=progressbar.progressbar)

    graph = xml_graph.graph

    tool_version = input_rr_graph.getroot().attrib['tool_version']
    tool_comment = input_rr_graph.getroot().attrib['tool_comment']

    delayless_switch = graph.get_delayless_switch_id()

    print('{} reading channels definitions.'.format(now()))
    with open(args.channels) as f:
        channels = json.load(f)

    segment_id = graph.get_segment_id_from_name('dummy')

    track_wire_map = {}

    print('{} add nodes for all channels.'.format(now()))
    used_channels = 0
    for idx, channel in progressbar.progressbar(enumerate(channels['channels'])):
        # Don't use dead channels if using an ROI.
        # Consider a channel alive if at least 1 wire in the node is part of a
        # live tile.
        if use_roi:
            alive = False
            for tile, wire in channel['wires']:
                loc = grid.loc_of_tilename(tile)
                if roi.tile_in_roi(loc) or tile in synth_tiles['tiles']:
                    alive = True
                    break

            if not alive:
                continue

        used_channels += 1
        nodes = []
        track_list = []
        for idx2, track_dict in enumerate(channel['tracks']):
            if track_dict['direction'] == 'X':
                track_dict['x_low'] = max(track_dict['x_low'], 1)
            elif track_dict['direction'] == 'Y':
                track_dict['y_low'] = max(track_dict['y_low'], 1)
            track = tracks.Track(**track_dict)
            track_list.append(track)

            nodes.append(graph.add_track(track=track, segment_id=segment_id, name='track_{}_{}'.format(idx, idx2)))

        for a_idx, b_idx in channel['track_connections']:
            graph.add_edge(nodes[a_idx], nodes[b_idx], delayless_switch, 'track_{}_to_{}'.format(a_idx, b_idx))
            graph.add_edge(nodes[b_idx], nodes[a_idx], delayless_switch, 'track_{}_to_{}'.format(b_idx, a_idx))

        tracks_model = tracks.Tracks(track_list, channel['track_connections'])

        for tile, wire in channel['wires']:
            track_wire_map[(tile, wire)] = (tracks_model, nodes)

    print('original {} final {}'.format(len(channels['channels']), used_channels))

    routing_switch = graph.get_switch_id('routing')

    pip_map = {}

    edges_with_mux = {}
    for idx, edge_with_mux in progressbar.progressbar(enumerate(channels['edges_with_mux'])):
        if edge_with_mux['pip'] not in edges_with_mux:
            edges_with_mux[edge_with_mux['pip']] = {}

        assert len(edge_with_mux['source_node']) == 1
        edges_with_mux[edge_with_mux['pip']][tuple(edge_with_mux['source_node'][0])] = edge_with_mux['destination_node']

    # Set of (src, sink, switch_id) tuples that pip edges have been sent to
    # VPR.  VPR cannot handle duplicate paths with the same switch id.
    pip_set = set()
    print('{} Adding edges'.format(now()))
    for loc in progressbar.progressbar(grid.tile_locations()):
        gridinfo = grid.gridinfo_at_loc(loc)
        tile_name = grid.tilename_at_loc(loc)

        if use_roi:
            if tile_name in synth_tiles['tiles']:
                assert len(synth_tiles['tiles'][tile_name]['pins']) == 1
                for pin in synth_tiles['tiles'][tile_name]['pins']:
                    tracks_model, track_nodes = track_wire_map[(tile_name, pin['wire'])]

                    option = list(tracks_model.get_tracks_for_wire_at_coord(loc))
                    assert len(option) > 0

                    if pin['port_type'] == 'input':
                        tile_type = 'BLK_SY-OUTPAD'
                        wire = 'outpad'
                    elif pin['port_type'] == 'output':
                        tile_type = 'BLK_SY-INPAD'
                        wire = 'inpad'
                    else:
                        assert False, pin

                    track_node = track_nodes[option[0][0]]
                    pin_name = graph.create_pin_name_from_tile_type_and_pin(
                            tile_type,
                            wire)

                    pin_node = graph.get_nodes_for_pin(loc, pin_name)

                    if pin['port_type'] == 'input':
                        graph.add_edge(
                                src_node=track_node,
                                sink_node=pin_node[0][0],
                                switch_id=routing_switch,
                                name='synth_{}_{}'.format(tile_name, pin['wire']),
                        )
                    elif pin['port_type'] == 'output':
                        graph.add_edge(
                                src_node=pin_node[0][0],
                                sink_node=track_node,
                                switch_id=routing_switch,
                                name='synth_{}_{}'.format(tile_name, pin['wire']),
                        )
                    else:
                        assert False, pin
            else:
                # Not a synth node, check if in ROI.
                if not roi.tile_in_roi(loc):
                    continue

        tile_type = db.get_tile_type(gridinfo.tile_type)

        for pip in tile_type.get_pips():
            if pip.is_pseudo:
                continue

            if not pip.is_directional:
                # TODO: Handle bidirectional pips?
                continue

            edge_node = make_connection(graph, track_wire_map, loc, tile_name, gridinfo.tile_type,
                            pip, routing_switch, edges_with_mux, grid, pip_set)

            if edge_node is not None:
                pip_map[(tile_name, pip.name)] = edge_node

    print('{} Writing node mapping.'.format(now()))
    node_mapping = {
            'pips': [],
            'tracks': []
        }

    for (tile, pip_name), edge in pip_map.items():
        node_mapping['pips'].append({
                'tile': tile,
                'pip': pip_name,
                'edge': edge
        })

    for (tile, wire), (_, nodes) in track_wire_map.items():
        node_mapping['tracks'].append({
                'tile': tile,
                'wire': wire,
                'nodes': nodes,
        })

    with open('node_mapping.pickle', 'wb') as f:
        pickle.dump(node_mapping, f)

    print('{} Create channels and serializing.'.format(now()))
    pool = multiprocessing.Pool(10)
    serialized_rr_graph = xml_graph.serialize_to_xml(
            tool_version=tool_version,
            tool_comment=tool_comment,
            pad_segment=segment_id,
            pool=pool,
    )

    print('{} Writing to disk.'.format(now()))
    with open(args.write_rr_graph, "wb") as f:
        f.write(serialized_rr_graph)
    print('{} Done.'.format(now()))
Example #10
0
def add_l_track(graph, x0, y0, x1, y1, segment_id, switch_id):
    """
    Add a "L"-shaped track consisting of two channel nodes and a switch
    between the given two grid coordinates. The (x0, y0) determines source
    location and (x1, y1) destination (sink) location.

    Returns a tuple with indices of the first and last node.
    """
    dx = x1 - x0
    dy = y1 - y0

    assert dx != 0 or dy != 0, (x0, y0)

    nodes = [None, None]

    # Go vertically first
    if abs(dy) >= abs(dx):
        xc, yc = x0, y1

        if abs(dy):
            track = tracks.Track(
                direction="Y",
                x_low=min(x0, xc),
                x_high=max(x0, xc),
                y_low=min(y0, yc),
                y_high=max(y0, yc),
            )
            nodes[0] = add_track(graph, track, segment_id)

        if abs(dx):
            track = tracks.Track(
                direction="X",
                x_low=min(xc, x1),
                x_high=max(xc, x1),
                y_low=min(yc, y1),
                y_high=max(yc, y1),
            )
            nodes[1] = add_track(graph, track, segment_id)

    # Go horizontally first
    else:
        xc, yc = x1, y0

        if abs(dx):
            track = tracks.Track(
                direction="X",
                x_low=min(x0, xc),
                x_high=max(x0, xc),
                y_low=min(y0, yc),
                y_high=max(y0, yc),
            )
            nodes[0] = add_track(graph, track, segment_id)

        if abs(dy):
            track = tracks.Track(
                direction="Y",
                x_low=min(xc, x1),
                x_high=max(xc, x1),
                y_low=min(yc, y1),
                y_high=max(yc, y1),
            )
            nodes[1] = add_track(graph, track, segment_id)

    # In case of a horizontal or vertical only track make both nodes the same
    assert nodes[0] is not None or nodes[1] is not None

    if nodes[0] is None:
        nodes[0] = nodes[1]
    if nodes[1] is None:
        nodes[1] = nodes[0]

    # Add edge connecting the two nodes if needed
    if nodes[0].id != nodes[1].id:
        add_edge(graph, nodes[0].id, nodes[1].id, switch_id)

    return nodes
Example #11
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--db_root',
                        help='Project X-Ray Database',
                        required=True)
    parser.add_argument('--channels',
                        help='Input JSON defining channel assignments',
                        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)
    grid = db.grid()

    edge_assignments = {}

    wires_in_tile_types = set()

    for tile_type in db.get_tile_types():
        type_obj = db.get_tile_type(tile_type)

        for wire in type_obj.get_wires():
            wires_in_tile_types.add((tile_type, wire))

        for site in type_obj.get_sites():
            for site_pin in site.site_pins:
                if site_pin.wire is None:
                    continue

                key = (tile_type, site_pin.wire)
                assert key not in edge_assignments, key
                edge_assignments[key] = []

    print('{} Reading channel data'.format(datetime.datetime.now()))
    with open(args.channels) as f:
        channels = json.load(f)
    print('{} Done reading channel data'.format(datetime.datetime.now()))

    direct_connections = set()

    # 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).
    for edge_with_mux in progressbar.progressbar(channels['edges_with_mux']):
        source_tile = None
        source_tile_type = None
        source_wire = None
        destination_tile = None
        destination_tile_type = None
        destination_wire = None

        for tile, wire in edge_with_mux['source_node']:
            tileinfo = grid.gridinfo_at_tilename(tile)
            tile_type = db.get_tile_type(tileinfo.tile_type)
            wire_info = tile_type.get_wire_info(wire)

            if len(wire_info.sites) == 1:
                assert source_tile is None, (tile, wire, source_tile)
                source_tile = tile
                source_tile_type = tileinfo.tile_type
                source_wire = wire

        for tile, wire in edge_with_mux['destination_node']:
            tileinfo = grid.gridinfo_at_tilename(tile)
            tile_type = db.get_tile_type(tileinfo.tile_type)
            wire_info = tile_type.get_wire_info(wire)

            if len(wire_info.sites) == 1:
                assert destination_tile is None, (tile, wire, destination_tile,
                                                  wire_info)
                destination_tile = tile
                destination_tile_type = tileinfo.tile_type
                destination_wire = wire

        assert source_tile is not None
        assert destination_tile is not None

        source_loc = grid.loc_of_tilename(source_tile)
        destination_loc = grid.loc_of_tilename(destination_tile)

        assert source_loc.grid_x == destination_loc.grid_x or source_loc.grid_y == destination_loc.grid_y, (
            source_tile, destination_tile, edge_with_mux['pip'])

        direct_connections.add(
            DirectConnection(
                from_pin='{}.{}'.format(source_tile_type, source_wire),
                to_pin='{}.{}'.format(destination_tile_type, destination_wire),
                switch_name='routing',
                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, ))

    wires_not_in_channels = {}
    for node in progressbar.progressbar(channels['node_not_in_channels']):
        reason = node['classification']

        for tile, wire in node['wires']:
            tileinfo = grid.gridinfo_at_tilename(tile)
            key = (tileinfo.tile_type, wire)

            # Sometimes nodes in particular tile instances are disconnected,
            # disregard classification changes if this is the case.
            if reason != '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, 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.
    for channel in progressbar.progressbar(channels['channels']):
        track_list = []
        for track in channel['tracks']:
            track_list.append(tracks.Track(**track))

        tracks_model = tracks.Tracks(track_list, channel['track_connections'])
        channel_nodes.append(tracks_model)

        for tile, wire in channel['wires']:
            tileinfo = grid.gridinfo_at_tilename(tile)
            key = (tileinfo.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)

            channel_wires_to_tracks[(tile, wire)] = tracks_model

    # Make sure all wires appear to have been assigned.
    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.
    for node in progressbar.progressbar(channels['node_not_in_channels']):
        reason = node['classification']

        assert reason != 'EDGE_WITH_SHORT'

        if reason == 'NULL':
            for tile, wire in node['wires']:
                tileinfo = grid.gridinfo_at_tilename(tile)
                tile_type = db.get_tile_type(tileinfo.tile_type)
                null_tile_wires.add((tileinfo.tile_type, wire))

        if reason == 'EDGES_TO_CHANNEL':

            num_sites = 0

            for tile, wire in node['wires']:
                tileinfo = grid.gridinfo_at_tilename(tile)
                loc = grid.loc_of_tilename(tile)
                tile_type = db.get_tile_type(tileinfo.tile_type)

                wire_info = tile_type.get_wire_info(wire)
                num_sites += len(wire_info.sites)
                for pip in wire_info.pips:
                    other_wire = prjxray.tile.get_other_wire_from_pip(
                        tile_type.get_pip_by_name(pip), wire)

                    key = (tile, other_wire)
                    if key in channel_wires_to_tracks:
                        tracks_model = channel_wires_to_tracks[key]

                        if len(wire_info.sites) > 0:
                            available_pins = set(
                                pin_dir for _, pin_dir in tracks_model.
                                get_tracks_for_wire_at_coord((loc.grid_x,
                                                              loc.grid_y)))
                            edge_assignments[(tileinfo.tile_type,
                                              wire)].append(available_pins)

    final_edge_assignments = {}
    for (tile_type, wire), available_pins in progressbar.progressbar(
            edge_assignments.items()):
        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[(tile_type,
                                    wire)] = [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[(tile_type, wire)] = [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[(tile_type, wire)] = pins

    for (tile_type, wire), available_pins in edge_assignments.items():
        pins = set(final_edge_assignments[(tile_type, wire)])

        for required_pins in available_pins:
            assert len(pins & set(required_pins)) > 0, (tile_type, wire, pins,
                                                        required_pins)

    pin_directions = {}
    for (tile_type, wire), pins in final_edge_assignments.items():
        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)