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