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