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