Пример #1
0
def read_switch(sw):
    timing = sw.timing
    sizing = sw.sizing

    return graph2.Switch(
        id=sw.id,
        name=str(sw.name),
        type=enum_from_string(graph2.SwitchType, sw.type),
        timing=graph2.SwitchTiming(
            r=timing.r,
            c_in=timing.cin,
            c_out=timing.cout,
            c_internal=timing.cinternal,
            t_del=timing.tdel,
        ),
        sizing=graph2.SwitchSizing(
            buf_size=sizing.bufSize,
            mux_trans_size=sizing.muxTransSize,
        ),
    )
Пример #2
0
def graph_from_xml(input_xml, progressbar=None):
    if progressbar is None:
        progressbar = lambda x: x

    switches = []
    for switch in input_xml.find('switches').iter('switch'):
        timing_xml = switch.find('timing')

        if timing_xml is not None:
            timing = graph2.SwitchTiming(
                r=float(timing_xml.attrib['R']),
                c_in=float(timing_xml.attrib['Cin']),
                c_out=float(timing_xml.attrib['Cout']),
                t_del=float(timing_xml.attrib['Tdel']),
            )
        else:
            timing = None

        sizing_xml = switch.find('sizing')

        if sizing_xml is not None:
            sizing = graph2.SwitchSizing(
                mux_trans_size=float(sizing_xml.attrib['mux_trans_size']),
                buf_size=float(sizing_xml.attrib['buf_size']),
            )
        else:
            sizing = None

        switches.append(
            graph2.Switch(
                id=int(switch.attrib['id']),
                type=enum_from_string(graph2.SwitchType,
                                      switch.attrib['type']),
                name=switch.attrib['name'],
                timing=timing,
                sizing=sizing,
            ))

    segments = []
    for segment in input_xml.find('segments').iter('segment'):
        timing_xml = segment.find('timing')

        if timing_xml is not None:
            timing = graph2.SegmentTiming(
                r_per_meter=float(timing_xml.attrib['R_per_meter']),
                c_per_meter=float(timing_xml.attrib['C_per_meter']),
            )
        else:
            timing = None

        segments.append(
            graph2.Segment(
                id=int(segment.attrib['id']),
                name=segment.attrib['name'],
                timing=timing,
            ))

    block_types = []
    for block_type in input_xml.find('block_types').iter('block_type'):
        pin_classes = []

        for pin_class in block_type.iter('pin_class'):
            pins = []
            for pin in pin_class.iter('pin'):
                pins.append(
                    graph2.Pin(
                        ptc=int(pin.attrib['ptc']),
                        name=pin.text,
                    ))

            pin_classes.append(
                graph2.PinClass(
                    type=enum_from_string(graph2.PinType,
                                          pin_class.attrib['type']),
                    pin=pins,
                ))

        block_types.append(
            graph2.BlockType(
                id=int(block_type.attrib['id']),
                name=block_type.attrib['name'],
                width=int(block_type.attrib['width']),
                height=int(block_type.attrib['height']),
                pin_class=pin_classes,
            ))

    grid = []
    for grid_loc in input_xml.find('grid').iter('grid_loc'):
        grid.append(
            graph2.GridLoc(
                x=int(grid_loc.attrib['x']),
                y=int(grid_loc.attrib['y']),
                block_type_id=int(grid_loc.attrib['block_type_id']),
                width_offset=int(grid_loc.attrib['width_offset']),
                height_offset=int(grid_loc.attrib['height_offset']),
            ))

    nodes = []
    for node in progressbar(input_xml.find('rr_nodes').iter('node')):
        node_type = enum_from_string(graph2.NodeType, node.attrib['type'])
        if node_type in [
                graph2.NodeType.SOURCE, graph2.NodeType.SINK,
                graph2.NodeType.OPIN, graph2.NodeType.IPIN
        ]:

            loc_xml = node.find('loc')
            if loc_xml is not None:
                if 'side' in loc_xml.attrib:
                    side = enum_from_string(tracks.Direction,
                                            loc_xml.attrib['side'])
                else:
                    side = None

                loc = graph2.NodeLoc(x_low=int(loc_xml.attrib['xlow']),
                                     y_low=int(loc_xml.attrib['ylow']),
                                     x_high=int(loc_xml.attrib['xhigh']),
                                     y_high=int(loc_xml.attrib['yhigh']),
                                     ptc=int(loc_xml.attrib['ptc']),
                                     side=side)
            else:
                loc = None

            timing_xml = node.find('timing')
            if timing_xml is not None:
                timing = graph2.NodeTiming(
                    r=float(timing_xml.attrib['R']),
                    c=float(timing_xml.attrib['C']),
                )
            else:
                timing = None

            # Not expecting any metadata on the input.
            assert node.find('metadata') is None
            metadata = None
            nodes.append(
                graph2.Node(
                    id=int(node.attrib['id']),
                    type=node_type,
                    direction=graph2.NodeDirection.NO_DIR,
                    capacity=int(node.attrib['capacity']),
                    loc=loc,
                    timing=timing,
                    metadata=metadata,
                    segment=None,
                ))

    return dict(switches=switches,
                segments=segments,
                block_types=block_types,
                grid=grid,
                nodes=nodes)
