| import os.path | 
 | import re | 
 | from lib.rr_graph import graph2 | 
 | from lib.rr_graph import tracks | 
 | import gc | 
 |  | 
 | import capnp | 
 | import capnp.lib.capnp | 
 | capnp.remove_import_hook() | 
 |  | 
 | CAMEL_CASE_CAPITALS = re.compile('([A-Z]+)') | 
 |  | 
 | ENUM_CACHE = {} | 
 |  | 
 |  | 
 | def enum_from_string(enum_type, s): | 
 |     if s == 'uxsdInvalid': | 
 |         return None | 
 |  | 
 |     s = str(s) | 
 |     key = (id(enum_type), s) | 
 |     if key not in ENUM_CACHE: | 
 |         ENUM_CACHE[key] = enum_type[CAMEL_CASE_CAPITALS.sub(r'_\1', s).upper()] | 
 |     return ENUM_CACHE[key] | 
 |  | 
 |  | 
 | CAPNP_ENUM_CACHE = {} | 
 |  | 
 |  | 
 | def to_capnp_enum(enum_type, e): | 
 |     key = (id(enum_type), e) | 
 |  | 
 |     if key not in CAPNP_ENUM_CACHE: | 
 |         # Convert from snake_case to camelCase. | 
 |         parts = [] | 
 |         for idx, part in enumerate(e.name.split('_')): | 
 |             if idx == 0: | 
 |                 parts.append(part.lower()) | 
 |             else: | 
 |                 parts.append(part.capitalize()) | 
 |         camel_case_e = "".join(parts) | 
 |  | 
 |         CAPNP_ENUM_CACHE[key] = enum_type.__dict__[camel_case_e] | 
 |  | 
 |     return CAPNP_ENUM_CACHE[key] | 
 |  | 
 |  | 
 | def cleanup_capnp_leak(f): | 
 |     """ Cleanup capnp leak resulting from _parent pointers. """ | 
 |     popped = set() | 
 |     strays = {} | 
 |  | 
 |     # Some strays hold a reference to the input file | 
 |     strays.update( | 
 |         (id(obj), obj) | 
 |         for obj in gc.get_referrers(f) | 
 |         if 'capnp' in str(type(obj)) | 
 |     ) | 
 |  | 
 |     # Some strays are "floating" | 
 |     for obj in gc.get_objects(): | 
 |         type_str = str(type(obj)) | 
 |         if 'capnp.lib.capnp._DynamicStructReader' in type_str: | 
 |             strays[id(obj)] = obj | 
 |  | 
 |     if len(strays) > 0: | 
 |         # First expand all strays and find other capnp objects that still hold | 
 |         # a reference to them (via the _parent pointer). | 
 |         for obj_id in set(strays.keys()) - popped: | 
 |             popped.add(obj_id) | 
 |             strays.update( | 
 |                 (id(obj), obj) | 
 |                 for obj in gc.get_referrers(strays[obj_id]) | 
 |                 if 'capnp' in str(type(obj)) | 
 |             ) | 
 |  | 
 |         # Clear their _parent pointer | 
 |         for obj in strays.values(): | 
 |             obj._parent = None | 
 |  | 
 |         # Make sure none of the strays are still referred to by anything | 
 |         for obj in strays.values(): | 
 |             capnp_refs = [ | 
 |                 None for obj in gc.get_referrers(strays[obj_id]) | 
 |                 if 'capnp' in str(type(obj)) | 
 |             ] | 
 |             assert len(capnp_refs) == 0 | 
 |  | 
 |         # Make sure the file is not referenced by any files. | 
 |         capnp_refs = [ | 
 |             None for obj in gc.get_referrers(f) if 'capnp' in str(type(obj)) | 
 |         ] | 
 |         assert len(capnp_refs) == 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, | 
 |         ), | 
 |     ) | 
 |  | 
 |  | 
 | def read_segment(seg): | 
 |     timing = seg.timing | 
 |     return graph2.Segment( | 
 |         id=seg.id, | 
 |         name=str(seg.name), | 
 |         timing=graph2.SegmentTiming( | 
 |             r_per_meter=timing.rPerMeter, | 
 |             c_per_meter=timing.cPerMeter, | 
 |         ) | 
 |     ) | 
 |  | 
 |  | 
 | def read_pin(pin): | 
 |     return graph2.Pin( | 
 |         ptc=pin.ptc, | 
 |         name=str(pin.value), | 
 |     ) | 
 |  | 
 |  | 
 | def read_pin_class(pin_class): | 
 |     return graph2.PinClass( | 
 |         type=enum_from_string(graph2.PinType, pin_class.type), | 
 |         pin=[read_pin(pin) for pin in pin_class.pins] | 
 |     ) | 
 |  | 
 |  | 
 | def read_block_type(block_type): | 
 |     return graph2.BlockType( | 
 |         id=block_type.id, | 
 |         name=str(block_type.name), | 
 |         width=block_type.width, | 
 |         height=block_type.height, | 
 |         pin_class=[ | 
 |             read_pin_class(pin_class) for pin_class in block_type.pinClasses | 
 |         ] | 
 |     ) | 
 |  | 
 |  | 
 | def read_grid_loc(grid_loc): | 
 |     return graph2.GridLoc( | 
 |         x=grid_loc.x, | 
 |         y=grid_loc.y, | 
 |         block_type_id=grid_loc.blockTypeId, | 
 |         width_offset=grid_loc.widthOffset, | 
 |         height_offset=grid_loc.heightOffset, | 
 |     ) | 
 |  | 
 |  | 
 | def read_metadata(metadata): | 
 |     if len(metadata.metas) == 0: | 
 |         return None | 
 |     else: | 
 |         return [(str(m.name), str(m.value)) for m in metadata.metas] | 
 |  | 
 |  | 
 | def read_node(node, new_node_id=None): | 
 |     node_loc = node.loc | 
 |     node_timing = node.timing | 
 |  | 
 |     return graph2.Node( | 
 |         id=new_node_id if new_node_id is not None else node.id, | 
 |         type=enum_from_string(graph2.NodeType, node.type), | 
 |         direction=enum_from_string(graph2.NodeDirection, node.direction), | 
 |         capacity=node.capacity, | 
 |         loc=graph2.NodeLoc( | 
 |             x_low=node_loc.xlow, | 
 |             y_low=node_loc.ylow, | 
 |             x_high=node_loc.xhigh, | 
 |             y_high=node_loc.yhigh, | 
 |             ptc=node_loc.ptc, | 
 |             side=enum_from_string(tracks.Direction, node_loc.side), | 
 |         ), | 
 |         timing=graph2.NodeTiming(r=node_timing.r, c=node_timing.c), | 
 |         metadata=None, | 
 |         segment=graph2.NodeSegment(segment_id=node.segment.segmentId), | 
 |     ) | 
 |  | 
 |  | 
 | def read_edge(edge): | 
 |     return graph2.Edge( | 
 |         src_node=edge.srcNode, | 
 |         sink_node=edge.sinkNode, | 
 |         switch_id=edge.switchId, | 
 |         metadata=read_metadata(edge.metadata), | 
 |     ) | 
 |  | 
 |  | 
 | def graph_from_capnp( | 
 |         rr_graph_schema, | 
 |         input_file_name, | 
 |         progressbar=None, | 
 |         filter_nodes=True, | 
 |         load_edges=False, | 
 |         rebase_nodes=False, | 
 | ): | 
 |     """ | 
 |     Loads relevant information about the routing resource graph from an capnp | 
 |     file. | 
 |     """ | 
 |     if rebase_nodes: | 
 |         assert not load_edges | 
 |  | 
 |     if progressbar is None: | 
 |         progressbar = lambda x: x  # noqa: E731 | 
 |  | 
 |     with open(input_file_name, 'rb') as f: | 
 |         graph = rr_graph_schema.RrGraph.read( | 
 |             f, traversal_limit_in_words=2**63 - 1 | 
 |         ) | 
 |  | 
 |         root_attrib = { | 
 |             'tool_comment': str(graph.toolComment), | 
 |             'tool_name': str(graph.toolName), | 
 |             'tool_version': str(graph.toolVersion), | 
 |         } | 
 |  | 
 |         switches = [read_switch(sw) for sw in graph.switches.switches] | 
 |         segments = [read_segment(seg) for seg in graph.segments.segments] | 
 |         block_types = [ | 
 |             read_block_type(block_type) | 
 |             for block_type in graph.blockTypes.blockTypes | 
 |         ] | 
 |         grid = [read_grid_loc(g) for g in graph.grid.gridLocs] | 
 |  | 
 |         nodes = [] | 
 |         for n in progressbar(graph.rrNodes.nodes): | 
 |             if filter_nodes and n.type not in ['source', 'sink', 'opin', 'ipin' | 
 |                                                ]: | 
 |                 continue | 
 |  | 
 |             if rebase_nodes: | 
 |                 node = read_node(n, new_node_id=len(nodes)) | 
 |             else: | 
 |                 node = read_node(n) | 
 |  | 
 |             nodes.append(node) | 
 |  | 
 |         edges = [] | 
 |         if load_edges: | 
 |             edges = [read_edge(e) for e in graph.rrEdges.edges] | 
 |  | 
 |         # File back capnp objects cannot outlive their input file, | 
 |         # so verify that no dangling references exist. | 
 |         del graph | 
 |         gc.collect() | 
 |  | 
 |         # Cleanup leaked capnp objects due to _parent in Cython. | 
 |         cleanup_capnp_leak(f) | 
 |  | 
 |         return dict( | 
 |             root_attrib=root_attrib, | 
 |             switches=switches, | 
 |             segments=segments, | 
 |             block_types=block_types, | 
 |             grid=grid, | 
 |             nodes=nodes, | 
 |             edges=edges | 
 |         ) | 
 |  | 
 |  | 
 | class Graph(object): | 
 |     def __init__( | 
 |             self, | 
 |             rr_graph_schema_fname, | 
 |             input_file_name, | 
 |             output_file_name=None, | 
 |             progressbar=None, | 
 |             build_pin_edges=True, | 
 |             rebase_nodes=True, | 
 |             filter_nodes=True, | 
 |     ): | 
 |         if progressbar is None: | 
 |             progressbar = lambda x: x  # noqa: E731 | 
 |  | 
 |         self.input_file_name = input_file_name | 
 |         self.progressbar = progressbar | 
 |         self.output_file_name = output_file_name | 
 |  | 
 |         self.rr_graph_schema = capnp.load( | 
 |             rr_graph_schema_fname, | 
 |             imports=[os.path.dirname(os.path.dirname(capnp.__file__))] | 
 |         ) | 
 |  | 
 |         graph_input = graph_from_capnp( | 
 |             rr_graph_schema=self.rr_graph_schema, | 
 |             input_file_name=input_file_name, | 
 |             progressbar=progressbar, | 
 |             filter_nodes=filter_nodes, | 
 |             rebase_nodes=rebase_nodes, | 
 |         ) | 
 |         graph_input['build_pin_edges'] = build_pin_edges | 
 |  | 
 |         self.root_attrib = graph_input["root_attrib"] | 
 |         del graph_input["root_attrib"] | 
 |  | 
 |         self.graph = graph2.Graph(**graph_input) | 
 |  | 
 |     def _write_channels(self, rr_graph, channels): | 
 |         """ | 
 |         Writes the RR graph channels. | 
 |         """ | 
 |  | 
 |         rr_graph.channels.channel.chanWidthMax = channels.chan_width_max | 
 |         rr_graph.channels.channel.xMax = channels.x_max | 
 |         rr_graph.channels.channel.xMin = channels.x_min | 
 |         rr_graph.channels.channel.yMax = channels.y_max | 
 |         rr_graph.channels.channel.yMin = channels.y_min | 
 |  | 
 |         xLists = rr_graph.channels.init('xLists', len(channels.x_list)) | 
 |         for out_x_list, x_list in zip(xLists, channels.x_list): | 
 |             out_x_list.index = x_list.index | 
 |             out_x_list.info = x_list.info | 
 |  | 
 |         yLists = rr_graph.channels.init('yLists', len(channels.y_list)) | 
 |         for out_y_list, y_list in zip(yLists, channels.y_list): | 
 |             out_y_list.index = y_list.index | 
 |             out_y_list.info = y_list.info | 
 |  | 
 |     def _write_nodes(self, rr_graph, num_nodes, nodes, node_remap): | 
 |         """ Serialize list of Node objects to capnp. | 
 |  | 
 |         Note that this method is extremely hot, len(nodes) is order 1-10 million. | 
 |         Almost any modification of this function has a significant effect on | 
 |         performance, so any modification to this function should be tested for | 
 |         performance and correctness before commiting. | 
 |  | 
 |         """ | 
 |  | 
 |         rr_nodes = rr_graph.rrNodes.init('nodes', num_nodes) | 
 |  | 
 |         nodes_written = 0 | 
 |  | 
 |         node_iter = iter(nodes) | 
 |  | 
 |         for out_node, node in zip(rr_nodes, node_iter): | 
 |             nodes_written += 1 | 
 |  | 
 |             out_node.id = node_remap(node.id) | 
 |             out_node.type = to_capnp_enum( | 
 |                 self.rr_graph_schema.NodeType, node.type | 
 |             ) | 
 |             out_node.capacity = node.capacity | 
 |  | 
 |             if node.direction is not None: | 
 |                 out_node.direction = to_capnp_enum( | 
 |                     self.rr_graph_schema.NodeDirection, node.direction | 
 |                 ) | 
 |  | 
 |             node_loc = out_node.loc | 
 |             node_loc.ptc = node.loc.ptc | 
 |             if node.loc.side is not None: | 
 |                 node_loc.side = to_capnp_enum( | 
 |                     self.rr_graph_schema.LocSide, node.loc.side | 
 |                 ) | 
 |             node_loc.xhigh = node.loc.x_high | 
 |             node_loc.xlow = node.loc.x_low | 
 |             node_loc.yhigh = node.loc.y_high | 
 |             node_loc.ylow = node.loc.y_low | 
 |  | 
 |             if node.timing is not None: | 
 |                 timing = out_node.timing | 
 |                 timing.c = node.timing.c | 
 |                 timing.r = node.timing.r | 
 |  | 
 |             if node.segment is not None: | 
 |                 segment = out_node.segment | 
 |                 segment.segmentId = node.segment.segment_id | 
 |  | 
 |             if node.metadata is not None and len(node.metadata) > 0: | 
 |                 metas = out_node.metadata.init('metas', len(node.metadata)) | 
 |                 for out_meta, meta in zip(metas, node.metadata): | 
 |                     out_meta.name = meta.name | 
 |                     out_meta.value = meta.value | 
 |  | 
 |         assert nodes_written == num_nodes, 'Unwritten nodes!' | 
 |  | 
 |         try: | 
 |             _ = next(node_iter) | 
 |             assert False, 'Unwritten nodes!' | 
 |         except StopIteration: | 
 |             pass | 
 |  | 
 |     def _write_edges(self, rr_graph, num_edges, edges, node_remap): | 
 |         """ Serialize list of edge tuples objects to capnp. | 
 |  | 
 |         edge tuples are (src_node(int), sink_node(int), switch_id(int), metadata(NodeMetadata)). | 
 |  | 
 |         metadata may be None. | 
 |  | 
 |         Note that this method is extremely hot, len(edges) is order 5-50 million. | 
 |         Almost any modification of this function has a significant effect on | 
 |         performance, so any modification to this function should be tested for | 
 |         performance and correctness before commiting. | 
 |  | 
 |         """ | 
 |  | 
 |         out_edges = rr_graph.rrEdges.init('edges', num_edges) | 
 |  | 
 |         edges_written = 0 | 
 |         edges_iter = iter(edges) | 
 |         for out_edge, (src_node, sink_node, switch_id, | 
 |                        metadata) in zip(out_edges, edges_iter): | 
 |             edges_written += 1 | 
 |             out_edge.srcNode = node_remap(src_node) | 
 |             out_edge.sinkNode = node_remap(sink_node) | 
 |             out_edge.switchId = switch_id | 
 |  | 
 |             if metadata is not None and len(metadata) > 0: | 
 |                 metas = out_edge.metadata.init('metas', len(metadata)) | 
 |                 for out_meta, (name, value) in zip(metas, metadata): | 
 |                     out_meta.name = name | 
 |                     out_meta.value = value | 
 |  | 
 |         assert edges_written == num_edges, 'Unwritten edges!' | 
 |  | 
 |         try: | 
 |             _ = next(edges_iter) | 
 |             assert False, 'Unwritten edges!' | 
 |         except StopIteration: | 
 |             pass | 
 |  | 
 |     def _write_switches(self, rr_graph): | 
 |         """ | 
 |         Writes the RR graph switches. | 
 |         """ | 
 |         switches = rr_graph.switches.init('switches', len(self.graph.switches)) | 
 |         for out_switch, switch in zip(switches, self.graph.switches): | 
 |             out_switch.id = switch.id | 
 |             out_switch.name = switch.name | 
 |             out_switch.type = to_capnp_enum( | 
 |                 self.rr_graph_schema.SwitchType, switch.type | 
 |             ) | 
 |  | 
 |             if switch.timing: | 
 |                 timing = out_switch.timing | 
 |                 timing.cin = switch.timing.c_in | 
 |                 timing.cinternal = switch.timing.c_internal | 
 |                 timing.cout = switch.timing.c_out | 
 |                 timing.r = switch.timing.r | 
 |                 timing.tdel = switch.timing.t_del | 
 |  | 
 |             if switch.sizing: | 
 |                 sizing = out_switch.sizing | 
 |                 sizing.bufSize = switch.sizing.buf_size | 
 |                 sizing.muxTransSize = switch.sizing.mux_trans_size | 
 |  | 
 |     def _write_segments(self, rr_graph): | 
 |         """ | 
 |         Writes the RR graph segments. | 
 |         """ | 
 |  | 
 |         segments = rr_graph.segments.init('segments', len(self.graph.segments)) | 
 |  | 
 |         for out_segment, segment in zip(segments, self.graph.segments): | 
 |             out_segment.id = segment.id | 
 |             out_segment.name = segment.name | 
 |  | 
 |             if segment.timing: | 
 |                 timing = out_segment.timing | 
 |                 timing.cPerMeter = segment.timing.c_per_meter | 
 |                 timing.rPerMeter = segment.timing.r_per_meter | 
 |  | 
 |     def _write_block_types(self, rr_graph): | 
 |         """ | 
 |         Writes the RR graph block types. | 
 |         """ | 
 |  | 
 |         block_types = rr_graph.blockTypes.init( | 
 |             'blockTypes', len(self.graph.block_types) | 
 |         ) | 
 |  | 
 |         for out_blk, blk in zip(block_types, self.graph.block_types): | 
 |             out_blk.id = blk.id | 
 |             out_blk.name = blk.name | 
 |             out_blk.width = blk.width | 
 |             out_blk.height = blk.height | 
 |  | 
 |             pin_classes = out_blk.init('pinClasses', len(blk.pin_class)) | 
 |  | 
 |             for out_pin_class, pin_class in zip(pin_classes, blk.pin_class): | 
 |                 out_pin_class.type = to_capnp_enum( | 
 |                     self.rr_graph_schema.PinType, pin_class.type | 
 |                 ) | 
 |  | 
 |                 pins = out_pin_class.init('pins', len(pin_class.pin)) | 
 |  | 
 |                 for out_pin, pin in zip(pins, pin_class.pin): | 
 |                     out_pin.ptc = pin.ptc | 
 |                     out_pin.value = pin.name | 
 |  | 
 |     def _write_grid(self, rr_graph): | 
 |         """ | 
 |         Writes the RR graph grid. | 
 |         """ | 
 |  | 
 |         grid_locs = rr_graph.grid.init('gridLocs', len(self.graph.grid)) | 
 |         for out_grid_loc, grid_loc in zip(grid_locs, self.graph.grid): | 
 |             out_grid_loc.x = grid_loc.x | 
 |             out_grid_loc.y = grid_loc.y | 
 |             out_grid_loc.blockTypeId = grid_loc.block_type_id | 
 |             out_grid_loc.widthOffset = grid_loc.width_offset | 
 |             out_grid_loc.heightOffset = grid_loc.height_offset | 
 |  | 
 |     def serialize_to_capnp( | 
 |             self, | 
 |             channels_obj, | 
 |             num_nodes, | 
 |             nodes_obj, | 
 |             num_edges, | 
 |             edges_obj, | 
 |             node_remap=lambda x: x | 
 |     ): | 
 |         """ | 
 |         Writes the routing graph to the capnp file. | 
 |         """ | 
 |  | 
 |         self.graph.check_ptc() | 
 |  | 
 |         rr_graph = self.rr_graph_schema.RrGraph.new_message() | 
 |         rr_graph.toolComment = self.root_attrib['tool_comment'] | 
 |         rr_graph.toolName = self.root_attrib['tool_name'] | 
 |         rr_graph.toolVersion = self.root_attrib['tool_version'] | 
 |  | 
 |         self._write_channels(rr_graph, channels_obj) | 
 |         self._write_switches(rr_graph) | 
 |         self._write_segments(rr_graph) | 
 |         self._write_block_types(rr_graph) | 
 |         self._write_grid(rr_graph) | 
 |         self._write_nodes(rr_graph, num_nodes, nodes_obj, node_remap) | 
 |         self._write_edges(rr_graph, num_edges, edges_obj, node_remap) | 
 |  | 
 |         # Open the file | 
 |         with open(self.output_file_name, "wb") as f: | 
 |             rr_graph.write(f) | 
 |  | 
 |     def add_switch(self, switch): | 
 |         """ Add switch into graph model. | 
 |  | 
 |         Typically switches are imported from the architecture definition, | 
 |         however VPR will not save unused switches from the arch.  In this | 
 |         case, the switches must be added back during routing import. | 
 |  | 
 |         Important note: any switch present in the rr graph must also be present | 
 |         in the architecture definition. | 
 |  | 
 |         """ | 
 |  | 
 |         # Add to Graph2 data structure | 
 |         switch_id = self.graph.add_switch(switch) | 
 |  | 
 |         return switch_id |