blob: 2bb250a086ae7ba2963be08162d5777b6b765df7 [file] [log] [blame] [edit]
#!/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()