Пример #3
0
def graph_from_xml(input_file_name, progressbar=None, filter_nodes=True):
    """
    Loads relevant information about the routing resource graph from an XML
    file.
    """

    if progressbar is None:
        progressbar = lambda x: x  # noqa: E731

    root_attrib = {}
    switches = []
    segments = []
    block_types = []
    grid = []
    nodes = []

    # Itertate over XML elements
    switch_timing = None
    switch_sizing = None
    segment_timing = None
    pins = []
    pin_classes = []
    node_loc = None
    node_timing = None

    for path, element in progressbar(iterate_xml(input_file_name)):

        # Root tag
        if path == "" and element.tag == "rr_graph":
            root_attrib = dict(element.attrib)

        # Switch timing
        if path == "rr_graph/switches/switch" and element.tag == "timing":
            switch_timing = graph2.SwitchTiming(
                r=float(element.attrib['R']),
                c_in=float(element.attrib['Cin']),
                c_out=float(element.attrib['Cout']),
                c_internal=float(element.attrib.get('Cinternal', 0)),
                t_del=float(element.attrib['Tdel']),
            )

        # Switch sizing
        if path == "rr_graph/switches/switch" and element.tag == "sizing":
            switch_sizing = graph2.SwitchSizing(
                mux_trans_size=float(element.attrib['mux_trans_size']),
                buf_size=float(element.attrib['buf_size']),
            )

        # Switch
        if path == "rr_graph/switches" and element.tag == "switch":
            switches.append(
                graph2.Switch(
                    id=int(element.attrib['id']),
                    type=enum_from_string(graph2.SwitchType,
                                          element.attrib['type']),
                    name=element.attrib['name'],
                    timing=switch_timing,
                    sizing=switch_sizing,
                ))

            switch_timing = None
            switch_sizing = None

        # Segment timing
        if path == "rr_graph/segments/segment" and element.tag == "timing":
            segment_timing = graph2.SegmentTiming(
                r_per_meter=float(element.attrib['R_per_meter']),
                c_per_meter=float(element.attrib['C_per_meter']),
            )

        # Segment
        if path == "rr_graph/segments" and element.tag == "segment":
            segments.append(
                graph2.Segment(
                    id=int(element.attrib['id']),
                    name=element.attrib['name'],
                    timing=segment_timing,
                ))

            segment_timing = None

        # Block type - pin
        if path == "rr_graph/block_types/block_type/pin_class" and element.tag == "pin":
            pins.append(
                graph2.Pin(
                    ptc=int(element.attrib['ptc']),
                    name=element.text,
                ))

        # Block type - pin_class
        if path == "rr_graph/block_types/block_type" and element.tag == "pin_class":
            pin_classes.append(
                graph2.PinClass(
                    type=enum_from_string(graph2.PinType,
                                          element.attrib['type']),
                    pin=pins,
                ))

            pins = []

        # Block type
        if path == "rr_graph/block_types" and element.tag == "block_type":
            block_types.append(
                graph2.BlockType(
                    id=int(element.attrib['id']),
                    name=element.attrib['name'],
                    width=int(element.attrib['width']),
                    height=int(element.attrib['height']),
                    pin_class=pin_classes,
                ))

            pin_classes = []

        # Grid
        if path == "rr_graph/grid" and element.tag == "grid_loc":
            grid.append(
                graph2.GridLoc(
                    x=int(element.attrib['x']),
                    y=int(element.attrib['y']),
                    block_type_id=int(element.attrib['block_type_id']),
                    width_offset=int(element.attrib['width_offset']),
                    height_offset=int(element.attrib['height_offset']),
                ))

        # Node - loc
        if path == "rr_graph/rr_nodes/node" and element.tag == "loc":
            if 'side' in element.attrib:
                side = enum_from_string(tracks.Direction,
                                        element.attrib['side'])
            else:
                side = None

            node_loc = graph2.NodeLoc(x_low=int(element.attrib['xlow']),
                                      y_low=int(element.attrib['ylow']),
                                      x_high=int(element.attrib['xhigh']),
                                      y_high=int(element.attrib['yhigh']),
                                      ptc=int(element.attrib['ptc']),
                                      side=side)

        # Node - timing
        if path == "rr_graph/rr_nodes/node" and element.tag == "timing":
            node_timing = graph2.NodeTiming(
                r=float(element.attrib['R']),
                c=float(element.attrib['C']),
            )

        # Node
        if path == "rr_graph/rr_nodes" and element.tag == "node":
            node_type = enum_from_string(graph2.NodeType,
                                         element.attrib['type'])

            if filter_nodes and node_type not in [
                    graph2.NodeType.SOURCE, graph2.NodeType.SINK,
                    graph2.NodeType.OPIN, graph2.NodeType.IPIN
            ]:
                continue

            # Dropping metadata for now
            metadata = None

            nodes.append(
                graph2.Node(
                    id=int(element.attrib['id']),
                    type=node_type,
                    direction=graph2.NodeDirection.NO_DIR,
                    capacity=int(element.attrib['capacity']),
                    loc=node_loc,
                    timing=node_timing,
                    metadata=metadata,
                    segment=None,
                    canonical_loc=None,
                    connection_box=None,
                ))

            node_loc = None
            node_timing = None

    return dict(root_attrib=root_attrib,
                switches=switches,
                segments=segments,
                block_types=block_types,
                grid=grid,
                nodes=nodes)
