blob: 2fd931aa7451a9b94993bb87e8778115a76e2f59 [file] [log] [blame] [edit]
""" rr graph library that is not tied to the underlying serialization format
and provides simple fast lookup required to build real FPGA rr graph fabrics.
"""
from __future__ import print_function
from collections import namedtuple
from enum import Enum
from .tracks import Track
from lib.rr_graph import channel2
from lib import progressbar_utils
class SwitchType(Enum):
"""Enumeration of allowed VPR switch type
See: https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-switches-switch
""" # noqa: E501
INVALID_SWITCH_TYPE = 0
MUX = 1
TRISTATE = 2
PASS_GATE = 3
SHORT = 4
BUFFER = 5
class NodeType(Enum):
"""VPR Node type. This is a superset of Type in channel.py
See: https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-nodes-node
""" # noqa: E501
INVALID_NODE_TYPE = 0
CHANX = 1
CHANY = 2
SOURCE = 3
SINK = 4
OPIN = 5
IPIN = 6
class NodeDirection(Enum):
"""VPR Node Direction. This is a superset of Direction in channel.py
See: https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-nodes-node
""" # noqa: E501
NO_DIR = 0
INC_DIR = 1
DEC_DIR = 2
BI_DIR = 3
class PinType(Enum):
"""Enum for PinClass type
See: https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-blocks-pin_class
""" # noqa: E501
NONE = 0
OPEN = 1
OUTPUT = 2
INPUT = 3
class ChannelList(namedtuple('ChannelList', 'index info')):
"""VPR `x_list` and `y_list` tags in the channels
"""
class Channels(namedtuple(
'Channels', 'chan_width_max x_min y_min x_max y_max x_list y_list')):
"""Encapsulation for VPR channels tag
See: https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-channel-channel
""" # noqa: E501
class SwitchTiming(namedtuple('SwitchTiming',
'r c_in c_out c_internal t_del')):
"""Encapsulation for timing attributes of a VPR switch
see: https://vtr-verilog-to-routing.readthedocs.io/en/latest/arch/reference.html#switches
"""
class SwitchSizing(namedtuple('SwitchSizing', 'mux_trans_size buf_size')):
"""Encapsulation for sizing attributes of a VPR switch
see: https://vtr-verilog-to-routing.readthedocs.io/en/latest/arch/reference.html#switches
"""
class Switch(namedtuple('Switch', 'id name type timing sizing')):
"""Encapsulate VPR switch tag. Contains SwitchTiming and SwitchSizing tuples.
see: https://vtr-verilog-to-routing.readthedocs.io/en/latest/arch/reference.html#switches
"""
class SegmentTiming(namedtuple('SegmentTiming', 'r_per_meter c_per_meter')):
"""Encapsulation for timing attributes of a VPR segment.
see: https://vtr-verilog-to-routing.readthedocs.io/en/latest/arch/reference.html#wire-segments
"""
class Segment(namedtuple('Segment', 'id name timing')):
"""Encapsulate VPR segment tag. Contains SegmentTiming to encapsulate the timing attributes
see: https://vtr-verilog-to-routing.readthedocs.io/en/latest/arch/reference.html#wire-segments
"""
class Pin(namedtuple('Pin', 'ptc name')):
"""Encapsulation for VPR Pin tag
See: https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-blocks-pin
""" # noqa: E501
class PinClass(namedtuple('PinClass', 'type pin')):
"""Encapsulation for VPR PinClass tag
See: https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-blocks-pin_class
""" # noqa: E501
class BlockType(namedtuple('BlockType', 'id name width height pin_class')):
"""Encapsulation for VPR BlockType tag
See: https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-blocks-block_type
""" # noqa: E501
class GridLoc(namedtuple('GridLoc',
'x y block_type_id width_offset height_offset')):
"""
"""
class NodeTiming(namedtuple('NodeTiming', 'r c')):
"""https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-nodes-timing
"""
class NodeLoc(namedtuple('NodeLoc', 'x_low y_low x_high y_high side ptc')):
"""https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-nodes-loc
"""
class NodeMetadata(namedtuple('NodeMetadata',
'name x_offset y_offset z_offset value')):
"""https://vtr-verilog-to-routing.readthedocs.io/en/latest/arch/reference.html#architecture-metadata
"""
class NodeSegment(namedtuple('NodeSegment', 'segment_id')):
"""https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-nodes-segment
"""
class Node(namedtuple(
'Node', 'id type direction capacity loc timing metadata segment')):
"""https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-nodes-node
"""
class Edge(namedtuple('Edge', 'src_node sink_node switch_id metadata')):
"""https://vtr-verilog-to-routing.readthedocs.io/en/latest/vpr/file_formats.html#tag-edges-edge
"""
class GraphInput(namedtuple('GraphInput',
'switches segments block_types grid')):
"""Top level encapsulation of input Graph
"""
def process_track(track):
channel_model = channel2.Channel(track)
channel_model.pack_tracks()
return channel_model
class Graph(object):
""" Simple object for working with VPR RR graph.
This class does not handle serialization. A format specific class handles
serdes takes.
"""
def __init__(
self,
switches,
segments,
block_types,
grid,
nodes,
edges=None,
build_pin_edges=True
):
self.switches = switches
self.next_switch_id = max(switch.id for switch in self.switches) + 1
self.switch_name_map = {}
self.delayless_switch = None
for idx, switch in enumerate(self.switches):
assert idx == switch.id
assert switch.name not in self.switch_name_map
self.switch_name_map[switch.name] = switch.id
assert '__vpr_delayless_switch__' in self.switch_name_map, self.switch_name_map.keys(
)
self.delayless_switch = self.switch_name_map['__vpr_delayless_switch__'
]
self.segments = segments
self.segment_name_map = {}
for idx, segment in enumerate(self.segments):
assert idx == segment.id
assert segment.name not in self.segment_name_map
self.segment_name_map[segment.name] = segment.id
self.block_types = block_types
self.grid = grid
self.tracks = []
self.nodes = nodes
self.nodes.sort(key=lambda node: node.id)
self.edges = edges if edges is not None else []
# Map of (x, y) to GridLoc definitions.
self.loc_map = {}
# Maps grid location and pin class index to node index
# (x, y, pin class idx) -> node_idx
self.loc_pin_class_map = {}
# Maps grid location and pin index to node index
# (x, y, pin idx) -> [(node_idx, side)]
self.loc_pin_map = {}
# Maps pin name to block type id and pin idx.
# pin name -> block type id, pin class idx, pin idx
self.pin_name_map = {}
self.pin_ptc_to_name_map = {}
# Create pin_name_map and sanity check block_types.
for idx, block_type in enumerate(self.block_types):
assert idx == block_type.id
for pin_class_idx, pin_class in enumerate(block_type.pin_class):
for pin in pin_class.pin:
assert pin.name not in self.pin_name_map
self.pin_name_map[
pin.name] = (block_type.id, pin_class_idx, pin.ptc)
self.pin_ptc_to_name_map[(block_type.id,
pin.ptc)] = pin.name
# Create mapping from grid locations and pins to nodes.
for idx, node in enumerate(self.nodes):
assert node.id == idx, (idx, node)
if node.type in (
NodeType.IPIN,
NodeType.OPIN,
):
key = (node.loc.x_low, node.loc.y_low, node.loc.ptc)
if key not in self.loc_pin_map:
self.loc_pin_map[key] = []
self.loc_pin_map[key].append((node.id, node.loc.side))
if node.type in (
NodeType.SOURCE,
NodeType.SINK,
):
key = (node.loc.x_low, node.loc.y_low, node.loc.ptc)
assert key not in self.loc_pin_class_map, (
node, self.loc_pin_class_map[key]
)
self.loc_pin_class_map[key] = node.id
# Rebuild initial edges of IPIN -> SINK and SOURCE -> OPIN.
for loc in grid:
assert loc.block_type_id >= 0 and loc.block_type_id <= len(
self.block_types
), loc.block_type_id
block_type = self.block_types[loc.block_type_id]
key = (loc.x, loc.y)
assert key not in self.loc_map
self.loc_map[key] = loc
for pin_class_idx, pin_class in enumerate(block_type.pin_class):
pin_class_node = self.loc_pin_class_map[
(loc.x, loc.y, pin_class_idx)]
# Skip building IPIN -> SINK and OPIN -> SOURCE graph if edges
# are not required.
if not build_pin_edges:
continue
for pin in pin_class.pin:
for pin_node, _ in self.loc_pin_map[(loc.x, loc.y,
pin.ptc)]:
if pin_class.type == PinType.OUTPUT:
self.add_edge(
src_node=pin_class_node,
sink_node=pin_node,
switch_id=self.delayless_switch
)
elif pin_class.type == PinType.INPUT:
self.add_edge(
src_node=pin_node,
sink_node=pin_class_node,
switch_id=self.delayless_switch,
)
else:
assert False, (loc, pin_class)
def _create_node(
self,
type,
direction,
loc,
segment,
timing,
capacity=1,
metadata=None,
):
if timing is None:
if type in (NodeType.CHANX, NodeType.CHANY):
timing = NodeTiming(r=1, c=1)
else:
timing = NodeTiming(r=0, c=0)
self.nodes.append(
Node(
id=len(self.nodes),
type=type,
direction=direction,
capacity=capacity,
loc=loc,
timing=timing,
metadata=metadata,
segment=segment,
)
)
return self.nodes[-1].id
def get_segment_id_from_name(self, segment_name):
return self.segment_name_map[segment_name]
def get_delayless_switch_id(self):
return self.delayless_switch
def add_track(
self,
track,
segment_id,
capacity=1,
timing=None,
name=None,
ptc=None,
direction=NodeDirection.BI_DIR,
):
"""Take a Track and add node to the graph with supplimental data"""
if track.direction == 'X':
node_type = NodeType.CHANX
elif track.direction == 'Y':
node_type = NodeType.CHANY
else:
assert False, track
if name is not None:
metadata = [
NodeMetadata(
name=name,
x_offset=0,
y_offset=0,
z_offset=0,
value='',
)
]
else:
metadata = None
self.tracks.append(
self._create_node(
type=node_type,
direction=direction,
capacity=capacity,
loc=NodeLoc(
x_low=track.x_low,
y_low=track.y_low,
x_high=track.x_high,
y_high=track.y_high,
side=None,
ptc=ptc,
),
timing=timing,
segment=NodeSegment(segment_id=segment_id),
metadata=metadata,
)
)
return self.tracks[-1]
def create_pin_name_from_tile_type_and_pin(
self, tile_type, port_name, pin_idx=0
):
return '{}.{}[{}]'.format(tile_type, port_name, pin_idx)
def create_pin_name_from_tile_type_sub_tile_num_and_pin(
self, tile_type, sub_tile_num, port_name, pin_idx=0
):
return '{}[{}].{}[{}]'.format(
tile_type, sub_tile_num, port_name, pin_idx
)
def get_nodes_for_pin(self, loc, pin_name):
block_type_id, pin_class_idx, pin_idx = self.pin_name_map[pin_name]
grid_loc = self.loc_map[loc]
assert grid_loc.block_type_id == block_type_id
return self.loc_pin_map[(loc[0], loc[1], pin_idx)]
def _create_edge(
self, src_node, sink_node, switch_id, name=None, value=''
):
assert src_node >= 0 and src_node < len(self.nodes), src_node
assert sink_node >= 0 and sink_node < len(self.nodes), sink_node
assert switch_id >= 0 and switch_id < len(self.switches), switch_id
if name is not None:
metadata = [
NodeMetadata(
name=name, x_offset=0, y_offset=0, z_offset=0, value=value
)
]
else:
metadata = None
return Edge(
src_node=src_node,
sink_node=sink_node,
switch_id=switch_id,
metadata=metadata
)
def add_edge(self, src_node, sink_node, switch_id, name=None, value=''):
"""Add Edge to the graph
Appends a new edge to the graph and retruns the index in the edges list
"""
self.edges.append(
self._create_edge(
src_node=src_node,
sink_node=sink_node,
switch_id=switch_id,
name=name,
value=value
)
)
return len(self.edges) - 1
def add_switch(self, switch):
""" Inner add_switch method. Do not invoke directly.
This method adds a switch into the graph model. This method should
not be invoked directly, instead invoke add_switch on the serialization
graph object (e.g. rr_graph_xml.graph2.add_switch, etc).
"""
switch_dict = switch._asdict()
switch_dict['id'] = self.next_switch_id
self.next_switch_id += 1
switch = Switch(**switch_dict)
assert switch.name not in self.switch_name_map
self.switch_name_map[switch.name] = switch.id
self.switches.append(switch)
return switch.id
def check_ptc(self):
for node in self.nodes:
assert node.loc.ptc is not None, node
def set_track_ptc(self, track, ptc):
node_d = self.nodes[track]._asdict()
loc_d = self.nodes[track].loc._asdict()
assert loc_d['ptc'] is None
loc_d['ptc'] = ptc
node_d['loc'] = NodeLoc(**loc_d)
self.nodes[track] = Node(**node_d)
def create_channels(self, pad_segment, pool=None):
""" Pack tracks into channels and return Channels definition for tracks."""
assert len(self.tracks) > 0
xs = []
ys = []
for track in self.tracks:
track_node = self.nodes[track]
xs.append(track_node.loc.x_low)
xs.append(track_node.loc.x_high)
ys.append(track_node.loc.y_low)
ys.append(track_node.loc.y_high)
x_tracks = {}
y_tracks = {}
for track in self.tracks:
track_node = self.nodes[track]
if track_node.type == NodeType.CHANX:
assert track_node.loc.y_low == track_node.loc.y_high
x1, x2 = sorted((track_node.loc.x_low, track_node.loc.x_high))
if track_node.loc.y_low not in x_tracks:
x_tracks[track_node.loc.y_low] = []
x_tracks[track_node.loc.y_low].append((x1, x2, track))
elif track_node.type == NodeType.CHANY:
assert track_node.loc.x_low == track_node.loc.x_high
y1, y2 = sorted((track_node.loc.y_low, track_node.loc.y_high))
if track_node.loc.x_low not in y_tracks:
y_tracks[track_node.loc.x_low] = []
y_tracks[track_node.loc.x_low].append((y1, y2, track))
else:
assert False, track_node
x_list = []
y_list = []
x_channel_models = {}
y_channel_models = {}
if pool is not None:
for y in x_tracks:
x_channel_models[y] = pool.apply_async(
process_track, (x_tracks[y], )
)
for x in y_tracks:
y_channel_models[x] = pool.apply_async(
process_track, (y_tracks[x], )
)
for y in progressbar_utils.progressbar(range(max(x_tracks) + 1)):
if y in x_tracks:
if pool is None:
x_channel_models[y] = process_track(x_tracks[y])
else:
x_channel_models[y] = x_channel_models[y].get()
x_list.append(len(x_channel_models[y].trees))
for idx, tree in enumerate(x_channel_models[y].trees):
for i in tree:
self.set_track_ptc(track=i[2], ptc=idx)
else:
x_list.append(0)
for x in progressbar_utils.progressbar(range(max(y_tracks) + 1)):
if x in y_tracks:
if pool is None:
y_channel_models[x] = process_track(y_tracks[x])
else:
y_channel_models[x] = y_channel_models[x].get()
y_list.append(len(y_channel_models[x].trees))
for idx, tree in enumerate(y_channel_models[x].trees):
for i in tree:
self.set_track_ptc(track=i[2], ptc=idx)
else:
y_list.append(0)
x_min = min(xs)
y_min = min(ys)
x_max = max(xs)
y_max = max(ys)
num_padding = 0
for chan, channel_model in x_channel_models.items():
for ptc, start, end in channel_model.fill_empty(max(x_min, 1),
x_max):
num_padding += 1
self.add_track(
track=Track(
direction='X',
x_low=start,
y_low=chan,
x_high=end,
y_high=chan,
),
segment_id=pad_segment,
capacity=0,
timing=None,
ptc=ptc
)
for chan, channel_model in y_channel_models.items():
for ptc, start, end in channel_model.fill_empty(max(y_min, 1),
y_max):
num_padding += 1
self.add_track(
track=Track(
direction='Y',
x_low=chan,
y_low=start,
x_high=chan,
y_high=end,
),
segment_id=pad_segment,
capacity=0,
timing=None,
ptc=ptc
)
print('Number padding nodes {}'.format(num_padding))
return Channels(
chan_width_max=max(max(x_list), max(y_list)),
x_min=x_min,
y_min=y_min,
x_max=x_max,
y_max=y_max,
x_list=[ChannelList(idx, info) for idx, info in enumerate(x_list)],
y_list=[ChannelList(idx, info) for idx, info in enumerate(y_list)],
)
def block_type_at_loc(self, loc):
return self.block_types[self.loc_map[loc].block_type_id].name
def get_switch_id(self, switch_name):
return self.switch_name_map[switch_name]
def sort_nodes(self):
self.nodes.sort(key=lambda node: node.id)