blob: c54b8a022a773213b08403b2b1b205f9720ffc07 [file] [log] [blame] [edit]
import prjxray.db
from prjxray.roi import Roi
from prjxray.overlay import Overlay
from prjxray import grid_types
import simplejson as json
from lib import progressbar_utils
import datetime
import functools
from collections import namedtuple
from lib.rr_graph import tracks
from lib.rr_graph import graph2
from prjxray.site_type import SitePinDirection
from prjxray_constant_site_pins import yield_ties_to_wire
from lib.connection_database import get_track_model, get_wire_in_tile_from_pin_name
from lib.rr_graph.graph2 import NodeType
import re
import math
import numpy
from prjxray_db_cache import DatabaseCache
now = datetime.datetime.now
def get_node_type(conn, graph_node_pkey):
""" Returns the node type of a given graph node"""
c = conn.cursor()
c.execute(
"""
SELECT graph_node_type FROM graph_node WHERE pkey = ?""",
(graph_node_pkey, )
)
return c.fetchone()[0]
def get_pins(conn, site_type, site_pin):
""" Returns a set of the pin graph_nodes related to the input site type and pin names."""
c = conn.cursor()
c.execute(
"""
WITH pins(wire_in_tile_pkey) AS (
SELECT wire_in_tile.pkey FROM wire_in_tile
INNER JOIN site_pin ON site_pin.pkey = wire_in_tile.site_pin_pkey
INNER JOIN site_type ON site_pin.site_type_pkey = site_type.pkey
WHERE
site_type.name == ?
AND
site_pin.name == ?
)
SELECT graph_node.pkey FROM graph_node
INNER JOIN wire ON graph_node.node_pkey = wire.node_pkey
WHERE
wire.wire_in_tile_pkey IN (SELECT wire_in_tile_pkey FROM pins);
""", (
site_type,
site_pin,
)
)
return set(graph_node_pkey for (graph_node_pkey, ) in c.fetchall())
def add_graph_nodes_for_pins(conn, tile_type, wire, pin_directions):
""" Adds graph_node rows for each pin on a wire in a tile. """
(wire_in_tile_pkeys, site_pin_pkey) = get_wire_in_tile_from_pin_name(
conn=conn, tile_type_str=tile_type, wire_str=wire
)
# Determine if this should be an IPIN or OPIN based on the site_pin
# direction.
c = conn.cursor()
c.execute(
"""
SELECT direction FROM site_pin WHERE pkey = ?;""", (site_pin_pkey, )
)
(pin_direction, ) = c.fetchone()
pin_direction = SitePinDirection(pin_direction)
if pin_direction == SitePinDirection.IN:
node_type = NodeType.IPIN
elif pin_direction == SitePinDirection.OUT:
node_type = NodeType.OPIN
# FIXME: Support INOUT pins
elif pin_direction == SitePinDirection.INOUT:
node_type = NodeType.OPIN
else:
assert False, pin_direction
write_cur = conn.cursor()
write_cur.execute("""BEGIN EXCLUSIVE TRANSACTION;""")
for wire_in_tile_pkey in wire_in_tile_pkeys.values():
# Find all instances of this specific wire.
c.execute(
"""
SELECT pkey, node_pkey, tile_pkey
FROM wire WHERE wire_in_tile_pkey = ?;""",
(wire_in_tile_pkey, )
)
c3 = conn.cursor()
for wire_pkey, node_pkey, tile_pkey in c:
c3.execute(
"""
SELECT grid_x, grid_y FROM tile WHERE pkey = ?;""",
(tile_pkey, )
)
grid_x, grid_y = c3.fetchone()
updates = []
values = []
# Insert a graph_node per pin_direction.
for pin_direction in pin_directions:
write_cur.execute(
"""
INSERT INTO graph_node(
graph_node_type, node_pkey, x_low, x_high, y_low, y_high)
VALUES (?, ?, ?, ?, ?, ?)""", (
node_type.value,
node_pkey,
grid_x,
grid_x,
grid_y,
grid_y,
)
)
updates.append(
'{}_graph_node_pkey = ?'.format(
pin_direction.name.lower()
)
)
values.append(write_cur.lastrowid)
assert len(updates) > 0, (updates, wire_in_tile_pkey, wire_pkey)
# Update the wire with the graph_nodes in each direction, if
# applicable.
write_cur.execute(
"""
UPDATE wire SET {updates} WHERE pkey = ?;""".format(
updates=','.join(updates)
), values + [wire_pkey]
)
write_cur.execute("""COMMIT TRANSACTION;""")
write_cur.connection.commit()
class KnownSwitch(object):
def __init__(self, switch_pkey):
self.switch_pkey = switch_pkey
def get_pip_switch(self, src_wire_pkey, dest_wire_pkey):
assert src_wire_pkey is None
assert dest_wire_pkey is None
return self.switch_pkey
class Pip(object):
def __init__(self, c, tile_type, pip):
self.c = c
c.execute(
"""
SELECT
pkey,
src_wire_in_tile_pkey,
dest_wire_in_tile_pkey,
switch_pkey,
backward_switch_pkey,
is_directional,
is_pseudo,
can_invert
FROM
pip_in_tile
WHERE
name = ?
AND tile_type_pkey = (
SELECT
pkey
FROM
tile_type
WHERE
name = ?
);""", (pip, tile_type)
)
result = c.fetchone()
assert result is not None, (tile_type, pip)
(
self.pip_pkey, self.src_wire_in_tile_pkey,
self.dest_wire_in_tile_pkey, self.switch_pkey,
self.backward_switch_pkey, self.is_directional, self.is_pseudo,
self.can_invert
) = result
assert self.switch_pkey is not None, (pip, tile_type)
if self.is_directional:
assert self.switch_pkey == self.backward_switch_pkey
def __iter__(self):
yield "pip_pkey", self.pip_pkey
yield "src_wire_in_tile_pkey", self.src_wire_in_tile_pkey
yield "dest_wire_in_tile_pkey", self.dest_wire_in_tile_pkey
yield "switch_pkey", self.switch_pkey
yield "backward_switch_pkey", self.backward_switch_pkey
yield "is_directional", self.is_directional
yield "is_pseudo", self.is_pseudo
yield "can_invert", self.can_invert
def get_pip_switch(self, src_wire_pkey, dest_wire_pkey):
""" Return the switch_pkey for the given connection.
Selects either normal or backward switch from pip, or if switch is
already known, returns known switch.
It is not valid to provide a switch and provide src/dest/pip arguments.
Arguments
---------
src_wire_pkey : int
Source wire row primary key.
dest_wire_pkey : int
Destination wire row primary key.
Returns
-------
Switch row primary key to connect through specified pip.
"""
assert src_wire_pkey is not None
assert dest_wire_pkey is not None
if self.switch_pkey == self.backward_switch_pkey:
return self.switch_pkey
self.c.execute(
"SELECT wire_in_tile_pkey FROM wire WHERE pkey = ?",
(src_wire_pkey, )
)
src_wire_in_tile_pkey = self.c.fetchone()[0]
self.c.execute(
"SELECT wire_in_tile_pkey FROM wire WHERE pkey = ?",
(dest_wire_pkey, )
)
dest_wire_in_tile_pkey = self.c.fetchone()[0]
if src_wire_in_tile_pkey == self.src_wire_in_tile_pkey:
assert dest_wire_in_tile_pkey == self.dest_wire_in_tile_pkey
return self.switch_pkey
else:
assert src_wire_in_tile_pkey == self.dest_wire_in_tile_pkey
assert dest_wire_in_tile_pkey == self.src_wire_in_tile_pkey
return self.backward_switch_pkey
def create_find_pip(conn):
"""Returns a function that takes (tile_type, pip) and returns a tuple
containing: pip_in_tile_pkey, is_directional, is_pseudo, can_invert"""
c = conn.cursor()
@functools.lru_cache(maxsize=None)
def find_pip(tile_type, pip):
return Pip(c, tile_type, pip)
return find_pip
def create_find_wire(conn):
""" Returns a function finds a wire based on tile name and wire name.
Args:
conn: Database connection
Returns:
Function. See find_wire below for signature.
"""
c = conn.cursor()
@functools.lru_cache(maxsize=None)
def find_wire_in_tile(tile_type, wire):
c.execute(
"""
SELECT
pkey
FROM
wire_in_tile
WHERE
name = ?
AND phy_tile_type_pkey = (
SELECT
pkey
FROM
tile_type
WHERE
name = ?
);""", (wire, tile_type)
)
result = c.fetchone()
assert result is not None, (tile_type, wire)
return result[0]
@functools.lru_cache(maxsize=100000)
def find_wire(phy_tile, tile_type, wire):
""" Finds a wire in the database.
Args:
phy_tile (str): Physical tile name
tile_type (str): Type of tile name
wire (str): Wire name
Returns:
Tuple (wire_pkey, phy_tile_pkey, node_pkey), where:
wire_pkey (int): Primary key of wire table
tile_pkey (int): Primary key of VPR tile row that contains
this wire.
phy_tile_pkey (int): Primary key of physical tile row that
contains this wire.
node_pkey (int): Primary key of node table that is the node
this wire belongs too.
"""
wire_in_tile_pkey = find_wire_in_tile(tile_type, wire)
c.execute(
"""
SELECT
pkey,
tile_pkey,
phy_tile_pkey,
node_pkey
FROM
wire
WHERE
wire_in_tile_pkey = ?
AND phy_tile_pkey = (
SELECT
pkey
FROM
phy_tile
WHERE
name = ?
);""", (wire_in_tile_pkey, phy_tile)
)
result = c.fetchone()
assert result is not None, (
phy_tile, tile_type, wire, wire_in_tile_pkey
)
return result
return find_wire
Pins = namedtuple('Pins', 'x y edge_map site_pin_direction')
OPPOSITE_DIRECTIONS = {
tracks.Direction.TOP: tracks.Direction.BOTTOM,
tracks.Direction.BOTTOM: tracks.Direction.TOP,
tracks.Direction.LEFT: tracks.Direction.RIGHT,
tracks.Direction.RIGHT: tracks.Direction.LEFT,
}
class Connector(object):
""" Connector is an object for joining two nodes.
Connector represents either a site pin within a specific tile or routing
channel made of one or more channel nodes.
"""
def __init__(self, conn, pins=None, tracks=None):
""" Create a Connector object.
Provide either pins or tracks, not both or neither.
Args:
pins (Pins namedtuple): If this Connector object represents a
site pin, provide the pins named arguments.
tracks (tuple of (tracks.Tracks, list of graph nodes)): If this
Connector object represents a routing channel, provide the
tracks named argument.
The tuple can most easily be constructed via
connection_database.get_track_model, which builds the Tracks
models and the graph node list.
"""
self.conn = conn
self.pins = pins
self.tracks = tracks
self.track_connections = {}
assert (self.pins is not None) ^ (self.tracks is not None)
def find_wire_node(
self, wire_pkey, graph_node_pkey, track_graph_node_pkey
):
""" Find/create graph node for site pin.
In order to support site pin timing modelling, an additional node
is required to support the timing model. This function returns that
node, along with the switch that should be used to connect the
IPIN/OPIN to that node. See diagram for details.
Arguments
---------
wire_pkey : int
Wire primary key to a wire attached to a site pin.
graph_node_pkey : int
Graph node primary key that represents which IPIN/OPIN node is
being used to connect the site pin to the routing graph.
track_graph_node_pkey : int
Graph node primary key that represents the first routing node this
site pin connects too. See diagram for details.
Returns
-------
site_pin_switch_pkey : int
Switch primary key to the switch to connect IPIN/OPIN node to
new site pin wire node. See diagram for details.
site_pin_graph_node_pkey : int
Graph node primary key that represents site pin wire node.
See diagram for details.
Diagram:
--+
| tile wire #1 +-----+ tile wire #2
+==>-------------->+ pip +--------------->
| ^-Site pin +-----+
--+
+----+ +-----+ +-----+
|OPIN+--edge #1->+CHAN1+--edge #2-->+CHAN2|->
+----+ +-----+ +-----+
The timing information from the site pin is encoded in edge #1.
The timing information from tile wire #1 is encoded in CHAN1.
The timing information from pip is encoded in edge #2.
The remaining timing information is encoded in edges and channels
as expected.
This function returns edge #1 as the site_pin_switch_pkey.
This function returns CHAN1 as site_pin_graph_node_pkey.
The diagram for an IPIN is the same, except reverse all the arrows.
"""
cur = self.conn.cursor()
cur.execute(
"""
SELECT site_wire_pkey FROM node WHERE pkey = (
SELECT node_pkey FROM wire WHERE pkey = ?
)
""", (wire_pkey, )
)
site_wire_pkey = cur.fetchone()[0]
cur.execute(
"""
SELECT
node_pkey,
top_graph_node_pkey,
bottom_graph_node_pkey,
right_graph_node_pkey,
left_graph_node_pkey,
site_pin_graph_node_pkey
FROM wire WHERE pkey = ?""", (site_wire_pkey, )
)
values = cur.fetchone()
node_pkey = values[0]
edge_nodes = values[1:5]
site_pin_graph_node_pkey = values[5]
cur.execute(
"""
SELECT
site_pin_switch_pkey
FROM
wire_in_tile
WHERE
site_pin_switch_pkey IS NOT NULL
AND
pkey IN (
SELECT
wire_in_tile_pkey
FROM
wire
WHERE
node_pkey IN (
SELECT
node_pkey
FROM
wire
WHERE
pkey = ?
)
)""", (wire_pkey, )
)
results = cur.fetchall()
assert len(results) == 1, (wire_pkey, results)
site_pin_switch_pkey = results[0][0]
assert site_pin_switch_pkey is not None, wire_pkey
assert graph_node_pkey in edge_nodes, (
wire_pkey, graph_node_pkey, track_graph_node_pkey, edge_nodes
)
if site_pin_graph_node_pkey is None:
assert track_graph_node_pkey is not None, (
wire_pkey, graph_node_pkey, track_graph_node_pkey, edge_nodes
)
is_lv_node = False
for (name, ) in cur.execute("""
SELECT wire_in_tile.name
FROM wire_in_tile
WHERE pkey IN (
SELECT wire_in_tile_pkey FROM wire WHERE node_pkey = ?
)""", (node_pkey, )):
if name.startswith('LV'):
is_lv_node = True
break
capacitance = 0
resistance = 0
for idx, (wire_cap, wire_res) in enumerate(cur.execute("""
SELECT wire_in_tile.capacitance, wire_in_tile.resistance
FROM wire_in_tile
WHERE pkey IN (
SELECT wire_in_tile_pkey FROM wire WHERE node_pkey = ?
)""", (node_pkey, ))):
capacitance += wire_cap
resistance + wire_res
if is_lv_node and idx == 1:
# Only use first 2 wire RC's, ignore the rest. It appears
# that some of the RC constant was lumped into the switch
# timing, so don't double count.
#
# FIXME: Note that this is a hack, and should be fixed if
# possible.
break
# This node does not exist, create it now
write_cur = self.conn.cursor()
write_cur.execute("INSERT INTO track DEFAULT VALUES")
new_track_pkey = write_cur.lastrowid
write_cur.execute(
"""
INSERT INTO
graph_node(
graph_node_type,
node_pkey,
x_low,
x_high,
y_low,
y_high,
capacity,
capacitance,
resistance,
track_pkey)
SELECT
graph_node_type,
?,
x_low,
x_high,
y_low,
y_high,
capacity,
?,
?,
?
FROM graph_node WHERE pkey = ?""", (
node_pkey,
capacitance,
resistance,
new_track_pkey,
track_graph_node_pkey,
)
)
site_pin_graph_node_pkey = write_cur.lastrowid
write_cur.execute(
"""
UPDATE wire SET site_pin_graph_node_pkey = ?
WHERE pkey = ?""", (
site_pin_graph_node_pkey,
wire_pkey,
)
)
write_cur.connection.commit()
return site_pin_switch_pkey, site_pin_graph_node_pkey
def get_edge_with_mux_switch(
self, src_wire_pkey, pip_pkey, dest_wire_pkey
):
""" Return switch_pkey for EDGE_WITH_MUX instance. """
cur = self.conn.cursor()
cur.execute(
"""
SELECT site_wire_pkey FROM node WHERE pkey = (
SELECT node_pkey FROM wire WHERE pkey = ?
);""", (src_wire_pkey, )
)
src_site_wire_pkey = cur.fetchone()[0]
cur.execute(
"""
SELECT site_wire_pkey FROM node WHERE pkey = (
SELECT node_pkey FROM wire WHERE pkey = ?
);""", (dest_wire_pkey, )
)
dest_site_wire_pkey = cur.fetchone()[0]
cur.execute(
"""
SELECT switch_pkey FROM edge_with_mux WHERE
src_wire_pkey = ?
AND
dest_wire_pkey = ?
AND
pip_in_tile_pkey = ?""", (
src_site_wire_pkey,
dest_site_wire_pkey,
pip_pkey,
)
)
result = cur.fetchone()
assert result is not None, (
src_site_wire_pkey,
dest_site_wire_pkey,
pip_pkey,
)
return result[0]
def find_connection_at_loc(self, loc):
assert self.tracks is not None
tracks_model, graph_nodes = self.tracks
if loc not in self.track_connections:
for idx in tracks_model.get_tracks_for_wire_at_coord(loc).values():
break
self.track_connections[loc] = idx
else:
idx = self.track_connections[loc]
assert idx is not None
return idx
def connect_at(
self,
loc,
other_connector,
pip,
src_wire_pkey=None,
dest_wire_pkey=None,
):
""" Connect two Connector objects at a location within the grid.
Arguments
---------
loc : prjxray.grid_types.GridLoc
Location within grid to make connection.
other_connector : Connector
Destination connection.
src_wire_pkey : int
Source wire pkey of pip being connected.
dest_wire_pkey : int
Destination wire pkey of pip being connected.
pip : Pip
Pip object of pip being connected.
Returns:
Tuple of (src_graph_node_pkey, dest_graph_node_pkey)
"""
if self.tracks and other_connector.tracks:
tracks_model, graph_nodes = self.tracks
idx1 = self.find_connection_at_loc(loc)
other_tracks_model, other_graph_nodes = other_connector.tracks
idx2 = other_connector.find_connection_at_loc(loc)
switch_pkey = pip.get_pip_switch(src_wire_pkey, dest_wire_pkey)
yield graph_nodes[idx1], switch_pkey, other_graph_nodes[
idx2], pip.pip_pkey
return
elif self.pins and other_connector.tracks:
assert self.pins.site_pin_direction == SitePinDirection.OUT
tracks_model, graph_nodes = other_connector.tracks
for pin_dir, idx in tracks_model.get_tracks_for_wire_at_coord(
grid_types.GridLoc(self.pins.x, self.pins.y)).items():
if pin_dir in self.pins.edge_map:
# Site pin -> Interconnect is modelled as:
#
# OPIN -> edge (Site pin) -> Wire CHAN -> edge (PIP) -> Interconnect CHAN node
#
src_node = self.pins.edge_map[pin_dir]
dest_track_node = graph_nodes[idx]
site_pin_switch_pkey, src_wire_node = self.find_wire_node(
src_wire_pkey, src_node, dest_track_node
)
switch_pkey = pip.get_pip_switch(
src_wire_pkey, dest_wire_pkey
)
yield (src_node, site_pin_switch_pkey, src_wire_node, None)
yield (
src_wire_node, switch_pkey, dest_track_node,
pip.pip_pkey
)
return
elif self.tracks and other_connector.pins:
assert other_connector.pins.site_pin_direction == SitePinDirection.IN
tracks_model, graph_nodes = self.tracks
for pin_dir, idx in tracks_model.get_tracks_for_wire_at_coord(
grid_types.GridLoc(other_connector.pins.x,
other_connector.pins.y)).items():
if pin_dir in other_connector.pins.edge_map:
# Interconnect -> Site pin is modelled as:
#
# Interconnect CHAN node -> edge (PIP) -> Wire CHAN -> edge (Site pin) -> IPIN
#
src_track_node = graph_nodes[idx]
dest_node = other_connector.pins.edge_map[pin_dir]
site_pin_switch_pkey, dest_wire_node = self.find_wire_node(
dest_wire_pkey, dest_node, src_track_node
)
switch_pkey = pip.get_pip_switch(
src_wire_pkey, dest_wire_pkey
)
yield (
src_track_node, switch_pkey, dest_wire_node,
pip.pip_pkey
)
yield (
dest_wire_node, site_pin_switch_pkey, dest_node, None
)
return
elif self.pins and other_connector.pins and not pip.is_pseudo:
assert self.pins.site_pin_direction == SitePinDirection.OUT, dict(
pip
)
assert other_connector.pins.site_pin_direction == SitePinDirection.IN, dict(
pip
)
switch_pkey = self.get_edge_with_mux_switch(
src_wire_pkey, pip.pip_pkey, dest_wire_pkey
)
if len(self.pins.edge_map) == 1 and len(
other_connector.pins.edge_map) == 1:
# If there is only one choice, make it.
src_node = list(self.pins.edge_map.values())[0]
dest_node = list(other_connector.pins.edge_map.values())[0]
yield (src_node, switch_pkey, dest_node, pip.pip_pkey)
return
for pin_dir in self.pins.edge_map:
if OPPOSITE_DIRECTIONS[pin_dir
] in other_connector.pins.edge_map:
src_node = self.pins.edge_map[pin_dir]
dest_node = other_connector.pins.edge_map[
OPPOSITE_DIRECTIONS[pin_dir]]
yield (src_node, switch_pkey, dest_node, pip.pip_pkey)
return
# If there is a pseudo pip that needs to be explicitly routed through,
# two CHAN nodes are first created before and after IPIN and OPIN and
# connected with an edge accordingly
elif self.pins and other_connector.pins and pip.is_pseudo:
switch_pkey = pip.get_pip_switch(src_wire_pkey, dest_wire_pkey)
for pin_dir in self.pins.edge_map:
if pin_dir in other_connector.pins.edge_map:
src_node = self.pins.edge_map[pin_dir]
src_node_type = get_node_type(self.conn, src_node)
assert NodeType(
src_node_type
) == NodeType.IPIN, "src node for ppip is not an IPIN ({}, {})".format(
src_node, src_node_type
)
dest_node = other_connector.pins.edge_map[pin_dir]
dest_node_type = get_node_type(self.conn, dest_node)
assert NodeType(
dest_node_type
) == NodeType.OPIN, "dest node for ppip is not an OPIN ({}, {})".format(
src_node, dest_node_type
)
src_wire_switch_pkey, src_wire_node = self.find_wire_node(
src_wire_pkey, src_node, None
)
dest_wire_switch_pkey, dest_wire_node = self.find_wire_node(
dest_wire_pkey, dest_node, None
)
yield (
src_wire_node, switch_pkey, dest_wire_node,
pip.pip_pkey
)
return
assert False, (
self.tracks, self.pins, other_connector.tracks,
other_connector.pins, loc
)
def create_find_connector(conn):
""" Returns a function returns a Connector object for a given wire and node.
Args:
conn: Database connection
Returns:
Function. See find_connector below for signature.
"""
c = conn.cursor()
@functools.lru_cache(maxsize=100000)
def find_connector(wire_pkey, node_pkey):
""" Finds Connector for a wire and node in the database.
Args:
wire_pkey (int): Primary key into wire table of target wire
node_pkey (int): Primary key into node table of parent node of
specified wire.
Returns:
None if wire is disconnected, otherwise returns Connector objet.
"""
# Find all graph_nodes for this node.
c.execute(
"""
SELECT pkey, track_pkey, graph_node_type, x_low, x_high, y_low, y_high FROM graph_node
WHERE node_pkey = ?;""", (node_pkey, )
)
graph_nodes = c.fetchall()
# If there are no graph nodes, this wire is likely disconnected.
if len(graph_nodes) == 0:
return
# If this is a track (e.g. track_pkey is not NULL), then verify
# all graph_nodes for the specified node belong to the same track,
# and then retrieved and return the connector for the track.
track_pkey = graph_nodes[0][1]
if track_pkey is not None:
for node in graph_nodes:
assert node[1] == track_pkey
return Connector(
conn=conn, tracks=get_track_model(conn, track_pkey)
)
# Check if this node has a special track. This is being used to
# denote the GND and VCC track connections on TIEOFF HARD0 and HARD1.
c.execute(
"""
SELECT
track_pkey,
site_wire_pkey
FROM
node
WHERE
pkey = ?;""", (node_pkey, )
)
for track_pkey, site_wire_pkey in c:
if track_pkey is not None and site_wire_pkey is not None:
return Connector(
conn=conn, tracks=get_track_model(conn, track_pkey)
)
# This is not a track, so it must be a site pin. Make sure the
# graph_nodes share a type and verify that it is in fact a site pin.
node_type = graph2.NodeType(graph_nodes[0][2])
for node in graph_nodes:
assert node_type == graph2.NodeType(
node[2]
), (node_pkey, node_type, graph2.NodeType(node[2]))
assert node_type in [graph2.NodeType.IPIN, graph2.NodeType.OPIN]
if node_type == graph2.NodeType.IPIN:
site_pin_direction = SitePinDirection.IN
elif node_type == graph2.NodeType.OPIN:
site_pin_direction = SitePinDirection.OUT
else:
assert False, node_type
# Build the edge_map (map of edge direction to graph node).
c.execute(
"""
SELECT
top_graph_node_pkey,
bottom_graph_node_pkey,
left_graph_node_pkey,
right_graph_node_pkey
FROM
wire
WHERE
node_pkey = ?;""", (node_pkey, )
)
all_graph_node_pkeys = c.fetchall()
graph_node_pkeys = None
for keys in all_graph_node_pkeys:
if any(keys):
assert graph_node_pkeys is None
graph_node_pkeys = keys
# This wire may not have an connections, if so return now.
if graph_node_pkeys is None:
return
edge_map = {}
for edge, graph_node in zip(
(
tracks.Direction.TOP,
tracks.Direction.BOTTOM,
tracks.Direction.LEFT,
tracks.Direction.RIGHT,
),
graph_node_pkeys,
):
if graph_node is not None:
edge_map[edge] = graph_node
assert len(edge_map) == len(graph_nodes), (
edge_map, graph_node_pkeys, graph_nodes
)
# Make sure that all graph nodes for this wire are in the edge_map
# and at the same grid coordinate.
x = graph_nodes[0][3]
y = graph_nodes[0][5]
for pkey, _, _, x_low, x_high, y_low, y_high in graph_nodes:
assert x == x_low, (wire_pkey, node_pkey, x, x_low, x_high)
assert x == x_high, (wire_pkey, node_pkey, x, x_low, x_high)
assert y == y_low, (wire_pkey, node_pkey, y, y_low, y_high)
assert y == y_high, (wire_pkey, node_pkey, y, y_low, y_high)
assert pkey in edge_map.values(), (pkey, edge_map)
return Connector(
conn=conn,
pins=Pins(
edge_map=edge_map,
x=x,
y=y,
site_pin_direction=site_pin_direction,
)
)
return find_connector
def create_const_connectors(conn):
c = conn.cursor()
c.execute(
"""
SELECT vcc_track_pkey, gnd_track_pkey FROM constant_sources;
"""
)
vcc_track_pkey, gnd_track_pkey = c.fetchone()
const_connectors = {}
const_connectors[0] = Connector(
conn=conn, tracks=get_track_model(conn, gnd_track_pkey)
)
const_connectors[1] = Connector(
conn=conn, tracks=get_track_model(conn, vcc_track_pkey)
)
return const_connectors
def create_get_tile_loc(conn):
c = conn.cursor()
@functools.lru_cache(maxsize=None)
def get_tile_loc(tile_pkey):
c.execute(
"SELECT grid_x, grid_y FROM tile WHERE pkey = ?", (tile_pkey, )
)
return grid_types.GridLoc(*c.fetchone())
return get_tile_loc
def yield_edges(
const_connectors, delayless_switch, phy_tile_pkey, src_connector,
sink_connector, pip, pip_obj, src_wire_pkey, sink_wire_pkey, loc,
forward
):
if forward:
for (src_graph_node_pkey, switch_pkey, dest_graph_node_pkey,
pip_pkey) in src_connector.connect_at(
pip=pip_obj, src_wire_pkey=src_wire_pkey,
dest_wire_pkey=sink_wire_pkey, loc=loc,
other_connector=sink_connector):
assert switch_pkey is not None, (
pip, src_graph_node_pkey, dest_graph_node_pkey, phy_tile_pkey,
pip_pkey
)
yield (
src_graph_node_pkey, dest_graph_node_pkey, switch_pkey,
phy_tile_pkey, pip_pkey, False
)
if not forward and not pip.is_directional:
for (src_graph_node_pkey, switch_pkey, dest_graph_node_pkey,
pip_pkey) in sink_connector.connect_at(
pip=pip_obj, src_wire_pkey=sink_wire_pkey,
dest_wire_pkey=src_wire_pkey, loc=loc,
other_connector=src_connector):
assert switch_pkey is not None, (
pip, src_graph_node_pkey, dest_graph_node_pkey, phy_tile_pkey,
pip_pkey
)
yield (
src_graph_node_pkey, dest_graph_node_pkey, switch_pkey,
phy_tile_pkey, pip_pkey, True
)
if forward:
# Make additional connections to constant network if the sink needs it.
for constant_src in yield_ties_to_wire(pip.net_to):
for (src_graph_node_pkey, switch_pkey, dest_graph_node_pkey
) in const_connectors[constant_src].connect_at(
pip=delayless_switch, loc=loc,
other_connector=sink_connector):
assert switch_pkey is not None, (
pip, src_graph_node_pkey, dest_graph_node_pkey,
phy_tile_pkey, pip_pkey
)
yield (
src_graph_node_pkey, dest_graph_node_pkey, switch_pkey,
phy_tile_pkey, None, False
)
def make_connection(
conn, input_only_nodes, output_only_nodes, find_wire, find_pip,
find_connector, get_tile_loc, tile_name, tile_type, pip,
delayless_switch, const_connectors, forward
):
""" Attempt to connect graph nodes on either side of a pip.
Args:
input_only_nodes (set of node_pkey): Nodes that can only be used as
sinks. This is because a synthetic tile will use this node as a
source.
output_only_nodes (set of node_pkey): Nodes that can only be used as
sources. This is because a synthetic tile will use this node as a
sink.
find_wire (function): Return value from create_find_wire.
find_pip (function): Return value from create_find_pip.
find_connector (function): Return value from create_find_connector.
tile_name (str): Name of tile pip belongs too.
pip (prjxray.tile.Pip): Pip being connected.
switch_pkey (int): Primary key to switch table of switch to be used
in this connection.
Returns:
None if connection cannot be made, otherwise returns tuple of:
src_graph_node_pkey (int) - Primary key into graph_node table of
source.
dest_graph_node_pkey (int) - Primary key into graph_node table of
destination.
switch_pkey (int) - Primary key into switch table of switch used
in connection.
phy_tile_pkey (int) - Primary key into table of parent physical
tile of the pip.
pip_pkey (int) - Primary key into pip_in_tile table for this pip.
"""
src_wire_pkey, tile_pkey, phy_tile_pkey, src_node_pkey = find_wire(
tile_name, tile_type, pip.net_from
)
sink_wire_pkey, tile_pkey2, phy_tile_pkey2, sink_node_pkey = find_wire(
tile_name, tile_type, pip.net_to
)
assert phy_tile_pkey == phy_tile_pkey2
# Skip nodes that are reserved because of ROI
if src_node_pkey in input_only_nodes:
return
if sink_node_pkey in output_only_nodes:
return
src_connector = find_connector(src_wire_pkey, src_node_pkey)
if src_connector is None:
return
sink_connector = find_connector(sink_wire_pkey, sink_node_pkey)
if sink_connector is None:
return
pip_obj = find_pip(tile_type, pip.name)
# Generally pseudo-pips are skipped, with the exception for BUFHCE related pips,
# for which we want to create a routing path to have VPR route thorugh these pips.
assert not pip_obj.is_pseudo or "CLK_HROW_CK" in pip.name
loc = get_tile_loc(tile_pkey)
for edge in yield_edges(
const_connectors=const_connectors,
delayless_switch=delayless_switch, phy_tile_pkey=phy_tile_pkey,
src_connector=src_connector, sink_connector=sink_connector,
pip=pip, pip_obj=pip_obj, src_wire_pkey=src_wire_pkey,
sink_wire_pkey=sink_wire_pkey, loc=loc, forward=forward):
yield edge
def mark_track_liveness(conn, input_only_nodes, output_only_nodes):
""" Checks tracks for liveness.
Iterates over all graph nodes that are routing tracks and determines if
at least one graph edge originates from or two the track.
Args:
conn (sqlite3.Connection): Connection database
"""
alive_tracks = set()
c = conn.cursor()
write_cur = conn.cursor()
for graph_node_pkey, node_pkey, track_pkey in c.execute("""
SELECT
pkey,
node_pkey,
track_pkey
FROM
graph_node
WHERE
track_pkey IS NOT NULL;"""):
if track_pkey in alive_tracks:
continue
if node_pkey in input_only_nodes or node_pkey in output_only_nodes:
alive_tracks.add(track_pkey)
continue
write_cur.execute(
"""SELECT count(switch_pkey) FROM graph_edge WHERE
src_graph_node_pkey = ?;""", (graph_node_pkey, )
)
src_count = write_cur.fetchone()[0]
write_cur.execute(
"""SELECT count(switch_pkey) FROM graph_edge WHERE
dest_graph_node_pkey = ?;""", (graph_node_pkey, )
)
sink_count = write_cur.fetchone()[0]
write_cur.execute(
"""
SELECT count() FROM (
SELECT dest_graph_node_pkey FROM graph_edge WHERE src_graph_node_pkey = ?
UNION
SELECT src_graph_node_pkey FROM graph_edge WHERE dest_graph_node_pkey = ?
);""", (graph_node_pkey, graph_node_pkey)
)
active_other_nodes = write_cur.fetchone()[0]
if src_count > 0 and sink_count > 0 and active_other_nodes > 1:
alive_tracks.add(track_pkey)
c.execute("SELECT count(pkey) FROM track;")
track_count = c.fetchone()[0]
print(
"{} Alive tracks {} / {}".format(
now(), len(alive_tracks), track_count
)
)
write_cur.execute("""BEGIN EXCLUSIVE TRANSACTION;""")
for (track_pkey, ) in c.execute("""SELECT pkey FROM track;"""):
write_cur.execute(
"UPDATE track SET alive = ? WHERE pkey = ?;",
(track_pkey in alive_tracks, track_pkey)
)
write_cur.execute("""COMMIT TRANSACTION;""")
print('{} Track aliveness committed'.format(now()))
write_cur.execute("""BEGIN EXCLUSIVE TRANSACTION;""")
write_cur.execute("""CREATE INDEX alive_tracks ON track(alive);""")
write_cur.execute(
"""CREATE INDEX graph_node_x_index ON graph_node(x_low);"""
)
write_cur.execute(
"""CREATE INDEX graph_node_y_index ON graph_node(y_low);"""
)
write_cur.execute("""COMMIT TRANSACTION;""")
def direction_to_enum(pin):
""" Converts string to tracks.Direction. """
for direction in tracks.Direction:
if direction._name_ == pin:
return direction
assert False
def build_channels(conn):
x_channel_models = {}
y_channel_models = {}
cur = conn.cursor()
cur.execute(
"""
SELECT MIN(x_low), MAX(x_high), MIN(y_low), MAX(y_high) FROM graph_node
INNER JOIN track
ON track.pkey = graph_node.track_pkey
WHERE track.alive;"""
)
x_min, x_max, y_min, y_max = cur.fetchone()
for x in progressbar_utils.progressbar(range(x_min, x_max + 1)):
cur.execute(
"""
SELECT
graph_node.y_low,
graph_node.y_high,
graph_node.pkey
FROM graph_node
INNER JOIN track
ON track.pkey = graph_node.track_pkey
WHERE
track_pkey IS NOT NULL
AND
track.alive
AND
graph_node_type = ?
AND
x_low = ?;""", (graph2.NodeType.CHANY.value, x)
)
data = list(cur)
y_channel_models[x] = graph2.process_track(data)
for y in progressbar_utils.progressbar(range(y_min, y_max + 1)):
cur.execute(
"""
SELECT
graph_node.x_low,
graph_node.x_high,
graph_node.pkey
FROM graph_node
INNER JOIN track
ON track.pkey = graph_node.track_pkey
WHERE
track_pkey IS NOT NULL
AND
track.alive
AND
graph_node_type = ?
AND
y_low = ?;""", (graph2.NodeType.CHANX.value, y)
)
data = list(cur)
x_channel_models[y] = graph2.process_track(data)
x_list = []
y_list = []
write_cur = conn.cursor()
write_cur.execute("""BEGIN EXCLUSIVE TRANSACTION;""")
for y in progressbar_utils.progressbar(range(y_max + 1)):
if y in x_channel_models:
x_list.append(len(x_channel_models[y].trees))
for idx, tree in enumerate(x_channel_models[y].trees):
for i in tree:
write_cur.execute(
'UPDATE graph_node SET ptc = ? WHERE pkey = ?;',
(idx, i[2])
)
else:
x_list.append(0)
for x in progressbar_utils.progressbar(range(x_max + 1)):
if x in y_channel_models:
y_list.append(len(y_channel_models[x].trees))
for idx, tree in enumerate(y_channel_models[x].trees):
for i in tree:
write_cur.execute(
'UPDATE graph_node SET ptc = ? WHERE pkey = ?;',
(idx, i[2])
)
else:
y_list.append(0)
write_cur.execute(
"""
INSERT INTO channel(chan_width_max, x_min, x_max, y_min, y_max) VALUES
(?, ?, ?, ?, ?);""",
(max(max(x_list), max(y_list)), x_min, x_max, y_min, y_max)
)
for idx, info in enumerate(x_list):
write_cur.execute(
"""
INSERT INTO x_list(idx, info) VALUES (?, ?);""", (idx, info)
)
for idx, info in enumerate(y_list):
write_cur.execute(
"""
INSERT INTO y_list(idx, info) VALUES (?, ?);""", (idx, info)
)
write_cur.execute("""COMMIT TRANSACTION;""")
def verify_channels(conn):
""" Verify PTC numbers in channels.
No duplicate PTC's.
Violation of this requirement results in a check failure during rr graph
loading.
"""
c = conn.cursor()
chan_ptcs = {}
for (graph_node_pkey, alive, graph_node_type, x_low, x_high, y_low, y_high,
ptc, capacity) in c.execute(
"""
SELECT
graph_node.pkey,
track.alive,
graph_node.graph_node_type,
graph_node.x_low,
graph_node.x_high,
graph_node.y_low,
graph_node.y_high,
graph_node.ptc,
graph_node.capacity
FROM graph_node
INNER JOIN track
ON graph_node.track_pkey = track.pkey
WHERE (graph_node_type = ? or graph_node_type = ?);""",
(graph2.NodeType.CHANX.value, graph2.NodeType.CHANY.value)):
if not alive and capacity != 0:
assert ptc is None, graph_node_pkey
continue
assert ptc is not None, graph_node_pkey
for x in range(x_low, x_high + 1):
for y in range(y_low, y_high + 1):
key = (graph_node_type, x, y)
if key not in chan_ptcs:
chan_ptcs[key] = []
chan_ptcs[key].append((graph_node_pkey, ptc))
for key in chan_ptcs:
ptcs = {}
for graph_node_pkey, ptc in chan_ptcs[key]:
assert ptc not in ptcs, (ptcs[ptc], graph_node_pkey)
ptcs[ptc] = graph_node_pkey
def set_pin_connection(
conn, write_cur, pin_graph_node_pkey, forward, graph_node_pkey, tracks
):
""" Sets pin connection box location canonical location.
Tracks that are a part of the pinfeed also get this location.
"""
cur = conn.cursor()
cur2 = conn.cursor()
cur.execute(
"""SELECT node_pkey, graph_node_type FROM graph_node WHERE pkey = ?""",
(pin_graph_node_pkey, )
)
pin_node_pkey, graph_node_type = cur.fetchone()
source_wires = []
sink_wires = []
cur.execute(
"""SELECT pkey FROM wire WHERE node_pkey = (
SELECT node_pkey FROM graph_node WHERE pkey = ?
)""", (graph_node_pkey, )
)
for (wire_pkey, ) in cur:
cur2.execute(
"""SELECT count() FROM pip_in_tile WHERE src_wire_in_tile_pkey = (
SELECT wire_in_tile_pkey FROM wire WHERE pkey = ?
) AND pip_in_tile.is_pseudo = 0""", (wire_pkey, )
)
has_forward_pip = cur2.fetchone()[0]
cur2.execute(
"""SELECT count() FROM pip_in_tile WHERE dest_wire_in_tile_pkey = (
SELECT wire_in_tile_pkey FROM wire WHERE pkey = ?
) AND pip_in_tile.is_pseudo = 0""", (wire_pkey, )
)
has_backward_pip = cur2.fetchone()[0]
if forward:
if has_forward_pip:
source_wires.append(wire_pkey)
if has_backward_pip:
sink_wires.append(wire_pkey)
else:
if has_forward_pip:
sink_wires.append(wire_pkey)
if has_backward_pip:
source_wires.append(wire_pkey)
if len(source_wires) > 1:
if forward:
# Ambiguous output location, just use input pips, which should
# have only 1 phy_tile location.
cur2.execute(
"""
WITH wires_in_graph_node(phy_tile_pkey, phy_tile_type_pkey, wire_in_tile_pkey) AS (
SELECT wire.phy_tile_pkey, phy_tile.tile_type_pkey, wire.wire_in_tile_pkey
FROM graph_node
INNER JOIN wire ON wire.node_pkey = graph_node.node_pkey
INNER JOIN phy_tile ON wire.phy_tile_pkey = phy_tile.pkey
WHERE graph_node.pkey = ?
)
SELECT DISTINCT wire.phy_tile_pkey, pip_in_tile.is_directional
FROM wires_in_graph_node
INNER JOIN pip_in_tile
ON
pip_in_tile.dest_wire_in_tile_pkey = wires_in_graph_node.wire_in_tile_pkey
AND
pip_in_tile.tile_type_pkey = wires_in_graph_node.phy_tile_type_pkey
INNER JOIN wire
ON
wire.wire_in_tile_pkey = pip_in_tile.src_wire_in_tile_pkey
AND
wire.phy_tile_pkey = wires_in_graph_node.phy_tile_pkey;
""", (graph_node_pkey, )
)
src_phy_tiles = cur2.fetchall()
if len(src_phy_tiles) > 1:
# Try pruning bi-directional pips
src_phy_tiles = [
(phy_tile_pkey, is_directional)
for (phy_tile_pkey, is_directional) in src_phy_tiles
if is_directional
]
assert len(src_phy_tiles) == 1, (
pin_graph_node_pkey, graph_node_pkey, source_wires, tracks,
src_phy_tiles
)
phy_tile_pkey = src_phy_tiles[0][0]
else:
# Have an ambiguous source, see if there is an unambigous sink.
#
# Remove sinks that are also sources (e.g. bidirectional wires)
sink_wires = list(set(sink_wires) - set(source_wires))
if len(sink_wires) == 1:
cur.execute(
"SELECT phy_tile_pkey FROM wire WHERE pkey = ?",
(sink_wires[0], )
)
source_wires = sink_wires
phy_tile_pkey = cur.fetchone()[0]
else:
assert False, (
pin_graph_node_pkey, graph_node_pkey, source_wires,
sink_wires, tracks
)
return
elif len(source_wires) == 1:
cur.execute(
"SELECT phy_tile_pkey FROM wire WHERE pkey = ?",
(source_wires[0], )
)
phy_tile_pkey = cur.fetchone()[0]
elif len(sink_wires) == 1:
cur.execute(
"SELECT phy_tile_pkey FROM wire WHERE pkey = ?", (sink_wires[0], )
)
source_wires = sink_wires
phy_tile_pkey = cur.fetchone()[0]
else:
return
for track_pkey in tracks:
write_cur.execute(
"UPDATE track SET canon_phy_tile_pkey = ? WHERE pkey = ?", (
phy_tile_pkey,
track_pkey,
)
)
if not forward:
assert NodeType(graph_node_type) == NodeType.IPIN
source_wire_pkey = source_wires[0]
write_cur.execute(
"""
UPDATE graph_node SET connection_box_wire_pkey = ? WHERE pkey = ?
""", (
source_wire_pkey,
pin_graph_node_pkey,
)
)
def walk_and_mark_segment(
conn, write_cur, graph_node_pkey, forward, segment_pkey, unknown_pkey,
pin_graph_node_pkey, tracks, visited_nodes
):
""" Recursive function to walk along a node and mark segments.
This algorithm is used for marking INPINFEED and OUTPINFEED on nodes
starting from an IPIN or OPIN edge.
In addition, the canonical location of the connection box IPIN/OPIN nodes
is the canonical location of the routing interface. For example, the
CLBLL_R tile is located to the right of the INT_R tile. The routing
lookahead routes to the INT_R (e.g. a x-1 of the CLBLL_R). So the
canonical location of the CLBLL_R IPIN is the INT_R tile, not the CLBLL_R
tile.
"""
cur = conn.cursor()
# Update track segment's to segment_pkey (e.g. INPINFEED or OUTPINFEED).
cur.execute(
"""SELECT graph_node_type FROM graph_node WHERE pkey = ?""",
(graph_node_pkey, )
)
graph_node_type = NodeType(cur.fetchone()[0])
if graph_node_type in [NodeType.CHANX, NodeType.CHANY]:
cur.execute(
"SELECT track_pkey FROM graph_node WHERE pkey = ?",
(graph_node_pkey, )
)
track_pkey = cur.fetchone()[0]
assert track_pkey is not None
cur.execute(
"SELECT segment_pkey FROM track WHERE pkey = ?", (track_pkey, )
)
old_segment_pkey = cur.fetchone()[0]
if old_segment_pkey == unknown_pkey or old_segment_pkey is None:
tracks.append(track_pkey)
write_cur.execute(
"UPDATE track SET segment_pkey = ? WHERE pkey = ?", (
segment_pkey,
track_pkey,
)
)
else:
track_pkey = None
# Traverse to the next graph node.
if forward:
cur.execute(
"""
SELECT
graph_edge.dest_graph_node_pkey,
graph_node.track_pkey
FROM
graph_edge
INNER JOIN graph_node ON graph_node.pkey = graph_edge.dest_graph_node_pkey
WHERE
src_graph_node_pkey = ?
""", (graph_node_pkey, )
)
next_nodes = cur.fetchall()
else:
cur.execute(
"""
SELECT
graph_edge.src_graph_node_pkey,
graph_node.track_pkey
FROM
graph_edge
INNER JOIN graph_node ON graph_node.pkey = graph_edge.src_graph_node_pkey
WHERE
dest_graph_node_pkey = ?
""", (graph_node_pkey, )
)
next_nodes = cur.fetchall()
if not forward:
# Some nodes simply lead to GND/VCC tieoff pins, these should not
# stop the walk, as they are not relevant to connection box.
next_non_tieoff_nodes = []
for (next_graph_node_pkey, next_track) in next_nodes:
cur.execute(
"""
SELECT count() FROM constant_sources WHERE
vcc_track_pkey = (SELECT track_pkey FROM graph_node WHERE pkey = ?)
OR
gnd_track_pkey = (SELECT track_pkey FROM graph_node WHERE pkey = ?)
""", (
next_graph_node_pkey,
next_graph_node_pkey,
)
)
if cur.fetchone()[0] == 0:
next_non_tieoff_nodes.append(
(next_graph_node_pkey, next_track)
)
if len(next_non_tieoff_nodes) == 1:
(next_node, next_track) = next_non_tieoff_nodes[0]
next_nodes = next_non_tieoff_nodes
if len(next_nodes) == 1:
# This is a simple edge, keep walking.
(next_node, next_track) = next_nodes[0]
else:
next_other_nodes = []
for next_node, next_track in next_nodes:
# Shorted groups will have edges back to previous nodes, but they
# will be in the same track, so ignore these.
if next_node in visited_nodes and track_pkey == next_track:
continue
else:
next_other_nodes.append((next_node, next_track))
if len(next_other_nodes) == 1:
# This is a simple edge, keep walking.
(next_node, next_track) = next_other_nodes[0]
else:
next_node = None
if next_node is not None and next_node not in visited_nodes:
# If there is a next node, keep walking
visited_nodes.add(next_node)
walk_and_mark_segment(
conn=conn,
write_cur=write_cur,
graph_node_pkey=next_node,
forward=forward,
segment_pkey=segment_pkey,
unknown_pkey=unknown_pkey,
pin_graph_node_pkey=pin_graph_node_pkey,
tracks=tracks,
visited_nodes=visited_nodes
)
else:
# There is not a next node, update the connection box of the IPIN/OPIN
# the walk was started from.
set_pin_connection(
conn=conn,
write_cur=write_cur,
pin_graph_node_pkey=pin_graph_node_pkey,
forward=forward,
graph_node_pkey=graph_node_pkey,
tracks=tracks
)
def active_graph_node(conn, graph_node_pkey, forward):
""" Returns true if an edge in the specified direction exists. """
cur = conn.cursor()
if forward:
cur.execute(
"""
SELECT count(*) FROM graph_edge WHERE src_graph_node_pkey = ? LIMIT 1
""", (graph_node_pkey, )
)
else:
cur.execute(
"""
SELECT count(*) FROM graph_edge WHERE dest_graph_node_pkey = ? LIMIT 1
""", (graph_node_pkey, )
)
return cur.fetchone()[0] > 0
def annotate_pin_feeds(conn, ccio_sites):
""" Identifies and annotates pin feed channels.
Some channels are simply paths from IPIN's or OPIN's. Set
pin_classification to either IPIN_FEED or OPIN_FEED. During track creation
if these nodes are not given a specific segment, they will be assigned as
INPINFEED or OUTPINFEED.
"""
write_cur = conn.cursor()
cur = conn.cursor()
segments = {}
for segment_pkey, segment_name in cur.execute(
"SELECT pkey, name FROM segment"):
segments[segment_name] = segment_pkey
# Find BUFHCE OPIN's, so that walk_and_mark_segment uses correct segment
# type.
bufhce_opins = get_pins(conn, "BUFHCE", "O")
# Find BUFGCTRL OPIN's, so that walk_and_mark_segment uses correct segment
# type.
bufg_opins = get_pins(conn, "BUFGCTRL", "O")
# Find BUFGCTRL IPIN's, so that walk_and_mark_segment uses correct segment
# type.
bufg_ipins = set()
for nipins in range(2):
bufg_ipins |= get_pins(conn, "BUFGCTRL", "I{}".format(nipins))
# Find PLL OPIN's, so that walk_and_mark_segment uses correct segment
# type.
pll_opins = set()
for nclk in range(6):
pll_opins |= get_pins(conn, "PLLE2_ADV", "CLKOUT{}".format(nclk))
# Find PLL IPIN's, so that walk_and_mark_segment uses correct segment
# type.
pll_ipins = set()
for nclk in range(2):
pll_ipins |= get_pins(conn, "PLLE2_ADV", "CLKIN{}".format(nclk + 1))
ccio_opins = set()
# Find graph nodes for IOI_ILOGIC0_O for IOPAD_M's that are CCIO tiles (
# e.g. have dedicate clock paths).
for ccio_site in ccio_sites:
ccio_ilogic = ccio_site.replace('IOB', 'ILOGIC')
cur.execute(
"""
WITH ilogic_o_wires(wire_in_tile_pkey) AS (
SELECT wire_in_tile.pkey FROM wire_in_tile
INNER JOIN site_pin ON site_pin.pkey = wire_in_tile.site_pin_pkey
INNER JOIN site_type ON site_pin.site_type_pkey = site_type.pkey
WHERE
site_type.name == "ILOGICE3"
AND
site_pin.name == "O"
AND
wire_in_tile.site_pkey IN (
SELECT site_pkey FROM site_instance WHERE name = ?
)
)
SELECT graph_node.pkey FROM graph_node
INNER JOIN wire ON graph_node.node_pkey = wire.node_pkey
WHERE
wire.wire_in_tile_pkey IN (SELECT wire_in_tile_pkey FROM ilogic_o_wires)
AND
wire.phy_tile_pkey = (SELECT phy_tile_pkey FROM site_instance WHERE name = ?);
""", (ccio_ilogic, ccio_ilogic)
)
for (graph_node_pkey, ) in cur:
ccio_opins.add(graph_node_pkey)
write_cur.execute("""BEGIN EXCLUSIVE TRANSACTION;""")
# Walk from OPIN's first.
for (graph_node_pkey, node_pkey) in cur.execute("""
SELECT graph_node.pkey, graph_node.node_pkey
FROM graph_node
WHERE graph_node.graph_node_type = ?
""", (NodeType.OPIN.value, )):
if not active_graph_node(conn, graph_node_pkey, forward=True):
continue
if graph_node_pkey in bufhce_opins:
segment_pkey = segments["HCLK_ROWS"]
elif graph_node_pkey in bufg_opins:
segment_pkey = segments["GCLK_OUTPINFEED"]
elif graph_node_pkey in pll_opins:
segment_pkey = segments["PLL_OUTPINFEED"]
elif graph_node_pkey in ccio_opins:
segment_pkey = segments["CCIO_OUTPINFEED"]
else:
segment_pkey = segments["OUTPINFEED"]
walk_and_mark_segment(
conn,
write_cur,
graph_node_pkey,
forward=True,
segment_pkey=segment_pkey,
unknown_pkey=segments["unknown"],
pin_graph_node_pkey=graph_node_pkey,
tracks=list(),
visited_nodes=set()
)
# Walk from IPIN's next.
for (graph_node_pkey, ) in cur.execute("""
SELECT graph_node.pkey
FROM graph_node
WHERE graph_node.graph_node_type = ?
""", (NodeType.IPIN.value, )):
if not active_graph_node(conn, graph_node_pkey, forward=False):
continue
if graph_node_pkey in bufg_ipins:
segment_pkey = segments["GCLK_INPINFEED"]
elif graph_node_pkey in pll_ipins:
segment_pkey = segments["PLL_INPINFEED"]
else:
segment_pkey = segments["INPINFEED"]
walk_and_mark_segment(
conn,
write_cur,
graph_node_pkey,
forward=False,
segment_pkey=segment_pkey,
unknown_pkey=segments["unknown"],
pin_graph_node_pkey=graph_node_pkey,
tracks=list(),
visited_nodes=set()
)
write_cur.execute("""COMMIT TRANSACTION;""")
def set_track_canonical_loc(conn):
""" For each track, compute a canonical location.
This canonical location should be consisent across instances of the track
type. For example, a 6 length NW should always have a canonical location
at the SE corner of the the track.
For bidirection wires (generally long segments), use a consisent
canonilization.
"""
write_cur = conn.cursor()
cur = conn.cursor()
cur2 = conn.cursor()
cur3 = conn.cursor()
write_cur.execute("""BEGIN EXCLUSIVE TRANSACTION;""")
cur.execute("SELECT pkey FROM track WHERE alive")
tracks = cur.fetchall()
for (track_pkey, ) in progressbar_utils.progressbar(tracks):
source_wires = []
for (wire_pkey, ) in cur2.execute("""
SELECT pkey FROM wire WHERE node_pkey IN (
SELECT pkey FROM node WHERE track_pkey = ?
)""", (track_pkey, )):
cur3.execute(
"""
SELECT count(*)
FROM pip_in_tile
WHERE
dest_wire_in_tile_pkey = (SELECT wire_in_tile_pkey FROM wire WHERE pkey = ?)
LIMIT 1
""", (wire_pkey, )
)
pips_to_wire = cur3.fetchone()[0]
if pips_to_wire > 0:
cur3.execute(
"""
SELECT grid_x, grid_y FROM phy_tile WHERE pkey = (
SELECT phy_tile_pkey FROM wire WHERE pkey = ?
)""", (wire_pkey, )
)
grid_x, grid_y = cur3.fetchone()
source_wires.append(((grid_x, grid_y), wire_pkey))
if len(source_wires) > 0:
source_wire_pkey = min(source_wires, key=lambda x: x[0])[1]
write_cur.execute(
"""
UPDATE track
SET canon_phy_tile_pkey = (SELECT phy_tile_pkey FROM wire WHERE pkey = ?)
WHERE pkey = ?
""", (source_wire_pkey, track_pkey)
)
write_cur.execute("""COMMIT TRANSACTION;""")
def get_segment_length(segment_lengths):
if len(segment_lengths) == 0:
return 1
median_length = int(math.ceil(numpy.median(segment_lengths)))
return max(1, median_length)
def compute_segment_lengths(conn):
""" Determine segment lengths used for cost normalization. """
cur = conn.cursor()
cur2 = conn.cursor()
cur3 = conn.cursor()
cur4 = conn.cursor()
write_cur = conn.cursor()
write_cur.execute("""BEGIN EXCLUSIVE TRANSACTION;""")
for (segment_pkey, ) in cur.execute("SELECT pkey FROM segment"):
segment_lengths = []
# Get all tracks with this segment
for (track_pkey, src_phy_tile_pkey) in cur2.execute("""
SELECT pkey, canon_phy_tile_pkey FROM track
WHERE
canon_phy_tile_pkey IS NOT NULL
AND
segment_pkey = ?
""", (segment_pkey, )):
segment_length = 1
cur4.execute(
"SELECT grid_x, grid_y FROM phy_tile WHERE pkey = ?",
(src_phy_tile_pkey, )
)
src_x, src_y = cur4.fetchone()
# Get tiles downstream of this track.
for (dest_phy_tile_pkey, ) in cur3.execute("""
SELECT DISTINCT canon_phy_tile_pkey FROM track WHERE pkey IN (
SELECT track_pkey FROM graph_node WHERE pkey IN (
SELECT dest_graph_node_pkey FROM graph_edge WHERE src_graph_node_pkey IN (
SELECT pkey FROM graph_node WHERE track_pkey = ?
)
)
) AND canon_phy_tile_pkey IS NOT NULL
""", (track_pkey, )):
if src_phy_tile_pkey == dest_phy_tile_pkey:
continue
cur4.execute(
"SELECT grid_x, grid_y FROM phy_tile WHERE pkey = ?",
(dest_phy_tile_pkey, )
)
dest_x, dest_y = cur4.fetchone()
segment_length = max(
segment_length,
abs(dest_x - src_x) + abs(dest_y - src_y)
)
segment_lengths.append(segment_length)
write_cur.execute(
"UPDATE segment SET length = ? WHERE pkey = ?", (
get_segment_length(segment_lengths),
segment_pkey,
)
)
write_cur.execute("""COMMIT TRANSACTION;""")
def commit_edges(write_cur, edges):
write_cur.execute("""BEGIN EXCLUSIVE TRANSACTION;""")
write_cur.executemany(
"""
INSERT INTO graph_edge(
src_graph_node_pkey, dest_graph_node_pkey, switch_pkey,
phy_tile_pkey, pip_in_tile_pkey, backward) VALUES (?, ?, ?, ?, ?, ?)""",
edges
)
write_cur.execute("""COMMIT TRANSACTION;""")
REMOVE_TRAILING_NUM = re.compile(r'[0-9]+$')
def pip_sort_key(forward_pip):
""" Sort pips to match canonical order. """
forward, pip = forward_pip
count = (
len(REMOVE_TRAILING_NUM.sub('', pip.net_to)) +
len(REMOVE_TRAILING_NUM.sub('', pip.net_from))
)
if forward:
return (pip.is_pseudo, count, pip.net_to, pip.net_from)
else:
return (pip.is_pseudo, count, pip.net_from, pip.net_to)
def make_sorted_pips(pips):
out_pips = []
for pip in pips:
# Add forward copy of pip
out_pips.append((True, pip))
# Add backward copy of pip if not directional.
if not pip.is_directional:
out_pips.append((False, pip))
out_pips.sort(key=pip_sort_key)
return out_pips
def create_edge_indices(conn):
write_cur = conn.cursor()
write_cur.execute("""BEGIN EXCLUSIVE TRANSACTION;""")
write_cur.execute(
"""CREATE INDEX src_node_index ON graph_edge(src_graph_node_pkey);"""
)
write_cur.execute(
"""CREATE INDEX dest_node_index ON graph_edge(dest_graph_node_pkey);"""
)
write_cur.execute("""CREATE INDEX node_track_index ON node(track_pkey);""")
write_cur.execute("""COMMIT TRANSACTION;""")
write_cur.connection.commit()
print('{} Indices created, marking track liveness'.format(now()))
def create_and_insert_edges(
db, grid, conn, use_roi, roi, input_only_nodes, output_only_nodes
):
write_cur = conn.cursor()
write_cur.execute(
'SELECT pkey FROM switch WHERE name = ?;',
('__vpr_delayless_switch__', )
)
delayless_switch_pkey = write_cur.fetchone()[0]
delayless_switch = KnownSwitch(delayless_switch_pkey)
find_pip = create_find_pip(conn)
find_wire = create_find_wire(conn)
find_connector = create_find_connector(conn)
get_tile_loc = create_get_tile_loc(conn)
const_connectors = create_const_connectors(conn)
sorted_pips = {}
num_edges = 0
edges = []
for loc in progressbar_utils.progressbar(grid.tile_locations()):
edge_set = set()
gridinfo = grid.gridinfo_at_loc(loc)
tile_name = grid.tilename_at_loc(loc)
# Not a synth node, check if in ROI.
if use_roi and not roi.tile_in_roi(loc):
continue
tile_type = db.get_tile_type(gridinfo.tile_type)
if tile_type not in sorted_pips:
sorted_pips[tile_type] = make_sorted_pips(tile_type.get_pips())
for forward, pip in sorted_pips[tile_type]:
# FIXME: The PADOUT0/1 connections do not work.
#
# These connections are used for:
# - XADC
# - Differential signal signal connection between pads.
#
# Issue tracking fix:
# https://github.com/SymbiFlow/f4pga-arch-defs/issues/1033
if 'PADOUT0' in pip.name and 'DIFFI_IN1' not in pip.name:
continue
if 'PADOUT1' in pip.name and 'DIFFI_IN0' not in pip.name:
continue
# These edges are used for bringing general interconnect to the
# horizontal clock buffers. This should only be used when routing
# clocks.
if 'CLK_HROW_CK_INT_' in pip.name:
continue
# Generally pseudo-pips are skipped, with the exception for BUFHCE related pips,
# for which we want to create a routing path to have VPR route thorugh these pips.
if pip.is_pseudo and "CLK_HROW_CK" not in pip.name:
continue
# Filter out PIPs related to MIO and DDR pins of the Zynq7 PS.
# These PIPs are actually not there, they are just informative.
if "PS72_" in pip.net_to or "PS72_" in pip.net_from:
continue
connections = make_connection(
conn=conn,
input_only_nodes=input_only_nodes,
output_only_nodes=output_only_nodes,
find_pip=find_pip,
find_wire=find_wire,
find_connector=find_connector,
get_tile_loc=get_tile_loc,
tile_name=tile_name,
tile_type=gridinfo.tile_type,
pip=pip,
delayless_switch=delayless_switch,
const_connectors=const_connectors,
forward=forward,
)
if connections:
for connection in connections:
key = tuple(connection[0:3])
if key in edge_set:
continue
edge_set.add(key)
edges.append(connection)
if len(edges) > 1000:
commit_edges(write_cur, edges)
num_edges += len(edges)
edges = []
print('{} Created {} edges, inserted'.format(now(), num_edges))
def get_ccio_sites(grid):
ccio_sites = set()
for tile in grid.tiles():
gridinfo = grid.gridinfo_at_tilename(tile)
for site, pin_function in gridinfo.pin_functions.items():
if 'SRCC' in pin_function or 'MRCC' in pin_function:
if gridinfo.sites[site][-1] == 'M':
ccio_sites.add(site)
return ccio_sites
def create_edges(args):
db = prjxray.db.Database(args.db_root, args.part)
grid = db.grid()
with DatabaseCache(args.connection_database) as conn:
with open(args.pin_assignments) as f:
pin_assignments = json.load(f)
tile_wires = []
for tile_type, wire_map in pin_assignments['pin_directions'].items():
for wire in wire_map.keys():
tile_wires.append((tile_type, wire))
for tile_type, wire in progressbar_utils.progressbar(tile_wires):
pins = [
direction_to_enum(pin)
for pin in pin_assignments['pin_directions'][tile_type][wire]
]
add_graph_nodes_for_pins(conn, tile_type, wire, pins)
if args.overlay:
assert args.synth_tiles
use_roi = True
with open(args.synth_tiles) as f:
synth_tiles = json.load(f)
region_dict = dict()
for r in synth_tiles['info']:
bounds = (
r['GRID_X_MIN'], r['GRID_X_MAX'], r['GRID_Y_MIN'],
r['GRID_Y_MAX']
)
region_dict[r['name']] = bounds
roi = Overlay(region_dict=region_dict)
print('{} generating routing graph for Overlay.'.format(now()))
elif args.synth_tiles:
use_roi = True
with open(args.synth_tiles) as f:
synth_tiles = json.load(f)
roi = Roi(
db=db,
x1=synth_tiles['info']['GRID_X_MIN'],
y1=synth_tiles['info']['GRID_Y_MIN'],
x2=synth_tiles['info']['GRID_X_MAX'],
y2=synth_tiles['info']['GRID_Y_MAX'],
)
print('{} generating routing graph for ROI.'.format(now()))
elif args.graph_limit:
use_roi = True
x_min, y_min, x_max, y_max = map(int, args.graph_limit.split(','))
roi = Roi(
db=db,
x1=x_min,
y1=y_min,
x2=x_max,
y2=y_max,
)
synth_tiles = {'tiles': {}}
else:
use_roi = False
output_only_nodes = set()
input_only_nodes = set()
print('{} Finding nodes belonging to ROI'.format(now()))
if use_roi:
find_wire = create_find_wire(conn)
for loc in progressbar_utils.progressbar(grid.tile_locations()):
gridinfo = grid.gridinfo_at_loc(loc)
tile_name = grid.tilename_at_loc(loc)
if tile_name in synth_tiles['tiles']:
synth_tile = synth_tiles['tiles'][tile_name]
for pin in synth_tile['pins']:
if pin['port_type'] not in ['input', 'output']:
continue
_, _, _, node_pkey = find_wire(
tile_name, gridinfo.tile_type, pin['wire']
)
is_input = pin['port_type'] == 'input'
is_output = pin['port_type'] == 'output'
assert is_input or is_output, pin
if is_input:
# This track can be used as a sink.
input_only_nodes.add(node_pkey)
elif is_output:
# This track can be used as a src.
output_only_nodes.add(node_pkey)
# Adding all wires outside of the ROI relative to this synth tile
# to the input/output only nodes, so that the corresponding nodes
# have only outgoing or incoming edges
for tile, wires in synth_tile['wires_outside_roi'
].items():
tile_type = grid.gridinfo_at_tilename(
tile
).tile_type
for wire in wires:
_, _, _, node_pkey = find_wire(
tile, tile_type, wire
)
if is_input:
# This track can be used as a sink.
input_only_nodes.add(node_pkey)
elif is_output:
# This track can be used as a src.
output_only_nodes.add(node_pkey)
create_and_insert_edges(
db=db,
grid=grid,
conn=conn,
use_roi=use_roi,
roi=roi if use_roi else None,
input_only_nodes=input_only_nodes,
output_only_nodes=output_only_nodes
)
create_edge_indices(conn)
mark_track_liveness(conn, input_only_nodes, output_only_nodes)
return get_ccio_sites(grid)