| #!/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() |