| """ Graph object that handles serialization and deserialization from XML. """ | |
| from lib.rr_graph import graph2 | |
| from lib.rr_graph.graph2 import NodeDirection | |
| from lib.rr_graph import tracks | |
| import lxml.etree as ET | |
| # Set to True once | |
| # https://github.com/verilog-to-routing/vtr-verilog-to-routing/compare/c_internal | |
| # is merged and included in VTR conda. | |
| VPR_HAS_C_INTERNAL_SUPPORT = True | |
| # For debugging purposes: | |
| # 0 - debugging off, | |
| # 1 - indent output XML, | |
| # 2 - write only one element of each kind. | |
| DEBUG = 0 | |
| def enum_from_string(enum_type, s): | |
| return enum_type[s.upper()] | |
| def iterate_xml(xml_file, load_edges): | |
| """ | |
| A generator function that allows to incrementally walk over an XML tree | |
| while reading it from a file thus allowing to greatly reduce memory | |
| usage. | |
| """ | |
| doc = ET.iterparse(xml_file, events=('start', 'end')) | |
| _, root = next(doc) | |
| yield "", root | |
| path = root.tag | |
| in_edge = False | |
| for event, element in doc: | |
| if event == 'start': | |
| if in_edge: | |
| continue | |
| if not load_edges and element.tag == 'edge': | |
| in_edge = True | |
| continue | |
| path += "/" + element.tag | |
| if event == 'end': | |
| if in_edge: | |
| if element.tag == 'edge': | |
| in_edge = False | |
| element.clear() | |
| continue | |
| path = path.rsplit('/', maxsplit=1)[0] | |
| yield path, element | |
| element.clear() | |
| root.clear() | |
| def graph_from_xml( | |
| input_file_name, progressbar=None, filter_nodes=True, load_edges=False | |
| ): | |
| """ | |
| 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 = [] | |
| edges = [] | |
| # Itertate over XML elements | |
| switch_timing = None | |
| switch_sizing = None | |
| segment_timing = None | |
| pins = [] | |
| pin_classes = [] | |
| node_loc = None | |
| node_timing = None | |
| node_segment = None | |
| for path, element in progressbar(iterate_xml(input_file_name, | |
| load_edges=load_edges)): | |
| # 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.get('R', 0)), | |
| c_in=float(element.attrib.get('Cin', 0)), | |
| c_out=float(element.attrib.get('Cout', 0)), | |
| c_internal=float(element.attrib.get('Cinternal', 0)), | |
| t_del=float(element.attrib.get('Tdel', 0)), | |
| ) | |
| # 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.get('R_per_meter', 0.0)), | |
| c_per_meter=float(element.get('C_per_meter', 0.0)), | |
| ) | |
| # 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 - segment | |
| if path == "rr_graph/rr_nodes/node" and element.tag == "segment": | |
| node_segment = int(element.attrib['segment_id']) | |
| # 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=node_segment, | |
| ) | |
| ) | |
| node_loc = None | |
| node_timing = None | |
| node_segment = None | |
| # Edge | |
| if path == "rr_graph/rr_edges" and element.tag == "edge": | |
| if load_edges: | |
| edges.append( | |
| graph2.Edge( | |
| src_node=int(element.attrib['src_node']), | |
| sink_node=int(element.attrib['sink_node']), | |
| switch_id=int(element.attrib['switch_id']), | |
| metadata=None # FIXME: Add reading edge metadata | |
| ) | |
| ) | |
| 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, | |
| 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 | |
| graph_input = graph_from_xml( | |
| input_file_name, progressbar, filter_nodes=filter_nodes | |
| ) | |
| graph_input['build_pin_edges'] = build_pin_edges | |
| self.root_attrib = graph_input["root_attrib"] | |
| del graph_input["root_attrib"] | |
| if rebase_nodes: | |
| rebase_nodes = [] | |
| for node in graph_input['nodes']: | |
| node_d = node._asdict() | |
| node_d['id'] = len(rebase_nodes) | |
| rebase_nodes.append(graph2.Node(**node_d)) | |
| graph_input['nodes'] = rebase_nodes | |
| self.graph = graph2.Graph(**graph_input) | |
| self.xf = None | |
| self.xf_tag = [] | |
| if DEBUG > 0: | |
| self._write_xml = self._write_xml_debug | |
| else: | |
| self._write_xml = self._write_xml_no_debug | |
| def _write_xml_debug(self, text): | |
| """ | |
| Writes to the XML file | |
| """ | |
| self.xf.write(" " * len(self.xf_tag) + text + "\n") | |
| def _write_xml_no_debug(self, text): | |
| self.xf.write(text) | |
| def _begin_xml_tag(self, tag, attrib={}, value=None, term=False): | |
| """ | |
| Writes beginning of an XML tag. If term=True then terminates it | |
| immediately. | |
| """ | |
| s = "<{}".format(tag) | |
| s += "".join([' {}="{}"'.format(k, str(v)) for k, v in attrib.items()]) | |
| if value and term: | |
| s += ">{}</{}>".format(value, tag) | |
| else: | |
| s += "/>" if term is True else ">" | |
| self._write_xml(s) | |
| if not term: | |
| self.xf_tag.append(tag) | |
| def _end_xml_tag(self): | |
| """ | |
| Finishes the current XML tag. | |
| """ | |
| assert len(self.xf_tag) | |
| tag = self.xf_tag[-1] | |
| self.xf_tag = self.xf_tag[:-1] | |
| self._write_xml("</{}>".format(tag)) | |
| def _write_xml_tag(self, tag, attrib={}, value=None): | |
| """ | |
| A wrapper func. to write a tag and immediately close it | |
| """ | |
| self._begin_xml_tag(tag, attrib, value, True) | |
| def _write_xml_header(self): | |
| """ | |
| Writes the RR graph XML header. | |
| """ | |
| self._begin_xml_tag("rr_graph", self.root_attrib) | |
| def _write_channels(self, channels): | |
| """ | |
| Writes the RR graph channels. | |
| """ | |
| self._begin_xml_tag("channels") | |
| attrib = { | |
| "chan_width_max": channels.chan_width_max, | |
| "x_min": channels.x_min, | |
| "y_min": channels.y_min, | |
| "x_max": channels.x_max, | |
| "y_max": channels.y_max, | |
| } | |
| self._write_xml_tag("channel", attrib) | |
| for list in channels.x_list: | |
| self._write_xml_tag( | |
| "x_list", { | |
| "index": list.index, | |
| "info": list.info | |
| } | |
| ) | |
| if DEBUG >= 2: | |
| break | |
| for list in channels.y_list: | |
| self._write_xml_tag( | |
| "y_list", { | |
| "index": list.index, | |
| "info": list.info | |
| } | |
| ) | |
| if DEBUG >= 2: | |
| break | |
| self._end_xml_tag() | |
| def _write_nodes(self, nodes, node_remap): | |
| """ Serialize list of Node objects to XML. | |
| 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. | |
| """ | |
| self._begin_xml_tag("rr_nodes") | |
| for node in nodes: | |
| attrib = { | |
| "id": node_remap(node.id), | |
| "type": node.type.name, | |
| "capacity": node.capacity | |
| } | |
| if node.direction != NodeDirection.NO_DIR: | |
| attrib["direction"] = node.direction.name | |
| self._begin_xml_tag("node", attrib) | |
| attrib = { | |
| "xlow": node.loc.x_low, | |
| "xhigh": node.loc.x_high, | |
| "ylow": node.loc.y_low, | |
| "yhigh": node.loc.y_high, | |
| "ptc": node.loc.ptc, | |
| } | |
| if node.loc.side is not None: | |
| attrib["side"] = node.loc.side.name | |
| self._write_xml_tag("loc", attrib) | |
| if node.timing is not None: | |
| attrib = { | |
| "R": node.timing.r, | |
| "C": node.timing.c, | |
| } | |
| self._write_xml_tag("timing", attrib) | |
| if node.metadata is not None and len(node.metadata) > 0: | |
| self._begin_xml_tag("metadata") | |
| for m in node.metadata: | |
| self._write_xml_tag("meta", {"name": m.name}, m.value) | |
| self._end_xml_tag() | |
| if node.segment is not None: | |
| attrib = {"segment_id": node.segment.segment_id} | |
| self._write_xml_tag("segment", attrib) | |
| self._end_xml_tag() | |
| if DEBUG >= 2: | |
| break | |
| self._end_xml_tag() | |
| def _write_edges(self, edges, node_remap): | |
| """ Serialize list of edge tuples objects to XML. | |
| 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. | |
| """ | |
| self._begin_xml_tag("rr_edges") | |
| for src_node, sink_node, switch_id, metadata in edges: | |
| attrib = { | |
| "src_node": node_remap(src_node), | |
| "sink_node": node_remap(sink_node), | |
| "switch_id": switch_id, | |
| } | |
| if metadata is not None and len(metadata) > 0: | |
| self._begin_xml_tag("edge", attrib) | |
| self._begin_xml_tag("metadata") | |
| for name, value in metadata: | |
| self._write_xml_tag("meta", {"name": name}, value) | |
| self._end_xml_tag() | |
| self._end_xml_tag() | |
| else: | |
| self._write_xml_tag("edge", attrib) | |
| self._end_xml_tag() | |
| def _write_switches(self): | |
| """ | |
| Writes the RR graph switches. | |
| """ | |
| self._begin_xml_tag("switches") | |
| for switch in self.graph.switches: | |
| attrib = { | |
| "id": switch.id, | |
| "type": switch.type.name.lower(), | |
| "name": switch.name, | |
| } | |
| self._begin_xml_tag("switch", attrib) | |
| if switch.timing: | |
| attrib = { | |
| "R": switch.timing.r, | |
| "Cin": switch.timing.c_in, | |
| "Cout": switch.timing.c_out, | |
| "Tdel": switch.timing.t_del, | |
| } | |
| if VPR_HAS_C_INTERNAL_SUPPORT: | |
| attrib["Cinternal"] = switch.timing.c_internal | |
| self._write_xml_tag("timing", attrib) | |
| if switch.sizing: | |
| attrib = { | |
| "mux_trans_size": switch.sizing.mux_trans_size, | |
| "buf_size": switch.sizing.buf_size | |
| } | |
| self._write_xml_tag("sizing", attrib) | |
| self._end_xml_tag() | |
| if DEBUG >= 2: | |
| break | |
| self._end_xml_tag() | |
| def _write_segments(self): | |
| """ | |
| Writes the RR graph segments. | |
| """ | |
| self._begin_xml_tag("segments") | |
| for segment in self.graph.segments: | |
| attrib = { | |
| "id": segment.id, | |
| "name": segment.name, | |
| } | |
| self._begin_xml_tag("segment", attrib) | |
| if segment.timing: | |
| attrib = { | |
| "R_per_meter": segment.timing.r_per_meter, | |
| "C_per_meter": segment.timing.c_per_meter, | |
| } | |
| self._write_xml_tag("timing", attrib) | |
| self._end_xml_tag() | |
| if DEBUG >= 2: | |
| break | |
| self._end_xml_tag() | |
| def _write_block_types(self): | |
| """ | |
| Writes the RR graph block types. | |
| """ | |
| self._begin_xml_tag("block_types") | |
| for blk in self.graph.block_types: | |
| attrib = { | |
| "id": blk.id, | |
| "name": blk.name, | |
| "width": blk.width, | |
| "height": blk.height, | |
| } | |
| self._begin_xml_tag("block_type", attrib) | |
| for pin_class in blk.pin_class: | |
| self._begin_xml_tag( | |
| "pin_class", {"type": pin_class.type.name.upper()} | |
| ) | |
| for pin in pin_class.pin: | |
| self._write_xml_tag("pin", {"ptc": pin.ptc}, pin.name) | |
| if DEBUG >= 2: | |
| break | |
| self._end_xml_tag() | |
| if DEBUG >= 2: | |
| break | |
| self._end_xml_tag() | |
| if DEBUG >= 2: | |
| break | |
| self._end_xml_tag() | |
| def _write_grid(self): | |
| """ | |
| Writes the RR graph grid. | |
| """ | |
| self._begin_xml_tag("grid") | |
| for loc in self.graph.grid: | |
| attrib = { | |
| "x": loc.x, | |
| "y": loc.y, | |
| "block_type_id": loc.block_type_id, | |
| "width_offset": loc.width_offset, | |
| "height_offset": loc.height_offset, | |
| } | |
| self._write_xml_tag("grid_loc", attrib) | |
| if DEBUG >= 2: | |
| break | |
| self._end_xml_tag() | |
| def serialize_to_xml( | |
| self, channels_obj, nodes_obj, edges_obj, node_remap=lambda x: x | |
| ): | |
| """ | |
| Writes the routing graph to the XML file. | |
| """ | |
| self.graph.check_ptc() | |
| # Open the file | |
| with open(self.output_file_name, "w") as xf: | |
| self.xf = xf | |
| self.xf_tag = [] | |
| # Write header | |
| self._write_xml_header() | |
| self._write_channels(channels_obj) | |
| self._write_switches() | |
| self._write_segments() | |
| self._write_block_types() | |
| self._write_grid() | |
| self._write_nodes(nodes_obj, node_remap) | |
| self._write_edges(edges_obj, node_remap) | |
| # Write footer | |
| self._end_xml_tag() | |
| 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 |