Пример #4
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('--write_rr_node_map',
                        required=True,
                        help='Output map of graph_node_pkey to rr inode file')
    parser.add_argument('--connection_database',
                        help='Database of fabric connectivity',
                        required=True)
    parser.add_argument(
        '--synth_tiles',
        help=
        'If using an ROI, synthetic tile defintion from prjxray-arch-import')
    parser.add_argument(
        '--graph_limit',
        help='Limit grid to specified dimensions in x_min,y_min,x_max,y_max',
    )

    args = parser.parse_args()

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

    synth_tiles = None
    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()))
    elif args.graph_limit:
        use_roi = True
        x_min, y_min, x_max, y_max = map(int, args.graph_limit.split(','))
        roi = Roi(
            db=db,
            x1=x_min,
            y1=y_min,
            x2=x_max,
            y2=y_max,
        )
    else:
        use_roi = False
        roi = None
        synth_tiles = None

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

    if synth_tiles is None:
        synth_tiles = find_constant_network(input_rr_graph)

    xml_graph = xml_graph2.Graph(
        input_rr_graph,
        progressbar=progressbar_utils.progressbar,
        output_file_name=args.write_rr_graph,
    )

    graph = xml_graph.graph

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

    with DatabaseCache(args.connection_database, True) as conn:
        cur = conn.cursor()
        for name, internal_capacitance, drive_resistance, intrinsic_delay, \
                switch_type in cur.execute("""
SELECT
    name,
    internal_capacitance,
    drive_resistance,
    intrinsic_delay,
    switch_type
FROM
    switch;"""):
            # Add back missing switchs, which were unused in arch xml, and so
            # were not  emitted in rrgraph XML.
            #
            # TODO: This can be removed once
            # https://github.com/verilog-to-routing/vtr-verilog-to-routing/issues/354
            # is fixed.

            try:
                graph.get_switch_id(name)
                continue
            except KeyError:
                xml_graph.add_switch(
                    graph2.Switch(
                        id=None,
                        name=name,
                        type=graph2.SwitchType[switch_type.upper()],
                        timing=graph2.SwitchTiming(
                            r=drive_resistance,
                            c_in=0.0,
                            c_out=0.0,
                            c_internal=internal_capacitance,
                            t_del=intrinsic_delay,
                        ),
                        sizing=graph2.SwitchSizing(
                            mux_trans_size=0,
                            buf_size=0,
                        ),
                    ))

        # Mapping of graph_node.pkey to rr node id.
        node_mapping = {}

        print('{} Creating connection box list'.format(now()))
        connection_box_map = create_connection_boxes(conn, graph)

        # Match site pins rr nodes with graph_node's in the connection_database.
        print('{} Importing graph nodes'.format(now()))
        import_graph_nodes(conn, graph, node_mapping, connection_box_map)

        # Walk all track graph nodes and add them.
        print('{} Creating tracks'.format(now()))
        segment_id = graph.get_segment_id_from_name('dummy')
        create_track_rr_graph(conn, graph, node_mapping, use_roi, roi,
                              synth_tiles, segment_id)

        # 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.
        if use_roi:
            print('{} Adding synthetic edges'.format(now()))
            add_synthetic_edges(conn, graph, node_mapping, grid, synth_tiles)

        print('{} Creating channels.'.format(now()))
        channels_obj = create_channels(conn)

        x_dim, y_dim = phy_grid_dims(conn)
        connection_box_obj = graph.create_connection_box_object(x_dim=x_dim,
                                                                y_dim=y_dim)

        print('{} Serializing to disk.'.format(now()))
        with xml_graph:
            xml_graph.start_serialize_to_xml(
                tool_version=tool_version,
                tool_comment=tool_comment,
                channels_obj=channels_obj,
                connection_box_obj=connection_box_obj,
            )

            xml_graph.serialize_nodes(yield_nodes(xml_graph.graph.nodes))
            xml_graph.serialize_edges(
                import_graph_edges(conn, graph, node_mapping))

        print('{} Writing node map.'.format(now()))
        with open(args.write_rr_node_map, 'wb') as f:
            pickle.dump(node_mapping, f)
        print('{} Done writing node map.'.format(now()))
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--db_root',
                        required=True,
                        help='Project X-Ray Database')
    parser.add_argument('--part', required=True, help='FPGA part')
    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('--write_rr_node_map',
                        required=True,
                        help='Output map of graph_node_pkey to rr inode file')
    parser.add_argument('--connection_database',
                        help='Database of fabric connectivity',
                        required=True)
    parser.add_argument(
        '--synth_tiles',
        help=
        'If using an ROI, synthetic tile defintion from prjxray-arch-import')
    parser.add_argument('--overlay',
                        action='store_true',
                        required=False,
                        help='Use synth tiles for Overlay instead of ROI')
    parser.add_argument(
        '--graph_limit',
        help='Limit grid to specified dimensions in x_min,y_min,x_max,y_max',
    )
    parser.add_argument(
        '--vpr_capnp_schema_dir',
        help='Directory container VPR schema files',
    )

    print('{} Starting routing import'.format(now()))
    args = parser.parse_args()

    db = prjxray.db.Database(args.db_root, args.part)
    populate_hclk_cmt_tiles(db)

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

        region_dict = dict()
        for r in synth_tiles['info']:
            bounds = (r['GRID_X_MIN'], r['GRID_X_MAX'], r['GRID_Y_MIN'],
                      r['GRID_Y_MAX'])
            region_dict[r['name']] = bounds

        roi = Overlay(region_dict=region_dict)

        print('{} generating routing graph for Overlay.'.format(now()))
    elif 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()))
    elif args.graph_limit:
        use_roi = True
        x_min, y_min, x_max, y_max = map(int, args.graph_limit.split(','))
        roi = Roi(
            db=db,
            x1=x_min,
            y1=y_min,
            x2=x_max,
            y2=y_max,
        )
    else:
        use_roi = False
        roi = None
        synth_tiles = None

    capnp_graph = capnp_graph2.Graph(
        rr_graph_schema_fname=os.path.join(args.vpr_capnp_schema_dir,
                                           'rr_graph_uxsdcxx.capnp'),
        input_file_name=args.read_rr_graph,
        progressbar=progressbar_utils.progressbar,
        output_file_name=args.write_rr_graph,
    )

    graph = capnp_graph.graph

    if synth_tiles is None:
        synth_tiles = find_constant_network(graph)

    if args.overlay:
        synth_tiles_const = find_constant_network(graph)
        synth_tiles['tiles'].update(synth_tiles_const['tiles'])

    with sqlite3.connect("file:{}?mode=ro".format(args.connection_database),
                         uri=True) as conn:

        populate_bufg_rebuf_map(conn)

        cur = conn.cursor()
        for name, internal_capacitance, drive_resistance, intrinsic_delay, penalty_cost, \
                switch_type in cur.execute("""
SELECT
    name,
    internal_capacitance,
    drive_resistance,
    intrinsic_delay,
    penalty_cost,
    switch_type
FROM
    switch;"""):
            # Add back missing switchs, which were unused in arch xml, and so
            # were not  emitted in rrgraph XML.
            #
            # TODO: This can be removed once
            # https://github.com/verilog-to-routing/vtr-verilog-to-routing/issues/354
            # is fixed.

            try:
                graph.get_switch_id(name)
                continue
            except KeyError:
                capnp_graph.add_switch(
                    graph2.Switch(
                        id=None,
                        name=name,
                        type=graph2.SwitchType[switch_type.upper()],
                        timing=graph2.SwitchTiming(
                            r=drive_resistance,
                            c_in=0.0,
                            c_out=0.0,
                            c_internal=internal_capacitance,
                            t_del=intrinsic_delay,
                            p_cost=penalty_cost,
                        ),
                        sizing=graph2.SwitchSizing(
                            mux_trans_size=0,
                            buf_size=0,
                        ),
                    ))

        # Mapping of graph_node.pkey to rr node id.
        node_mapping = {}

        print('{} Creating connection box list'.format(now()))
        connection_box_map = create_connection_boxes(conn, graph)

        # Match site pins rr nodes with graph_node's in the connection_database.
        print('{} Importing graph nodes'.format(now()))
        import_graph_nodes(conn, graph, node_mapping, connection_box_map)

        # Walk all track graph nodes and add them.
        print('{} Creating tracks'.format(now()))
        segment_id = graph.get_segment_id_from_name('dummy')
        create_track_rr_graph(conn, graph, node_mapping, use_roi, roi,
                              synth_tiles, segment_id)

        # 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.
        print('{} Adding synthetic edges'.format(now()))
        add_synthetic_edges(conn, graph, node_mapping, grid, synth_tiles,
                            args.overlay)

        print('{} Creating channels.'.format(now()))
        channels_obj = create_channels(conn)

        node_remap = create_node_remap(capnp_graph.graph.nodes, channels_obj)

        x_dim, y_dim = phy_grid_dims(conn)
        connection_box_obj = graph.create_connection_box_object(x_dim=x_dim,
                                                                y_dim=y_dim)

        num_edges = get_number_graph_edges(conn, graph, node_mapping)
        print('{} Serializing to disk.'.format(now()))

        capnp_graph.serialize_to_capnp(
            channels_obj=channels_obj,
            connection_box_obj=connection_box_obj,
            num_nodes=len(capnp_graph.graph.nodes),
            nodes_obj=yield_nodes(capnp_graph.graph.nodes),
            num_edges=num_edges,
            edges_obj=import_graph_edges(conn, graph, node_mapping),
            node_remap=node_remap,
        )

        for k in node_mapping:
            node_mapping[k] = node_remap(node_mapping[k])

        print('{} Writing node map.'.format(now()))
        with open(args.write_rr_node_map, 'wb') as f:
            pickle.dump(node_mapping, f)
        print('{} Done writing node map.'.format(now()))
