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