|  | #!/usr/bin/env python3 | 
|  | """ | 
|  | rr_graph docs: http://docs.verilogtorouting.org/en/latest/vpr/file_formats/ | 
|  | etree docs: http://lxml.de/api/lxml.etree-module.html | 
|  |  | 
|  | general philosophy | 
|  | Data structures should generally be conceptual objects rather than direct XML manipulation | 
|  | Generally one class per XML node type | 
|  | However, RRGraph is special cased, operating on XML directly and holding | 
|  |  | 
|  | class Pin(MostlyReadOnly): | 
|  | xml: can import but doesn't keep track of a node | 
|  | class PinClass(MostlyReadOnly): | 
|  | xml: can import but doesn't keep track of a node | 
|  | class BlockType(MostlyReadOnly): | 
|  | xml: can import but doesn't keep track of a node | 
|  | class Block(MostlyReadOnly): | 
|  | for <grid_loc> | 
|  | class BlockGrid: | 
|  | Was: BlockGraph | 
|  | xml: nothing, handled by intneral Block objects though | 
|  | class RoutingGraph: | 
|  | holds pins + edges | 
|  | xml: updated as pins are added | 
|  | inconsistent with the rest of the project | 
|  | However, outside generally only add pins through objects | 
|  | so they don't see the XML directly | 
|  | except print does iterate directly over the XML | 
|  | class Graph: | 
|  | Top level class holding everything together has | 
|  |  | 
|  | enums | 
|  | class BlockTypeEdge(enum.Enum): | 
|  | lightweight enum type | 
|  | class PinClassDirection(enum.Enum): | 
|  | lightweight enum type | 
|  |  | 
|  | XXX: parse comments? Maybe can do a pass removing them | 
|  | """ | 
|  |  | 
|  | import enum | 
|  | import io | 
|  | import re | 
|  |  | 
|  | from collections import namedtuple, OrderedDict | 
|  | from types import MappingProxyType | 
|  |  | 
|  | import lxml.etree as ET | 
|  |  | 
|  | from . import Position | 
|  | from . import Size | 
|  | from . import Offset | 
|  | from . import node_pos, single_element | 
|  | from .channel import Channels, Track | 
|  |  | 
|  | from ..asserts import assert_eq | 
|  | from ..asserts import assert_is | 
|  | from ..asserts import assert_type | 
|  | from ..asserts import assert_type_or_none | 
|  |  | 
|  | from ..collections_extra import MostlyReadOnly | 
|  |  | 
|  | _DEFAULT_MARKER = [] | 
|  |  | 
|  |  | 
|  | def dict_next_id(d): | 
|  | current_ids = [-1] + list(d.keys()) | 
|  | return max(current_ids) + 1 | 
|  |  | 
|  |  | 
|  | def parse_net( | 
|  | s, | 
|  | _r=re.compile( | 
|  | "^(.*\\.)?([^.\\[]*[^0-9\\[.]+[^.\\[]*)?(\\[([0-9]+|[0-9]+:[0-9]+)]|[0-9]+|)$" | 
|  | ) | 
|  | ): | 
|  | """ | 
|  | Parses a Verilog/Verilog-To-Routing net/port definition. | 
|  |  | 
|  | The general form of this net/port definition is; | 
|  |  | 
|  | block_name.portname[startpin:endpin] | 
|  |  | 
|  | Almost all parts of the definition are optional. See the examples below. | 
|  |  | 
|  | Returns: | 
|  | - tuple (block_name, port_name, list of pin numbers) | 
|  |  | 
|  | Fully specified | 
|  | >>> parse_net('a.b[0]') | 
|  | ('a', 'b', [0]) | 
|  | >>> parse_net('c.d[1]') | 
|  | ('c', 'd', [1]) | 
|  | >>> parse_net('c.d[40]') | 
|  | ('c', 'd', [40]) | 
|  | >>> parse_net('VPR_PAD.outpad[0]') | 
|  | ('VPR_PAD', 'outpad', [0]) | 
|  |  | 
|  | Fully specified with more complex block names | 
|  | >>> parse_net('a.b.c[0]') | 
|  | ('a.b', 'c', [0]) | 
|  | >>> parse_net('c-d.e[11]') | 
|  | ('c-d', 'e', [11]) | 
|  |  | 
|  | Fully specified with block names that include square brackets | 
|  | >>> parse_net('a.b[2].c[0]') | 
|  | ('a.b[2]', 'c', [0]) | 
|  | >>> parse_net('c-d[3].e[11]') | 
|  | ('c-d[3]', 'e', [11]) | 
|  |  | 
|  | Fully specified range of pins | 
|  | >>> parse_net('a.b[11:8]') | 
|  | ('a', 'b', [8, 9, 10, 11]) | 
|  | >>> parse_net('c.d[8:11]') | 
|  | ('c', 'd', [8, 9, 10, 11]) | 
|  |  | 
|  | Net with no pin index. | 
|  | >>> parse_net('VPR_PAD.outpad') | 
|  | ('VPR_PAD', 'outpad', None) | 
|  |  | 
|  | Net with no block | 
|  | >>> parse_net('outpad[10]') | 
|  | (None, 'outpad', [10]) | 
|  | >>> parse_net('outpad[10:12]') | 
|  | (None, 'outpad', [10, 11, 12]) | 
|  | >>> parse_net('outpad[12:10]') | 
|  | (None, 'outpad', [10, 11, 12]) | 
|  |  | 
|  | No block or pin index | 
|  | >>> parse_net('outpad') | 
|  | (None, 'outpad', None) | 
|  | >>> parse_net('outpad0') | 
|  | (None, 'outpad0', None) | 
|  | >>> parse_net('0outpad') | 
|  | (None, '0outpad', None) | 
|  |  | 
|  | >>> parse_net('0') | 
|  | (None, None, [0]) | 
|  | """ | 
|  |  | 
|  | g = _r.match(s) | 
|  | if not g: | 
|  | raise TypeError("Pin {!r} not parsed.".format(s)) | 
|  | block_name, port_name, pin_full, pin_idx = g.groups() | 
|  |  | 
|  | if not block_name: | 
|  | block_name = None | 
|  | else: | 
|  | assert block_name[-1] == ".", block_name | 
|  | block_name = block_name[:-1] | 
|  |  | 
|  | if not port_name: | 
|  | port_name = None | 
|  | else: | 
|  | assert "." not in port_name, port_name | 
|  |  | 
|  | if not pin_full: | 
|  | start, end = None, None | 
|  | pins = None | 
|  | elif pin_idx: | 
|  | assert_eq(pin_full[0], '[') | 
|  | assert_eq(pin_full[-1], ']') | 
|  | assert_eq(len(pin_full), len(pin_idx) + 2) | 
|  |  | 
|  | if ":" in pin_idx: | 
|  | assert_eq(pin_idx.count(':'), 1) | 
|  | start, end = (int(b) for b in pin_idx.split(":", 1)) | 
|  | else: | 
|  | start = int(pin_idx) | 
|  | end = start | 
|  | else: | 
|  | start, end = int(pin_full), int(pin_full) | 
|  |  | 
|  | if start is not None and end is not None: | 
|  | if start > end: | 
|  | end, start = start, end | 
|  | end += 1 | 
|  |  | 
|  | pins = list(range(start, end)) | 
|  |  | 
|  | return block_name, port_name, pins | 
|  |  | 
|  |  | 
|  | class Pin(MostlyReadOnly): | 
|  | """ | 
|  | A Pin turns into on IPIN/OPIN node for each block. | 
|  |  | 
|  | Attributes | 
|  | ---------- | 
|  |  | 
|  | port_name : string | 
|  |  | 
|  | port_index : int | 
|  |  | 
|  | block_type_name : string | 
|  | block_type_subblk : int | 
|  | When a block has `capacity > 1` there are multiple "subblocks" | 
|  | contained. | 
|  |  | 
|  | block_type_index : int | 
|  | Starts at 0 within a block and increments for each pin. | 
|  | Equal to the ptc property in the rr_graph XML. | 
|  |  | 
|  | ptc : int | 
|  | Alias for block_type_index | 
|  |  | 
|  | direction : PinClassDirection | 
|  | If the pin is an clock, input or output. | 
|  |  | 
|  | side : RoutingNodeSide | 
|  | Side of the block the pin is found on. | 
|  | """ | 
|  |  | 
|  | __slots__ = [ | 
|  | "_pin_class", | 
|  | "_port_name", | 
|  | "_port_index", | 
|  | "_block_type_name", | 
|  | "_block_type_subblk", | 
|  | "_block_type_index", | 
|  | "_side", | 
|  | ] | 
|  |  | 
|  | # Index within a specific BlockType | 
|  | # BlockType has multiple pin classes | 
|  | # PinClass has multiple pins, usually 1 | 
|  | @property | 
|  | def ptc(self): | 
|  | return self.block_type_index | 
|  |  | 
|  | @property | 
|  | def direction(self): | 
|  | return self.pin_class.direction | 
|  |  | 
|  | @property | 
|  | def block_type_fullname(self): | 
|  | bt_name = None | 
|  | if self.block_type_name is not None: | 
|  | bt_name = self.block_type_name | 
|  | elif self.pin_class is not None: | 
|  | bt_name = self.pin_class.block_type_name | 
|  | if bt_name and self.block_type_subblk is not None: | 
|  | return "{}[{}]".format(bt_name, self.block_type_subblk) | 
|  | return bt_name | 
|  |  | 
|  | @property | 
|  | def name(self): | 
|  | """<portname>[<port_index>]""" | 
|  | assert_type( | 
|  | self._port_name, str, | 
|  | "Pin doesn't have port_name {}".format(repr(self)) | 
|  | ) | 
|  | assert_type( | 
|  | self._port_index, int, | 
|  | "Pin doesn't have port_index {}".format(repr(self)) | 
|  | ) | 
|  | return "{}[{}]".format(self.port_key, self._port_index) | 
|  |  | 
|  | @property | 
|  | def port_key(self): | 
|  | if self.block_type_subblk is not None: | 
|  | return "[{}]{}".format(self.block_type_subblk, self.port_name) | 
|  | else: | 
|  | return self.port_name | 
|  |  | 
|  | @property | 
|  | def xmlname(self): | 
|  | """Give name as originally in the XML. <block_type_name>.<name>""" | 
|  | return "{}.{}".format(self.block_type_name, self.name) | 
|  |  | 
|  | def __init__( | 
|  | self, | 
|  | pin_class=None, | 
|  | port_name=None, | 
|  | port_index=None, | 
|  | block_type_name=None, | 
|  | block_type_subblk=None, | 
|  | block_type_index=None, | 
|  | side=None | 
|  | ): | 
|  |  | 
|  | assert_type_or_none(pin_class, PinClass) | 
|  |  | 
|  | assert_type_or_none(port_name, str) | 
|  | assert_type_or_none(port_index, int) | 
|  |  | 
|  | assert_type_or_none(block_type_name, str) | 
|  | assert_type_or_none(block_type_subblk, int) | 
|  | assert_type_or_none(block_type_index, int) | 
|  |  | 
|  | assert_type_or_none(side, RoutingNodeSide) | 
|  |  | 
|  | self._pin_class = pin_class | 
|  | self._port_name = port_name | 
|  | self._port_index = port_index | 
|  | self._block_type_name = block_type_name | 
|  | self._block_type_subblk = block_type_subblk | 
|  | self._block_type_index = block_type_index | 
|  | self._side = side | 
|  |  | 
|  | if pin_class is not None: | 
|  | pin_class._add_pin(self) | 
|  |  | 
|  | def __str__(self): | 
|  | return "{}({})->{}[{}]".format( | 
|  | self.block_type_fullname, self.block_type_index, self.port_name, | 
|  | self.port_index | 
|  | ) | 
|  |  | 
|  | @classmethod | 
|  | def from_text(cls, pin_class, text, block_type_index=None): | 
|  | """Create a Pin object from a textual pin string. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | pin_class : PinClass | 
|  |  | 
|  | text : str | 
|  | Textual pin definition | 
|  |  | 
|  | block_type_index : int or None, optional | 
|  |  | 
|  | Examples | 
|  | ---------- | 
|  |  | 
|  | >>> pin = Pin.from_text(None, '0') | 
|  | >>> pin | 
|  | Pin(pin_class=None, port_name=None, port_index=None, block_type_name=None, block_type_subblk=None, block_type_index=0, side=None) | 
|  | >>> str(pin) | 
|  | 'None(0)->None[None]' | 
|  |  | 
|  | >>> pin = Pin.from_text(None, '10') | 
|  | >>> pin | 
|  | Pin(pin_class=None, port_name=None, port_index=None, block_type_name=None, block_type_subblk=None, block_type_index=10, side=None) | 
|  | >>> str(pin) | 
|  | 'None(10)->None[None]' | 
|  |  | 
|  | >>> pin = Pin.from_text(None, 'bt.outpad[2]') | 
|  | >>> pin | 
|  | Pin(pin_class=None, port_name='outpad', port_index=2, block_type_name='bt', block_type_subblk=None, block_type_index=None, side=None) | 
|  | >>> str(pin) | 
|  | 'bt(None)->outpad[2]' | 
|  |  | 
|  | >>> pin = Pin.from_text(None, 'bt[3].outpad[2]') | 
|  | >>> pin | 
|  | Pin(pin_class=None, port_name='outpad', port_index=2, block_type_name='bt', block_type_subblk=3, block_type_index=None, side=None) | 
|  | >>> str(pin) | 
|  | 'bt[3](None)->outpad[2]' | 
|  |  | 
|  | """  # noqa: E501 | 
|  | assert_type(text, str) | 
|  | block_type_name, port_name, pins = parse_net(text.strip()) | 
|  | assert pins is not None, text.strip() | 
|  |  | 
|  | if block_type_name and '[' in block_type_name: | 
|  | _, block_type_name, (block_type_subblk, ) = parse_net( | 
|  | block_type_name.strip() | 
|  | ) | 
|  | else: | 
|  | block_type_subblk = None | 
|  |  | 
|  | assert_eq(len(pins), 1) | 
|  | if block_type_index is None and port_name is None: | 
|  | block_type_index = pins[0] | 
|  | port_index = None | 
|  | else: | 
|  | port_index = pins[0] | 
|  |  | 
|  | return cls( | 
|  | pin_class=pin_class, | 
|  | port_name=port_name, | 
|  | port_index=port_index, | 
|  | block_type_name=block_type_name, | 
|  | block_type_subblk=block_type_subblk, | 
|  | block_type_index=block_type_index, | 
|  | ) | 
|  |  | 
|  | @classmethod | 
|  | def from_xml(cls, pin_class, pin_node): | 
|  | """Create a Pin object from an XML rr_graph node. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | pin_class : PinClass | 
|  |  | 
|  | pin_node : ET._Element | 
|  | An `<pin>` XML node from an rr_graph. | 
|  |  | 
|  | Examples | 
|  | ---------- | 
|  | >>> pc = PinClass(BlockType(name="bt"), direction=PinClassDirection.INPUT) | 
|  | >>> xml_string = '<pin ptc="1">bt.outpad[2]</pin>' | 
|  | >>> pin = Pin.from_xml(pc, ET.fromstring(xml_string)) | 
|  | >>> pin | 
|  | Pin(pin_class=PinClass(), port_name='outpad', port_index=2, block_type_name='bt', block_type_subblk=None, block_type_index=1, side=None) | 
|  | >>> str(pin) | 
|  | 'bt(1)->outpad[2]' | 
|  | >>> pin.ptc | 
|  | 1 | 
|  | """  # noqa: E501 | 
|  | assert pin_node.tag == "pin" | 
|  | block_type_index = int(pin_node.attrib["ptc"]) | 
|  |  | 
|  | return cls.from_text( | 
|  | pin_class, | 
|  | pin_node.text.strip(), | 
|  | block_type_index=block_type_index | 
|  | ) | 
|  |  | 
|  |  | 
|  | class PinClassDirection(enum.Enum): | 
|  | INPUT = "input" | 
|  | OUTPUT = "output" | 
|  | CLOCK = "clock" | 
|  | UNKNOWN = "unknown" | 
|  |  | 
|  | def __repr__(self): | 
|  | return repr(self.value) | 
|  |  | 
|  | def __str__(self): | 
|  | return str(enum.Enum.__str__(self)).replace("PinClassDirection", "PCD") | 
|  |  | 
|  |  | 
|  | class PinClass(MostlyReadOnly): | 
|  | """ | 
|  |  | 
|  | All pins inside a pin class are equivalent. | 
|  |  | 
|  | ie same net. Would a LUT with swappable inputs count? | 
|  | For <pin_class> nodes | 
|  |  | 
|  | A PinClass turns into one SOURCE (when direction==OUTPUT) or SINK (when | 
|  | direction in (INPUT, CLOCK)) per each block. | 
|  |  | 
|  | Attributes | 
|  | ---------- | 
|  | block_type : BlockType | 
|  |  | 
|  | direction : PinClassDirection | 
|  |  | 
|  | pins : tuple of Pin | 
|  | Pin inside this PinClass object. Useful for doing `for p in pc.pins`. | 
|  |  | 
|  | port_name : str | 
|  | Name of the port this PinClass represents. In the form of; | 
|  | port_name[pin_idx] | 
|  | port_name[pin_idx:pin_idx] | 
|  |  | 
|  | block_type_name : str | 
|  | """ | 
|  |  | 
|  | __slots__ = ["_block_type", "_direction", "_pins"] | 
|  |  | 
|  | @property | 
|  | def port_name(self): | 
|  | """ | 
|  | >>> bg = BlockGrid() | 
|  | >>> bt = BlockType(g=bg, id=0, name="B") | 
|  | >>> c1 = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT) | 
|  | >>> c2 = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT) | 
|  | >>> c3 = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT) | 
|  | >>> p0 = Pin(pin_class=c1, port_name="P1", port_index=0) | 
|  | >>> p1 = Pin(pin_class=c2, port_name="P1", port_index=1) | 
|  | >>> p2 = Pin(pin_class=c2, port_name="P1", port_index=2) | 
|  | >>> p3 = Pin(pin_class=c2, port_name="P1", port_index=3) | 
|  | >>> p4 = Pin(pin_class=c3, port_name="P2", port_index=0) | 
|  | >>> c1.port_name | 
|  | 'P1[0]' | 
|  | >>> c2.port_name | 
|  | 'P1[3:1]' | 
|  | >>> c3.port_name | 
|  | 'P2[0]' | 
|  | """ | 
|  | port_indexes = [p.port_index for p in self.pins] | 
|  | pin_start = min(port_indexes) | 
|  | pin_end = max(port_indexes) | 
|  | assert_eq(port_indexes, list(range(pin_start, pin_end + 1))) | 
|  | if pin_start == pin_end: | 
|  | return "{}[{}]".format(self.pins[0].port_name, pin_end) | 
|  | return "{}[{}:{}]".format(self.pins[0].port_name, pin_end, pin_start) | 
|  |  | 
|  | @property | 
|  | def block_type_name(self): | 
|  | if self.block_type is None: | 
|  | return None | 
|  | return self.block_type.name | 
|  |  | 
|  | def __init__(self, block_type=None, direction=None, pins=None): | 
|  | assert_type_or_none(block_type, BlockType) | 
|  | assert_type_or_none(direction, PinClassDirection) | 
|  |  | 
|  | self._block_type = block_type | 
|  | self._direction = direction | 
|  | # Although pins within a pin class have no defined order, | 
|  | # preserve input XML ordering for consistency | 
|  | self._pins = [] | 
|  |  | 
|  | if block_type is not None: | 
|  | block_type._add_pin_class(self) | 
|  |  | 
|  | if pins is not None: | 
|  | for p in pins: | 
|  | self._add_pin(p) | 
|  |  | 
|  | @classmethod | 
|  | def from_xml(cls, block_type, pin_class_node): | 
|  | """ | 
|  | block_type: block this belongs to | 
|  | pin_class_node: XML object to parse | 
|  |  | 
|  | >>> bt = BlockType(name="bt") | 
|  | >>> xml_string1 = ''' | 
|  | ... <pin_class type="INPUT"> | 
|  | ...   <pin ptc="2">bt.outpad[3]</pin> | 
|  | ...   <pin ptc="3">bt.outpad[4]</pin> | 
|  | ... </pin_class> | 
|  | ... ''' | 
|  | >>> pc = PinClass.from_xml(bt, ET.fromstring(xml_string1)) | 
|  | >>> pc # doctest: +ELLIPSIS | 
|  | PinClass(block_type=BlockType(), direction='input', pins=[...]) | 
|  | >>> len(pc.pins) | 
|  | 2 | 
|  | >>> pc.pins[0] | 
|  | Pin(pin_class=PinClass(), port_name='outpad', port_index=3, block_type_name='bt', block_type_subblk=None, block_type_index=2, side=None) | 
|  | >>> pc.pins[1] | 
|  | Pin(pin_class=PinClass(), port_name='outpad', port_index=4, block_type_name='bt', block_type_subblk=None, block_type_index=3, side=None) | 
|  | >>> for p in pc.pins: | 
|  | ...   print("{}[{}]".format(p.port_name, p.port_index)) | 
|  | outpad[3] | 
|  | outpad[4] | 
|  | >>> pc.port_name | 
|  | 'outpad[4:3]' | 
|  |  | 
|  | >>> bt = BlockType(name="a") | 
|  | >>> xml_string2 = ''' | 
|  | ... <pin_class type="INPUT"> | 
|  | ...   <pin ptc="0">a.b[1]</pin> | 
|  | ... </pin_class> | 
|  | ... ''' | 
|  | >>> pc = PinClass.from_xml(bt, ET.fromstring(xml_string2)) | 
|  | >>> pc # doctest: +ELLIPSIS | 
|  | PinClass(block_type=BlockType(), direction='input', pins=[...]) | 
|  | >>> len(pc.pins) | 
|  | 1 | 
|  | >>> pc.pins[0] | 
|  | Pin(pin_class=PinClass(), port_name='b', port_index=1, block_type_name='a', block_type_subblk=None, block_type_index=0, side=None) | 
|  |  | 
|  | >>> bt = BlockType(name="a") | 
|  | >>> xml_string3 = ''' | 
|  | ... <pin_class type="OUTPUT"> | 
|  | ...   <pin ptc="2">a.b[5]</pin> | 
|  | ...   <pin ptc="3">a.b[6]</pin> | 
|  | ...   <pin ptc="4">a.b[7]</pin> | 
|  | ... </pin_class> | 
|  | ... ''' | 
|  | >>> pc = PinClass.from_xml(bt, ET.fromstring(xml_string3)) | 
|  | >>> pc # doctest: +ELLIPSIS | 
|  | PinClass(block_type=BlockType(), direction='output', pins=[...]) | 
|  | >>> len(pc.pins) | 
|  | 3 | 
|  | >>> pc.pins[0] | 
|  | Pin(pin_class=PinClass(), port_name='b', port_index=5, block_type_name='a', block_type_subblk=None, block_type_index=2, side=None) | 
|  | >>> pc.pins[1] | 
|  | Pin(pin_class=PinClass(), port_name='b', port_index=6, block_type_name='a', block_type_subblk=None, block_type_index=3, side=None) | 
|  | >>> pc.pins[2] | 
|  | Pin(pin_class=PinClass(), port_name='b', port_index=7, block_type_name='a', block_type_subblk=None, block_type_index=4, side=None) | 
|  | """  # noqa: E501 | 
|  | assert_eq(pin_class_node.tag, "pin_class") | 
|  | assert "type" in pin_class_node.attrib | 
|  | class_direction = getattr( | 
|  | PinClassDirection, pin_class_node.attrib["type"] | 
|  | ) | 
|  | assert_type(class_direction, PinClassDirection) | 
|  |  | 
|  | pc_obj = cls(block_type, class_direction) | 
|  |  | 
|  | pin_nodes = list(pin_class_node.iterfind("./pin")) | 
|  | # Old format with pins described in text field | 
|  | if len(pin_nodes) == 0: | 
|  | for n in pin_class_node.text.split(): | 
|  | pc_obj._add_pin(Pin.from_text(pc_obj, n)) | 
|  | # New format using XML nodes | 
|  | else: | 
|  | for pin_node in pin_nodes: | 
|  | pc_obj._add_pin(Pin.from_xml(pc_obj, pin_node)) | 
|  |  | 
|  | return pc_obj | 
|  |  | 
|  | def __str__(self): | 
|  | return "{}.PinClass({}, [{}])".format( | 
|  | self.block_type_name, | 
|  | self.direction, | 
|  | ", ".join(str(i) for i in sorted(self.pins)), | 
|  | ) | 
|  |  | 
|  | def _add_pin(self, pin): | 
|  | assert_type(pin, Pin) | 
|  |  | 
|  | # PinClass.from_xml() and Pin.from_xml() both call add_pin | 
|  | if pin in self._pins: | 
|  | return | 
|  |  | 
|  | # Verify the pin has matching properties. | 
|  | # -------------------------------------------- | 
|  | if pin.pin_class is not None: | 
|  | assert_eq(pin.pin_class, self) | 
|  |  | 
|  | # if pin.port_name is not None: | 
|  | #     assert_eq(pin.port_name, self.port_name) | 
|  |  | 
|  | # if pin.port_index is not None: | 
|  | #     assert_not_in(pin.port_index, self.ports_index) | 
|  |  | 
|  | if pin.block_type_name is not None: | 
|  | assert_eq(pin._block_type_name, self.block_type_name) | 
|  |  | 
|  | # Fill out any unset properties | 
|  | # -------------------------------------------- | 
|  | if pin.pin_class is None: | 
|  | pin._pin_class = self | 
|  |  | 
|  | self._pins.append(pin) | 
|  |  | 
|  | # if pin.port_name is None: | 
|  | #     pin._port_name = self.port_name | 
|  |  | 
|  | # if pin.port_index is None: | 
|  | #     pin._port_index = dict_next_id(self.block_type._ports[self.port_name]) | 
|  |  | 
|  | if pin.block_type_name is None: | 
|  | pin._block_type_name = self.block_type_name | 
|  |  | 
|  | if self.block_type is not None: | 
|  | self.block_type._add_pin(pin) | 
|  |  | 
|  |  | 
|  | class BlockType(MostlyReadOnly): | 
|  | """ | 
|  | For <block_type> nodes | 
|  |  | 
|  | Attributes | 
|  | ---------- | 
|  | graph : Graph | 
|  |  | 
|  | id : int | 
|  |  | 
|  | name : str | 
|  | Name of the block type. | 
|  |  | 
|  | size : Size | 
|  | Size of the block type, default is `Size(1, 1)`. | 
|  |  | 
|  | pin_classes : tuple of PinClass | 
|  | Pin classes that this block contains. | 
|  |  | 
|  | pins_index : mapping[int] -> Pin | 
|  | ptc value to pin mapping. | 
|  |  | 
|  | ports_index : mapping[str] -> mapping[int] -> Pin | 
|  |  | 
|  | pins : tuple of Pin | 
|  | List of pins on this BlockType. | 
|  |  | 
|  | ports : tuple of str | 
|  | List of ports on this BlockType. | 
|  | """ | 
|  |  | 
|  | @property | 
|  | def pins(self): | 
|  | return tuple(self._pins_index.values()) | 
|  |  | 
|  | @property | 
|  | def ports(self): | 
|  | return tuple(self._ports_index.keys()) | 
|  |  | 
|  | @property | 
|  | def positions(self): | 
|  | for x in range(0, self.size.width): | 
|  | for y in range(0, self.size.height): | 
|  | yield Offset(x, y) | 
|  |  | 
|  | __slots__ = [ | 
|  | "_graph", | 
|  | "_id", | 
|  | "_name", | 
|  | "_size", | 
|  | "_pin_classes", | 
|  | "_pins_index", | 
|  | "_ports_index", | 
|  | ] | 
|  |  | 
|  | def __init__( | 
|  | self, g=None, id=-1, name="", size=Size(1, 1), pin_classes=None | 
|  | ): | 
|  | assert_type_or_none(g, BlockGrid) | 
|  | assert_type_or_none(id, int) | 
|  | assert_type_or_none(name, str) | 
|  | assert_type_or_none(size, Size) | 
|  |  | 
|  | self._graph = g | 
|  | self._id = id | 
|  | self._name = name | 
|  | self._size = size | 
|  |  | 
|  | self._pin_classes = [] | 
|  | self._pins_index = {} | 
|  | self._ports_index = {} | 
|  | if pin_classes is not None: | 
|  | for pc in pin_classes: | 
|  | self._add_pin_class(pc) | 
|  |  | 
|  | if g is not None: | 
|  | g.add_block_type(self) | 
|  |  | 
|  | def to_string(self, extra=False): | 
|  | if not extra: | 
|  | return "BlockType({name})".format(name=self.name) | 
|  | else: | 
|  | return "in 0x{graph_id:x} (pin_classes=[{pin_class_num} classes] pins_index=[{pins_index_num} pins])".format(  # noqa: E501 | 
|  | graph_id=id(self._graph), | 
|  | pin_class_num=len(self.pin_classes), | 
|  | pins_index_num=len(self.pins_index) | 
|  | ) | 
|  |  | 
|  | @classmethod | 
|  | def from_xml(cls, g, block_type_node): | 
|  | """ | 
|  |  | 
|  | >>> xml_string = ''' | 
|  | ... <block_type id="1" name="VPR_PAD" width="2" height="3"> | 
|  | ...   <pin_class type="OUTPUT"> | 
|  | ...     <pin ptc="0">VPR_PAD.outpad[0]</pin> | 
|  | ...   </pin_class> | 
|  | ...   <pin_class type="OUTPUT"> | 
|  | ...     <pin ptc="1">VPR_PAD.outpad[1]</pin> | 
|  | ...   </pin_class> | 
|  | ...   <pin_class type="INPUT"> | 
|  | ...     <pin ptc="2">VPR_PAD.inpad[0]</pin> | 
|  | ...   </pin_class> | 
|  | ... </block_type> | 
|  | ... ''' | 
|  | >>> bt = BlockType.from_xml(None, ET.fromstring(xml_string)) | 
|  | >>> bt # doctest: +ELLIPSIS | 
|  | BlockType(graph=None, id=1, name='VPR_PAD', size=Size(w=2, h=3), pin_classes=[...], pins_index={...}) | 
|  | >>> len(bt.pin_classes) | 
|  | 3 | 
|  | >>> bt.pin_classes[0].direction | 
|  | 'output' | 
|  | >>> bt.pin_classes[0] # doctest: +ELLIPSIS | 
|  | PinClass(block_type=BlockType(), direction='output', pins=[...]) | 
|  | >>> bt.pin_classes[0].pins[0] | 
|  | Pin(pin_class=PinClass(), port_name='outpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=0, side=None) | 
|  | >>> bt.pin_classes[1].direction | 
|  | 'output' | 
|  | >>> bt.pin_classes[1].pins[0] | 
|  | Pin(pin_class=PinClass(), port_name='outpad', port_index=1, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=1, side=None) | 
|  | >>> bt.pin_classes[2].direction | 
|  | 'input' | 
|  | >>> bt.pin_classes[2].pins[0] | 
|  | Pin(pin_class=PinClass(), port_name='inpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=2, side=None) | 
|  |  | 
|  | Multiple pins in a single pinclass. | 
|  | >>> xml_string = ''' | 
|  | ... <block_type id="1" name="VPR_PAD" width="2" height="3"> | 
|  | ...   <pin_class type="OUTPUT"> | 
|  | ...     <pin ptc="0">VPR_PAD.outpad[0]</pin> | 
|  | ...     <pin ptc="1">VPR_PAD.outpad[1]</pin> | 
|  | ...   </pin_class> | 
|  | ...   <pin_class type="INPUT"> | 
|  | ...     <pin ptc="2">VPR_PAD.inpad[0]</pin> | 
|  | ...   </pin_class> | 
|  | ... </block_type> | 
|  | ... ''' | 
|  | >>> bt = BlockType.from_xml(None, ET.fromstring(xml_string)) | 
|  | >>> bt # doctest: +ELLIPSIS | 
|  | BlockType(graph=None, id=1, name='VPR_PAD', size=Size(w=2, h=3), pin_classes=[...], pins_index={...}, ports_index={...}) | 
|  | >>> bt.pin_classes[0] # doctest: +ELLIPSIS | 
|  | PinClass(block_type=BlockType(), direction='output', pins=[...]) | 
|  | >>> len(bt.pins_index) | 
|  | 3 | 
|  | >>> len(bt.pin_classes) | 
|  | 2 | 
|  | >>> len(bt.pin_classes[0].pins) | 
|  | 2 | 
|  | >>> len(bt.pin_classes[1].pins) | 
|  | 1 | 
|  | >>> bt.pin_classes[0].pins[0] | 
|  | Pin(pin_class=PinClass(), port_name='outpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=0, side=None) | 
|  | >>> bt.pin_classes[0].pins[1] | 
|  | Pin(pin_class=PinClass(), port_name='outpad', port_index=1, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=1, side=None) | 
|  | >>> bt.pin_classes[1].pins[0] | 
|  | Pin(pin_class=PinClass(), port_name='inpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=2, side=None) | 
|  | >>> | 
|  |  | 
|  | Multiple subblocks within a block_type | 
|  | >>> xml_string = ''' | 
|  | ... <block_type id="1" name="VPR_PAD" width="2" height="3"> | 
|  | ...   <pin_class type="OUTPUT"> | 
|  | ...     <pin ptc="0">VPR_PAD[0].outpad[0]</pin> | 
|  | ...   </pin_class> | 
|  | ...   <pin_class type="INPUT"> | 
|  | ...     <pin ptc="1">VPR_PAD[0].inpad[0]</pin> | 
|  | ...   </pin_class> | 
|  | ...   <pin_class type="OUTPUT"> | 
|  | ...     <pin ptc="2">VPR_PAD[1].outpad[0]</pin> | 
|  | ...   </pin_class> | 
|  | ...   <pin_class type="INPUT"> | 
|  | ...     <pin ptc="3">VPR_PAD[1].inpad[0]</pin> | 
|  | ...   </pin_class> | 
|  | ... </block_type> | 
|  | ... ''' | 
|  | >>> bt = BlockType.from_xml(None, ET.fromstring(xml_string)) | 
|  | >>> bt # doctest: +ELLIPSIS | 
|  | BlockType(graph=None, id=1, name='VPR_PAD', size=Size(w=2, h=3), pin_classes=[...], pins_index={...}, ports_index={...}) | 
|  | >>> bt.pin_classes[0] # doctest: +ELLIPSIS | 
|  | PinClass(block_type=BlockType(), direction='output', pins=[...]) | 
|  | >>> len(bt.pins_index) | 
|  | 4 | 
|  | >>> len(bt.pin_classes) | 
|  | 4 | 
|  | >>> # All the pin classes should only have one pin | 
|  | >>> bt.pin_classes[0].pins | 
|  | (Pin(pin_class=PinClass(), port_name='outpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=0, block_type_index=0, side=None),) | 
|  | >>> bt.pin_classes[1].pins | 
|  | (Pin(pin_class=PinClass(), port_name='inpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=0, block_type_index=1, side=None),) | 
|  | >>> bt.pin_classes[2].pins | 
|  | (Pin(pin_class=PinClass(), port_name='outpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=1, block_type_index=2, side=None),) | 
|  | >>> bt.pin_classes[3].pins | 
|  | (Pin(pin_class=PinClass(), port_name='inpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=1, block_type_index=3, side=None),) | 
|  | """  # noqa: E501 | 
|  | assert block_type_node.tag == "block_type", block_type_node | 
|  | block_type_id = int(block_type_node.attrib['id']) | 
|  | block_type_name = block_type_node.attrib['name'].strip() | 
|  | block_type_width = int(block_type_node.attrib['width']) | 
|  | block_type_height = int(block_type_node.attrib['height']) | 
|  |  | 
|  | bt = cls( | 
|  | g, block_type_id, block_type_name, | 
|  | Size(block_type_width, block_type_height) | 
|  | ) | 
|  | for pin_class_node in block_type_node.iterfind("./pin_class"): | 
|  | bt._add_pin_class(PinClass.from_xml(bt, pin_class_node)) | 
|  | return bt | 
|  |  | 
|  | def _could_add_pin(self, pin): | 
|  | if pin.block_type_index is not None: | 
|  | if pin.block_type_index in self._pins_index: | 
|  | assert_is(pin, self._pins_index[pin.block_type_index]) | 
|  |  | 
|  | def _add_pin(self, pin): | 
|  | """ | 
|  |  | 
|  | >>> pc = PinClass(direction=PinClassDirection.INPUT) | 
|  | >>> len(pc.pins) | 
|  | 0 | 
|  | >>> pc._add_pin(Pin()) | 
|  | >>> len(pc.pins) | 
|  | 1 | 
|  | >>> bt = BlockType() | 
|  | >>> len(bt.pins_index) | 
|  | 0 | 
|  | >>> bt._add_pin_class(pc) | 
|  | >>> len(bt.pins_index) | 
|  | 1 | 
|  |  | 
|  | """ | 
|  | assert_type(pin, Pin) | 
|  | self._could_add_pin(pin) | 
|  |  | 
|  | # Verify the pin has matching properties. | 
|  | # -------------------------------------------- | 
|  | assert pin.pin_class is not None | 
|  |  | 
|  | if pin.port_key is not None and pin.port_index is not None: | 
|  | if pin.port_key in self._ports_index: | 
|  | if pin.port_index in self._ports_index[pin.port_key]: | 
|  | assert_eq( | 
|  | pin, self._ports_index[pin.port_key][pin.port_index] | 
|  | ) | 
|  |  | 
|  | if pin.block_type_name is not None: | 
|  | assert_eq(pin._block_type_name, self.name) | 
|  |  | 
|  | if pin.block_type_index is not None: | 
|  | if pin.block_type_index in self._pins_index: | 
|  | assert_eq(pin, self._pins_index[pin.block_type_index]) | 
|  |  | 
|  | # Fill out any unset properties | 
|  | # -------------------------------------------- | 
|  | if pin.block_type_name is None: | 
|  | pin._block_type_name = self.name | 
|  |  | 
|  | if pin.block_type_index is None: | 
|  | pin._block_type_index = dict_next_id(self._pins_index) | 
|  | self._pins_index[pin.block_type_index] = pin | 
|  |  | 
|  | if pin.port_key is not None: | 
|  | if pin.port_key not in self._ports_index: | 
|  | self._ports_index[pin.port_key] = {} | 
|  |  | 
|  | if pin.port_index is None: | 
|  | pin._port_index = dict_next_id(self._ports_index[pin.port_key]) | 
|  | self._ports_index[pin.port_key][pin.port_index] = pin | 
|  |  | 
|  | def _add_pin_class(self, pin_class): | 
|  | assert_type(pin_class, PinClass) | 
|  | for p in pin_class.pins: | 
|  | self._could_add_pin(p) | 
|  |  | 
|  | if pin_class.block_type is None: | 
|  | pin_class.block_type = self | 
|  | assert self is pin_class.block_type | 
|  |  | 
|  | for p in pin_class.pins: | 
|  | self._add_pin(p) | 
|  |  | 
|  | if pin_class not in self._pin_classes: | 
|  | self._pin_classes.append(pin_class) | 
|  |  | 
|  |  | 
|  | class Block(MostlyReadOnly): | 
|  | """For <grid_loc> nodes""" | 
|  |  | 
|  | __slots__ = ["_graph", "_block_type", "_position", "_offset"] | 
|  |  | 
|  | @property | 
|  | def x(self): | 
|  | return self.position.x | 
|  |  | 
|  | @property | 
|  | def y(self): | 
|  | return self.position.y | 
|  |  | 
|  | @property | 
|  | def pins(self): | 
|  | return self.block_type.pins | 
|  |  | 
|  | @property | 
|  | def positions(self): | 
|  | for offset in self.block_type.positions: | 
|  | yield self.position + offset | 
|  |  | 
|  | def __init__( | 
|  | self, | 
|  | g=None, | 
|  | block_type_id=None, | 
|  | block_type=None, | 
|  | position=None, | 
|  | offset=Offset(0, 0) | 
|  | ): | 
|  | assert_type_or_none(g, BlockGrid) | 
|  | assert_type_or_none(block_type_id, int) | 
|  | assert_type_or_none(block_type, BlockType) | 
|  | assert_type_or_none(position, Position) | 
|  | assert_type_or_none(offset, Offset) | 
|  |  | 
|  | if block_type_id is not None: | 
|  | if g is not None: | 
|  | assert block_type is None | 
|  | assert g.block_types is not None | 
|  | block_type = g.block_types[block_type_id] | 
|  | else: | 
|  | raise TypeError("Must provide g with numeric block_type") | 
|  |  | 
|  | self._graph = g | 
|  | self._block_type = block_type | 
|  | self._position = position | 
|  | self._offset = offset | 
|  |  | 
|  | @classmethod | 
|  | def from_xml(cls, g, grid_loc_node): | 
|  | """ | 
|  | >>> g = BlockGrid() | 
|  | >>> g.add_block_type(BlockType(id=0, name="bt")) | 
|  | >>> xml_string = ''' | 
|  | ... <grid_loc x="0" y="0" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | ... ''' | 
|  | >>> bl1 = Block.from_xml(g, ET.fromstring(xml_string)) | 
|  | >>> bl1 # doctest: +ELLIPSIS | 
|  | Block(graph=BG(0x...), block_type=BlockType(), position=P(x=0, y=0), offset=Offset(w=0, h=0)) | 
|  | >>> | 
|  | >>> xml_string = ''' | 
|  | ... <grid_loc x="2" y="5" block_type_id="0" width_offset="1" height_offset="2"/> | 
|  | ... ''' | 
|  | >>> bl2 = Block.from_xml(g, ET.fromstring(xml_string)) | 
|  | >>> bl2 # doctest: +ELLIPSIS | 
|  | Block(graph=BG(0x...), block_type=BlockType(), position=P(x=2, y=5), offset=Offset(w=1, h=2)) | 
|  | """  # noqa: E501 | 
|  | assert grid_loc_node.tag == "grid_loc" | 
|  |  | 
|  | block_type_id = int(grid_loc_node.attrib["block_type_id"]) | 
|  | pos = Position( | 
|  | int(grid_loc_node.attrib["x"]), int(grid_loc_node.attrib["y"]) | 
|  | ) | 
|  | offset = Offset( | 
|  | int(grid_loc_node.attrib["width_offset"]), | 
|  | int(grid_loc_node.attrib["height_offset"]) | 
|  | ) | 
|  | return Block( | 
|  | g=g, block_type_id=block_type_id, position=pos, offset=offset | 
|  | ) | 
|  |  | 
|  | def ptc2pin(self, ptc): | 
|  | """Return Pin for the given ptc (Pin.block_type_index)""" | 
|  | return self.block_type.pins_index[ptc] | 
|  |  | 
|  | def __str__(self): | 
|  | return '%s@%s' % (self.block_type.name, self.position) | 
|  |  | 
|  |  | 
|  | class BlockGrid: | 
|  | """ | 
|  | For <grid> | 
|  | Stores blocks (tiles) | 
|  | Stores grid + type | 
|  | Does not have routing | 
|  | """ | 
|  |  | 
|  | def __init__(self): | 
|  | # block Pos to BlockType | 
|  | self.block_grid = {} | 
|  | # block id to BlockType | 
|  | self.block_types = LookupMap(BlockType) | 
|  |  | 
|  | def __repr__(self): | 
|  | return "BG(0x{:x})".format(id(self)) | 
|  |  | 
|  | def _next_block_type_id(self): | 
|  | return len(self.block_types) | 
|  |  | 
|  | def add_block_type(self, block_type): | 
|  | assert_type_or_none(block_type, BlockType) | 
|  |  | 
|  | if block_type.id is None: | 
|  | block_type.id = self.block_types._next_id() | 
|  |  | 
|  | self.block_types.add(block_type) | 
|  |  | 
|  | def add_block(self, block): | 
|  | assert_type_or_none(block, Block) | 
|  | assert block.offset == (0, 0), block | 
|  | for pos in block.positions: | 
|  | assert ( | 
|  | pos not in self.block_grid or self.block_grid[pos] is None | 
|  | or self.block_grid[pos] is block | 
|  | ) | 
|  | self.block_grid[pos] = block | 
|  |  | 
|  | @property | 
|  | def size(self): | 
|  | x_max = max(p.x for p in self.block_grid) | 
|  | y_max = max(p.y for p in self.block_grid) | 
|  | return Size(x_max + 1, y_max + 1) | 
|  |  | 
|  | def blocks(self, positions): | 
|  | """Get the block objects for the given positions. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | positions: sequence of Position | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | list of Block | 
|  | """ | 
|  | return [self.block_grid[pos] for pos in positions] | 
|  |  | 
|  | def block_types_for(self, col=None, row=None): | 
|  | ss = [] | 
|  | for pos in sorted(self.block_grid): | 
|  | if col is not None: | 
|  | if pos.x != col: | 
|  | continue | 
|  | if row is not None: | 
|  | if pos.y != row: | 
|  | continue | 
|  |  | 
|  | ss.append(self.block_grid[pos].block_type) | 
|  | return ss | 
|  |  | 
|  | def blocks_for(self, col=None, row=None): | 
|  | ss = [] | 
|  | for pos in sorted(self.block_grid): | 
|  | if col is not None: | 
|  | if pos.x != col: | 
|  | continue | 
|  | if row is not None: | 
|  | if pos.y != row: | 
|  | continue | 
|  |  | 
|  | block = self.block_grid[pos] | 
|  | if block.position != pos: | 
|  | continue | 
|  | ss.append(block) | 
|  | return ss | 
|  |  | 
|  | def __getitem__(self, pos): | 
|  | return self.block_grid[pos] | 
|  |  | 
|  | def __iter__(self): | 
|  | for pos in sorted(self.block_grid): | 
|  | block = self.block_grid[pos] | 
|  | if block.position != pos: | 
|  | continue | 
|  | yield self.block_grid[pos] | 
|  |  | 
|  |  | 
|  | SegmentTiming = namedtuple("SegmentTiming", ("R_per_meter", "C_per_meter")) | 
|  |  | 
|  |  | 
|  | class Segment(MostlyReadOnly): | 
|  | """ | 
|  | A segment. | 
|  |  | 
|  | Attributes | 
|  | ---------- | 
|  | id : int | 
|  | name : str | 
|  |  | 
|  | timing : SegmentTiming | 
|  | """ | 
|  | __slots__ = [ | 
|  | "_id", | 
|  | "_name", | 
|  | "_timing", | 
|  | ] | 
|  |  | 
|  | def __init__(self, id, name, timing=None): | 
|  | assert_type(id, int) | 
|  | assert_type(name, str) | 
|  | assert_type_or_none(timing, SegmentTiming) | 
|  | self._id = id | 
|  | self._name = name | 
|  | self._timing = timing | 
|  |  | 
|  | @classmethod | 
|  | def from_xml(cls, segment_xml): | 
|  | """Create Segment object from an ET.Element XML node. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | switch_xml : ET._Element | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | Segment | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  | >>> xml_string = ''' | 
|  | ... <segment id="0" name="span"> | 
|  | ...     <timing R_per_meter="101" C_per_meter="2.25000005e-14"/> | 
|  | ... </segment> | 
|  | ... ''' | 
|  | >>> Segment.from_xml(ET.fromstring(xml_string)) | 
|  | Segment(id=0, name='span', timing=SegmentTiming(R_per_meter=101.0, C_per_meter=2.25000005e-14)) | 
|  | """  # noqa: E501 | 
|  | assert_type(segment_xml, ET._Element) | 
|  | seg_id = int(segment_xml.get('id')) | 
|  | name = segment_xml.get('name') | 
|  |  | 
|  | timing = None | 
|  | timings = list(segment_xml.iterfind('timing')) | 
|  | if len(timings) == 1: | 
|  | timing = timings[0] | 
|  | timing_r = float(timing.get('R_per_meter', 0)) | 
|  | timing_c = float(timing.get('C_per_meter', 0)) | 
|  | timing = SegmentTiming(R_per_meter=timing_r, C_per_meter=timing_c) | 
|  | else: | 
|  | assert len(timings) == 0 | 
|  | return cls(seg_id, name, timing) | 
|  |  | 
|  | def to_xml(self, segments_xml): | 
|  | timing_xml = ET.SubElement( | 
|  | segments_xml, 'segment', { | 
|  | 'id': str(self.id), | 
|  | 'name': self.name | 
|  | } | 
|  | ) | 
|  | if self.timing: | 
|  | ET.SubElement( | 
|  | timing_xml, "timing", | 
|  | {k: str(v) | 
|  | for k, v in self.timing.items()} | 
|  | ) | 
|  |  | 
|  |  | 
|  | SwitchTiming = namedtuple("SwitchTiming", ("R", "Cin", "Cout", "Tdel")) | 
|  | SwitchSizing = namedtuple("SwitchSizing", ("mux_trans_size", "buf_size")) | 
|  |  | 
|  |  | 
|  | class SwitchType(enum.Enum): | 
|  | MUX = "mux" | 
|  | TRISTATE = "tristate" | 
|  | PASS_GATE = "pass_gate" | 
|  | SHORT = "short" | 
|  | BUFFER = "buffer" | 
|  |  | 
|  |  | 
|  | class Switch(MostlyReadOnly): | 
|  | """A Switch. | 
|  |  | 
|  | Attributes | 
|  | ---------- | 
|  | id : int | 
|  | name : str | 
|  | type : SwitchType | 
|  |  | 
|  | timing : SwitchTiming | 
|  | sizing : SwitchSizing | 
|  | """ | 
|  | __slots__ = [ | 
|  | "_id", | 
|  | "_name", | 
|  | "_type", | 
|  | "_timing", | 
|  | "_sizing", | 
|  | ] | 
|  |  | 
|  | def __init__(self, id, type, name, timing=None, sizing=None): | 
|  | assert_type(id, int) | 
|  | assert_type(type, SwitchType) | 
|  | assert_type(name, str) | 
|  | assert_type_or_none(timing, SwitchTiming) | 
|  | assert_type_or_none(sizing, SwitchSizing) | 
|  | self._id = id | 
|  | self._name = name | 
|  | self._type = type | 
|  | self._timing = timing | 
|  | self._sizing = sizing | 
|  |  | 
|  | def to_xml(self, parent_node): | 
|  | sw_node = ET.Element( | 
|  | "switch", | 
|  | attrib=OrderedDict( | 
|  | ( | 
|  | ("id", str(self._id)), | 
|  | ("name", self._name), | 
|  | ("type", self._type.value), | 
|  | ) | 
|  | ) | 
|  | ) | 
|  | ET.SubElement( | 
|  | sw_node, | 
|  | "timing", | 
|  | attrib=OrderedDict( | 
|  | ( | 
|  | ("R", str(self._timing.R)), | 
|  | ("Cin", str(self._timing.Cin)), | 
|  | ("Cout", str(self._timing.Cout)), | 
|  | ("Cinternal", "0"), | 
|  | ("Tdel", str(self._timing.Tdel)), | 
|  | ) | 
|  | ) | 
|  | ) | 
|  | ET.SubElement( | 
|  | sw_node, | 
|  | "sizing", | 
|  | attrib=OrderedDict( | 
|  | ( | 
|  | ("mux_trans_size", str(self._sizing.mux_trans_size)), | 
|  | ("buf_size", str(self._sizing.buf_size)), | 
|  | ) | 
|  | ), | 
|  | ) | 
|  | if parent_node is not None: | 
|  | parent_node.append(sw_node) | 
|  | return sw_node | 
|  |  | 
|  | @classmethod | 
|  | def from_xml(cls, switch_xml): | 
|  | """Create Switch object from an ET._Element XML node. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | switch_xml : ET._Element | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | Switch | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  |  | 
|  | >>> xml_string = ''' | 
|  | ... <switch id="0" type="mux" name="buffer"> | 
|  | ...  <timing R="551" Cin="7.70000012e-16" Cout="4.00000001e-15" Tdel="5.80000006e-11"/> | 
|  | ...  <sizing mux_trans_size="2.63073993" buf_size="27.6459007"/> | 
|  | ... </switch> | 
|  | ... ''' | 
|  | >>> sw = Switch.from_xml(ET.fromstring(xml_string)) | 
|  | >>> sw | 
|  | Switch(id=0, name='buffer', type=<SwitchType.MUX: 'mux'>, timing=SwitchTiming(R=551.0, Cin=7.70000012e-16, Cout=4.00000001e-15, Tdel=5.80000006e-11), sizing=SwitchSizing(mux_trans_size=2.63073993, buf_size=27.6459007)) | 
|  | >>> print(ET.tostring(sw.to_xml(None), pretty_print=True).decode('utf-8').strip()) | 
|  | <switch id="0" name="buffer" type="mux"> | 
|  | <timing R="551.0" Cin="7.70000012e-16" Cout="4.00000001e-15" Cinternal="0" Tdel="5.80000006e-11"/> | 
|  | <sizing mux_trans_size="2.63073993" buf_size="27.6459007"/> | 
|  | </switch> | 
|  | """  # noqa: E501 | 
|  | assert_type(switch_xml, ET._Element) | 
|  | sw_id = int(switch_xml.attrib.get('id')) | 
|  | sw_type = SwitchType(switch_xml.attrib.get('type')) | 
|  | name = switch_xml.attrib.get('name') | 
|  |  | 
|  | timing = None | 
|  | timings = list(switch_xml.iterfind('timing')) | 
|  | if len(timings) == 1: | 
|  | timing = timings[0] | 
|  | timing_r = float(timing.get('R', 0)) | 
|  | timing_cin = float(timing.get('Cin', 0)) | 
|  | timing_cout = float(timing.get('Cout', 0)) | 
|  | timing_tdel = float(timing.get('Tdel', 0)) | 
|  | timing = SwitchTiming( | 
|  | timing_r, timing_cin, timing_cout, timing_tdel | 
|  | ) | 
|  | else: | 
|  | assert len(timings) == 0 | 
|  |  | 
|  | sizing = None | 
|  | sizings = list(switch_xml.iterfind('sizing')) | 
|  | if len(sizings) == 1: | 
|  | sizing = sizings[0] | 
|  | sizing_mux_trans_size = float(sizing.get('mux_trans_size', 0)) | 
|  | sizing_buf_size = float(sizing.get('buf_size', 0)) | 
|  | sizing = SwitchSizing(sizing_mux_trans_size, sizing_buf_size) | 
|  | else: | 
|  | assert len(sizings) == 0 | 
|  |  | 
|  | return cls( | 
|  | id=sw_id, type=sw_type, name=name, timing=timing, sizing=sizing | 
|  | ) | 
|  |  | 
|  |  | 
|  | class LookupMap: | 
|  | """Store a way to lookup by ID or name.""" | 
|  |  | 
|  | def __init__(self, obj_type): | 
|  | self.obj_type = obj_type | 
|  | self._names = {} | 
|  | self._ids = {} | 
|  |  | 
|  | def clear(self): | 
|  | self._names.clear() | 
|  | self._ids.clear() | 
|  |  | 
|  | def add(self, obj): | 
|  | assert_type(obj, self.obj_type) | 
|  | assert_type(obj.id, int) | 
|  | assert_type(obj.name, str) | 
|  |  | 
|  | assert obj.id not in self._ids, obj.id | 
|  | assert obj.name not in self._names, obj.name | 
|  |  | 
|  | self._names[obj.name] = obj | 
|  | self._ids[obj.id] = obj | 
|  |  | 
|  | def __getitem__(self, key): | 
|  | if isinstance(key, int): | 
|  | return self._ids[key] | 
|  | elif isinstance(key, str): | 
|  | return self._names[key] | 
|  |  | 
|  | def __iter__(self): | 
|  | return self._names.itervalues() | 
|  |  | 
|  | @property | 
|  | def names(self): | 
|  | return self._names.iterkeys() | 
|  |  | 
|  | @property | 
|  | def ids(self): | 
|  | return self._ids.iterkeys() | 
|  |  | 
|  | def next_id(self): | 
|  | return max(self._ids.keys()) + 1 | 
|  |  | 
|  |  | 
|  | class RoutingGraphPrinter: | 
|  | @classmethod | 
|  | def node(cls, xml_node, block_grid=None): | 
|  | """Get a globally unique name for an `node` in the rr_nodes. | 
|  |  | 
|  | Without a block graph, the name won't include the block type. | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node id="0" type="SINK" capacity="1"> | 
|  | ...   <loc xlow="0" ylow="3" xhigh="0" yhigh="3" ptc="0"/> | 
|  | ...   <timing R="0" C="0"/> | 
|  | ... </node> | 
|  | ... ''')) | 
|  | '0 X000Y003[00].SINK-<' | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node id="1" type="SOURCE" capacity="1"> | 
|  | ...   <loc xlow="1" ylow="2" xhigh="1" yhigh="2" ptc="1"/> | 
|  | ...   <timing R="0" C="0"/> | 
|  | ... </node> | 
|  | ... ''')) | 
|  | '1 X001Y002[01].SRC-->' | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node id="2" type="IPIN" capacity="1"> | 
|  | ...   <loc xlow="2" ylow="1" xhigh="2" yhigh="1" side="TOP" ptc="0"/> | 
|  | ...   <timing R="0" C="0"/> | 
|  | ... </node> | 
|  | ... ''')) | 
|  | '2 X002Y001[00].T-PIN<' | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node id="6" type="OPIN" capacity="1"> | 
|  | ...   <loc xlow="3" ylow="0" xhigh="3" yhigh="0" side="RIGHT" ptc="1"/> | 
|  | ...   <timing R="0" C="0"/> | 
|  | ... </node> | 
|  | ... ''')) | 
|  | '6 X003Y000[01].R-PIN>' | 
|  |  | 
|  | With a block graph, the name will include the block type. | 
|  | >>> bg = simple_test_block_grid() | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node id="0" type="SINK" capacity="1"> | 
|  | ...   <loc xlow="0" ylow="3" xhigh="0" yhigh="3" ptc="0"/> | 
|  | ...   <timing R="0" C="0"/> | 
|  | ... </node> | 
|  | ... '''), bg) | 
|  | '0 X000Y003_INBLOCK[00].C[3:0]-SINK-<' | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node id="1" type="SOURCE" capacity="1"> | 
|  | ...   <loc xlow="1" ylow="2" xhigh="1" yhigh="2" ptc="1"/> | 
|  | ...   <timing R="0" C="0"/> | 
|  | ... </node> | 
|  | ... '''), bg) | 
|  | '1 X001Y002_DUALBLK[01].B[0]-SRC-->' | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node id="2" type="IPIN" capacity="1"> | 
|  | ...   <loc xlow="2" ylow="1" xhigh="2" yhigh="1" side="TOP" ptc="0"/> | 
|  | ...   <timing R="0" C="0"/> | 
|  | ... </node> | 
|  | ... '''), bg) | 
|  | '2 X002Y001_DUALBLK[00].A[0]-T-PIN<' | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node id="6" type="OPIN" capacity="1"> | 
|  | ...   <loc xlow="3" ylow="0" xhigh="3" yhigh="0" side="RIGHT" ptc="1"/> | 
|  | ...   <timing R="0" C="0"/> | 
|  | ... </node> | 
|  | ... '''), bg) | 
|  | '6 X003Y000_OUTBLOK[01].D[1]-R-PIN>' | 
|  |  | 
|  | Edges don't require a block graph, as they have the full information on | 
|  | the node. | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node capacity="1" direction="INC_DIR" id="372" type="CHANX"> | 
|  | ...   <loc ptc="4" xhigh="3" xlow="3" yhigh="0" ylow="0"/> | 
|  | ...   <timing C="2.72700004e-14" R="101"/> | 
|  | ...   <segment segment_id="1"/> | 
|  | ... </node> | 
|  | ... ''')) | 
|  | '372 X003Y000--04->X003Y000' | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node capacity="1" direction="DEC_DIR" id="373" type="CHANY"> | 
|  | ...   <loc ptc="5" xhigh="3" xlow="3" yhigh="0" ylow="0"/> | 
|  | ...   <timing C="2.72700004e-14" R="101"/> | 
|  | ...   <segment segment_id="1"/> | 
|  | ... </node> | 
|  | ... ''')) | 
|  | '373 X003Y000<|05||X003Y000' | 
|  | >>> RoutingGraphPrinter.node(ET.fromstring(''' | 
|  | ... <node capacity="1" direction="BI_DIR" id="374" type="CHANX"> | 
|  | ...   <loc ptc="5" xhigh="3" xlow="3" yhigh="0" ylow="0"/> | 
|  | ...   <timing C="2.72700004e-14" R="101"/> | 
|  | ...   <segment segment_id="1"/> | 
|  | ... </node> | 
|  | ... ''')) | 
|  | '374 X003Y000<-05->X003Y000' | 
|  |  | 
|  | 'X002Y003_BT[00].T-PIN<' | 
|  | 'X002Y003_BT[00].L-PIN<' | 
|  | 'X002Y003_BT[00].R-PIN<' | 
|  | 'X002Y003_BT[00].B-PIN<' | 
|  | 'X002Y003_BT[00].SINK<' | 
|  | 'X002Y003_BT[00].SRC>' | 
|  | 'X002Y003_BT[00].T-PIN>' | 
|  | 'X002Y003_BT[00].L-PIN>' | 
|  | 'X002Y003_BT[00].R-PIN>' | 
|  | 'X002Y003_BT[00].B-PIN>' | 
|  |  | 
|  | 'X003Y000--05->X003Y000' | 
|  | 'X003Y000<-05--X003Y000' | 
|  |  | 
|  | 'X003Y000||05|>X003Y000' | 
|  | 'X003Y000<|05||X003Y000' | 
|  | """ | 
|  | assert_type(xml_node, ET._Element) | 
|  |  | 
|  | loc_node = list(xml_node.iterfind("./loc"))[0] | 
|  | low = Position( | 
|  | int(loc_node.attrib["xlow"]), int(loc_node.attrib["ylow"]) | 
|  | ) | 
|  | high = Position( | 
|  | int(loc_node.attrib["xhigh"]), int(loc_node.attrib["yhigh"]) | 
|  | ) | 
|  | ptc = int(loc_node.attrib["ptc"]) | 
|  | edge = loc_node.attrib.get("side", " ")[0] | 
|  |  | 
|  | if block_grid is not None: | 
|  | block = block_grid[low] | 
|  | else: | 
|  | block = None | 
|  |  | 
|  | node_id = RoutingGraph._get_xml_id(xml_node) | 
|  | type_str = None | 
|  | node_type = RoutingNodeType.from_xml(xml_node) | 
|  | if False: | 
|  | pass | 
|  | elif node_type in (RoutingNodeType.CHANX, RoutingNodeType.CHANY): | 
|  | direction = xml_node.attrib.get("direction") | 
|  | direction_fmt = { | 
|  | 'INC_DIR': '{f}{f}{ptc:02d}{f}>', | 
|  | 'DEC_DIR': '<{f}{ptc:02d}{f}{f}', | 
|  | 'BI_DIR': '<{f}{ptc:02d}{f}>', | 
|  | }.get(direction, None) | 
|  | assert direction_fmt, "Bad direction %s" % direction | 
|  | return "{} X{:03d}Y{:03d}{}X{:03d}Y{:03d}".format( | 
|  | node_id, low.x, low.y, | 
|  | direction_fmt.format( | 
|  | f={ | 
|  | RoutingNodeType.CHANX: '-', | 
|  | RoutingNodeType.CHANY: '|' | 
|  | }[node_type], | 
|  | ptc=ptc | 
|  | ), high.x, high.y | 
|  | ) | 
|  | elif node_type is RoutingNodeType.SINK: | 
|  | type_str = "SINK-<" | 
|  | # FIXME: Check high == block.position + block.block_type.size | 
|  | elif node_type is RoutingNodeType.SOURCE: | 
|  | type_str = "SRC-->" | 
|  | # FIXME: Check high == block.position + block.block_type.size | 
|  | elif node_type is RoutingNodeType.IPIN: | 
|  | assert edge in "TLRB", edge | 
|  | type_str = "{}-PIN<".format(edge) | 
|  | elif node_type is RoutingNodeType.OPIN: | 
|  | assert edge in "TLRB", edge | 
|  | type_str = "{}-PIN>".format(edge) | 
|  | else: | 
|  | assert False, "Unknown node_type {}".format(node_type) | 
|  | assert type_str | 
|  |  | 
|  | if block_grid is not None: | 
|  | block = block_grid[low] | 
|  | block_name = "_" + block.block_type.name | 
|  | x = block.position.x | 
|  | y = block.position.y | 
|  |  | 
|  | if node_type in (RoutingNodeType.IPIN, RoutingNodeType.OPIN): | 
|  | try: | 
|  | ptc_str = "[{:02d}].{}-".format( | 
|  | ptc, block.block_type.pins_index[ptc].name | 
|  | ) | 
|  | except TypeError: | 
|  | ptc_str = "[{:02d} :-(].".format(ptc) | 
|  | elif node_type in (RoutingNodeType.SINK, RoutingNodeType.SOURCE): | 
|  | try: | 
|  | pc = block.block_type.pin_classes[ptc] | 
|  | ptc_str = "[{:02d}].{}-".format(ptc, pc.port_name) | 
|  | except TypeError: | 
|  | ptc_str = "[{:02d} :-(].".format(ptc) | 
|  | else: | 
|  | ptc_str = "[{:02d}].".format(ptc) | 
|  |  | 
|  | else: | 
|  | block_name = "" | 
|  | x = low.x | 
|  | y = low.y | 
|  | ptc_str = "[{:02d}].".format(ptc) | 
|  |  | 
|  | return "{id} X{x:03d}Y{y:03d}{t}{i}{s}".format( | 
|  | id=node_id, t=block_name, x=x, y=y, i=ptc_str, s=type_str | 
|  | ) | 
|  |  | 
|  | @classmethod | 
|  | def edge(cls, routing, xml_node, block_grid=None, flip=False): | 
|  | """Get a globally unique name for an `edge` in the rr_edges. | 
|  |  | 
|  | An edge goes between two `node` objects. | 
|  |  | 
|  | >>> bg = simple_test_block_grid() | 
|  | >>> xml_string1 = ''' | 
|  | ... <rr_graph> | 
|  | ...  <rr_nodes> | 
|  | ...   <node id="0" type="SOURCE" capacity="1"> | 
|  | ...     <loc xlow="0" ylow="3" xhigh="0" yhigh="3" ptc="0"/> | 
|  | ...     <timing R="0" C="0"/> | 
|  | ...   </node> | 
|  | ...   <node capacity="1" direction="INC_DIR" id="1" type="CHANY"> | 
|  | ...     <loc ptc="5" xhigh="3" xlow="0" yhigh="0" ylow="3"/> | 
|  | ...     <timing C="2.72700004e-14" R="101"/> | 
|  | ...     <segment segment_id="1"/> | 
|  | ...   </node> | 
|  | ...  </rr_nodes> | 
|  | ... <rr_edges /> | 
|  | ... <switches /> | 
|  | ... </rr_graph> | 
|  | ... ''' | 
|  | >>> rg = RoutingGraph(xml_graph=ET.fromstring(xml_string1)) | 
|  | >>> RoutingGraphPrinter.edge(rg, ET.fromstring(''' | 
|  | ... <edge sink_node="1" src_node="0" switch_id="1"/> | 
|  | ... ''')) | 
|  | '0 X000Y003[00].SRC--> ->>- 1 X000Y003||05|>X003Y000' | 
|  | >>> RoutingGraphPrinter.edge(rg, ET.fromstring(''' | 
|  | ... <edge sink_node="1" src_node="0" switch_id="1"/> | 
|  | ... '''), bg) | 
|  | '0 X000Y003_INBLOCK[00].C[3:0]-SRC--> ->>- 1 X000Y003||05|>X003Y000' | 
|  | """ | 
|  | src_node, snk_node = routing.nodes_for_edge(xml_node) | 
|  | if flip: | 
|  | s = "{} -<<- {}" | 
|  | else: | 
|  | s = "{} ->>- {}" | 
|  | return s.format( | 
|  | cls.node(src_node, block_grid=block_grid), | 
|  | cls.node(snk_node, block_grid=block_grid) | 
|  | ) | 
|  |  | 
|  |  | 
|  | class MappingLocalNames(dict): | 
|  | """ | 
|  | Class for keeping track of the local name for a given node. | 
|  | """ | 
|  |  | 
|  | def __init__(self, *args, type=ET._Element, **kw): | 
|  | self.type = type | 
|  | self.localnames = {} | 
|  | dict.__init__(self, *args, **kw) | 
|  |  | 
|  | def add(self, pos, name, value): | 
|  | assert_type(value, self.type) | 
|  | assert_type(pos, Position) | 
|  | assert_type(name, str) | 
|  |  | 
|  | self[(pos, name)] = value | 
|  |  | 
|  | def clear(self): | 
|  | self.localnames.clear() | 
|  | dict.clear(self) | 
|  |  | 
|  | def __setitem__(self, key, value): | 
|  | """ | 
|  | map[(Position, name)] = type | 
|  | """ | 
|  | assert_type(value, self.type) | 
|  |  | 
|  | pos, name = key | 
|  | assert_type(pos, Position) | 
|  | assert_type(name, str) | 
|  |  | 
|  | if pos not in self.localnames: | 
|  | self.localnames[pos] = set() | 
|  | if key in self: | 
|  | assert_eq(self[key], value, msg="%s already exist!" % (key, )) | 
|  | self.localnames[pos].add(name) | 
|  |  | 
|  | dict.__setitem__(self, key, value) | 
|  |  | 
|  | def __getitem__(self, key): | 
|  | """ | 
|  | map[pos] -> list of node_id | 
|  | map[(position, name)] = node_id | 
|  | """ | 
|  | if isinstance(key, Position): | 
|  | return self.localnames[key] | 
|  | else: | 
|  | assert_type(key, tuple) | 
|  | assert_eq(len(key), 2) | 
|  | assert_type(key[0], Position) | 
|  | assert_type(key[1], str) | 
|  | return dict.__getitem__(self, key) | 
|  |  | 
|  |  | 
|  | class MappingGlobalNames(dict): | 
|  | """ | 
|  | Class for keeping track of the global names for a given node. | 
|  | """ | 
|  |  | 
|  | def add(self, name, xml_node): | 
|  | self[name] = xml_node | 
|  |  | 
|  | def __setitem__(self, name, xml_node): | 
|  | """ | 
|  | map[name] = ET._Element | 
|  | """ | 
|  | assert_type(name, str) | 
|  | assert_type(xml_node, ET._Element) | 
|  | assert name not in self, "{} in {}".format(name, self) | 
|  | dict.__setitem__(self, name, xml_node) | 
|  |  | 
|  |  | 
|  | _RoutingNodeTiming = namedtuple("RoutingNodeTiming", ("R", "C")) | 
|  |  | 
|  |  | 
|  | class RoutingNodeTiming(_RoutingNodeTiming): | 
|  | def to_xml(self): | 
|  | return ET.Element('timing', {'R': str(self.R), 'C': str(self.C)}) | 
|  |  | 
|  |  | 
|  | class RoutingNodeType(enum.Enum): | 
|  | IPIN = 'IPIN' | 
|  | OPIN = 'OPIN' | 
|  | SINK = 'SINK' | 
|  | SOURCE = 'SOURCE' | 
|  | CHANX = 'CHANX' | 
|  | CHANY = 'CHANY' | 
|  |  | 
|  | @classmethod | 
|  | def from_xml(cls, xml_node): | 
|  | assert xml_node.tag == "node", xml_node | 
|  | return RoutingNodeType(xml_node.attrib["type"]) | 
|  |  | 
|  | @property | 
|  | def track(self): | 
|  | """Is this RoutingNodeType a track?""" | 
|  | return self in (RoutingNodeType.CHANX, RoutingNodeType.CHANY) | 
|  |  | 
|  | @property | 
|  | def output(self): | 
|  | """Is this RoutingNodeType an output?""" | 
|  | return self in (RoutingNodeType.OPIN, RoutingNodeType.SOURCE) | 
|  |  | 
|  | @property | 
|  | def input(self): | 
|  | """Is this RoutingNodeType an input?""" | 
|  | return self in (RoutingNodeType.IPIN, RoutingNodeType.SINK) | 
|  |  | 
|  | @property | 
|  | def pin(self): | 
|  | """Is this RoutingNodeType an pin?""" | 
|  | return self in (RoutingNodeType.OPIN, RoutingNodeType.IPIN) | 
|  |  | 
|  | @property | 
|  | def pin_class(self): | 
|  | """Is this RoutingNodeType an pin_class?""" | 
|  | return self in (RoutingNodeType.SINK, RoutingNodeType.SOURCE) | 
|  |  | 
|  | @property | 
|  | def can_sink(self): | 
|  | """Can be a destination of an edge.""" | 
|  | # -> XXX | 
|  | return self in ( | 
|  | RoutingNodeType.IPIN, RoutingNodeType.CHANX, RoutingNodeType.CHANY | 
|  | ) | 
|  |  | 
|  | @property | 
|  | def can_source(self): | 
|  | """Can be a source of an edge.""" | 
|  | # XXX -> | 
|  | return self in ( | 
|  | RoutingNodeType.OPIN, RoutingNodeType.CHANX, RoutingNodeType.CHANY | 
|  | ) | 
|  |  | 
|  |  | 
|  | def _metadata(parent_node): | 
|  | elements = list(parent_node.iterfind("metadata")) | 
|  | if len(elements) == 1: | 
|  | return elements[0] | 
|  | return None | 
|  |  | 
|  |  | 
|  | def _set_metadata(parent_node, key, value, offset=None): | 
|  | metadata = _metadata(parent_node) | 
|  | if metadata is None: | 
|  | metadata = ET.SubElement(parent_node, "metadata") | 
|  |  | 
|  | attribs = [("name", key)] | 
|  |  | 
|  | metanode = None | 
|  | for node in metadata.iterfind("./meta"): | 
|  | matches = True | 
|  | for n, v in attribs: | 
|  | if node.attrib.get(n, None) != v: | 
|  | matches = False | 
|  | break | 
|  | if matches: | 
|  | metanode = node | 
|  | break | 
|  | else: | 
|  | metanode = ET.SubElement(metadata, "meta", attrib=dict(attribs)) | 
|  |  | 
|  | for n, v in attribs: | 
|  | metanode.attrib[n] = v | 
|  | metanode.text = str(value) | 
|  |  | 
|  |  | 
|  | _get_metadata_sentry = [] | 
|  |  | 
|  |  | 
|  | def _get_metadata(parent_node, key, default=_get_metadata_sentry): | 
|  | metanode = None | 
|  |  | 
|  | metadata = _metadata(parent_node) | 
|  | if metadata is not None: | 
|  | for node in metadata.iterfind("./meta"): | 
|  | if node.attrib["name"] == key: | 
|  | metanode = node | 
|  | break | 
|  |  | 
|  | if metanode is None: | 
|  | if default is not _get_metadata_sentry: | 
|  | return default | 
|  | else: | 
|  | raise ValueError( | 
|  | "No metadata {} on\n{}".format( | 
|  | key, | 
|  | ET.tostring(parent_node, | 
|  | pretty_print=True).decode().strip() | 
|  | ) | 
|  | ) | 
|  |  | 
|  | return metanode.text | 
|  |  | 
|  |  | 
|  | class RoutingNodeSide(enum.Enum): | 
|  | LEFT = 'LEFT' | 
|  | RIGHT = 'RIGHT' | 
|  | TOP = 'TOP' | 
|  | BOTTOM = 'BOTTOM' | 
|  |  | 
|  |  | 
|  | class RoutingNodeDir(enum.Enum): | 
|  | INC_DIR = 'INC_DIR' | 
|  | DEC_DIR = 'DEC_DIR' | 
|  | BI_DIR = 'BI_DIR' | 
|  |  | 
|  |  | 
|  | class RoutingNode(ET.ElementBase): | 
|  | TAG = "node" | 
|  |  | 
|  | def set_metadata(self, key, value, offset=None): | 
|  | _set_metadata(self, key, value, offset=offset) | 
|  |  | 
|  | def get_metadata(self, key, default=_get_metadata_sentry): | 
|  | return _get_metadata(self, key, default) | 
|  |  | 
|  |  | 
|  | class RoutingEdge(ET.ElementBase): | 
|  | TAG = "edge" | 
|  |  | 
|  | def set_metadata(self, key, value, offset=None): | 
|  | _set_metadata(self, key, value, offset=offset) | 
|  |  | 
|  | def get_metadata(self, key, default=_get_metadata_sentry): | 
|  | return _get_metadata(self, key, default) | 
|  |  | 
|  |  | 
|  | class RoutingGraph: | 
|  | """ | 
|  | The RoutingGraph object keeps track of the actual "graph" found in | 
|  | rr_graph.xml files. | 
|  |  | 
|  | The Graph is represented by two XML node types, they are; `<rr_nodes>` and | 
|  | `<rr_edges>` objects which are connected by the ID objects. | 
|  |  | 
|  |  | 
|  | """ | 
|  |  | 
|  | @staticmethod | 
|  | def _get_xml_id(xml_node): | 
|  | node_id = xml_node.get('id', None) | 
|  | if node_id is not None: | 
|  | node_id = int(node_id) | 
|  | return node_id | 
|  |  | 
|  | @staticmethod | 
|  | def set_metadata(node, key, value, offset=None): | 
|  | """ | 
|  | Examples | 
|  | -------- | 
|  | >>> # Works with edges | 
|  | >>> r = simple_test_routing() | 
|  | >>> sw = Switch(id=0, type=SwitchType.MUX, name="sw") | 
|  | >>> r.create_edge_with_ids(0, 1, sw) | 
|  | >>> e1 = r.get_edge_by_id(4) | 
|  | >>> # Call directly on the edge | 
|  | >>> e1.get_metadata("test", default=":-(") | 
|  | ':-(' | 
|  | >>> e1.set_metadata("test", "123") | 
|  | >>> print(ET.tostring(e1, pretty_print=True).decode().strip()) | 
|  | <edge src_node="0" sink_node="1" switch_id="0"> | 
|  | <metadata> | 
|  | <meta name="test">123</meta> | 
|  | </metadata> | 
|  | </edge> | 
|  | >>> e1.get_metadata("test", default=":-(") | 
|  | '123' | 
|  | >>> # Or via the routing object | 
|  | >>> r.set_metadata(e1, "test", "234") | 
|  | >>> r.get_metadata(e1, "test") | 
|  | '234' | 
|  | >>> # Exception if no default provided | 
|  | >>> r.get_metadata(e1, "not_found") | 
|  | Traceback (most recent call last): | 
|  | ... | 
|  | ValueError: No metadata not_found on | 
|  | <edge src_node="0" sink_node="1" switch_id="0"> | 
|  | <metadata> | 
|  | <meta name="test">234</meta> | 
|  | </metadata> | 
|  | </edge> | 
|  | >>> r.set_metadata(e1, "test", 1) | 
|  | >>> # Works with nodes | 
|  | >>> n1 = r.get_node_by_id(0) | 
|  | >>> # Call directly on the node | 
|  | >>> n1.get_metadata("test", default=":-(") | 
|  | ':-(' | 
|  | >>> n1.set_metadata("test", "123") | 
|  | >>> print(ET.tostring(n1, pretty_print=True).decode().strip()) | 
|  | <node id="0" type="SOURCE" capacity="1"> | 
|  | <loc xlow="0" ylow="0" xhigh="0" yhigh="0" ptc="0"/> | 
|  | <timing R="0" C="0"/> | 
|  | <metadata> | 
|  | <meta name="test">123</meta> | 
|  | </metadata> | 
|  | </node> | 
|  | >>> n1.get_metadata("test", default=":-(") | 
|  | '123' | 
|  | >>> # Or via the routing object | 
|  | >>> r.set_metadata(n1, "test", "234") | 
|  | >>> r.get_metadata(n1, "test") | 
|  | '234' | 
|  |  | 
|  | """ | 
|  | _set_metadata(node, key, value, offset) | 
|  |  | 
|  | @staticmethod | 
|  | def get_metadata(node, key, default=_get_metadata_sentry): | 
|  | return _get_metadata(node, key, default) | 
|  |  | 
|  | def __init__(self, xml_graph=None, verbose=True, clear_fabric=False): | 
|  | """ | 
|  | >>> g = simple_test_graph() | 
|  | """ | 
|  | self.verbose = verbose | 
|  |  | 
|  | # Lookup XML node for given an ID | 
|  | self.id2element = {RoutingNode: {}, RoutingEdge: {}} | 
|  | # Names for each node at a given position | 
|  | self.localnames = MappingLocalNames(type=ET._Element) | 
|  | # Global names for each node | 
|  | self.globalnames = MappingGlobalNames() | 
|  |  | 
|  | self._cache_nodes2edges = {} | 
|  |  | 
|  | if xml_graph is None: | 
|  | xml_graph = ET.Element("rr_graph") | 
|  | ET.SubElement(xml_graph, "rr_nodes") | 
|  | ET.SubElement(xml_graph, "rr_edges") | 
|  |  | 
|  | self._xml_graph = xml_graph | 
|  |  | 
|  | if clear_fabric: | 
|  | self.clear() | 
|  | else: | 
|  | for node in self._xml_parent(RoutingNode): | 
|  | self._add_xml_element(node, existing=True) | 
|  | for edge in self._xml_parent(RoutingEdge): | 
|  | self._add_xml_element(edge, existing=True, add_id_attrib=False) | 
|  | self._build_cache_node2edge() | 
|  |  | 
|  | def clear(self): | 
|  | """Delete the existing rr_nodes and rr_edges.""" | 
|  | self._xml_parent(RoutingNode).clear() | 
|  | self._xml_parent(RoutingEdge).clear() | 
|  |  | 
|  | self.id2element[RoutingNode].clear() | 
|  | self.id2element[RoutingEdge].clear() | 
|  |  | 
|  | self.localnames.clear() | 
|  | self.globalnames.clear() | 
|  |  | 
|  | self._cache_nodes2edges.clear() | 
|  |  | 
|  | def _xml_type(self, xml_node): | 
|  | """Get the type of an ET._Element object. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | xml_node: RoutingNode or RoutingEdge | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | RoutingNode.__class__ or RoutingEdge.__class___ | 
|  | """ | 
|  | if xml_node.tag == "node": | 
|  | return RoutingNode | 
|  | elif xml_node.tag == "edge": | 
|  | return RoutingEdge | 
|  | else: | 
|  | assert False, xml_node.tag | 
|  |  | 
|  | def _xml_parent(self, xml_type): | 
|  | """Get the ET._Element parent for a give type.""" | 
|  | if xml_type is RoutingNode: | 
|  | return single_element(self._xml_graph, 'rr_nodes') | 
|  | elif xml_type is RoutingEdge: | 
|  | return single_element(self._xml_graph, 'rr_edges') | 
|  |  | 
|  | def _ids_map(self, xml_type): | 
|  | """Get the ID mapping for a give type.""" | 
|  | return self.id2element[xml_type] | 
|  |  | 
|  | def _next_id(self, xml_type): | 
|  | """Get the next ID available for a give type.""" | 
|  | return len(self._ids_map(xml_type)) | 
|  |  | 
|  | def _add_xml_element(self, xml_node, existing=False, add_id_attrib=True): | 
|  | """Add an existing ET._Element object to the map. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | xml_node: RoutingNode or RoutingEdge | 
|  | """ | 
|  | xml_type = self._xml_type(xml_node) | 
|  |  | 
|  | # If the XML node doesn't have an ID, allocate it one. | 
|  | node_id = self._get_xml_id(xml_node) | 
|  | if node_id is None: | 
|  | new_node_id = self._next_id(xml_type) | 
|  | if add_id_attrib: | 
|  | xml_node.attrib['id'] = str(new_node_id) | 
|  | else: | 
|  | new_node_id = node_id | 
|  |  | 
|  | # Make sure we don't duplicate IDs | 
|  | ids2element = self._ids_map(xml_type) | 
|  | if new_node_id in ids2element: | 
|  | assert xml_node is ids2element[ | 
|  | new_node_id], "Error at {}: {} ({}) is not {} ({})".format( | 
|  | new_node_id, xml_node, ET.tostring(xml_node), | 
|  | ids2element[new_node_id], | 
|  | ET.tostring(ids2element[new_node_id]) | 
|  | ) | 
|  | else: | 
|  | ids2element[new_node_id] = xml_node | 
|  |  | 
|  | # FIXME: Make sure the node is included on the XML parent. | 
|  | # if node_id is None: | 
|  | #    pass | 
|  | if not existing: | 
|  | parent = self._xml_parent(xml_type) | 
|  | parent.append(xml_node) | 
|  | self._add_cache_node2edge(xml_node, new_node_id) | 
|  |  | 
|  | def _add_cache_node2edge(self, xml_node, node_id): | 
|  | xml_type = self._xml_type(xml_node) | 
|  | if xml_type == RoutingNode: | 
|  | assert node_id not in self._cache_nodes2edges | 
|  | self._cache_nodes2edges[node_id] = set() | 
|  | elif xml_type == RoutingEdge: | 
|  | src_id, snk_id = self.node_ids_for_edge(xml_node) | 
|  | self._cache_nodes2edges[src_id].add(node_id) | 
|  | self._cache_nodes2edges[snk_id].add(node_id) | 
|  | else: | 
|  | assert False, "Unknown xml_node {}".format(xml_node) | 
|  |  | 
|  | def _build_cache_node2edge(self): | 
|  | assert len(self._cache_nodes2edges) == 0 | 
|  | for node_id, node in self._ids_map(RoutingNode).items(): | 
|  | self._add_cache_node2edge(node, node_id) | 
|  |  | 
|  | for edge_id, edge in self._ids_map(RoutingEdge).items(): | 
|  | self._add_cache_node2edge(edge, edge_id) | 
|  |  | 
|  | def get_by_name(self, name, pos=None, default=_DEFAULT_MARKER): | 
|  | """Get the RoutingNode using name (and pos). | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | name: str | 
|  | pos: Position | 
|  | default: Optional value to return if not found. | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | RoutingNode (ET._Element) | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  | FIXME: Add example. | 
|  |  | 
|  | """ | 
|  | assert_type(name, str) | 
|  | assert_type_or_none(pos, Position) | 
|  |  | 
|  | r = _DEFAULT_MARKER | 
|  | if pos is not None: | 
|  | r = self.localnames.get((pos, name), _DEFAULT_MARKER) | 
|  | if r is _DEFAULT_MARKER: | 
|  | r = self.globalnames.get(name, _DEFAULT_MARKER) | 
|  | if r is not _DEFAULT_MARKER: | 
|  | return r | 
|  | if default is not _DEFAULT_MARKER: | 
|  | return default | 
|  | else: | 
|  | raise KeyError( | 
|  | "No node named {} globally or locally at {}".format(name, pos) | 
|  | ) | 
|  |  | 
|  | def get_node_by_id(self, node_id): | 
|  | """Get the RoutingNode from a given ID. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | node_id: int | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | RoutingNode (ET._Element) | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  | >>> r = simple_test_routing() | 
|  | >>> RoutingGraphPrinter.node(r.get_node_by_id(0)) | 
|  | '0 X000Y000[00].SRC-->' | 
|  | >>> RoutingGraphPrinter.node(r.get_node_by_id(1)) | 
|  | '1 X000Y000[00].R-PIN>' | 
|  | >>> RoutingGraphPrinter.node(r.get_node_by_id(2)) | 
|  | '2 X000Y000<-00->X000Y010' | 
|  | >>> RoutingGraphPrinter.node(r.get_node_by_id(3)) | 
|  | '3 X000Y010[00].L-PIN<' | 
|  | >>> RoutingGraphPrinter.node(r.get_node_by_id(4)) | 
|  | '4 X000Y010[00].SINK-<' | 
|  | >>> RoutingGraphPrinter.node(r.get_node_by_id(5)) | 
|  | Traceback (most recent call last): | 
|  | ... | 
|  | KeyError: 5 | 
|  |  | 
|  | """ | 
|  | return self._ids_map(RoutingNode)[node_id] | 
|  |  | 
|  | def get_edge_by_id(self, node_id): | 
|  | """Get the RoutingEdge from a given ID. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | node_id: int | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | RoutingEdge (ET._Element) | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  | >>> r = simple_test_routing() | 
|  | >>> RoutingGraphPrinter.edge(r, r.get_edge_by_id(0)) | 
|  | '0 X000Y000[00].SRC--> ->>- 1 X000Y000[00].R-PIN>' | 
|  | >>> RoutingGraphPrinter.edge(r, r.get_edge_by_id(1)) | 
|  | '1 X000Y000[00].R-PIN> ->>- 2 X000Y000<-00->X000Y010' | 
|  | >>> RoutingGraphPrinter.edge(r, r.get_edge_by_id(2)) | 
|  | '2 X000Y000<-00->X000Y010 ->>- 3 X000Y010[00].L-PIN<' | 
|  | >>> RoutingGraphPrinter.edge(r, r.get_edge_by_id(3)) | 
|  | '3 X000Y010[00].L-PIN< ->>- 4 X000Y010[00].SINK-<' | 
|  | >>> r.get_edge_by_id(4) | 
|  | Traceback (most recent call last): | 
|  | ... | 
|  | KeyError: 4 | 
|  | """ | 
|  | return self._ids_map(RoutingEdge)[node_id] | 
|  |  | 
|  | @staticmethod | 
|  | def node_ids_for_edge(xml_node): | 
|  | """Return the node ids associated with given edge. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | xml_node: RoutingEdge | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | (int, int) | 
|  | Source RoutingNode ID, Sink RoutingNode ID | 
|  |  | 
|  | Example | 
|  | ------- | 
|  | >>> e = ET.fromstring('<edge src_node="0" sink_node="1" switch_id="1"/>') | 
|  | >>> RoutingGraph.node_ids_for_edge(e) | 
|  | (0, 1) | 
|  | """ | 
|  | assert xml_node.tag == 'edge' | 
|  | src_node_id = int(xml_node.attrib.get("src_node", 0)) | 
|  | snk_node_id = int(xml_node.attrib.get("sink_node", 0)) | 
|  | return src_node_id, snk_node_id | 
|  |  | 
|  | def nodes_for_edge(self, xml_node): | 
|  | """Return all nodes associated with given edge. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | xml_node: RoutingEdge | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | (RoutingNode, RoutingNode) | 
|  | Source RoutingNode, Sink RoutingNode - XML nodes (ET._Element) | 
|  | associated with the given edge. | 
|  |  | 
|  | Example | 
|  | -------- | 
|  | >>> r = simple_test_routing() | 
|  | >>> e1 = r.get_edge_by_id(0) | 
|  | >>> RoutingGraphPrinter.edge(r, e1) | 
|  | '0 X000Y000[00].SRC--> ->>- 1 X000Y000[00].R-PIN>' | 
|  | >>> [RoutingGraphPrinter.node(n) for n in r.nodes_for_edge(e1)] | 
|  | ['0 X000Y000[00].SRC-->', '1 X000Y000[00].R-PIN>'] | 
|  | >>> e2 = r.get_edge_by_id(1) | 
|  | >>> RoutingGraphPrinter.edge(r, e2) | 
|  | '1 X000Y000[00].R-PIN> ->>- 2 X000Y000<-00->X000Y010' | 
|  | >>> [RoutingGraphPrinter.node(n) for n in r.nodes_for_edge(e2)] | 
|  | ['1 X000Y000[00].R-PIN>', '2 X000Y000<-00->X000Y010'] | 
|  | """ | 
|  | src_node_id, snk_node_id = self.node_ids_for_edge(xml_node) | 
|  |  | 
|  | ids2element = self._ids_map(RoutingNode) | 
|  | assert snk_node_id in ids2element, "{:r} in {}".format( | 
|  | snk_node_id, ids2element.keys() | 
|  | ) | 
|  | assert src_node_id in ids2element, "{:r} in {}".format( | 
|  | src_node_id, ids2element.keys() | 
|  | ) | 
|  | return ids2element[src_node_id], ids2element[snk_node_id] | 
|  |  | 
|  | def edges_for_allnodes(self): | 
|  | """Return a mapping between edges and associated nodes. | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | dict[int] -> list of RoutingEdge | 
|  | Mapping from RoutingNode ID to XML edges (ET._Element) associated | 
|  | with the given node. | 
|  |  | 
|  | Example | 
|  | ------- | 
|  |  | 
|  | """ | 
|  | return MappingProxyType(self._cache_nodes2edges) | 
|  |  | 
|  | def edges_for_node(self, xml_node): | 
|  | """Return all edges associated with given node. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | xml_node: RoutingNode | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | list of RoutingEdge | 
|  | XML edges (ET._Element) associated with the given node. | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  | >>> r = simple_test_routing() | 
|  | >>> [RoutingGraphPrinter.edge(r, e) for e in r.edges_for_node(r.get_node_by_id(1))] | 
|  | ['0 X000Y000[00].SRC--> ->>- 1 X000Y000[00].R-PIN>', '1 X000Y000[00].R-PIN> ->>- 2 X000Y000<-00->X000Y010'] | 
|  | >>> [RoutingGraphPrinter.edge(r, e) for e in r.edges_for_node(r.get_node_by_id(2))] | 
|  | ['1 X000Y000[00].R-PIN> ->>- 2 X000Y000<-00->X000Y010', '2 X000Y000<-00->X000Y010 ->>- 3 X000Y010[00].L-PIN<'] | 
|  | """  # noqa: E501 | 
|  | return [ | 
|  | self.get_edge_by_id(i) | 
|  | for i in self.edges_for_allnodes()[self._get_xml_id(xml_node)] | 
|  | ] | 
|  |  | 
|  | ###################################################################### | 
|  | # Constructor methods | 
|  | ###################################################################### | 
|  |  | 
|  | def create_node( | 
|  | self, | 
|  | low, | 
|  | high, | 
|  | ptc, | 
|  | ntype, | 
|  | direction=None, | 
|  | segment_id=None, | 
|  | side=None, | 
|  | timing=None, | 
|  | capacity=1, | 
|  | metadata={} | 
|  | ): | 
|  | """Create an node. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | low : Position | 
|  | high : Position | 
|  | ptc : int | 
|  | ntype : RoutingNodeType | 
|  |  | 
|  | direction : RoutingNodeDir, optional | 
|  | segment_id : int, optional | 
|  | side : RoutingNodeSide, optional | 
|  | timing : RoutingNodeTiming, optional | 
|  | metadata : {str: Any}, optional | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | RoutingNode | 
|  | """ | 
|  | if isinstance(ntype, str): | 
|  | ntype = RoutingNodeType[ntype] | 
|  |  | 
|  | # <node> | 
|  | attrib = OrderedDict( | 
|  | ( | 
|  | ('id', str(self._next_id(RoutingNode))), | 
|  | ('type', ntype.value), | 
|  | ('capacity', str(capacity)), | 
|  | ) | 
|  | ) | 
|  | if ntype.track: | 
|  | assert direction is not None | 
|  | attrib['direction'] = direction.value | 
|  | elif not ntype.pin_class: | 
|  | assert low == high, (low, high) | 
|  |  | 
|  | node = RoutingNode(attrib=attrib) | 
|  |  | 
|  | # <loc> needed for all nodes | 
|  | attrib = { | 
|  | 'xlow': str(low.x), | 
|  | 'ylow': str(low.y), | 
|  | 'xhigh': str(high.x), | 
|  | 'yhigh': str(high.y), | 
|  | 'ptc': str(ptc), | 
|  | } | 
|  | if ntype.pin: | 
|  | assert_type(side, RoutingNodeSide) | 
|  | attrib['side'] = side.value | 
|  | else: | 
|  | assert side is None | 
|  | ET.SubElement(node, 'loc', attrib) | 
|  |  | 
|  | # <timing> needed for all nodes | 
|  | if timing is None: | 
|  | if ntype.track: | 
|  | # Seems to confuse VPR when 0 | 
|  | # XXX: consider requiring the user to give instead of defaulting | 
|  | timing = RoutingNodeTiming(R=1e-9, C=1e-9) | 
|  | else: | 
|  | timing = RoutingNodeTiming(R=0, C=0) | 
|  | assert len(timing) == 2 | 
|  | assert_type(timing, RoutingNodeTiming) | 
|  | assert_type(timing.R, (float, int)) | 
|  | assert_type(timing.C, (float, int)) | 
|  | node.append(timing.to_xml()) | 
|  |  | 
|  | # <segment> needed for CHANX/CHANY nodes | 
|  | if ntype.track: | 
|  | assert_type(segment_id, int) | 
|  | ET.SubElement(node, 'segment', {'segment_id': str(segment_id)}) | 
|  |  | 
|  | for offset, values in metadata.items(): | 
|  | for k, v in values.items(): | 
|  | node.set_metadata(k, v, offset=offset) | 
|  |  | 
|  | self._add_xml_element(node) | 
|  |  | 
|  | return node | 
|  |  | 
|  | def create_edge_with_ids( | 
|  | self, src_node_id, sink_node_id, switch, metadata={}, bidir=None | 
|  | ): | 
|  | """Create an RoutingEdge between given IDs for two RoutingNodes. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | src_node_id : int | 
|  | sink_node_id : int | 
|  | switch : Switch | 
|  | metadata : {str: Any}, optional | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | RoutingEdge (ET._Element) | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  | >>> r = simple_test_routing() | 
|  | >>> sw = Switch(id=0, type=SwitchType.MUX, name="sw") | 
|  | >>> r.create_edge_with_ids(0, 1, sw) | 
|  | >>> e1 = r.get_edge_by_id(4) | 
|  | >>> RoutingGraphPrinter.edge(r, e1) | 
|  | '0 X000Y000[00].SRC--> ->>- 1 X000Y000[00].R-PIN>' | 
|  |  | 
|  | The code protects against invalid edge creation; | 
|  | >>> r.create_edge_with_ids(0, 2, sw) | 
|  | Traceback (most recent call last): | 
|  | ... | 
|  | TypeError: RoutingNodeType.SOURCE -> RoutingNodeType.CHANX not valid, Only SOURCE -> OPIN is valid | 
|  | 0 X000Y000[00].SRC--> b'<node id="0" type="SOURCE" capacity="1"><loc xlow="0" ylow="0" xhigh="0" yhigh="0" ptc="0"/><timing R="0" C="0"/></node>' | 
|  | -> | 
|  | 2 X000Y000<-00->X000Y010 b'<node id="2" type="CHANX" capacity="1" direction="BI_DIR"><loc xlow="0" ylow="0" xhigh="0" yhigh="10" ptc="0"/><timing R="1e-09" C="1e-09"/><segment segment_id="0"/></node>' | 
|  | >>> r.create_edge_with_ids(1, 4, sw) | 
|  | Traceback (most recent call last): | 
|  | ... | 
|  | TypeError: RoutingNodeType.OPIN -> RoutingNodeType.SINK not valid, Only OPIN -> IPIN, CHANX, CHANY (IE A sink) is valid | 
|  | 1 X000Y000[00].R-PIN> b'<node id="1" type="OPIN" capacity="1"><loc xlow="0" ylow="0" xhigh="0" yhigh="0" ptc="0" side="RIGHT"/><timing R="0" C="0"/></node>' | 
|  | -> | 
|  | 4 X000Y010[00].SINK-< b'<node id="4" type="SINK" capacity="1"><loc xlow="0" ylow="10" xhigh="0" yhigh="10" ptc="0"/><timing R="0" C="0"/></node>' | 
|  | """  # noqa: E501 | 
|  |  | 
|  | id2node = self.id2element[RoutingNode] | 
|  | assert src_node_id in id2node, src_node_id | 
|  | src_node_type = RoutingNodeType.from_xml(id2node[src_node_id]) | 
|  | assert sink_node_id in id2node, sink_node_id | 
|  | sink_node_type = RoutingNodeType.from_xml(id2node[sink_node_id]) | 
|  |  | 
|  | valid, msg = self._is_valid(src_node_type, sink_node_type) | 
|  | if not valid: | 
|  | src_node = id2node[src_node_id] | 
|  | sink_node = id2node[sink_node_id] | 
|  | raise TypeError( | 
|  | "{} -> {} not valid, {}\n{} {}\n  ->\n{} {}".format( | 
|  | src_node_type, | 
|  | sink_node_type, | 
|  | msg, | 
|  | RoutingGraphPrinter.node(src_node), | 
|  | ET.tostring(src_node), | 
|  | RoutingGraphPrinter.node(sink_node), | 
|  | ET.tostring(sink_node), | 
|  | ) | 
|  | ) | 
|  |  | 
|  | sw_bidir = switch.type in (SwitchType.SHORT, SwitchType.PASS_GATE) | 
|  | if bidir is None: | 
|  | bidir = sw_bidir | 
|  | elif sw_bidir: | 
|  | assert bidir, "Switch type {} must be bidir {} ({})".format( | 
|  | switch, (sw_bidir, bidir), (sink_node_id, src_node_id) | 
|  | ) | 
|  |  | 
|  | self._create_edge_with_ids(src_node_id, sink_node_id, switch, metadata) | 
|  |  | 
|  | valid, msg = self._is_valid(sink_node_type, src_node_type) | 
|  | if valid and bidir: | 
|  | self._create_edge_with_ids( | 
|  | sink_node_id, src_node_id, switch, metadata | 
|  | ) | 
|  |  | 
|  | @staticmethod | 
|  | def _is_valid(src_node_type, sink_node_type): | 
|  | valid = False | 
|  | if False: | 
|  | pass | 
|  | elif src_node_type == RoutingNodeType.IPIN: | 
|  | msg = "Only IPIN -> SINK valid" | 
|  | valid = (sink_node_type == RoutingNodeType.SINK) | 
|  | elif src_node_type == RoutingNodeType.OPIN: | 
|  | msg = "Only OPIN -> IPIN, CHANX, CHANY (IE A sink) is valid" | 
|  | valid = sink_node_type.can_sink | 
|  | elif src_node_type == RoutingNodeType.SINK: | 
|  | msg = "SINK can't be a source." | 
|  | valid = False | 
|  | elif src_node_type == RoutingNodeType.SOURCE: | 
|  | msg = "Only SOURCE -> OPIN is valid" | 
|  | valid = (sink_node_type == RoutingNodeType.OPIN) | 
|  | elif src_node_type == RoutingNodeType.CHANX: | 
|  | msg = "Only CHANX -> IPIN, CHANX, CHANY (IE A sink) is valid" | 
|  | valid = sink_node_type.can_sink | 
|  | elif src_node_type == RoutingNodeType.CHANY: | 
|  | msg = "Only CHANY -> IPIN, CHANX, CHANY (IE A sink) is valid" | 
|  | valid = sink_node_type.can_sink | 
|  | else: | 
|  | assert False | 
|  | return valid, msg | 
|  |  | 
|  | def _create_edge_with_ids( | 
|  | self, src_node_id, sink_node_id, switch, metadata={} | 
|  | ): | 
|  | # <edge src_node="34" sink_node="44" switch_id="1"/> | 
|  | assert_type(src_node_id, int) | 
|  | assert_type(sink_node_id, int) | 
|  | assert_type(switch, Switch) | 
|  |  | 
|  | edge = RoutingEdge( | 
|  | attrib=OrderedDict( | 
|  | ( | 
|  | ('src_node', | 
|  | str(src_node_id)), ('sink_node', str(sink_node_id)), | 
|  | ('switch_id', str(switch.id)) | 
|  | ) | 
|  | ) | 
|  | ) | 
|  |  | 
|  | for offset, values in metadata.items(): | 
|  | for k, v in values.items(): | 
|  | edge.set_metadata(k, v, offset=offset) | 
|  |  | 
|  | self._add_xml_element(edge, add_id_attrib=False) | 
|  |  | 
|  | def create_edge_with_nodes( | 
|  | self, src_node, sink_node, switch, metadata={}, bidir=None | 
|  | ): | 
|  | """Create an RoutingEdge between given two RoutingNodes. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | src_node : RoutingNode | 
|  | sink_node : RoutingNode | 
|  | switch : Switch | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | RoutingEdge | 
|  | """ | 
|  | # <edge src_node="34" sink_node="44" switch_id="1"/> | 
|  | assert_type(src_node, ET._Element) | 
|  | assert_eq(src_node.tag, "node") | 
|  | assert_type(sink_node, ET._Element) | 
|  | assert_eq(sink_node.tag, "node") | 
|  |  | 
|  | self.create_edge_with_ids( | 
|  | self._get_xml_id(src_node), | 
|  | self._get_xml_id(sink_node), | 
|  | switch, | 
|  | metadata=metadata, | 
|  | bidir=bidir | 
|  | ) | 
|  |  | 
|  |  | 
|  | def pin_meta_always_right(*a, **kw): | 
|  | return (RoutingNodeSide.RIGHT, Offset(0, 0)) | 
|  |  | 
|  |  | 
|  | class Graph: | 
|  | """ | 
|  | Top level representation, holds the XML root | 
|  | For <rr_graph> node | 
|  | """ | 
|  |  | 
|  | def __init__( | 
|  | self, | 
|  | rr_graph_file=None, | 
|  | verbose=False, | 
|  | clear_fabric=False, | 
|  | switch_name=None, | 
|  | pin_meta=pin_meta_always_right | 
|  | ): | 
|  | """ | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | rr_graph_file : filename | 
|  | verbose : bool | 
|  | clear_fabric : bool | 
|  | Remove the rr_graph (IE All nodes and edges - and thus channels too). | 
|  | pin_meta : callable(Block, Pin) -> (RoutingNodeSide, Offset) | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  |  | 
|  | Look at the segments via name or ID number; | 
|  | >>> g = simple_test_graph() | 
|  | >>> g.segments[0] | 
|  | Segment(id=0, name='local', ...) | 
|  | >>> g.segments["local"] | 
|  | Segment(id=0, name='local', ...) | 
|  |  | 
|  | Look at the switches via name or ID number; | 
|  | >>> g = simple_test_graph() | 
|  | >>> g.switches[0] | 
|  | Switch(id=0, name='mux', type=<SwitchType.MUX: 'mux'>, ...) | 
|  | >>> g.switches[1] | 
|  | Switch(id=1, name='__vpr_delayless_switch__', type=<SwitchType.MUX: 'mux'>, ...) | 
|  | >>> g.switches["mux"] | 
|  | Switch(id=0, name='mux', type=<SwitchType.MUX: 'mux'>, ...) | 
|  | >>> g.switches["__vpr_delayless_switch__"] | 
|  | Switch(id=1, name='__vpr_delayless_switch__', type=<SwitchType.MUX: 'mux'>, ...) | 
|  |  | 
|  | Look at the block grid; | 
|  | >>> g = simple_test_graph() | 
|  | >>> g.block_grid.size | 
|  | Size(w=4, h=3) | 
|  | >>> g.block_grid[Position(0, 0)] | 
|  | Block(..., position=P(x=0, y=0), offset=Offset(w=0, h=0)) | 
|  | >>> g.block_grid[Position(2, 1)] | 
|  | Block(..., position=P(x=2, y=1), offset=Offset(w=0, h=0)) | 
|  | >>> g.block_grid[Position(4, 4)] | 
|  | Traceback (most recent call last): | 
|  | ... | 
|  | KeyError: P(x=4, y=4) | 
|  | >>> g.block_grid.block_types["IBUF"] | 
|  | BlockType(graph=..., id=1, name='IBUF', size=Size(w=1, h=1), ...) | 
|  | >>> g.block_grid.block_types[2] | 
|  | BlockType(graph=..., id=2, name='OBUF', size=Size(w=1, h=1), ...) | 
|  | >>> for block in g.block_grid.blocks_for(row=1): | 
|  | ...     print(block.position, block.block_type.name) | 
|  | P(x=0, y=1) IBUF | 
|  | P(x=1, y=1) TILE | 
|  | P(x=2, y=1) OBUF | 
|  | P(x=3, y=1) EMPTY | 
|  | >>> for bt in g.block_grid.block_types_for(row=1): | 
|  | ...     print(bt.name) | 
|  | IBUF | 
|  | TILE | 
|  | OBUF | 
|  | EMPTY | 
|  |  | 
|  | """ | 
|  | self.verbose = verbose | 
|  |  | 
|  | self.segments = LookupMap(Segment) | 
|  | self.switches = LookupMap(Switch) | 
|  |  | 
|  | # Read in existing file | 
|  | if rr_graph_file: | 
|  | self.block_grid = BlockGrid() | 
|  | self._xml_graph = ET.parse( | 
|  | rr_graph_file, ET.XMLParser(remove_blank_text=True) | 
|  | ) | 
|  | self._import_block_types() | 
|  | self._import_block_grid() | 
|  | self._import_segments() | 
|  | self._import_switches() | 
|  | else: | 
|  | self._xml_graph = ET.Element("rr_graph") | 
|  | ET.SubElement(self._xml_graph, "rr_nodes") | 
|  | ET.SubElement(self._xml_graph, "rr_edges") | 
|  |  | 
|  | self.switches.add( | 
|  | Switch( | 
|  | id=self.switches.next_id(), | 
|  | type="mux", | 
|  | name="__vpr_delayless_switch__" | 
|  | ) | 
|  | ) | 
|  |  | 
|  | self.routing = RoutingGraph( | 
|  | self._xml_graph, verbose=verbose, clear_fabric=clear_fabric | 
|  | ) | 
|  |  | 
|  | # Recreate the routing nodes for blocks if we cleared the routing | 
|  | if clear_fabric: | 
|  | switch = self.switches[switch_name] | 
|  | self.create_block_pins_fabric(switch=switch, pin_meta=pin_meta) | 
|  | else: | 
|  | self._index_pin_localnames() | 
|  |  | 
|  | # Channels import requires rr_nodes | 
|  | self.channels = Channels(self.block_grid.size - Size(1, 1)) | 
|  | if rr_graph_file: | 
|  | self._import_xml_channels() | 
|  |  | 
|  | def _index_pin_localnames(self): | 
|  | for node in self.routing._xml_parent(RoutingNode): | 
|  | if node.tag == ET.Comment: | 
|  | continue | 
|  |  | 
|  | ntype = node.get('type') | 
|  | loc = single_element(node, 'loc') | 
|  | pos_low, pos_high = node_pos(node) | 
|  | ptc = int(loc.get('ptc')) | 
|  |  | 
|  | if ntype in ('IPIN', 'OPIN'): | 
|  | assert pos_low == pos_high, (pos_low, pos_high) | 
|  | pos = pos_low | 
|  |  | 
|  | # Lookup Block/<grid_loc> | 
|  | # ptc is the associated pin ptc value of the block_type | 
|  | block = self.block_grid[pos] | 
|  | assert_type(block, Block) | 
|  | pin = block.ptc2pin(ptc) | 
|  | assert pin.name is not None, pin.name | 
|  | self.routing.localnames.add(pos, pin.name, node) | 
|  |  | 
|  | def _import_block_types(self): | 
|  | # Create in the block_types information | 
|  | for block_type in self._xml_graph.iterfind("./block_types/block_type"): | 
|  | BlockType.from_xml(self.block_grid, block_type) | 
|  |  | 
|  | def _import_block_grid(self): | 
|  | for block_xml in self._xml_graph.iterfind("./grid/grid_loc"): | 
|  | b = Block.from_xml(self.block_grid, block_xml) | 
|  | if b.offset == (0, 0): | 
|  | self.block_grid.add_block(b) | 
|  | size = self.block_grid.size | 
|  | assert size.x > 0 | 
|  | assert size.y > 0 | 
|  |  | 
|  | def _import_segments(self): | 
|  | for segment_xml in self._xml_graph.iterfind("./segments/segment"): | 
|  | self.segments.add(Segment.from_xml(segment_xml)) | 
|  |  | 
|  | def _import_switches(self): | 
|  | for switch_xml in self._xml_graph.iterfind("./switches/switch"): | 
|  | self.switches.add(Switch.from_xml(switch_xml)) | 
|  |  | 
|  | def _import_xml_channels(self): | 
|  | self.channels.from_xml_nodes(self.routing._xml_parent(RoutingNode)) | 
|  |  | 
|  | def add_switch(self, sw): | 
|  | assert_type(sw, Switch) | 
|  | self.switches.add(sw) | 
|  | switches = single_element(self._xml_graph, "./switches") | 
|  | sw.to_xml(switches) | 
|  |  | 
|  | def create_block_pins_fabric( | 
|  | self, switch=None, pin_meta=pin_meta_always_right | 
|  | ): | 
|  | if switch is None: | 
|  | switch = self.switches[0] | 
|  | self.create_nodes_from_blocks(self.block_grid, switch, pin_meta) | 
|  |  | 
|  | def set_tooling(self, name, version, comment): | 
|  | root = self._xml_graph.getroot() | 
|  | root.set("tool_name", name) | 
|  | root.set("tool_version", version) | 
|  | root.set("tool_comment", comment) | 
|  |  | 
|  | def create_node_from_pin(self, block, pin, side, offset): | 
|  | """Creates an IPIN/OPIN RoutingNode from `class Pin` object. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | block : Block | 
|  | pin : Pin | 
|  | side : RoutingNodeSide | 
|  | offset : Offset | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | RoutingNode | 
|  | """ | 
|  | assert_type(block, Block) | 
|  | assert_type(pin, Pin) | 
|  | assert_type(pin.pin_class, PinClass) | 
|  | assert_type(pin.pin_class.block_type, BlockType) | 
|  |  | 
|  | pc = pin.pin_class | 
|  | # Connection within the same tile | 
|  | pos = block.position + offset | 
|  |  | 
|  | pin_node = None | 
|  | if pc.direction in (PinClassDirection.INPUT, PinClassDirection.CLOCK): | 
|  | pin_node = self.routing.create_node( | 
|  | pos, pos, pin.ptc, 'IPIN', side=side | 
|  | ) | 
|  | elif pin.pin_class.direction in (PinClassDirection.OUTPUT, ): | 
|  | pin_node = self.routing.create_node( | 
|  | pos, pos, pin.ptc, 'OPIN', side=side | 
|  | ) | 
|  | else: | 
|  | assert False, "Unknown dir of {}.{}".format(pin, pin.pin_class) | 
|  |  | 
|  | assert pin_node is not None, pin_node | 
|  |  | 
|  | if self.verbose: | 
|  | print( | 
|  | "Adding pin {:55s} on tile ({:12s}, {:12s})@{:4d} {}".format( | 
|  | str(pin), str(pos), str(pos), pin.ptc, | 
|  | RoutingGraphPrinter.node(pin_node, self.block_grid) | 
|  | ) | 
|  | ) | 
|  |  | 
|  | self.routing.localnames.add(pos, pin.name, pin_node) | 
|  |  | 
|  | return pin_node | 
|  |  | 
|  | def create_node_from_track(self, track, capacity=1): | 
|  | """ | 
|  | Creates the CHANX/CHANY node for a Track object. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | track : channels.Track | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | RoutingNode | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  | """ | 
|  | assert_type(track, Track) | 
|  | assert track.idx is not None | 
|  |  | 
|  | track_node = self.routing.create_node( | 
|  | track.start, | 
|  | track.end, | 
|  | track.idx, | 
|  | track.type.value, | 
|  | direction=track.direction, | 
|  | segment_id=track.segment_id, | 
|  | capacity=capacity | 
|  | ) | 
|  |  | 
|  | if track.name is not None: | 
|  | self.routing.globalnames.add(track.name, track_node) | 
|  |  | 
|  | return track_node | 
|  |  | 
|  | def create_nodes_from_pin_class(self, block, pin_class, switch, pin_meta): | 
|  | """Creates a SOURCE or SINK RoutingNode from a `class PinClass` object. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | block : Block | 
|  | pin_class : PinClass | 
|  | switch : Switch | 
|  | pin_meta : callable(Block, Pin) -> (RoutingNodeSide, Offset) | 
|  | """ | 
|  | assert_type(block, Block) | 
|  | assert_type(block.block_type, BlockType) | 
|  | assert_type(pin_class, PinClass) | 
|  | assert_type(pin_class.block_type, BlockType) | 
|  | assert_eq(block.block_type, pin_class.block_type) | 
|  | assert_type(switch, Switch) | 
|  |  | 
|  | pos_low = block.position | 
|  | pos_high = block.position + pin_class.block_type.size - Size(1, 1) | 
|  |  | 
|  | # Assuming only one pin per class for now | 
|  | assert len( | 
|  | pin_class.pins | 
|  | ) == 1, 'Expect one pin per pin class, got %s' % (pin_class.pins, ) | 
|  |  | 
|  | pin = pin_class.pins[0] | 
|  | if pin_class.direction in (PinClassDirection.INPUT, | 
|  | PinClassDirection.CLOCK): | 
|  | # Sink node | 
|  | sink_node = self.routing.create_node( | 
|  | pos_low, pos_high, pin.ptc, 'SINK' | 
|  | ) | 
|  |  | 
|  | if self.verbose: | 
|  | print( | 
|  | "Adding snk {:55s} on tile ({:12s}, {:12s}) {}".format( | 
|  | str(pin_class), str(pos_low), str(pos_high), | 
|  | RoutingGraphPrinter.node(sink_node, self.block_grid) | 
|  | ) | 
|  | ) | 
|  |  | 
|  | for p in pin_class.pins: | 
|  | pin_node = self.create_node_from_pin( | 
|  | block, p, *pin_meta(block, p) | 
|  | ) | 
|  |  | 
|  | # Edge PIN->SINK | 
|  | self.routing.create_edge_with_nodes( | 
|  | pin_node, sink_node, switch | 
|  | ) | 
|  |  | 
|  | elif pin_class.direction in (PinClassDirection.OUTPUT, ): | 
|  | # Source node | 
|  | src_node = self.routing.create_node( | 
|  | pos_low, pos_high, pin.ptc, 'SOURCE' | 
|  | ) | 
|  |  | 
|  | if self.verbose: | 
|  | print( | 
|  | "Adding src {:55s} on tile ({:12s}, {:12s}) {}".format( | 
|  | str(pin_class), str(pos_low), str(pos_high), | 
|  | RoutingGraphPrinter.node(src_node, self.block_grid) | 
|  | ) | 
|  | ) | 
|  |  | 
|  | for p in pin_class.pins: | 
|  | pin_node = self.create_node_from_pin( | 
|  | block, p, *pin_meta(block, p) | 
|  | ) | 
|  |  | 
|  | # Edge SOURCE->PIN | 
|  | self.routing.create_edge_with_nodes(src_node, pin_node, switch) | 
|  |  | 
|  | else: | 
|  | assert False, "Unknown dir of {} for {}".format( | 
|  | pin_class.direction, str(pin_class) | 
|  | ) | 
|  |  | 
|  | def create_nodes_from_block(self, block, switch, pin_meta): | 
|  | """ | 
|  | Creates the SOURCE/SINK nodes for each pin class | 
|  | Creates the IPIN/OPIN nodes for each pin inside a pin class. | 
|  | Creates the edges which connect these together. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | block : Block | 
|  | switch : Switch | 
|  | pin_meta : callable(Block, Pin) -> (RoutingNodeSide, Offset) | 
|  |  | 
|  | Examples | 
|  | -------- | 
|  | """ | 
|  | for pc in block.block_type.pin_classes: | 
|  | self.create_nodes_from_pin_class(block, pc, switch, pin_meta) | 
|  |  | 
|  | def create_nodes_from_blocks(self, blocks, switch, pin_meta): | 
|  | """ | 
|  | Parameters | 
|  | ---------- | 
|  | block : Block | 
|  | switch : Switch | 
|  | pin_meta : callable(Block, Pin) -> (RoutingNodeSide, Offset) | 
|  | """ | 
|  | for block in blocks: | 
|  | self.create_nodes_from_block(block, switch, pin_meta) | 
|  |  | 
|  | def connect_pin_to_track(self, block, pin, track, switch): | 
|  | """ | 
|  | Create an edge from given pin in block to given track with switching properties of switch | 
|  | """ | 
|  | assert_type(block, Block) | 
|  | assert_type(pin, Pin) | 
|  | assert_type(track, Track) | 
|  | assert_type(switch, Switch) | 
|  |  | 
|  | pin_node = self.routing.localnames[(block.position, pin.name)] | 
|  | track_node = self.routing.globalnames[track.name] | 
|  | if pin.direction == PinClassDirection.OUTPUT: | 
|  | self.routing.create_edge_with_nodes(pin_node, track_node, switch) | 
|  | else: | 
|  | self.routing.create_edge_with_nodes(track_node, pin_node, switch) | 
|  |  | 
|  | def connect_track_to_track(self, src, dst, switch): | 
|  | assert_type(src, Track) | 
|  | assert_type(dst, Track) | 
|  | src_node = self.routing.globalnames[src.name] | 
|  | dst_node = self.routing.globalnames[dst.name] | 
|  | self.routing.create_edge_with_nodes(src_node, dst_node, switch) | 
|  |  | 
|  | def create_xy_track( | 
|  | self, | 
|  | start, | 
|  | end, | 
|  | segment, | 
|  | idx=None, | 
|  | name=None, | 
|  | typeh=None, | 
|  | direction=None, | 
|  | capacity=1 | 
|  | ): | 
|  | """Create track object and corresponding nodes""" | 
|  | if not isinstance(start, Position): | 
|  | start = Position(*start) | 
|  | assert_type(start, Position) | 
|  | if not isinstance(end, Position): | 
|  | end = Position(*end) | 
|  | assert_type(end, Position) | 
|  |  | 
|  | track = self.channels.create_xy_track( | 
|  | start, | 
|  | end, | 
|  | segment.id, | 
|  | idx=idx, | 
|  | name=name, | 
|  | typeh=typeh, | 
|  | direction=direction | 
|  | ) | 
|  | track_node = self.create_node_from_track(track, capacity) | 
|  |  | 
|  | return track, track_node | 
|  |  | 
|  | def pad_channels(self, segment): | 
|  | """Workaround for https://github.com/verilog-to-routing/vtr-verilog-to-routing/issues/339""" | 
|  | for track in self.channels.pad_channels(segment): | 
|  | self.create_node_from_track(track, capacity=0) | 
|  |  | 
|  | def extract_pin_meta(self): | 
|  | """Export pin placement as pin_meta[(block, pin)] to import when rebuilding pin nodes""" | 
|  | sides = MappingLocalNames(type=RoutingNodeSide) | 
|  | offsets = MappingLocalNames(type=Offset) | 
|  | for block in self.block_grid: | 
|  | for pin_class in block.block_type.pin_classes: | 
|  | for pin in pin_class.pins: | 
|  | for offset in block.block_type.positions: | 
|  | pos = block.position + offset | 
|  | try: | 
|  | node = self.routing.localnames[(pos, pin.name)] | 
|  | except KeyError: | 
|  | continue | 
|  |  | 
|  | assert node is not None, "{}:{} not found at {}\n{}".format( | 
|  | block, pin.name, list(block.positions), | 
|  | self.routing.localnames | 
|  | ) | 
|  | side = single_element(node, 'loc').get('side') | 
|  | assert side is not None, ET.tostring(node) | 
|  | sides[(block.position, | 
|  | pin.name)] = RoutingNodeSide(side) | 
|  | offsets[(block.position, pin.name)] = offset | 
|  | return sides, offsets | 
|  |  | 
|  | def to_xml(self): | 
|  | """Return an ET object representing this rr_graph""" | 
|  | self.set_tooling("g.py", "dev", "Generated from black magic") | 
|  |  | 
|  | # <rr_nodes>, <rr_edges>, and <switches> should be good as is | 
|  | # note <rr_nodes> includes channel tracks, but not width definitions | 
|  |  | 
|  | # FIXME: regenerate <block_types> | 
|  | # FIXME: regenerate <grid> | 
|  |  | 
|  | self.channels.to_xml(self._xml_graph) | 
|  | return self._xml_graph | 
|  |  | 
|  | def connect_all( | 
|  | self, start, end, name, segment, metadata={}, spine=None, | 
|  | switch=None | 
|  | ): | 
|  | """Add a track which is present at all tiles within a range. | 
|  |  | 
|  | Returns: | 
|  | List of ET.RoutingNode | 
|  | """ | 
|  | assert_type(start, Position) | 
|  | assert_type(end, Position) | 
|  | assert_type(name, str) | 
|  | assert_type(segment, Segment) | 
|  | assert_type(metadata, dict) | 
|  | assert_type_or_none(spine, int) | 
|  | if spine is None: | 
|  | spine = start.y + (end.y - start.y) // 2 | 
|  |  | 
|  | assert start.x <= end.x, "x - {} < {}".format(start, end) | 
|  | assert start.y <= end.y, "y - {} < {}".format(start, end) | 
|  |  | 
|  | if switch is None: | 
|  | switch = self.switches["short"] | 
|  |  | 
|  | # Vertical wires | 
|  | v_tracks = [] | 
|  | for x in range(start.x, end.x + 1): | 
|  | spos = Position(x, start.y) | 
|  | epos = Position(x, end.y) | 
|  | track, track_node = self.create_xy_track( | 
|  | spos, | 
|  | epos, | 
|  | segment=segment, | 
|  | typeh=Track.Type.Y, | 
|  | direction=Track.Direction.BI | 
|  | ) | 
|  | v_tracks.append(track_node) | 
|  |  | 
|  | for offset, values in metadata.items(): | 
|  | for k, v in values.items(): | 
|  | track_node.set_metadata(k, v, offset=offset) | 
|  |  | 
|  | for y in range(start.y, end.y + 1): | 
|  | pos = Position(x, y) | 
|  | self.routing.localnames.add(pos, name, track_node) | 
|  |  | 
|  | # One horizontal wire | 
|  | spos = Position(start.x, spine) | 
|  | epos = Position(end.x, spine) | 
|  | track, track_node = self.create_xy_track( | 
|  | spos, | 
|  | epos, | 
|  | segment=segment, | 
|  | typeh=Track.Type.X, | 
|  | direction=Track.Direction.BI | 
|  | ) | 
|  |  | 
|  | for offset, values in metadata.items(): | 
|  | for k, v in values.items(): | 
|  | track_node.set_metadata(k, v, offset=offset) | 
|  |  | 
|  | for x in range(start.x, end.x + 1): | 
|  | pos = Position(x, spine) | 
|  | self.routing.localnames.add(pos, name + "_h", track_node) | 
|  |  | 
|  | assert_eq(len(v_tracks), len(range(start.x, end.x + 1))) | 
|  | # Connect the vertical wires to the horizontal one to make a single | 
|  | # global network | 
|  | for i, x in enumerate(range(start.x, end.x + 1)): | 
|  | pos = Position(x, spine) | 
|  | self.routing.create_edge_with_nodes( | 
|  | v_tracks[i], track_node, switch, bidir=True | 
|  | ) | 
|  |  | 
|  | return v_tracks + [track_node] | 
|  |  | 
|  |  | 
|  | def simple_test_routing(): | 
|  | """ | 
|  | >>> r = simple_test_routing() | 
|  | """ | 
|  | routing = RoutingGraph() | 
|  | routing.create_node( | 
|  | Position(0, 0), Position(0, 0), 0, ntype=RoutingNodeType.SOURCE | 
|  | ) | 
|  | routing.create_node( | 
|  | Position(0, 0), | 
|  | Position(0, 0), | 
|  | 0, | 
|  | ntype=RoutingNodeType.OPIN, | 
|  | side=RoutingNodeSide.RIGHT | 
|  | ) | 
|  | routing.create_node( | 
|  | Position(0, 0), | 
|  | Position(0, 10), | 
|  | 0, | 
|  | ntype=RoutingNodeType.CHANX, | 
|  | segment_id=0, | 
|  | direction=RoutingNodeDir.BI_DIR | 
|  | ) | 
|  | routing.create_node( | 
|  | Position(0, 10), | 
|  | Position(0, 10), | 
|  | 0, | 
|  | ntype=RoutingNodeType.IPIN, | 
|  | side=RoutingNodeSide.LEFT | 
|  | ) | 
|  | routing.create_node( | 
|  | Position(0, 10), Position(0, 10), 0, ntype=RoutingNodeType.SINK | 
|  | ) | 
|  | sw = Switch(id=0, name="sw", type=SwitchType.MUX) | 
|  | routing.create_edge_with_ids(0, 1, sw)  # SRC->OPIN | 
|  | routing.create_edge_with_ids(1, 2, sw)  # OPIN->CHANX | 
|  | routing.create_edge_with_ids(2, 3, sw)  # CHANX->IPIN | 
|  | routing.create_edge_with_ids(3, 4, sw)  # IPIN->SINK | 
|  | return routing | 
|  |  | 
|  |  | 
|  | def simple_test_block_grid(): | 
|  | """ | 
|  | >>> bg = simple_test_block_grid() | 
|  | """ | 
|  | bg = BlockGrid() | 
|  |  | 
|  | # Create a block type with one input and one output pin | 
|  | bt = BlockType(g=bg, id=0, name="DUALBLK") | 
|  | pci = PinClass(block_type=bt, direction=PinClassDirection.INPUT) | 
|  | Pin(pin_class=pci, port_name="A", port_index=0) | 
|  | pco = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT) | 
|  | Pin(pin_class=pco, port_name="B", port_index=0) | 
|  |  | 
|  | # Create a block type with one input class with 4 pins | 
|  | bt = BlockType(g=bg, id=1, name="INBLOCK") | 
|  | pci = PinClass(block_type=bt, direction=PinClassDirection.INPUT) | 
|  | Pin(pin_class=pci, port_name="C", port_index=0) | 
|  | Pin(pin_class=pci, port_name="C", port_index=1) | 
|  | Pin(pin_class=pci, port_name="C", port_index=2) | 
|  | Pin(pin_class=pci, port_name="C", port_index=3) | 
|  |  | 
|  | # Create a block type with out input class with 2 pins | 
|  | bt = BlockType(g=bg, id=2, name="OUTBLOK") | 
|  | pci = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT) | 
|  | Pin(pin_class=pci, port_name="D", port_index=0) | 
|  | Pin(pin_class=pci, port_name="D", port_index=1) | 
|  | Pin(pin_class=pci, port_name="D", port_index=2) | 
|  | Pin(pin_class=pci, port_name="D", port_index=3) | 
|  |  | 
|  | # Add some blocks | 
|  | bg.add_block(Block(g=bg, block_type_id=1, position=Position(0, 0))) | 
|  | bg.add_block(Block(g=bg, block_type_id=1, position=Position(0, 1))) | 
|  | bg.add_block(Block(g=bg, block_type_id=1, position=Position(0, 2))) | 
|  | bg.add_block(Block(g=bg, block_type_id=1, position=Position(0, 3))) | 
|  |  | 
|  | bg.add_block(Block(g=bg, block_type_id=0, position=Position(1, 0))) | 
|  | bg.add_block(Block(g=bg, block_type_id=0, position=Position(1, 1))) | 
|  | bg.add_block(Block(g=bg, block_type_id=0, position=Position(1, 2))) | 
|  | bg.add_block(Block(g=bg, block_type_id=0, position=Position(1, 3))) | 
|  |  | 
|  | bg.add_block(Block(g=bg, block_type_id=0, position=Position(2, 0))) | 
|  | bg.add_block(Block(g=bg, block_type_id=0, position=Position(2, 1))) | 
|  | bg.add_block(Block(g=bg, block_type_id=0, position=Position(2, 2))) | 
|  | bg.add_block(Block(g=bg, block_type_id=0, position=Position(2, 3))) | 
|  |  | 
|  | bg.add_block(Block(g=bg, block_type_id=2, position=Position(3, 0))) | 
|  | bg.add_block(Block(g=bg, block_type_id=2, position=Position(3, 1))) | 
|  | bg.add_block(Block(g=bg, block_type_id=2, position=Position(3, 2))) | 
|  | bg.add_block(Block(g=bg, block_type_id=2, position=Position(3, 3))) | 
|  |  | 
|  | return bg | 
|  |  | 
|  |  | 
|  | def simple_test_graph(**kwargs): | 
|  | """ | 
|  | Simple graph, containing one input block, one pass through block and one | 
|  | output block, with some routing between them. | 
|  |  | 
|  | The rr_graph was generated by running the following in the tests directory; | 
|  | # make \\ | 
|  | ARCH=testarch \\ | 
|  | DEVICE_SUBARCH=wire-bidir-min \\ | 
|  | DEVICE=1x1.min \\ | 
|  | ROUTE_CHAN_WIDTH=1 \\ | 
|  | clean wire.rr_graph.xml | 
|  |  | 
|  | >>> g = simple_test_graph() | 
|  | """ | 
|  |  | 
|  | xml_str = """ | 
|  | <rr_graph tool_name="vpr" tool_version="ga5684b2e4" tool_comment="f4pga-arch-defs/testarch/devices/wire-bidir-min/arch.merged.xml"> | 
|  | <channels> | 
|  | <channel chan_width_max ="1" x_min="1" y_min="1" x_max="1" y_max="1"/> | 
|  | <x_list index ="0" info="1"/> | 
|  | <x_list index ="1" info="1"/> | 
|  | <y_list index ="0" info="1"/> | 
|  | <y_list index ="1" info="1"/> | 
|  | <y_list index ="2" info="1"/> | 
|  | </channels> | 
|  | <switches> | 
|  | <switch id="0" type="mux" name="mux"> | 
|  | <timing R="551" Cin="7.70000012e-16" Cout="4.00000001e-15" Tdel="5.80000006e-11"/> | 
|  | <sizing mux_trans_size="2.63073993" buf_size="27.6459007"/> | 
|  | </switch> | 
|  | <switch id="1" type="mux" name="__vpr_delayless_switch__"> | 
|  | <timing R="0" Cin="0" Cout="0" Tdel="0"/> | 
|  | <sizing mux_trans_size="0" buf_size="0"/> | 
|  | </switch> | 
|  | </switches> | 
|  |  | 
|  | <segments> | 
|  | <segment id="0" name="local"> | 
|  | <timing R_per_meter="101" C_per_meter="2.25000005e-14"/> | 
|  | </segment> | 
|  | </segments> | 
|  |  | 
|  | <block_types> | 
|  | <block_type id="0" name="EMPTY" width="1" height="1"> | 
|  | </block_type> | 
|  | <block_type id="1" name="IBUF" width="1" height="1"> | 
|  | <pin_class type="OUTPUT"> | 
|  | <pin ptc="0">IBUF.I[0]</pin> | 
|  | </pin_class> | 
|  | </block_type> | 
|  | <block_type id="2" name="OBUF" width="1" height="1"> | 
|  | <pin_class type="INPUT"> | 
|  | <pin ptc="0">OBUF.O[0]</pin> | 
|  | </pin_class> | 
|  | </block_type> | 
|  | <block_type id="3" name="TILE" width="1" height="1"> | 
|  | <pin_class type="INPUT"> | 
|  | <pin ptc="0">TILE.IN[0]</pin> | 
|  | </pin_class> | 
|  | <pin_class type="OUTPUT"> | 
|  | <pin ptc="1">TILE.OUT[0]</pin> | 
|  | </pin_class> | 
|  | </block_type> | 
|  | </block_types> | 
|  |  | 
|  | <grid> | 
|  | <grid_loc x="0" y="0" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="0" y="1" block_type_id="1" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="0" y="2" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="1" y="0" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="1" y="1" block_type_id="3" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="1" y="2" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="2" y="0" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="2" y="1" block_type_id="2" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="2" y="2" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="3" y="0" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="3" y="1" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | <grid_loc x="3" y="2" block_type_id="0" width_offset="0" height_offset="0"/> | 
|  | </grid> | 
|  |  | 
|  | <rr_nodes> | 
|  | <node id="0" type="SOURCE" capacity="1"> | 
|  | <loc xlow="0" ylow="1" xhigh="0" yhigh="1" ptc="0"/> | 
|  | <timing R="0" C="0"/> | 
|  | </node> | 
|  | <node id="1" type="OPIN" capacity="1"> | 
|  | <loc xlow="0" ylow="1" xhigh="0" yhigh="1" side="RIGHT" ptc="0"/> | 
|  | <timing R="0" C="0"/> | 
|  | </node> | 
|  | <node id="2" type="SINK" capacity="1"> | 
|  | <loc xlow="1" ylow="1" xhigh="1" yhigh="1" ptc="0"/> | 
|  | <timing R="0" C="0"/> | 
|  | </node> | 
|  | <node id="3" type="SOURCE" capacity="1"> | 
|  | <loc xlow="1" ylow="1" xhigh="1" yhigh="1" ptc="1"/> | 
|  | <timing R="0" C="0"/> | 
|  | </node> | 
|  | <node id="4" type="IPIN" capacity="1"> | 
|  | <loc xlow="1" ylow="1" xhigh="1" yhigh="1" side="RIGHT" ptc="0"/> | 
|  | <timing R="0" C="0"/> | 
|  | </node> | 
|  | <node id="5" type="OPIN" capacity="1"> | 
|  | <loc xlow="1" ylow="1" xhigh="1" yhigh="1" side="RIGHT" ptc="1"/> | 
|  | <timing R="0" C="0"/> | 
|  | </node> | 
|  | <node id="6" type="SINK" capacity="1"> | 
|  | <loc xlow="2" ylow="1" xhigh="2" yhigh="1" ptc="0"/> | 
|  | <timing R="0" C="0"/> | 
|  | </node> | 
|  | <node id="7" type="IPIN" capacity="1"> | 
|  | <loc xlow="2" ylow="1" xhigh="2" yhigh="1" side="RIGHT" ptc="0"/> | 
|  | <timing R="0" C="0"/> | 
|  | </node> | 
|  | <node id="8" type="CHANX" direction="BI_DIR" capacity="1"> | 
|  | <loc xlow="1" ylow="0" xhigh="1" yhigh="0" ptc="0"/> | 
|  | <timing R="101" C="3.60400017e-14"/> | 
|  | <segment segment_id="0"/> | 
|  | </node> | 
|  | <node id="9" type="CHANX" direction="BI_DIR" capacity="1"> | 
|  | <loc xlow="2" ylow="0" xhigh="2" yhigh="0" ptc="0"/> | 
|  | <timing R="101" C="3.60400017e-14"/> | 
|  | <segment segment_id="0"/> | 
|  | </node> | 
|  | <node id="10" type="CHANX" direction="BI_DIR" capacity="1"> | 
|  | <loc xlow="1" ylow="1" xhigh="1" yhigh="1" ptc="0"/> | 
|  | <timing R="101" C="3.60400017e-14"/> | 
|  | <segment segment_id="0"/> | 
|  | </node> | 
|  | <node id="11" type="CHANX" direction="BI_DIR" capacity="1"> | 
|  | <loc xlow="2" ylow="1" xhigh="2" yhigh="1" ptc="0"/> | 
|  | <timing R="101" C="3.60400017e-14"/> | 
|  | <segment segment_id="0"/> | 
|  | </node> | 
|  | <node id="12" type="CHANY" direction="BI_DIR" capacity="1"> | 
|  | <loc xlow="0" ylow="1" xhigh="0" yhigh="1" ptc="0"/> | 
|  | <timing R="101" C="3.60400017e-14"/> | 
|  | <segment segment_id="0"/> | 
|  | </node> | 
|  | <node id="13" type="CHANY" direction="BI_DIR" capacity="1"> | 
|  | <loc xlow="1" ylow="1" xhigh="1" yhigh="1" ptc="0"/> | 
|  | <timing R="101" C="4.48100012e-14"/> | 
|  | <segment segment_id="0"/> | 
|  | </node> | 
|  | <node id="14" type="CHANY" direction="BI_DIR" capacity="1"> | 
|  | <loc xlow="2" ylow="1" xhigh="2" yhigh="1" ptc="0"/> | 
|  | <timing R="101" C="3.28100008e-14"/> | 
|  | <segment segment_id="0"/> | 
|  | </node> | 
|  | </rr_nodes> | 
|  |  | 
|  | <rr_edges> | 
|  | <edge src_node="0" sink_node="1" switch_id="1"/> | 
|  | <edge src_node="1" sink_node="12" switch_id="0"/> | 
|  | <edge src_node="3" sink_node="5" switch_id="1"/> | 
|  | <edge src_node="4" sink_node="2" switch_id="1"/> | 
|  | <edge src_node="5" sink_node="13" switch_id="0"/> | 
|  | <edge src_node="7" sink_node="6" switch_id="1"/> | 
|  | <edge src_node="8" sink_node="9" switch_id="0"/> | 
|  | <edge src_node="8" sink_node="13" switch_id="0"/> | 
|  | <edge src_node="8" sink_node="12" switch_id="0"/> | 
|  | <edge src_node="9" sink_node="8" switch_id="0"/> | 
|  | <edge src_node="9" sink_node="14" switch_id="0"/> | 
|  | <edge src_node="9" sink_node="13" switch_id="0"/> | 
|  | <edge src_node="10" sink_node="11" switch_id="0"/> | 
|  | <edge src_node="10" sink_node="13" switch_id="0"/> | 
|  | <edge src_node="10" sink_node="12" switch_id="0"/> | 
|  | <edge src_node="11" sink_node="10" switch_id="0"/> | 
|  | <edge src_node="11" sink_node="14" switch_id="0"/> | 
|  | <edge src_node="11" sink_node="13" switch_id="0"/> | 
|  | <edge src_node="12" sink_node="10" switch_id="0"/> | 
|  | <edge src_node="12" sink_node="8" switch_id="0"/> | 
|  | <edge src_node="13" sink_node="11" switch_id="0"/> | 
|  | <edge src_node="13" sink_node="9" switch_id="0"/> | 
|  | <edge src_node="13" sink_node="10" switch_id="0"/> | 
|  | <edge src_node="13" sink_node="8" switch_id="0"/> | 
|  | <edge src_node="13" sink_node="4" switch_id="0"/> | 
|  | <edge src_node="14" sink_node="11" switch_id="0"/> | 
|  | <edge src_node="14" sink_node="9" switch_id="0"/> | 
|  | <edge src_node="14" sink_node="7" switch_id="0"/> | 
|  | </rr_edges> | 
|  | </rr_graph> | 
|  | """  # noqa: E501 | 
|  | return Graph(io.StringIO(xml_str), **kwargs) | 
|  |  | 
|  |  | 
|  | def test_create_block_pins_fabric(): | 
|  | """ | 
|  | >>> test_create_block_pins_fabric() | 
|  | """ | 
|  | # 2 input pins, 2 output pins | 
|  | # - 2 * SINK,   2 * IPIN, 2 edges | 
|  | # - 2 * SOURCE, 2 * OPIN, 2 edges | 
|  | # Should have added 4 edges to connect edge to pin | 
|  | g1 = simple_test_graph() | 
|  |  | 
|  | # Clear the fabric | 
|  | g1.routing.clear() | 
|  | assert_eq(len(g1.routing._xml_parent(RoutingNode)), 0) | 
|  | assert_eq(len(g1.routing._xml_parent(RoutingEdge)), 0) | 
|  |  | 
|  | # Create the fabric for the block pins | 
|  | g1.create_block_pins_fabric() | 
|  | assert_eq(len(g1.routing._xml_parent(RoutingNode)), 8) | 
|  | assert_eq(len(g1.routing._xml_parent(RoutingEdge)), 4) | 
|  |  | 
|  | # Check clearing on import | 
|  | g2 = simple_test_graph(clear_fabric=True) | 
|  | assert_eq(len(g2.routing._xml_parent(RoutingNode)), 8) | 
|  | assert_eq(len(g2.routing._xml_parent(RoutingEdge)), 4) | 
|  |  | 
|  |  | 
|  | def node_ptc(node): | 
|  | locs = list(node.iterfind("loc")) | 
|  | assert len(locs) == 1, locs | 
|  | loc = locs[0] | 
|  | return int(loc.get('ptc')) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | import doctest | 
|  |  | 
|  | print('Doctest begin') | 
|  | failure_count, test_count = doctest.testmod(optionflags=doctest.ELLIPSIS) | 
|  | assert test_count > 0 | 
|  | assert failure_count == 0, "Doctests failed!" | 
|  | print('Doctest end') | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |