| #!/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="symbiflow-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() |