| #!/usr/bin/env python3 | |
| from lib.rr_graph import graph2 | |
| from lib.rr_graph import tracks | |
| from lib.rr_graph import points | |
| import lib.rr_graph_xml.graph2 as xml_graph2 | |
| # bi-directional channels | |
| 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), | |
| ) | |
| def create_tracks_from_points( | |
| name, graph, unique_pos, short, grid_width, grid_height | |
| ): | |
| xs, ys = points.decompose_points_into_tracks( | |
| unique_pos, | |
| grid_width, | |
| grid_height, | |
| right_only=True, | |
| ) | |
| tracks_list, track_connections = tracks.make_tracks( | |
| xs, ys, unique_pos, grid_width, grid_height | |
| ) | |
| tracks_model = tracks.Tracks(tracks_list, track_connections) | |
| nodes = [] | |
| for idx, track in enumerate(tracks_list): | |
| nodes.append( | |
| graph.add_track( | |
| track=track, | |
| segment_id=graph.segments[0].id, | |
| capacity=1, | |
| name="{}{}".format(name, idx), | |
| ) | |
| ) | |
| for aidx, bidx in track_connections: | |
| graph.add_edge( | |
| src_node=nodes[aidx], | |
| sink_node=nodes[bidx], | |
| switch_id=short, | |
| ) | |
| graph.add_edge( | |
| src_node=nodes[bidx], | |
| sink_node=nodes[aidx], | |
| switch_id=short, | |
| ) | |
| return nodes, tracks_model | |
| def create_global_constant_tracks(graph, mux, short, grid_width, grid_height): | |
| """ Create channels for global fanout """ | |
| unique_pos = set() | |
| for x in range(grid_width): | |
| for y in range(grid_height): | |
| if x == 0: | |
| continue | |
| if y == 0: | |
| continue | |
| if x == grid_width - 1: | |
| continue | |
| if y == grid_height - 1: | |
| continue | |
| if x == grid_width - 2 and y == grid_height - 2: | |
| continue | |
| unique_pos.add((x, y)) | |
| vcc_nodes, vcc_tracks = create_tracks_from_points( | |
| "VCC", graph, unique_pos, short, grid_width, grid_height | |
| ) | |
| gnd_nodes, gnd_tracks = create_tracks_from_points( | |
| "GND", graph, unique_pos, short, grid_width, grid_height | |
| ) | |
| vcc_pin = graph.create_pin_name_from_tile_type_and_pin('VCC', 'VCC') | |
| gnd_pin = graph.create_pin_name_from_tile_type_and_pin('GND', 'GND') | |
| found_vcc = False | |
| found_gnd = False | |
| for loc in graph.grid: | |
| block_type = graph.block_types[loc.block_type_id] | |
| for pin_class in block_type.pin_class: | |
| for pin in pin_class.pin: | |
| if pin.name == vcc_pin: | |
| nodes = vcc_nodes | |
| tracks = vcc_tracks | |
| found_vcc = True | |
| elif pin.name == gnd_pin: | |
| nodes = gnd_nodes | |
| tracks = gnd_tracks | |
| found_gnd = True | |
| else: | |
| continue | |
| pin_map = {} | |
| for pin_node, pin_side in graph.loc_pin_map[(loc.x, loc.y, | |
| pin.ptc)]: | |
| pin_map[pin_side] = pin_node | |
| made_connection = False | |
| for pin_dir, idx in tracks.get_tracks_for_wire_at_coord( | |
| (loc.x, loc.y)).items(): | |
| if pin_dir in pin_map: | |
| made_connection = True | |
| graph.add_edge( | |
| src_node=pin_map[pin_side], | |
| sink_node=nodes[idx], | |
| switch_id=mux, | |
| ) | |
| break | |
| assert made_connection, (pin, pin_map) | |
| assert found_vcc | |
| assert found_gnd | |
| def channel_common(node): | |
| """ Return the value of the channel that is common. | |
| Args: | |
| node (graph2.Node): Node to get common dimension. | |
| """ | |
| if node.type == graph2.NodeType.CHANX: | |
| assert node.loc.y_low == node.loc.y_high | |
| return node.loc.y_low | |
| elif node.type == graph2.NodeType.CHANY: | |
| assert node.loc.x_low == node.loc.x_high | |
| return node.loc.x_low | |
| else: | |
| assert False, node.type | |
| def channel_start(node): | |
| """ Return the start value of the channel | |
| Args: | |
| node (graph2.Node): Node to get start dimension. | |
| """ | |
| if node.type == graph2.NodeType.CHANX: | |
| return node.loc.x_low | |
| elif node.type == graph2.NodeType.CHANY: | |
| return node.loc.y_low | |
| else: | |
| assert False, node.type | |
| def walk_pins(graph): | |
| """ Yields all pins from grid. | |
| Yield is tuple (graph2.GridLoc, graph2.PinClass, graph2.Pin, pin node idx, tracks.Direction). | |
| """ | |
| for loc in graph.grid: | |
| block_type = graph.block_types[loc.block_type_id] | |
| for pin_class_idx, pin_class in enumerate(block_type.pin_class): | |
| for pin in pin_class.pin: | |
| for pin_node, pin_side in graph.loc_pin_map[(loc.x, loc.y, | |
| pin.ptc)]: | |
| yield loc, pin_class, pin, pin_node, pin_side | |
| def connect_blocks_to_tracks( | |
| graph, grid_width, grid_height, rcw, switch, verbose=False | |
| ): | |
| ytracks = {} | |
| for inode in graph.tracks: | |
| if graph.nodes[inode].type == graph2.NodeType.CHANX: | |
| continue | |
| elif graph.nodes[inode].type == graph2.NodeType.CHANY: | |
| x = channel_common(graph.nodes[inode]) | |
| if x not in ytracks: | |
| ytracks[x] = [] | |
| ytracks[x].append(inode) | |
| else: | |
| assert False, graph.nodes[inode] | |
| special_pins = set( | |
| ( | |
| graph.create_pin_name_from_tile_type_and_pin('TILE', 'COUT'), | |
| graph.create_pin_name_from_tile_type_and_pin('TILE', 'CIN'), | |
| graph.create_pin_name_from_tile_type_and_pin('VCC', 'VCC'), | |
| graph.create_pin_name_from_tile_type_and_pin('GND', 'GND'), | |
| ) | |
| ) | |
| print("Indexing nodes") | |
| print("Skipping connecting block pins to CHANX") | |
| print("Connecting left-right block pins to CHANY") | |
| # Walk every pin and connect them to every track on the left or right, | |
| # depending on pin direction | |
| # | |
| # Pins in the TOP/BOTTOM direction are asserted to be the carry pins. | |
| for loc, pin_class, pin, pin_node, pin_side in walk_pins(graph): | |
| if pin.name in special_pins: | |
| continue | |
| if pin_side == tracks.Direction.LEFT: | |
| if loc.x == 0: | |
| continue | |
| tracks_for_pin = ytracks[loc.x - 1] | |
| elif pin_side == tracks.Direction.RIGHT: | |
| if loc.x == grid_width - 1: | |
| continue | |
| tracks_for_pin = ytracks[loc.x] | |
| else: | |
| assert False, pin_side | |
| if pin_class.type == graph2.PinType.OUTPUT: | |
| for track_inode in tracks_for_pin: | |
| graph.add_edge( | |
| src_node=pin_node, | |
| sink_node=track_inode, | |
| switch_id=switch, | |
| ) | |
| elif pin_class.type == graph2.PinType.INPUT: | |
| for track_inode in tracks_for_pin: | |
| graph.add_edge( | |
| src_node=track_inode, | |
| sink_node=pin_node, | |
| switch_id=switch, | |
| ) | |
| def connect_tracks_to_tracks(graph, switch, verbose=False): | |
| print("Connecting tracks to tracks") | |
| def try_connect(ainode, binode): | |
| """ Connect channel at ainode and binode if possible. | |
| Args: | |
| ainode (int): Node index of destination channel | |
| binode (int): Node index of source channel. | |
| """ | |
| atrack = graph.nodes[ainode] | |
| btrack = graph.nodes[binode] | |
| # Check if source channel can connect to destinatio channel. | |
| # Note this code assumes unidirectional channels are in use. | |
| if (btrack.direction == graph2.NodeDirection.INC_DIR | |
| and channel_start(btrack) <= channel_common(atrack)) \ | |
| or (btrack.direction == graph2.NodeDirection.DEC_DIR | |
| and channel_start(btrack) >= channel_common(atrack)): | |
| graph.add_edge(binode, ainode, switch) | |
| xtracks = [] | |
| ytracks = [] | |
| for inode in graph.tracks: | |
| if graph.nodes[inode].type == graph2.NodeType.CHANX: | |
| xtracks.append(inode) | |
| elif graph.nodes[inode].type == graph2.NodeType.CHANY: | |
| ytracks.append(inode) | |
| else: | |
| assert False, graph.nodes[inode] | |
| for xinode in xtracks: | |
| for yinode in ytracks: | |
| try_connect(xinode, yinode) | |
| try_connect(yinode, xinode) | |
| def rebuild_graph(fn, fn_out, rcw=6, verbose=False): | |
| """ | |
| Add rcw tracks spanning full channel to both X and Y channels | |
| Connect all of those to all the adjacent pins | |
| Fully connect tracks at intersections | |
| For intersections this means we actually have two edges per intersection | |
| since source and sink must be specified | |
| """ | |
| print('Importing input g') | |
| xml_graph = xml_graph2.Graph( | |
| fn, | |
| output_file_name=fn_out, | |
| ) | |
| graph = xml_graph.graph | |
| grid_width = max(p.x for p in graph.grid) + 1 | |
| grid_height = max(p.y for p in graph.grid) + 1 | |
| mux = graph.get_switch_id('mux') | |
| try: | |
| short = graph.get_switch_id('short') | |
| except KeyError: | |
| short = xml_graph.add_switch( | |
| graph2.Switch( | |
| id=None, | |
| name='short', | |
| type=graph2.SwitchType.SHORT, | |
| timing=None, | |
| sizing=graph2.SwitchSizing( | |
| mux_trans_size=0, | |
| buf_size=0, | |
| ), | |
| ) | |
| ) | |
| create_tracks(graph, grid_width, grid_height, rcw, verbose=verbose) | |
| create_global_constant_tracks(graph, mux, short, grid_width, grid_height) | |
| connect_blocks_to_tracks(graph, grid_width, grid_height, rcw, switch=mux) | |
| connect_tracks_to_tracks(graph, switch=mux, verbose=verbose) | |
| print("Completed rebuild") | |
| xml_graph.root_attrib["tool_version"] = "dev" | |
| xml_graph.root_attrib["tool_comment"] = "Generated from black magic" | |
| channels_obj = graph.create_channels(pad_segment=graph.segments[0].id) | |
| xml_graph.serialize_to_xml( | |
| channels_obj=channels_obj, | |
| nodes_obj=graph.nodes, | |
| edges_obj=graph.edges | |
| ) | |
| def main(): | |
| import argparse | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--verbose", action='store_true') | |
| parser.add_argument("--route_chan_width", type=int, default=20) | |
| parser.add_argument("--read_rr_graph") | |
| parser.add_argument("--write_rr_graph") | |
| args = parser.parse_args() | |
| rebuild_graph( | |
| args.read_rr_graph, | |
| args.write_rr_graph, | |
| rcw=args.route_chan_width, | |
| verbose=args.verbose | |
| ) | |
| if __name__ == "__main__": | |
| main() |