| 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 |