blob: eb725e0a0b5b7af8194ee1638a2c13d00d85d4a8 [file] [log] [blame]
""" Assign pin directions to all tile pins.
Tile pins are defined by one of two methods:
- Pins that are part of a direct connection (e.g. edge_with_mux) are assigned
based on the direction relationship between the two tiles, e.g. facing each
other.
- Pins that connect to a routing track face a routing track.
Tile pins may end up with multiple edges if the routing tracks are formed
differently throughout the grid.
No connection database modifications are made in
prjxray_assign_tile_pin_direction.
"""
import argparse
from collections import namedtuple
import prjxray.db
import prjxray.tile
import simplejson as json
from lib.rr_graph import tracks
from lib.connection_database import (
NodeClassification, yield_logical_wire_info_from_node, get_track_model,
node_to_site_pins, get_pin_name_of_wire
)
from prjxray_constant_site_pins import yield_ties_to_wire
from lib import progressbar_utils
import datetime
from prjxray_db_cache import DatabaseCache
now = datetime.datetime.now
DirectConnection = namedtuple(
'DirectConnection', 'from_pin to_pin switch_name x_offset y_offset'
)
def handle_direction_connections(conn, direct_connections, edge_assignments):
# Edges with mux should have one source tile and one destination_tile.
# The pin from the source_tile should face the destination_tile.
#
# It is expected that all edges_with_mux will lies in a line (e.g. X only or
# Y only).
c = conn.cursor()
for src_wire_pkey, dest_wire_pkey, pip_in_tile_pkey, switch_pkey in \
progressbar_utils.progressbar(
c.execute("""
SELECT src_wire_pkey, dest_wire_pkey, pip_in_tile_pkey, switch_pkey FROM edge_with_mux;"""
)):
c2 = conn.cursor()
# Get the node that is attached to the source.
c2.execute(
"""
SELECT node_pkey FROM wire WHERE pkey = ?""", (src_wire_pkey, )
)
(src_node_pkey, ) = c2.fetchone()
# Find the wire connected to the source.
src_wire = list(node_to_site_pins(conn, src_node_pkey))
assert len(src_wire) == 1
source_wire_pkey, src_tile_pkey, src_wire_in_tile_pkey = src_wire[0]
c2.execute(
"""
SELECT tile_type_pkey, grid_x, grid_y FROM tile WHERE pkey = ?""",
(src_tile_pkey, )
)
src_tile_type_pkey, source_loc_grid_x, source_loc_grid_y = c2.fetchone(
)
c2.execute(
"""
SELECT name FROM tile_type WHERE pkey = ?""", (src_tile_type_pkey, )
)
(source_tile_type, ) = c2.fetchone()
source_wire = get_pin_name_of_wire(conn, source_wire_pkey)
# Get the node that is attached to the sink.
c2.execute(
"""
SELECT node_pkey FROM wire WHERE pkey = ?""", (dest_wire_pkey, )
)
(dest_node_pkey, ) = c2.fetchone()
# Find the wire connected to the sink.
dest_wire = list(node_to_site_pins(conn, dest_node_pkey))
assert len(dest_wire) == 1
destination_wire_pkey, dest_tile_pkey, dest_wire_in_tile_pkey = dest_wire[
0]
c2.execute(
"""
SELECT tile_type_pkey, grid_x, grid_y FROM tile WHERE pkey = ?;""",
(dest_tile_pkey, )
)
dest_tile_type_pkey, destination_loc_grid_x, destination_loc_grid_y = c2.fetchone(
)
c2.execute(
"""
SELECT name FROM tile_type WHERE pkey = ?""", (dest_tile_type_pkey, )
)
(destination_tile_type, ) = c2.fetchone()
destination_wire = get_pin_name_of_wire(conn, destination_wire_pkey)
c2.execute(
"SELECT name FROM switch WHERE pkey = ?"
"", (switch_pkey, )
)
switch_name = c2.fetchone()[0]
direct_connections.add(
DirectConnection(
from_pin='{}.{}'.format(source_tile_type, source_wire),
to_pin='{}.{}'.format(destination_tile_type, destination_wire),
switch_name=switch_name,
x_offset=destination_loc_grid_x - source_loc_grid_x,
y_offset=destination_loc_grid_y - source_loc_grid_y,
)
)
if destination_loc_grid_x == source_loc_grid_x:
if destination_loc_grid_y > source_loc_grid_y:
source_dir = tracks.Direction.TOP
destination_dir = tracks.Direction.BOTTOM
else:
source_dir = tracks.Direction.BOTTOM
destination_dir = tracks.Direction.TOP
else:
if destination_loc_grid_x > source_loc_grid_x:
source_dir = tracks.Direction.RIGHT
destination_dir = tracks.Direction.LEFT
else:
source_dir = tracks.Direction.LEFT
destination_dir = tracks.Direction.RIGHT
edge_assignments[(source_tile_type,
source_wire)].append((source_dir, ))
edge_assignments[(destination_tile_type,
destination_wire)].append((destination_dir, ))
def handle_edges_to_channels(
conn, null_tile_wires, edge_assignments, channel_wires_to_tracks
):
c = conn.cursor()
c.execute(
"""
SELECT vcc_track_pkey, gnd_track_pkey FROM constant_sources;
"""
)
vcc_track_pkey, gnd_track_pkey = c.fetchone()
const_tracks = {
0: gnd_track_pkey,
1: vcc_track_pkey,
}
for node_pkey, classification in progressbar_utils.progressbar(c.execute(
"""
SELECT pkey, classification FROM node WHERE classification != ?;
""", (NodeClassification.CHANNEL.value, ))):
reason = NodeClassification(classification)
if reason == NodeClassification.NULL:
for (tile_type,
wire) in yield_logical_wire_info_from_node(conn, node_pkey):
null_tile_wires.add((tile_type, wire))
if reason != NodeClassification.EDGES_TO_CHANNEL:
continue
c2 = conn.cursor()
for wire_pkey, phy_tile_pkey, tile_pkey, wire_in_tile_pkey in c2.execute(
"""
SELECT
pkey, phy_tile_pkey, tile_pkey, wire_in_tile_pkey
FROM
wire
WHERE
node_pkey = ?;
""", (node_pkey, )):
c3 = conn.cursor()
c3.execute(
"""
SELECT grid_x, grid_y FROM tile WHERE pkey = ?;""", (tile_pkey, )
)
(grid_x, grid_y) = c3.fetchone()
c3.execute(
"""
SELECT
name
FROM
tile_type
WHERE
pkey = (
SELECT
tile_type_pkey
FROM
tile
WHERE
pkey = ?
);
""", (tile_pkey, )
)
(tile_type, ) = c3.fetchone()
wire = get_pin_name_of_wire(conn, wire_pkey)
if wire is None:
# This node has no site pin, don't need to assign pin direction.
continue
for other_phy_tile_pkey, other_wire_in_tile_pkey, pip_pkey, pip in c3.execute(
"""
WITH wires_from_node(wire_in_tile_pkey, phy_tile_pkey) AS (
SELECT
wire_in_tile_pkey,
phy_tile_pkey
FROM
wire
WHERE
node_pkey = ? AND phy_tile_pkey IS NOT NULL
),
other_wires(other_phy_tile_pkey, pip_pkey, other_wire_in_tile_pkey) AS (
SELECT
wires_from_node.phy_tile_pkey,
undirected_pips.pip_in_tile_pkey,
undirected_pips.other_wire_in_tile_pkey
FROM undirected_pips
INNER JOIN wires_from_node ON
undirected_pips.wire_in_tile_pkey = wires_from_node.wire_in_tile_pkey)
SELECT
other_wires.other_phy_tile_pkey,
other_wires.other_wire_in_tile_pkey,
pip_in_tile.pkey,
pip_in_tile.name
FROM
other_wires
INNER JOIN pip_in_tile
ON pip_in_tile.pkey == other_wires.pip_pkey
WHERE
pip_in_tile.is_directional = 1 AND pip_in_tile.is_pseudo = 0;
""", (node_pkey, )):
# Need to walk from the wire_in_tile table, to the wire table,
# to the node table and get track_pkey.
# other_wire_in_tile_pkey -> wire pkey -> node_pkey -> track_pkey
c4 = conn.cursor()
c4.execute(
"""
SELECT
track_pkey,
classification
FROM
node
WHERE
pkey = (
SELECT
node_pkey
FROM
wire
WHERE
phy_tile_pkey = ?
AND wire_in_tile_pkey = ?
);""", (other_phy_tile_pkey, other_wire_in_tile_pkey)
)
result = c4.fetchone()
assert result is not None, (
wire_pkey, pip_pkey, tile_pkey, wire_in_tile_pkey,
other_wire_in_tile_pkey
)
(track_pkey, classification) = result
# Some pips do connect to a track at all, e.g. null node
if track_pkey is None:
# TODO: Handle weird connections.
# other_node_class = NodeClassification(classification)
# assert other_node_class == NodeClassification.NULL, (
# node_pkey, pip_pkey, pip, other_node_class)
continue
tracks_model = channel_wires_to_tracks[track_pkey]
available_pins = set(
tracks_model.get_tracks_for_wire_at_coord(
(grid_x, grid_y)
).keys()
)
edge_assignments[(tile_type, wire)].append(available_pins)
for constant in yield_ties_to_wire(wire):
tracks_model = channel_wires_to_tracks[
const_tracks[constant]]
available_pins = set(
tracks_model.get_tracks_for_wire_at_coord(
(grid_x, grid_y)
).keys()
)
edge_assignments[(tile_type, wire)].append(available_pins)
def initialize_edge_assignments(db, conn):
""" Create initial edge_assignments map. """
c = conn.cursor()
c2 = conn.cursor()
c.execute(
"""
SELECT name, pkey FROM tile_type WHERE pkey IN (
SELECT DISTINCT tile_type_pkey FROM tile
);"""
)
tiles = dict(c)
edge_assignments = {}
wires_in_tile_types = set()
# First find out which tile types were split during VPR grid formation.
# These tile types should not get edge assignments directly, instead
# their sites will get edge assignements.
sites_as_tiles = set()
split_tile_types = set()
for site_pkey, tile_type_pkey in c.execute("""
SELECT site_pkey, tile_type_pkey FROM site_as_tile;
"""):
c2.execute(
"SELECT name FROM tile_type WHERE pkey = ?", (tile_type_pkey, )
)
split_tile_types.add(c2.fetchone()[0])
c2.execute(
"""
SELECT name FROM site_type WHERE pkey = (
SELECT site_type_pkey FROM site WHERE pkey = ?
);""", (site_pkey, )
)
site_type_name = c2.fetchone()[0]
sites_as_tiles.add(site_type_name)
# Initialize edge assignments for split tiles
for site_type in sites_as_tiles:
del tiles[site_type]
site_obj = db.get_site_type(site_type)
for site_pin in site_obj.get_site_pins():
key = (site_type, site_pin)
assert key not in edge_assignments, key
edge_assignments[key] = []
for tile_type in db.get_tile_types():
if tile_type not in tiles:
continue
del tiles[tile_type]
# Skip tile types that are split tiles
if tile_type in split_tile_types:
continue
(tile_type_pkey, ) = c.execute(
"""
SELECT pkey
FROM tile_type
WHERE name = ?
""", (tile_type, )
).fetchone()
for (wire, ) in c.execute("""
SELECT name
FROM wire_in_tile
WHERE tile_type_pkey = ?""", (tile_type_pkey, )):
wires_in_tile_types.add((tile_type, wire))
type_obj = db.get_tile_type(tile_type)
for site in type_obj.get_sites():
for site_pin in site.site_pins:
if site_pin.wire is None:
continue
# Skip if this wire is not in the database
c.execute(
"""
SELECT pkey
FROM wire_in_tile
WHERE name = ?
""", (site_pin.wire, )
)
if not c.fetchone():
continue
key = (tile_type, site_pin.wire)
assert key not in edge_assignments, key
edge_assignments[key] = []
for tile_type, tile_pkey in tiles.items():
assert tile_type not in split_tile_types
for (wire, ) in c.execute("""
SELECT name
FROM wire_in_tile
WHERE pkey in (
SELECT DISTINCT wire_in_tile_pkey
FROM wire
WHERE tile_pkey IN (
SELECT pkey
FROM tile
WHERE tile_type_pkey = ?)
);""", (tile_pkey, )):
wires_in_tile_types.add((tile_type, wire))
for (wire, ) in c.execute("""
SELECT DISTINCT name
FROM wire_in_tile
WHERE pkey in (
SELECT DISTINCT wire_in_tile_pkey
FROM wire
WHERE tile_pkey IN (
SELECT pkey
FROM tile
WHERE tile_type_pkey = ?)
)
AND
site_pin_pkey IS NOT NULL""", (tile_pkey, )):
key = (tile_type, wire)
assert key not in edge_assignments, key
edge_assignments[key] = []
return edge_assignments, wires_in_tile_types
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'--db_root', help='Project X-Ray Database', required=True
)
parser.add_argument('--part', help='FPGA part', required=True)
parser.add_argument(
'--connection_database',
help='Database of fabric connectivity',
required=True
)
parser.add_argument(
'--pin_assignments',
help="""
Output JSON assigning pins to tile types and direction connections""",
required=True
)
args = parser.parse_args()
db = prjxray.db.Database(args.db_root, args.part)
edge_assignments = {}
with DatabaseCache(args.connection_database, read_only=True) as conn:
c = conn.cursor()
edge_assignments, wires_in_tile_types = initialize_edge_assignments(
db, conn
)
direct_connections = set()
print('{} Processing direct connections.'.format(now()))
handle_direction_connections(
conn, direct_connections, edge_assignments
)
wires_not_in_channels = {}
c = conn.cursor()
print('{} Processing non-channel nodes.'.format(now()))
for node_pkey, classification in progressbar_utils.progressbar(
c.execute("""
SELECT pkey, classification FROM node WHERE classification != ?;
""", (NodeClassification.CHANNEL.value, ))):
reason = NodeClassification(classification)
for (tile_type,
wire) in yield_logical_wire_info_from_node(conn, node_pkey):
key = (tile_type, wire)
# Sometimes nodes in particular tile instances are disconnected,
# disregard classification changes if this is the case.
if reason != NodeClassification.NULL:
if key not in wires_not_in_channels:
wires_not_in_channels[key] = reason
else:
other_reason = wires_not_in_channels[key]
assert reason == other_reason, (
tile_type, wire, reason, other_reason
)
if key in wires_in_tile_types:
wires_in_tile_types.remove(key)
# List of nodes that are channels.
channel_nodes = []
# Map of (tile, wire) to track. This will be used to find channels for pips
# that come from EDGES_TO_CHANNEL.
channel_wires_to_tracks = {}
# Generate track models and verify that wires are either in a channel
# or not in a channel.
print('{} Creating models from tracks.'.format(now()))
for node_pkey, track_pkey in progressbar_utils.progressbar(c.execute(
"""
SELECT pkey, track_pkey FROM node WHERE classification = ?;
""", (NodeClassification.CHANNEL.value, ))):
assert track_pkey is not None
tracks_model, _ = get_track_model(conn, track_pkey)
channel_nodes.append(tracks_model)
channel_wires_to_tracks[track_pkey] = tracks_model
for (tile_type,
wire) in yield_logical_wire_info_from_node(conn, node_pkey):
key = (tile_type, wire)
# Make sure all wires in channels always are in channels
assert key not in wires_not_in_channels
if key in wires_in_tile_types:
wires_in_tile_types.remove(key)
# Make sure all wires appear to have been assigned.
if len(wires_in_tile_types) > 0:
for tile_type, wire in sorted(wires_in_tile_types):
print(tile_type, wire)
assert len(wires_in_tile_types) == 0
# Verify that all tracks are sane.
for node in channel_nodes:
node.verify_tracks()
null_tile_wires = set()
# Verify that all nodes that are classified as edges to channels have at
# least one site, and at least one live connection to a channel.
#
# If no live connections from the node are present, this node should've
# been marked as NULL during channel formation.
print('{} Handling edges to channels.'.format(now()))
handle_edges_to_channels(
conn, null_tile_wires, edge_assignments, channel_wires_to_tracks
)
print('{} Processing edge assignments.'.format(now()))
final_edge_assignments = {}
for key, available_pins in progressbar_utils.progressbar(
edge_assignments.items()):
(tile_type, wire) = key
available_pins = [pins for pins in available_pins if len(pins) > 0]
if len(available_pins) == 0:
if (tile_type, wire) not in null_tile_wires:
# TODO: Figure out what is going on with these wires. Appear to
# tile internal connections sometimes?
print((tile_type, wire))
final_edge_assignments[key] = [tracks.Direction.RIGHT]
continue
pins = set(available_pins[0])
for p in available_pins[1:]:
pins &= set(p)
if len(pins) > 0:
final_edge_assignments[key] = [list(pins)[0]]
else:
# More than 2 pins are required, final the minimal number of pins
pins = set()
for p in available_pins:
pins |= set(p)
while len(pins) > 2:
pins = list(pins)
prev_len = len(pins)
for idx in range(len(pins)):
pins_subset = list(pins)
del pins_subset[idx]
pins_subset = set(pins_subset)
bad_subset = False
for p in available_pins:
if len(pins_subset & set(p)) == 0:
bad_subset = True
break
if not bad_subset:
pins = list(pins_subset)
break
# Failed to remove any pins, stop.
if len(pins) == prev_len:
break
final_edge_assignments[key] = pins
for key, available_pins in edge_assignments.items():
(tile_type, wire) = key
pins = set(final_edge_assignments[key])
for required_pins in available_pins:
if len(required_pins) == 0:
continue
assert len(pins & set(required_pins)) > 0, (
tile_type, wire, pins, required_pins, available_pins
)
pin_directions = {}
for key, pins in progressbar_utils.progressbar(
final_edge_assignments.items()):
(tile_type, wire) = key
if tile_type not in pin_directions:
pin_directions[tile_type] = {}
pin_directions[tile_type][wire] = [pin._name_ for pin in pins]
with open(args.pin_assignments, 'w') as f:
json.dump(
{
'pin_directions':
pin_directions,
'direct_connections':
[d._asdict() for d in direct_connections],
},
f,
indent=2
)
print(
'{} Flushing database back to file "{}"'.format(
now(), args.connection_database
)
)
if __name__ == '__main__':
main()