| #!/usr/bin/env python3 | |
| import argparse | |
| import pickle | |
| import re | |
| from lib.rr_graph import tracks | |
| import lib.rr_graph.graph2 as rr | |
| import lib.rr_graph_xml.graph2 as rr_xml | |
| from lib import progressbar_utils | |
| from data_structs import Loc, ConnectionType | |
| from utils import fixup_pin_name | |
| from rr_utils import add_node, add_track, add_edge, connect | |
| from switchbox_model import SwitchboxModel, QmuxSwitchboxModel | |
| # ============================================================================= | |
| def is_hop(connection): | |
| """ | |
| Returns True if a connection represents a HOP wire. | |
| """ | |
| if connection.src.type == ConnectionType.SWITCHBOX and \ | |
| connection.dst.type == ConnectionType.SWITCHBOX: | |
| return True | |
| return False | |
| def is_tile(connection): | |
| """ | |
| Rtturns True for connections going to/from tile. | |
| """ | |
| if connection.src.type == ConnectionType.SWITCHBOX and \ | |
| connection.dst.type == ConnectionType.TILE: | |
| return True | |
| if connection.src.type == ConnectionType.TILE and \ | |
| connection.dst.type == ConnectionType.SWITCHBOX: | |
| return True | |
| return False | |
| def is_direct(connection): | |
| """ | |
| Returns True if the connections spans two tiles directly. Not necessarly | |
| at the same location | |
| """ | |
| if connection.src.type == ConnectionType.TILE and \ | |
| connection.dst.type == ConnectionType.TILE and \ | |
| connection.is_direct is True: | |
| return True | |
| return False | |
| def is_clock(connection): | |
| """ | |
| Returns True if the connection spans two clock cells | |
| """ | |
| if connection.src.type == ConnectionType.CLOCK or \ | |
| connection.dst.type == ConnectionType.CLOCK: | |
| return True | |
| return False | |
| def is_local(connection): | |
| """ | |
| Returns true if a connection is local. | |
| """ | |
| return (connection.src.loc.x, connection.src.loc.y) == \ | |
| (connection.dst.loc.x, connection.dst.loc.y) | |
| # ============================================================================= | |
| def get_vpr_switch_for_clock_cell(graph, cell, src, dst): | |
| # Get a switch to model the mux delay properly. First try using | |
| # the cell name | |
| try: | |
| switch_name = "{}.{}.{}.{}".format(cell.type, cell.name, src, dst) | |
| switch_id = graph.get_switch_id(switch_name) | |
| except KeyError: | |
| # Not found, try using the cell type | |
| try: | |
| switch_name = "{}.{}.{}.{}".format(cell.type, cell.type, src, dst) | |
| switch_id = graph.get_switch_id(switch_name) | |
| except KeyError: | |
| # Still not found, use the generic one | |
| switch_id = graph.get_switch_id("generic") | |
| print( | |
| "WARNING: No VPR switch found for '{}.{}' to '{}.{}'".format( | |
| cell.name, src, cell.name, dst | |
| ) | |
| ) | |
| return switch_id | |
| class QmuxModel(object): | |
| """ | |
| A model of a QMUX cell implemented in the "route through" manner using | |
| RR nodes and edges. | |
| A QMUX has the following clock inputs | |
| - 0: QCLKIN0 | |
| - 1: QCLKIN1 | |
| - 2: QCLKIN2 | |
| - 3: HSCKIN (from routing) | |
| The selection is controlled by a binary value of {IS1, IS0}. Both of the | |
| pins are connected to the switchbox. | |
| Since the QMUX is to be modelled using routing resources only, the part | |
| of the switchbox (of the whole switchbox) controlling the IS0 and IS1 pins | |
| will be removed. Appropriate fasm features will be attached to the edges | |
| that will model the QMUX. This will mimic the switchbox operation. | |
| The HSCKIN input is not modelled so the global clock network cannot be | |
| entered at a QMUX. | |
| """ | |
| def __init__( | |
| self, graph, cell, phy_loc, switchbox_model, connections, node_map | |
| ): | |
| self.graph = graph | |
| self.cell = cell | |
| self.phy_loc = phy_loc | |
| self.switchbox_model = switchbox_model | |
| self.connections = [c for c in connections if is_clock(c)] | |
| self.connection_loc_to_node = node_map | |
| self.ctrl_routes = {} | |
| self._build() | |
| def _build(self): | |
| """ | |
| Builds the QMUX cell model | |
| """ | |
| # Get routes for control pins | |
| self.ctrl_routes = self.switchbox_model.ctrl_routes[self.cell.name] | |
| # Check the routes, there has to be only one per const source | |
| for pin, pin_routes in self.ctrl_routes.items(): | |
| for net in pin_routes.keys(): | |
| assert len(pin_routes[net] | |
| ) == 1, (self.cell.name, pin, net, pin_routes[net]) | |
| pin_routes[net] = pin_routes[net][0] | |
| # Get segment id | |
| segment_id = self.graph.get_segment_id_from_name("clock") | |
| # Get the GMUX to QMUX connections | |
| nodes = {} | |
| for connection in self.connections: | |
| if connection.dst.type == ConnectionType.CLOCK: | |
| dst_cell, dst_pin = connection.dst.pin.split(".") | |
| if dst_cell == self.cell.name and dst_pin.startswith("QCLKIN"): | |
| ep = connection.dst | |
| try: | |
| nodes[dst_pin] = self.connection_loc_to_node[ep] | |
| except KeyError: | |
| print( | |
| "ERROR: Coulnd't find rr node for {}.{}".format( | |
| self.cell.name, pin | |
| ) | |
| ) | |
| # Get the QMUX to CAND connection | |
| for connection in self.connections: | |
| if connection.src.type == ConnectionType.CLOCK: | |
| src_cell, src_pin = connection.src.pin.split(".") | |
| if src_cell == self.cell.name and src_pin == "IZ": | |
| ep = connection.src | |
| try: | |
| nodes["IZ"] = self.connection_loc_to_node[ep] | |
| except KeyError: | |
| print( | |
| "ERROR: Coulnd't find rr node for {}.{}".format( | |
| self.cell.name, pin | |
| ) | |
| ) | |
| # Validate | |
| for pin in ["IZ", "QCLKIN0", "QCLKIN1", "QCLKIN2"]: | |
| if pin not in nodes: | |
| return | |
| # Add edges modelling the QMUX | |
| for i in [0, 1, 2]: | |
| pin = "QCLKIN{}".format(i) | |
| src_node = nodes[pin] | |
| dst_node = nodes["IZ"] | |
| # Make edge metadata | |
| metadata = self._get_metadata(i) | |
| if len(metadata): | |
| meta_name = "fasm_features" | |
| meta_value = "\n".join(metadata) | |
| else: | |
| meta_name = None | |
| meta_value = "" | |
| switch_id = get_vpr_switch_for_clock_cell( | |
| self.graph, | |
| self.cell, | |
| "QCLKIN0", # FIXME: Always use the QCLKIN0->IZ timing here | |
| "IZ" | |
| ) | |
| # Mux switch with appropriate timing and fasm metadata | |
| connect( | |
| self.graph, | |
| src_node, | |
| dst_node, | |
| switch_id=switch_id, | |
| segment_id=segment_id, | |
| meta_name=meta_name, | |
| meta_value=meta_value, | |
| ) | |
| def _get_metadata(self, selection): | |
| """ | |
| Formats fams metadata for the QMUX cell that enables the given QMUX | |
| input selection. | |
| """ | |
| metadata = [] | |
| # Map selection to {IS1, IS0}. | |
| # FIXME: Seems suspicious... Need to swap IS0 and IS1 ? | |
| SEL_TO_PINS = { | |
| 0: { | |
| "IS1": "GND", | |
| "IS0": "GND" | |
| }, | |
| 1: { | |
| "IS1": "VCC", | |
| "IS0": "GND" | |
| }, | |
| 2: { | |
| "IS1": "GND", | |
| "IS0": "VCC" | |
| }, | |
| } | |
| assert selection in SEL_TO_PINS, selection | |
| pins = SEL_TO_PINS[selection] | |
| # Format prefix | |
| prefix = "X{}Y{}.QMUX.QMUX".format(self.phy_loc.x, self.phy_loc.y) | |
| # Collect features | |
| for pin, net in pins.items(): | |
| # Get switchbox routing features (already prefixed) | |
| for muxsel in self.ctrl_routes[pin][net]: | |
| stage_id, switch_id, mux_id, pin_id = muxsel | |
| stage = self.switchbox_model.switchbox.stages[stage_id] | |
| metadata += SwitchboxModel.get_metadata_for_mux( | |
| self.phy_loc, stage, switch_id, mux_id, pin_id | |
| ) | |
| # These features control inverters on IS0 and IS1. The inverters | |
| # are not used hence they are always disabled. | |
| zinv_features = [ | |
| "I_invblock.I_J0.ZINV.IS0", | |
| "I_invblock.I_J1.ZINV.IS1", | |
| "I_invblock.I_J2.ZINV.IS0", | |
| "I_invblock.I_J3.ZINV.IS0", | |
| "I_invblock.I_J4.ZINV.IS1", | |
| ] | |
| for f in zinv_features: | |
| feature = "{}.{}".format(prefix, f) | |
| metadata.append(feature) | |
| return metadata | |
| class CandModel(object): | |
| """ | |
| A model of a CAND cell implemented in the "route through" manner using | |
| RR nodes and edges. | |
| A CAND cell has aninput IC connected to QMUX via a dedicated route, an | |
| output connected to its clock column and a dynamic enable input EN | |
| connected to the routing network. | |
| We won't use the enable input EN so its not modelled in any way. The | |
| CAND cell is statically enabled or disabled via the bitstream. There is | |
| a single edge that models the cell with appropriate fasm features attached. | |
| """ | |
| def __init__( | |
| self, graph, cell, phy_loc, connections, node_map, cand_node_map | |
| ): | |
| self.graph = graph | |
| self.cell = cell | |
| self.phy_loc = phy_loc | |
| self.connections = [c for c in connections if is_clock(c)] | |
| self.connection_loc_to_node = node_map | |
| self.cand_node_map = cand_node_map | |
| self._build() | |
| def _build(self): | |
| # Get segment id | |
| segment_id = self.graph.get_segment_id_from_name("clock") | |
| # Get the CAND name | |
| cand_name = self.cell.name.split("_", maxsplit=1)[0] | |
| # Get the column clock entry node | |
| col_node = self.cand_node_map[cand_name][self.cell.loc] | |
| # Get the QMUX to CAND connection | |
| for connection in self.connections: | |
| if connection.dst.type == ConnectionType.CLOCK: | |
| dst_cell, dst_pin = connection.dst.pin.split(".") | |
| if dst_cell == self.cell.name and dst_pin == "IC": | |
| ep = connection.dst | |
| break | |
| else: | |
| print( | |
| "ERROR: Coulnd't find rr node for {}.{}".format( | |
| self.cell.name, "IC" | |
| ) | |
| ) | |
| return | |
| # Get the node for the connection destination | |
| row_node = self.connection_loc_to_node[ep] | |
| # Edge metadata that when used switches the CAND cell from the | |
| # "Static Disable" to "Static Enable" mode. | |
| metadata = self._get_metadata() | |
| if len(metadata): | |
| meta_name = "fasm_features" | |
| meta_value = "\n".join(metadata) | |
| else: | |
| meta_name = None | |
| meta_value = "" | |
| # Get switch | |
| switch_id = get_vpr_switch_for_clock_cell( | |
| self.graph, self.cell, "IC", "IZ" | |
| ) | |
| # Mux switch with appropriate timing and fasm metadata | |
| connect( | |
| self.graph, | |
| row_node, | |
| col_node, | |
| switch_id=switch_id, | |
| segment_id=segment_id, | |
| meta_name=meta_name, | |
| meta_value=meta_value, | |
| ) | |
| def _get_metadata(self): | |
| """ | |
| Formats a list of fasm features to be appended to the CAND modelling | |
| edge. | |
| """ | |
| metadata = [] | |
| # Format prefix | |
| prefix = "X{}Y{}".format(self.phy_loc.x, self.phy_loc.y) | |
| # Get the CAND name | |
| cand_name = self.cell.name.split("_", maxsplit=1)[0] | |
| # Format the final fasm line | |
| metadata.append("{}.{}.I_hilojoint".format(prefix, cand_name)) | |
| return metadata | |
| # ============================================================================= | |
| def get_node_id_for_tile_pin(graph, loc, tile_type, pin_name): | |
| """ | |
| Returns a rr node associated with the given pin of the given tile type | |
| at the given location. | |
| """ | |
| nodes = None | |
| # First try without the capacity prefix | |
| if loc.z == 0: | |
| rr_pin_name = "TL-{}.{}[0]".format(tile_type, fixup_pin_name(pin_name)) | |
| try: | |
| nodes = graph.get_nodes_for_pin((loc.x, loc.y), rr_pin_name) | |
| except KeyError: | |
| pass | |
| # Didn't find, try with the capacity prefix | |
| if nodes is None: | |
| rr_pin_name = "TL-{}[{}].{}[0]".format( | |
| tile_type, loc.z, fixup_pin_name(pin_name) | |
| ) | |
| try: | |
| nodes = graph.get_nodes_for_pin((loc.x, loc.y), rr_pin_name) | |
| except KeyError: | |
| pass | |
| # Still not found. | |
| if nodes is None: | |
| return None | |
| # Got it | |
| assert len(nodes) == 1, (rr_pin_name, loc) | |
| return nodes[0][0] | |
| def build_tile_pin_to_node_map(graph, nodes_by_id, tile_types, tile_grid): | |
| """ | |
| Builds a map of tile pins (at given location!) to rr nodes. | |
| """ | |
| node_map = {} | |
| # Build the map for each tile instance in the grid. | |
| for loc, tile in tile_grid.items(): | |
| node_map[loc] = {} | |
| # Empty tiles do not have pins | |
| if tile is None: | |
| continue | |
| # For each pin of the tile | |
| for pin in tile_types[tile.type].pins: | |
| node_id = get_node_id_for_tile_pin(graph, loc, tile.type, pin.name) | |
| if node_id is None: | |
| print( | |
| "WARNING: No node for pin '{}' at {}".format( | |
| pin.name, loc | |
| ) | |
| ) | |
| continue | |
| # Convert to Node objects | |
| node = nodes_by_id[node_id] | |
| # Add to the map | |
| node_map[loc][pin.name] = node | |
| return node_map | |
| def build_tile_connection_map(graph, nodes_by_id, tile_grid, connections): | |
| """ | |
| Builds a map of connections to/from tiles and rr nodes. | |
| """ | |
| node_map = {} | |
| # Adds entry to the map | |
| def add_to_map(conn_loc): | |
| tile = tile_grid.get(conn_loc.loc, None) | |
| if tile is None: | |
| print( | |
| "WARNING: No tile for pin '{} at {}".format( | |
| conn_loc.pin, conn_loc.loc | |
| ) | |
| ) | |
| return | |
| # Get the VPR rr node for the pin | |
| node_id = get_node_id_for_tile_pin( | |
| graph, conn_loc.loc, tile.type, conn_loc.pin | |
| ) | |
| if node_id is None: | |
| print( | |
| "WARNING: No node for pin '{}' at ({},{})".format( | |
| conn_loc.pin, conn_loc.loc.x, conn_loc.loc.y | |
| ) | |
| ) | |
| return | |
| # Convert to Node objects | |
| node = nodes_by_id[node_id] | |
| # Add to the map | |
| node_map[conn_loc] = node | |
| # Look for connection endpoints that mention tiles | |
| endpoints = set( | |
| [c.src for c in connections if c.src.type == ConnectionType.TILE] | |
| ) | |
| endpoints |= set( | |
| [c.dst for c in connections if c.dst.type == ConnectionType.TILE] | |
| ) | |
| # Build the map | |
| for ep in endpoints: | |
| add_to_map(ep) | |
| return node_map | |
| # ============================================================================= | |
| def add_l_track(graph, x0, y0, x1, y1, segment_id, switch_id): | |
| """ | |
| Add a "L"-shaped track consisting of two channel nodes and a switch | |
| between the given two grid coordinates. The (x0, y0) determines source | |
| location and (x1, y1) destination (sink) location. | |
| Returns a tuple with indices of the first and last node. | |
| """ | |
| dx = x1 - x0 | |
| dy = y1 - y0 | |
| assert dx != 0 or dy != 0, (x0, y0) | |
| nodes = [None, None] | |
| # Go vertically first | |
| if abs(dy) >= abs(dx): | |
| xc, yc = x0, y1 | |
| if abs(dy): | |
| track = tracks.Track( | |
| direction="Y", | |
| x_low=min(x0, xc), | |
| x_high=max(x0, xc), | |
| y_low=min(y0, yc), | |
| y_high=max(y0, yc), | |
| ) | |
| nodes[0] = add_track(graph, track, segment_id) | |
| if abs(dx): | |
| track = tracks.Track( | |
| direction="X", | |
| x_low=min(xc, x1), | |
| x_high=max(xc, x1), | |
| y_low=min(yc, y1), | |
| y_high=max(yc, y1), | |
| ) | |
| nodes[1] = add_track(graph, track, segment_id) | |
| # Go horizontally first | |
| else: | |
| xc, yc = x1, y0 | |
| if abs(dx): | |
| track = tracks.Track( | |
| direction="X", | |
| x_low=min(x0, xc), | |
| x_high=max(x0, xc), | |
| y_low=min(y0, yc), | |
| y_high=max(y0, yc), | |
| ) | |
| nodes[0] = add_track(graph, track, segment_id) | |
| if abs(dy): | |
| track = tracks.Track( | |
| direction="Y", | |
| x_low=min(xc, x1), | |
| x_high=max(xc, x1), | |
| y_low=min(yc, y1), | |
| y_high=max(yc, y1), | |
| ) | |
| nodes[1] = add_track(graph, track, segment_id) | |
| # In case of a horizontal or vertical only track make both nodes the same | |
| assert nodes[0] is not None or nodes[1] is not None | |
| if nodes[0] is None: | |
| nodes[0] = nodes[1] | |
| if nodes[1] is None: | |
| nodes[1] = nodes[0] | |
| # Add edge connecting the two nodes if needed | |
| if nodes[0].id != nodes[1].id: | |
| add_edge(graph, nodes[0].id, nodes[1].id, switch_id) | |
| return nodes | |
| def add_track_chain(graph, direction, u, v0, v1, segment_id, switch_id): | |
| """ | |
| Adds a chain of tracks that span the grid in the given direction. | |
| Returns the first and last node of the chain along with a map of | |
| coordinates to nodes. | |
| """ | |
| node_by_v = {} | |
| prev_node = None | |
| # Make range generator | |
| if v0 > v1: | |
| coords = range(v0, v1 - 1, -1) | |
| else: | |
| coords = range(v0, v1 + 1) | |
| # Add track chain | |
| for v in coords: | |
| # Add track (node) | |
| if direction == "X": | |
| track = tracks.Track( | |
| direction=direction, | |
| x_low=v, | |
| x_high=v, | |
| y_low=u, | |
| y_high=u, | |
| ) | |
| elif direction == "Y": | |
| track = tracks.Track( | |
| direction=direction, | |
| x_low=u, | |
| x_high=u, | |
| y_low=v, | |
| y_high=v, | |
| ) | |
| else: | |
| assert False, direction | |
| curr_node = add_track(graph, track, segment_id) | |
| # Add edge from the previous one | |
| if prev_node is not None: | |
| add_edge(graph, prev_node.id, curr_node.id, switch_id) | |
| # No previous one, this is the first one | |
| else: | |
| start_node = curr_node | |
| node_by_v[v] = curr_node | |
| prev_node = curr_node | |
| return start_node, curr_node, node_by_v | |
| def add_tracks_for_const_network(graph, const, tile_grid): | |
| """ | |
| Builds a network of CHANX/CHANY and edges to propagate signal from a | |
| const source. | |
| The const network is purely artificial and does not correspond to any | |
| physical routing resources. | |
| Returns a map of const network nodes for each location. | |
| """ | |
| # Get the tilegrid span | |
| xs = set([loc.x for loc in tile_grid]) | |
| ys = set([loc.y for loc in tile_grid]) | |
| xmin, ymin = min(xs), min(ys) | |
| xmax, ymax = max(xs), max(ys) | |
| # Get segment id and switch id | |
| segment_id = graph.get_segment_id_from_name(const.lower()) | |
| switch_id = graph.get_delayless_switch_id() | |
| # Find the source tile | |
| src_loc = [ | |
| loc for loc, t in tile_grid.items() | |
| if t is not None and t.type == "SYN_{}".format(const) | |
| ] | |
| assert len(src_loc) == 1, const | |
| src_loc = src_loc[0] | |
| # Go down from the source to the edge of the tilegrid | |
| entry_node, col_node, _ = add_track_chain( | |
| graph, "Y", src_loc.x, src_loc.y, 1, segment_id, switch_id | |
| ) | |
| # Connect the tile OPIN to the column | |
| pin_name = "TL-SYN_{const}.{const}0_{const}[0]".format(const=const) | |
| opin_node = graph.get_nodes_for_pin((src_loc[0], src_loc[1]), pin_name) | |
| assert len(opin_node) == 1, pin_name | |
| add_edge(graph, opin_node[0][0], entry_node.id, switch_id) | |
| # Got left and right from the source column over the bottommost row | |
| row_entry_node1, _, row_node_map1 = add_track_chain( | |
| graph, "X", 0, src_loc.x, 1, segment_id, switch_id | |
| ) | |
| row_entry_node2, _, row_node_map2 = add_track_chain( | |
| graph, "X", 0, src_loc.x + 1, xmax - 1, segment_id, switch_id | |
| ) | |
| # Connect rows to the column | |
| add_edge(graph, col_node.id, row_entry_node1.id, switch_id) | |
| add_edge(graph, col_node.id, row_entry_node2.id, switch_id) | |
| row_node_map = {**row_node_map1, **row_node_map2} | |
| row_node_map[0] = row_node_map[1] | |
| # For each column add one that spand over the entire grid height | |
| const_node_map = {} | |
| for x in range(xmin, xmax): | |
| # Add the column | |
| col_entry_node, _, col_node_map = add_track_chain( | |
| graph, "Y", x, ymin + 1, ymax - 1, segment_id, switch_id | |
| ) | |
| # Add edge fom the horizontal row | |
| add_edge(graph, row_node_map[x].id, col_entry_node.id, switch_id) | |
| # Populate the const node map | |
| for y, node in col_node_map.items(): | |
| const_node_map[Loc(x=x, y=y, z=0)] = node | |
| return const_node_map | |
| def create_track_for_hop_connection(graph, connection): | |
| """ | |
| Creates a HOP wire track for the given connection | |
| """ | |
| # Determine whether the wire goes horizontally or vertically. | |
| if connection.src.loc.y == connection.dst.loc.y: | |
| direction = "X" | |
| elif connection.src.loc.x == connection.dst.loc.x: | |
| direction = "Y" | |
| else: | |
| assert False, connection | |
| assert connection.src.loc != connection.dst.loc, connection | |
| # Determine the connection length | |
| length = max( | |
| abs(connection.src.loc.x - connection.dst.loc.x), | |
| abs(connection.src.loc.y - connection.dst.loc.y) | |
| ) | |
| segment_name = "hop{}".format(length) | |
| # Add the track to the graph | |
| track = tracks.Track( | |
| direction=direction, | |
| x_low=min(connection.src.loc.x, connection.dst.loc.x), | |
| x_high=max(connection.src.loc.x, connection.dst.loc.x), | |
| y_low=min(connection.src.loc.y, connection.dst.loc.y), | |
| y_high=max(connection.src.loc.y, connection.dst.loc.y), | |
| ) | |
| node = add_track( | |
| graph, track, graph.get_segment_id_from_name(segment_name) | |
| ) | |
| return node | |
| # ============================================================================= | |
| def populate_hop_connections(graph, switchbox_models, connections): | |
| """ | |
| Populates HOP connections | |
| """ | |
| # Process connections | |
| bar = progressbar_utils.progressbar | |
| conns = [c for c in connections if is_hop(c)] | |
| for connection in bar(conns): | |
| # Get switchbox models | |
| src_switchbox_model = switchbox_models[connection.src.loc] | |
| dst_switchbox_model = switchbox_models[connection.dst.loc] | |
| # Get nodes | |
| src_node = src_switchbox_model.get_output_node(connection.src.pin) | |
| dst_node = dst_switchbox_model.get_input_node(connection.dst.pin) | |
| # Do not add the connection if one of the nodes is missing | |
| if src_node is None or dst_node is None: | |
| continue | |
| # Create the hop wire, use it as output node of the switchbox | |
| hop_node = create_track_for_hop_connection(graph, connection) | |
| # Connect | |
| connect(graph, src_node, hop_node) | |
| connect(graph, hop_node, dst_node) | |
| def populate_tile_connections( | |
| graph, switchbox_models, connections, connection_loc_to_node | |
| ): | |
| """ | |
| Populates switchbox to tile and tile to switchbox connections | |
| """ | |
| # Process connections | |
| bar = progressbar_utils.progressbar | |
| conns = [c for c in connections if is_tile(c)] | |
| for connection in bar(conns): | |
| # Connection to/from the local tile | |
| if is_local(connection): | |
| loc = connection.src.loc | |
| # No switchbox model at the loc, skip. | |
| if loc not in switchbox_models: | |
| continue | |
| # Get the switchbox model (both locs are the same) | |
| switchbox_model = switchbox_models[loc] | |
| # To tile | |
| if connection.dst.type == ConnectionType.TILE: | |
| if connection.dst not in connection_loc_to_node: | |
| print( | |
| "WARNING: No IPIN node for connection {}". | |
| format(connection) | |
| ) | |
| continue | |
| tile_node = connection_loc_to_node[connection.dst] | |
| sbox_node = switchbox_model.get_output_node(connection.src.pin) | |
| if sbox_node is None: | |
| continue | |
| connect(graph, sbox_node, tile_node) | |
| # From tile | |
| if connection.src.type == ConnectionType.TILE: | |
| if connection.src not in connection_loc_to_node: | |
| print( | |
| "WARNING: No OPIN node for connection {}". | |
| format(connection) | |
| ) | |
| continue | |
| tile_node = connection_loc_to_node[connection.src] | |
| sbox_node = switchbox_model.get_input_node(connection.dst.pin) | |
| if sbox_node is None: | |
| continue | |
| connect(graph, tile_node, sbox_node) | |
| # Connection to/from a foreign tile | |
| else: | |
| # Get segment id and switch id | |
| segment_id = graph.get_segment_id_from_name("special") | |
| switch_id = graph.get_delayless_switch_id() | |
| # Add a track connecting the two locations | |
| src_node, dst_node = add_l_track( | |
| graph, connection.src.loc.x, connection.src.loc.y, | |
| connection.dst.loc.x, connection.dst.loc.y, segment_id, | |
| switch_id | |
| ) | |
| # Connect the track | |
| eps = [connection.src, connection.dst] | |
| for i, ep in enumerate(eps): | |
| # Endpoint at tile | |
| if ep.type == ConnectionType.TILE: | |
| # To tile | |
| if ep == connection.dst: | |
| if ep not in connection_loc_to_node: | |
| print( | |
| "WARNING: No IPIN node for connection {}". | |
| format(connection) | |
| ) | |
| continue | |
| node = connection_loc_to_node[ep] | |
| connect(graph, dst_node, node, switch_id) | |
| # From tile | |
| elif ep == connection.src: | |
| if ep not in connection_loc_to_node: | |
| print( | |
| "WARNING: No OPIN node for connection {}". | |
| format(connection) | |
| ) | |
| continue | |
| node = connection_loc_to_node[ep] | |
| connect(graph, node, src_node, switch_id) | |
| # Endpoint at switchbox | |
| elif ep.type == ConnectionType.SWITCHBOX: | |
| # No switchbox model at the loc, skip. | |
| if ep.loc not in switchbox_models: | |
| continue | |
| # Get the switchbox model (both locs are the same) | |
| switchbox_model = switchbox_models[ep.loc] | |
| # To switchbox | |
| if ep == connection.dst: | |
| sbox_node = switchbox_model.get_input_node(ep.pin) | |
| if sbox_node is None: | |
| continue | |
| connect(graph, dst_node, sbox_node) | |
| # From switchbox | |
| elif ep == connection.src: | |
| sbox_node = switchbox_model.get_output_node(ep.pin) | |
| if sbox_node is None: | |
| continue | |
| connect(graph, sbox_node, src_node) | |
| def populate_direct_connections(graph, connections, connection_loc_to_node): | |
| """ | |
| Populates all direct tile-to-tile connections. | |
| """ | |
| # Process connections | |
| bar = progressbar_utils.progressbar | |
| conns = [c for c in connections if is_direct(c)] | |
| for connection in bar(conns): | |
| # Get segment id and switch id | |
| if connection.src.pin.startswith("CLOCK"): | |
| switch_id = graph.get_delayless_switch_id() | |
| else: | |
| switch_id = graph.get_delayless_switch_id() | |
| # Get tile nodes | |
| src_tile_node = connection_loc_to_node.get(connection.src, None) | |
| dst_tile_node = connection_loc_to_node.get(connection.dst, None) | |
| # Couldn't find at least one endpoint node | |
| if src_tile_node is None or dst_tile_node is None: | |
| if src_tile_node is None: | |
| print( | |
| "WARNING: No OPIN node for direct connection {}". | |
| format(connection) | |
| ) | |
| if dst_tile_node is None: | |
| print( | |
| "WARNING: No IPIN node for direct connection {}". | |
| format(connection) | |
| ) | |
| continue | |
| # Add the edge | |
| add_edge(graph, src_tile_node.id, dst_tile_node.id, switch_id) | |
| def populate_const_connections( | |
| graph, switchbox_models, tile_types, tile_grid, tile_pin_to_node, | |
| const_node_map | |
| ): | |
| """ | |
| Connects switchbox inputs that represent VCC and GND constants to | |
| nodes of the global const network. | |
| Also connect FAKE_CONST pins of tiles directly to the global const network. | |
| """ | |
| bar = progressbar_utils.progressbar | |
| # Connect the global const network to switchbox inputs | |
| for loc, switchbox_model in bar(switchbox_models.items()): | |
| # Look for input connected to a const | |
| for pin in switchbox_model.switchbox.inputs.values(): | |
| # Got a const input | |
| if pin.name in const_node_map: | |
| const_node = const_node_map[pin.name][loc] | |
| sbox_node = switchbox_model.get_input_node(pin.name) | |
| if sbox_node is None: | |
| continue | |
| connect( | |
| graph, | |
| const_node, | |
| sbox_node, | |
| ) | |
| # Add edges from the global const network to FAKE_CONST pins of tiles | |
| # that bypass the switchbox. | |
| switch_id = graph.get_switch_id("generic") | |
| for loc, tile in bar(tile_grid.items()): | |
| if tile is None: | |
| continue | |
| tile_type = tile_types[tile.type] | |
| if tile_type.fake_const_pin: | |
| tile_node = tile_pin_to_node[loc]["FAKE_CONST"] | |
| for const in ["GND", "VCC"]: | |
| const_node = const_node_map[const][loc] | |
| connect(graph, const_node, tile_node, switch_id=switch_id) | |
| def populate_cand_connections(graph, switchbox_models, cand_node_map): | |
| """ | |
| Populates global clock network to switchbox connections. These all the | |
| CANDn inputs of a switchbox. | |
| """ | |
| bar = progressbar_utils.progressbar | |
| for loc, switchbox_model in bar(switchbox_models.items()): | |
| # Look for input connected to a CAND | |
| for pin in switchbox_model.switchbox.inputs.values(): | |
| # Got a CAND input | |
| if pin.name in cand_node_map: | |
| cand_node = cand_node_map[pin.name][loc] | |
| sbox_node = switchbox_model.get_input_node(pin.name) | |
| if sbox_node is None: | |
| continue | |
| connect( | |
| graph, | |
| cand_node, | |
| sbox_node, | |
| ) | |
| # ============================================================================= | |
| def create_quadrant_clock_tracks(graph, connections, connection_loc_to_node): | |
| """ | |
| Creates tracks representing global clock network routes namely all | |
| connections between GMUXes and QMUXes as well as QMUXes to CANDs. | |
| """ | |
| node_map = {} | |
| # Get segment id and switch id | |
| segment_id = graph.get_segment_id_from_name("clock") | |
| switch_id = graph.get_delayless_switch_id() | |
| # Process connections | |
| bar = progressbar_utils.progressbar | |
| conns = [c for c in connections if is_clock(c)] | |
| for connection in bar(conns): | |
| # Source is a tile | |
| if connection.src.type == ConnectionType.TILE: | |
| src_node = connection_loc_to_node.get(connection.src, None) | |
| if src_node is None: | |
| print( | |
| "WARNING: No OPIN node for clock connection {}". | |
| format(connection) | |
| ) | |
| continue | |
| # Source is a switchbox. Skip as control inputs of CAND and QMUX are | |
| # not to be routed to a switchbox. | |
| elif connection.src.type == ConnectionType.SWITCHBOX: | |
| continue | |
| # Source is another global clock cell, do not connect it anywhere now. | |
| elif connection.src.type == ConnectionType.CLOCK: | |
| src_node = None | |
| else: | |
| assert False, connection | |
| # Destination is a tile | |
| if connection.dst.type == ConnectionType.TILE: | |
| dst_node = connection_loc_to_node.get(connection.dst, None) | |
| if dst_node is None: | |
| print( | |
| "WARNING: No IPIN node for clock connection {}". | |
| format(connection) | |
| ) | |
| continue | |
| # Destination is another global clock cell, do not connect it anywhere | |
| # now. | |
| elif connection.dst.type == ConnectionType.CLOCK: | |
| dst_node = None | |
| else: | |
| assert False, connection | |
| # Add a track connecting the two locations | |
| # Some CAND cells share the same physical location as QMUX cells. | |
| # In that case add a single "jump" node | |
| if connection.src.loc == connection.dst.loc: | |
| src_track_node = add_node( | |
| graph, connection.src.loc, "X", segment_id | |
| ) | |
| dst_track_node = src_track_node | |
| else: | |
| src_track_node, dst_track_node = add_l_track( | |
| graph, connection.src.loc.x, connection.src.loc.y, | |
| connection.dst.loc.x, connection.dst.loc.y, segment_id, | |
| switch_id | |
| ) | |
| # Connect the OPIN | |
| if src_node is not None: | |
| connect(graph, src_node, src_track_node) | |
| # Add to the node map. | |
| else: | |
| ep = connection.src | |
| # If not already there, add it | |
| if ep not in node_map: | |
| node_map[ep] = src_track_node | |
| # Add a connection to model the fan-out | |
| else: | |
| connect(graph, node_map[ep], src_track_node) | |
| # Connect the IPIN | |
| if dst_node is not None: | |
| connect(graph, dst_track_node, dst_node) | |
| # Add to the node map. Since this is a destination there cannot be any | |
| # fan-in. | |
| else: | |
| ep = connection.dst | |
| assert ep not in node_map, ep | |
| node_map[ep] = dst_track_node | |
| return node_map | |
| def create_column_clock_tracks(graph, clock_cells, quadrants): | |
| """ | |
| This function adds tracks for clock column routes. It returns a map of | |
| "assess points" to that tracks to be used by switchbox connections. | |
| """ | |
| CAND_RE = re.compile( | |
| r"^(?P<name>CAND[0-4])_(?P<quad>[A-Z]+)_(?P<col>[0-9]+)$" | |
| ) | |
| # Get segment id and switch id | |
| segment_id = graph.get_segment_id_from_name("clock") | |
| switch_id = graph.get_delayless_switch_id() | |
| # Process CAND cells | |
| cand_node_map = {} | |
| for cell in clock_cells.values(): | |
| # A clock column is defined by a CAND cell | |
| if cell.type != "CAND": | |
| continue | |
| # Get index and quadrant | |
| match = CAND_RE.match(cell.name) | |
| if not match: | |
| continue | |
| cand_name = match.group("name") | |
| cand_quad = match.group("quad") | |
| quadrant = quadrants[cand_quad] | |
| # Add track chains going upwards and downwards from the CAND cell | |
| up_entry_node, _, up_node_map = add_track_chain( | |
| graph, "Y", cell.loc.x, cell.loc.y, quadrant.y0, segment_id, | |
| switch_id | |
| ) | |
| dn_entry_node, _, dn_node_map = add_track_chain( | |
| graph, "Y", cell.loc.x, cell.loc.y + 1, quadrant.y1, segment_id, | |
| switch_id | |
| ) | |
| # Connect entry nodes | |
| cand_entry_node = up_entry_node | |
| add_edge(graph, cand_entry_node.id, dn_entry_node.id, switch_id) | |
| # Join node maps | |
| node_map = {**up_node_map, **dn_node_map} | |
| # Populate the global clock network to switchbox access map | |
| for y, node in node_map.items(): | |
| loc = Loc(x=cell.loc.x, y=y, z=0) | |
| if cand_name not in cand_node_map: | |
| cand_node_map[cand_name] = {} | |
| cand_node_map[cand_name][loc] = node | |
| return cand_node_map | |
| # ============================================================================= | |
| def yield_edges(edges): | |
| """ | |
| Yields edges in a format acceptable by the graph serializer. | |
| """ | |
| conns = set() | |
| # Process edges | |
| for edge in edges: | |
| # Reformat metadata | |
| if edge.metadata: | |
| metadata = [(meta.name, meta.value) for meta in edge.metadata] | |
| else: | |
| metadata = None | |
| # Check for repetition | |
| if (edge.src_node, edge.sink_node) in conns: | |
| print( | |
| "WARNING: Removing duplicated edge from {} to {}, metadata='{}'" | |
| .format(edge.src_node, edge.sink_node, metadata) | |
| ) | |
| continue | |
| conns.add((edge.src_node, edge.sink_node)) | |
| # Yield the edge | |
| yield (edge.src_node, edge.sink_node, edge.switch_id, metadata) | |
| # ============================================================================= | |
| def main(): | |
| # Parse arguments | |
| parser = argparse.ArgumentParser( | |
| description=__doc__, | |
| formatter_class=argparse.RawDescriptionHelpFormatter | |
| ) | |
| parser.add_argument( | |
| "--vpr-db", type=str, required=True, help="VPR database file" | |
| ) | |
| parser.add_argument( | |
| "--rr-graph-in", | |
| type=str, | |
| required=True, | |
| help="Input RR graph XML file" | |
| ) | |
| parser.add_argument( | |
| "--rr-graph-out", | |
| type=str, | |
| default="rr_graph.xml", | |
| help="Output RR graph XML file (def. rr_graph.xml)" | |
| ) | |
| args = parser.parse_args() | |
| # Load data from the database | |
| print("Loading database...") | |
| with open(args.vpr_db, "rb") as fp: | |
| db = pickle.load(fp) | |
| vpr_quadrants = db["vpr_quadrants"] | |
| vpr_clock_cells = db["vpr_clock_cells"] | |
| loc_map = db["loc_map"] | |
| vpr_tile_types = db["vpr_tile_types"] | |
| vpr_tile_grid = db["vpr_tile_grid"] | |
| vpr_switchbox_types = db["vpr_switchbox_types"] | |
| vpr_switchbox_grid = db["vpr_switchbox_grid"] | |
| connections = db["connections"] | |
| switches = db["switches"] | |
| # Load the routing graph, build SOURCE -> OPIN and IPIN -> SINK edges. | |
| print("Loading rr graph...") | |
| xml_graph = rr_xml.Graph( | |
| input_file_name=args.rr_graph_in, | |
| output_file_name=args.rr_graph_out, | |
| progressbar=progressbar_utils.progressbar | |
| ) | |
| # Add back the switches that were unused in the arch.xml and got pruned | |
| # byt VPR. | |
| for switch in switches: | |
| try: | |
| xml_graph.graph.get_switch_id(switch.name) | |
| continue | |
| except KeyError: | |
| xml_graph.add_switch( | |
| rr.Switch( | |
| id=None, | |
| name=switch.name, | |
| type=rr.SwitchType[switch.type.upper()], | |
| timing=rr.SwitchTiming( | |
| r=switch.r, | |
| c_in=switch.c_in, | |
| c_out=switch.c_out, | |
| c_internal=switch.c_int, | |
| t_del=switch.t_del, | |
| ), | |
| sizing=rr.SwitchSizing( | |
| mux_trans_size=0, | |
| buf_size=0, | |
| ), | |
| ) | |
| ) | |
| print("Building maps...") | |
| # Add a switch map to the graph | |
| switch_map = {} | |
| for switch in xml_graph.graph.switches: | |
| assert switch.id not in switch_map, switch | |
| switch_map[switch.id] = switch | |
| xml_graph.graph.switch_map = switch_map | |
| # Build node id to node map | |
| nodes_by_id = {node.id: node for node in xml_graph.graph.nodes} | |
| # Build tile pin names to rr node ids map | |
| tile_pin_to_node = build_tile_pin_to_node_map( | |
| xml_graph.graph, nodes_by_id, vpr_tile_types, vpr_tile_grid | |
| ) | |
| # Add const network | |
| const_node_map = {} | |
| for const in ["VCC", "GND"]: | |
| m = add_tracks_for_const_network(xml_graph.graph, const, vpr_tile_grid) | |
| const_node_map[const] = m | |
| # Connection loc (endpoint) to node map. Map ConnectionLoc objects to VPR | |
| # rr graph node ids. | |
| connection_loc_to_node = {} | |
| # Build a map of connections to/from tiles and rr nodes. The map points | |
| # to an IPIN/OPIN node for a connection loc that mentions it. | |
| node_map = build_tile_connection_map( | |
| xml_graph.graph, nodes_by_id, vpr_tile_grid, connections | |
| ) | |
| connection_loc_to_node.update(node_map) | |
| # Build the global clock network | |
| print("Building the global clock network...") | |
| # GMUX to QMUX and QMUX to CAND tracks | |
| node_map = create_quadrant_clock_tracks( | |
| xml_graph.graph, connections, connection_loc_to_node | |
| ) | |
| connection_loc_to_node.update(node_map) | |
| # Clock column tracks | |
| cand_node_map = create_column_clock_tracks( | |
| xml_graph.graph, vpr_clock_cells, vpr_quadrants | |
| ) | |
| # Add switchbox models. | |
| print("Building switchbox models...") | |
| switchbox_models = {} | |
| # Gather QMUX cells | |
| qmux_cells = {} | |
| for cell in vpr_clock_cells.values(): | |
| if cell.type == "QMUX": | |
| loc = cell.loc | |
| if loc not in qmux_cells: | |
| qmux_cells[loc] = {} | |
| qmux_cells[loc][cell.name] = cell | |
| # Create the models | |
| for loc, type in vpr_switchbox_grid.items(): | |
| phy_loc = loc_map.bwd[loc] | |
| # QMUX switchbox model | |
| if loc in qmux_cells: | |
| switchbox_models[loc] = QmuxSwitchboxModel( | |
| graph=xml_graph.graph, | |
| loc=loc, | |
| phy_loc=phy_loc, | |
| switchbox=vpr_switchbox_types[type], | |
| qmux_cells=qmux_cells[loc], | |
| connections=[c for c in connections if is_clock(c)] | |
| ) | |
| # Regular switchbox model | |
| else: | |
| switchbox_models[loc] = SwitchboxModel( | |
| graph=xml_graph.graph, | |
| loc=loc, | |
| phy_loc=phy_loc, | |
| switchbox=vpr_switchbox_types[type], | |
| ) | |
| # Build switchbox models | |
| for switchbox_model in progressbar_utils.progressbar( | |
| switchbox_models.values()): | |
| switchbox_model.build() | |
| # Build the global clock network cell models | |
| print("Building QMUX and CAND models...") | |
| # Add QMUX and CAND models | |
| for cell in progressbar_utils.progressbar(vpr_clock_cells.values()): | |
| phy_loc = loc_map.bwd[cell.loc] | |
| if cell.type == "QMUX": | |
| QmuxModel( | |
| graph=xml_graph.graph, | |
| cell=cell, | |
| phy_loc=phy_loc, | |
| switchbox_model=switchbox_models[cell.loc], | |
| connections=connections, | |
| node_map=connection_loc_to_node | |
| ) | |
| if cell.type == "CAND": | |
| CandModel( | |
| graph=xml_graph.graph, | |
| cell=cell, | |
| phy_loc=phy_loc, | |
| connections=connections, | |
| node_map=connection_loc_to_node, | |
| cand_node_map=cand_node_map | |
| ) | |
| # Populate connections to the switchbox models | |
| print("Populating connections...") | |
| populate_hop_connections(xml_graph.graph, switchbox_models, connections) | |
| populate_tile_connections( | |
| xml_graph.graph, switchbox_models, connections, connection_loc_to_node | |
| ) | |
| populate_direct_connections( | |
| xml_graph.graph, connections, connection_loc_to_node | |
| ) | |
| populate_cand_connections(xml_graph.graph, switchbox_models, cand_node_map) | |
| populate_const_connections( | |
| xml_graph.graph, switchbox_models, vpr_tile_types, vpr_tile_grid, | |
| tile_pin_to_node, const_node_map | |
| ) | |
| # Create channels from tracks | |
| pad_segment_id = xml_graph.graph.get_segment_id_from_name("pad") | |
| channels_obj = xml_graph.graph.create_channels(pad_segment=pad_segment_id) | |
| # Remove padding channels | |
| print("Removing padding nodes...") | |
| xml_graph.graph.nodes = [ | |
| n for n in xml_graph.graph.nodes if n.capacity > 0 | |
| ] | |
| # Build node id to node map again since there have been new nodes added. | |
| nodes_by_id = {node.id: node for node in xml_graph.graph.nodes} | |
| # Sanity check edges | |
| print("Sanity checking edges...") | |
| node_ids = set([n.id for n in xml_graph.graph.nodes]) | |
| for edge in xml_graph.graph.edges: | |
| assert edge.src_node in node_ids, edge | |
| assert edge.sink_node in node_ids, edge | |
| assert edge.src_node != edge.sink_node, edge | |
| # Sanity check IPIN/OPIN connections. There must be no tile completely | |
| # disconnected from the routing network | |
| print("Sanity checking tile connections...") | |
| connected_locs = set() | |
| for edge in xml_graph.graph.edges: | |
| src = nodes_by_id[edge.src_node] | |
| dst = nodes_by_id[edge.sink_node] | |
| if src.type == rr.NodeType.OPIN: | |
| loc = (src.loc.x_low, src.loc.y_low) | |
| connected_locs.add(loc) | |
| if dst.type == rr.NodeType.IPIN: | |
| loc = (src.loc.x_low, src.loc.y_low) | |
| connected_locs.add(loc) | |
| non_empty_locs = set( | |
| (loc.x, loc.y) for loc in xml_graph.graph.grid if loc.block_type_id > 0 | |
| ) | |
| unconnected_locs = non_empty_locs - connected_locs | |
| for loc in unconnected_locs: | |
| block_type = xml_graph.graph.block_type_at_loc(loc) | |
| print( | |
| " ERROR: Tile '{}' at ({}, {}) is not connected!".format( | |
| block_type, loc[0], loc[1] | |
| ) | |
| ) | |
| # Write the routing graph | |
| nodes_obj = xml_graph.graph.nodes | |
| edges_obj = xml_graph.graph.edges | |
| print("Serializing the rr graph...") | |
| xml_graph.serialize_to_xml( | |
| channels_obj=channels_obj, | |
| nodes_obj=nodes_obj, | |
| edges_obj=yield_edges(edges_obj), | |
| node_remap=lambda x: x, | |
| ) | |
| # ============================================================================= | |
| if __name__ == "__main__": | |
| main() |