Пример #6
0
def main():

    # Parse arguments
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)

    parser.add_argument("--vpr-db",
                        type=str,
                        required=True,
                        help="VPR database file")
    parser.add_argument("--rr-graph-in",
                        type=str,
                        required=True,
                        help="Input RR graph XML file")
    parser.add_argument("--rr-graph-out",
                        type=str,
                        default="rr_graph.xml",
                        help="Output RR graph XML file (def. rr_graph.xml)")

    args = parser.parse_args()

    # Load data from the database
    print("Loading database...")
    with open(args.vpr_db, "rb") as fp:
        db = pickle.load(fp)

        vpr_quadrants = db["vpr_quadrants"]
        vpr_clock_cells = db["vpr_clock_cells"]
        loc_map = db["loc_map"]
        vpr_tile_types = db["vpr_tile_types"]
        vpr_tile_grid = db["vpr_tile_grid"]
        vpr_switchbox_types = db["vpr_switchbox_types"]
        vpr_switchbox_grid = db["vpr_switchbox_grid"]
        connections = db["connections"]
        switches = db["switches"]

    # Load the routing graph, build SOURCE -> OPIN and IPIN -> SINK edges.
    print("Loading rr graph...")
    xml_graph = rr_xml.Graph(input_file_name=args.rr_graph_in,
                             output_file_name=args.rr_graph_out,
                             progressbar=progressbar_utils.progressbar)

    # Add back the switches that were unused in the arch.xml and got pruned
    # byt VPR.
    for switch in switches:
        try:
            xml_graph.graph.get_switch_id(switch.name)
            continue
        except KeyError:
            xml_graph.add_switch(
                rr.Switch(
                    id=None,
                    name=switch.name,
                    type=rr.SwitchType[switch.type.upper()],
                    timing=rr.SwitchTiming(
                        r=switch.r,
                        c_in=switch.c_in,
                        c_out=switch.c_out,
                        c_internal=switch.c_int,
                        t_del=switch.t_del,
                    ),
                    sizing=rr.SwitchSizing(
                        mux_trans_size=0,
                        buf_size=0,
                    ),
                ))

    print("Building maps...")

    # Add a switch map to the graph
    switch_map = {}
    for switch in xml_graph.graph.switches:
        assert switch.id not in switch_map, switch
        switch_map[switch.id] = switch

    xml_graph.graph.switch_map = switch_map

    # Build node id to node map
    nodes_by_id = {node.id: node for node in xml_graph.graph.nodes}

    # Build tile pin names to rr node ids map
    tile_pin_to_node = build_tile_pin_to_node_map(xml_graph.graph, nodes_by_id,
                                                  vpr_tile_types,
                                                  vpr_tile_grid)

    # Add const network
    const_node_map = {}
    for const in ["VCC", "GND"]:
        m = add_tracks_for_const_network(xml_graph.graph, const, vpr_tile_grid)
        const_node_map[const] = m

    # Connection loc (endpoint) to node map. Map ConnectionLoc objects to VPR
    # rr graph node ids.
    connection_loc_to_node = {}

    # Build a map of connections to/from tiles and rr nodes. The map points
    # to an IPIN/OPIN node for a connection loc that mentions it.
    node_map = build_tile_connection_map(xml_graph.graph, nodes_by_id,
                                         vpr_tile_grid, connections)
    connection_loc_to_node.update(node_map)

    # Build the global clock network
    print("Building the global clock network...")

    # GMUX to QMUX and QMUX to CAND tracks
    node_map = create_quadrant_clock_tracks(xml_graph.graph, connections,
                                            connection_loc_to_node)
    connection_loc_to_node.update(node_map)

    # Clock column tracks
    cand_node_map = create_column_clock_tracks(xml_graph.graph,
                                               vpr_clock_cells, vpr_quadrants)

    # Add switchbox models.
    print("Building switchbox models...")
    switchbox_models = {}

    # Gather QMUX cells
    qmux_cells = {}
    for cell in vpr_clock_cells.values():
        if cell.type == "QMUX":
            loc = cell.loc

            if loc not in qmux_cells:
                qmux_cells[loc] = {}

            qmux_cells[loc][cell.name] = cell

    # Create the models
    for loc, type in vpr_switchbox_grid.items():
        phy_loc = loc_map.bwd[loc]

        # QMUX switchbox model
        if loc in qmux_cells:
            switchbox_models[loc] = QmuxSwitchboxModel(
                graph=xml_graph.graph,
                loc=loc,
                phy_loc=phy_loc,
                switchbox=vpr_switchbox_types[type],
                qmux_cells=qmux_cells[loc],
                connections=[c for c in connections if is_clock(c)])

        # Regular switchbox model
        else:
            switchbox_models[loc] = SwitchboxModel(
                graph=xml_graph.graph,
                loc=loc,
                phy_loc=phy_loc,
                switchbox=vpr_switchbox_types[type],
            )

    # Build switchbox models
    for switchbox_model in progressbar_utils.progressbar(
            switchbox_models.values()):
        switchbox_model.build()

    # Build the global clock network cell models
    print("Building QMUX and CAND models...")

    # Add QMUX and CAND models
    for cell in progressbar_utils.progressbar(vpr_clock_cells.values()):
        phy_loc = loc_map.bwd[cell.loc]

        if cell.type == "QMUX":
            QmuxModel(graph=xml_graph.graph,
                      cell=cell,
                      phy_loc=phy_loc,
                      switchbox_model=switchbox_models[cell.loc],
                      connections=connections,
                      node_map=connection_loc_to_node)

        if cell.type == "CAND":
            CandModel(graph=xml_graph.graph,
                      cell=cell,
                      phy_loc=phy_loc,
                      connections=connections,
                      node_map=connection_loc_to_node,
                      cand_node_map=cand_node_map)

    # Populate connections to the switchbox models
    print("Populating connections...")
    populate_hop_connections(xml_graph.graph, switchbox_models, connections)
    populate_tile_connections(xml_graph.graph, switchbox_models, connections,
                              connection_loc_to_node)
    populate_direct_connections(xml_graph.graph, connections,
                                connection_loc_to_node)
    populate_cand_connections(xml_graph.graph, switchbox_models, cand_node_map)
    populate_const_connections(xml_graph.graph, switchbox_models,
                               vpr_tile_types, vpr_tile_grid, tile_pin_to_node,
                               const_node_map)

    # Create channels from tracks
    pad_segment_id = xml_graph.graph.get_segment_id_from_name("pad")
    channels_obj = xml_graph.graph.create_channels(pad_segment=pad_segment_id)

    # Remove padding channels
    print("Removing padding nodes...")
    xml_graph.graph.nodes = [
        n for n in xml_graph.graph.nodes if n.capacity > 0
    ]

    # Build node id to node map again since there have been new nodes added.
    nodes_by_id = {node.id: node for node in xml_graph.graph.nodes}

    # Sanity check edges
    print("Sanity checking edges...")
    node_ids = set([n.id for n in xml_graph.graph.nodes])
    for edge in xml_graph.graph.edges:
        assert edge.src_node in node_ids, edge
        assert edge.sink_node in node_ids, edge
        assert edge.src_node != edge.sink_node, edge

    # Sanity check IPIN/OPIN connections. There must be no tile completely
    # disconnected from the routing network
    print("Sanity checking tile connections...")

    connected_locs = set()
    for edge in xml_graph.graph.edges:
        src = nodes_by_id[edge.src_node]
        dst = nodes_by_id[edge.sink_node]

        if src.type == rr.NodeType.OPIN:
            loc = (src.loc.x_low, src.loc.y_low)
            connected_locs.add(loc)

        if dst.type == rr.NodeType.IPIN:
            loc = (src.loc.x_low, src.loc.y_low)
            connected_locs.add(loc)

    non_empty_locs = set((loc.x, loc.y) for loc in xml_graph.graph.grid
                         if loc.block_type_id > 0)

    unconnected_locs = non_empty_locs - connected_locs
    for loc in unconnected_locs:
        block_type = xml_graph.graph.block_type_at_loc(loc)
        print(" ERROR: Tile '{}' at ({}, {}) is not connected!".format(
            block_type, loc[0], loc[1]))

    # Write the routing graph
    nodes_obj = xml_graph.graph.nodes
    edges_obj = xml_graph.graph.edges

    print("Serializing the rr graph...")
    xml_graph.serialize_to_xml(
        channels_obj=channels_obj,
        nodes_obj=nodes_obj,
        edges_obj=yield_edges(edges_obj),
        node_remap=lambda x: x,
    )