blob: 36407bd64ee8720f9950c04b12d38ebd509da06a [file] [log] [blame]
#!/usr/bin/env python3
"""
Import the top level tile interconnect information from Project X-Ray database
files.
The Project X-Ray specifies the connections between tiles and the connect
between tiles and their sites. prjxray_tile_import generates a VPR pb_type
that has the correct tile pin names to connect to the routing fabric, and
connects those tile pins to each site within the tile.
"""
from __future__ import print_function
import argparse
import sys
import prjxray.db
import prjxray.site_type
import re
import sqlite3
import lxml.etree as ET
XI_URL = "http://www.w3.org/2001/XInclude"
XI_INCLUDE = "{%s}include" % XI_URL
VPR_TILE_PREFIX = 'BLK-TL-'
def add_vpr_tile_prefix(tile):
""" Add tile prefix.
This avoids namespace collision when embedding a site (e.g. SLICEL) as a
tile.
"""
return VPR_TILE_PREFIX + tile
def remove_vpr_tile_prefix(name):
""" Removes tile prefix.
Raises
------
Assert error if name does not start with VPR_TILE_PREFIX
"""
assert name.startswith(VPR_TILE_PREFIX), name
return name[len(VPR_TILE_PREFIX):]
def find_port(pin_name, ports):
if pin_name in ports:
return {
'pin_name': pin_name,
'pin_idx': None,
}
# Find trailing digits, which are assumed to be pin indicies, example:
# ADDRARDADDR13
m = re.search('([0-9]+)$', pin_name)
if m is None:
return None
prefix = pin_name[:-len(m.group(1))]
prefix_pin_idx = int(m.group(1))
# check if signal name ends with number and has num_pins > 1
# e.g. RXOSINTID0 which has num_pins=4, the real prefix is
# RXOSINTID0 not RXOSINTID
for p in ports.keys():
if prefix in p and p.strip(prefix).isnumeric():
prefix = p
prefix_pin_idx = int(pin_name.replace(prefix, ""))
if prefix in ports and prefix_pin_idx < ports[prefix]:
return {
'pin_name': prefix,
'pin_idx': prefix_pin_idx,
}
else:
return None
def object_ref(pb_name, pin_name, pin_idx=None):
pin_addr = ''
if pin_idx is not None:
pin_addr = '[{}]'.format(pin_idx)
return '{}.{}{}'.format(pb_name, pin_name, pin_addr)
def parse_site_type_instance(site_types):
""" Convert site_types argument into map.
Parameters
----------
site_types : str
Returns
-------
site_type_instances : map of str to list of str
Maps site type to array of site type instances. First instance of
site should use first element, second instance should use second
element, etc.
"""
site_type_instances = {}
for s in site_types.split(','):
site_type, site_type_instance = s.split('/')
if site_type not in site_type_instances:
site_type_instances[site_type] = []
site_type_instances[site_type].append(site_type_instance)
return site_type_instances
def add_direct(xml, input, output):
""" Add a direct tag to the interconnect_xml. """
ET.SubElement(
xml, 'direct', {
'name': '{}_to_{}'.format(input, output),
'input': input,
'output': output
}
)
def write_xml(f, xml):
""" Writes XML to disk. """
pb_type_str = ET.tostring(xml, pretty_print=True).decode('utf-8')
f.write(pb_type_str)
f.close()
class ModelXml(object):
""" Simple model.xml writter. """
def __init__(self, f, site_directory):
self.f = f
self.model_xml = ET.Element(
'models',
nsmap={'xi': XI_URL},
)
self.site_model = site_directory + "/{0}/{1}.model.xml"
def add_model_include(self, site_type, instance_name):
ET.SubElement(
self.model_xml, XI_INCLUDE, {
'href':
self.site_model.format(
site_type.lower(), instance_name.lower()
),
'xpointer':
"xpointer(models/child::node())"
}
)
def write_model(self):
write_xml(self.f, self.model_xml)
def start_pb_type(tile_name, f_pin_assignments, input_wires, output_wires):
""" Starts a pb_type by adding input, clock and output tags. """
pb_type_xml = ET.Element(
'pb_type',
{
'name': add_vpr_tile_prefix(tile_name),
},
nsmap={'xi': XI_URL},
)
pb_type_xml.append(ET.Comment(" Tile Inputs "))
# Input definitions for the TILE
for name in sorted(input_wires):
input_type = 'input'
if 'CLK' in name:
input_type = 'clock'
ET.SubElement(
pb_type_xml,
input_type,
{
'name': name,
'num_pins': '1'
},
)
pb_type_xml.append(ET.Comment(" Tile Outputs "))
for name in sorted(output_wires):
# Output definitions for the TILE
ET.SubElement(
pb_type_xml,
'output',
{
'name': name,
'num_pins': '1'
},
)
pb_type_xml.append(ET.Comment(" Internal Sites "))
return pb_type_xml
def import_tile(db, args):
""" Create a root-level pb_type with the pin names that match tile wires.
This will either have 1 intermediate pb_type per site, or 1 large site
for the entire tile if args.fused_sites is set to true.
"""
tile = db.get_tile_type(args.tile)
# Wires sink to a site within the tile are input wires.
input_wires = set()
# Wires source from a site within the tile are output wires.
output_wires = set()
if args.filter_x:
xs = list(map(int, args.filter_x.split(',')))
def x_filter_func(site):
return site.x in xs
x_filter = x_filter_func
else:
def x_filter_func(site):
return True
x_filter = x_filter_func
if not args.fused_sites:
site_type_instances = parse_site_type_instance(args.site_types)
imported_site_types = set()
ignored_site_types = set()
for site in tile.get_sites():
site_type = db.get_site_type(site.type)
if site.type not in site_type_instances:
ignored_site_types.add(site.type)
continue
imported_site_types.add(site.type)
for site_pin in site.site_pins:
site_type_pin = site_type.get_site_pin(site_pin.name)
if site_type_pin.direction == prjxray.site_type.SitePinDirection.IN:
if site_pin.wire is not None:
input_wires.add(site_pin.wire)
elif site_type_pin.direction == prjxray.site_type.SitePinDirection.OUT:
if site_pin.wire is not None:
output_wires.add(site_pin.wire)
else:
if site.type != "PS7":
assert False, site_type_pin.direction
# Make sure all requested site types actually get imported.
assert len(set(site_type_instances.keys()) - imported_site_types
) == 0, (site_type_instances.keys(), imported_site_types)
for ignored_site_type in ignored_site_types:
print(
'*** WARNING *** Ignored site type {} in tile type {}'.format(
ignored_site_type, args.tile
),
file=sys.stderr
)
else:
for site in tile.get_sites():
site_type = db.get_site_type(site.type)
for site_pin in site.site_pins:
site_type_pin = site_type.get_site_pin(site_pin.name)
if site_type_pin.direction == prjxray.site_type.SitePinDirection.IN:
if site_pin.wire is not None:
input_wires.add(site_pin.wire)
elif site_type_pin.direction == prjxray.site_type.SitePinDirection.OUT:
if site_pin.wire is not None:
output_wires.add(site_pin.wire)
else:
assert False, site_type_pin.direction
site_pbtype = args.site_directory + "/{0}/{1}.pb_type.xml"
##########################################################################
# Generate the model.xml file #
##########################################################################
model = ModelXml(f=args.output_model, site_directory=args.site_directory)
if args.fused_sites:
fused_site_name = args.tile.lower()
model.add_model_include(fused_site_name, fused_site_name)
##########################################################################
# Utility functions for pb_type #
##########################################################################
tile_name = args.tile
pb_type_xml = start_pb_type(
tile_name, args.pin_assignments, input_wires, output_wires
)
cell_names = {}
interconnect_xml = ET.Element('interconnect')
if not args.fused_sites:
site_type_count = {}
site_prefixes = {}
cells_idx = {}
models_added = set()
site_type_ports = {}
idx = 0
for site in tile.get_sites():
if site.type in ignored_site_types:
continue
if not x_filter(site):
continue
if site.type not in site_type_count:
site_type_count[site.type] = 0
site_prefixes[site.type] = []
cells_idx[idx] = site_type_count[site.type]
site_type_count[site.type] += 1
site_coords = args.site_coords.upper()
if site_coords == 'X':
site_prefix = '{}_X{}'.format(site.type, site.x)
elif site_coords == 'Y':
site_prefix = '{}_Y{}'.format(site.type, site.y)
elif site_coords == 'XY':
site_prefix = '{}.{}_X{}Y{}'.format(
site.type, site.type, site.x, site.y
)
else:
assert False, "Invalid --site-coords value '{}'".format(
site_coords
)
site_instance = site_type_instances[site.type][cells_idx[idx]]
idx += 1
if (site.type, site_instance) not in models_added:
models_added.add((site.type, site_instance))
model.add_model_include(site.type, site_instance)
site_type_path = site_pbtype.format(
site.type.lower(), site_instance.lower()
)
cell_pb_type = ET.ElementTree()
root_element = cell_pb_type.parse(site_type_path)
cell_names[site_instance] = root_element.attrib['name']
ports = {}
for inputs in root_element.iter('input'):
ports[inputs.attrib['name']] = int(inputs.attrib['num_pins'])
for clocks in root_element.iter('clock'):
ports[clocks.attrib['name']] = int(clocks.attrib['num_pins'])
for outputs in root_element.iter('output'):
ports[outputs.attrib['name']] = int(outputs.attrib['num_pins'])
assert site_instance not in site_type_ports, (
site_instance, site_type_ports.keys()
)
site_type_ports[site_instance] = ports
attrib = dict(root_element.attrib)
include_xml = ET.SubElement(pb_type_xml, 'pb_type', attrib)
ET.SubElement(
include_xml, XI_INCLUDE, {
'href':
site_type_path,
'xpointer':
"xpointer(pb_type/child::node()[local-name()!='metadata'])",
}
)
metadata_xml = ET.SubElement(include_xml, 'metadata')
if not args.no_fasm_prefix:
ET.SubElement(
metadata_xml, 'meta', {
'name': 'fasm_prefix',
}
).text = site_prefix
# Import pb_type metadata if it exists.
if any(child.tag == 'metadata' for child in root_element):
ET.SubElement(
metadata_xml, XI_INCLUDE, {
'href': site_type_path,
'xpointer': "xpointer(pb_type/metadata/child::node())",
}
)
# Prevent emitting empty metadata
if not any(child.tag == 'meta' for child in metadata_xml):
include_xml.remove(metadata_xml)
idx = 0
for site in tile.get_sites():
if site.type in ignored_site_types:
continue
site_idx = cells_idx[idx]
idx += 1
if not x_filter(site):
continue
site_instance = site_type_instances[site.type][site_idx]
site_name = cell_names[site_instance]
site_type = db.get_site_type(site.type)
interconnect_xml.append(ET.Comment(" Tile->Site "))
for site_pin in sorted(site.site_pins,
key=lambda site_pin: site_pin.name):
if site_pin.wire is None:
continue
port = find_port(site_pin.name, site_type_ports[site_instance])
if port is None:
print(
"*** WARNING *** Didn't find port for name {} for site type {}"
.format(site_pin.name, site.type),
file=sys.stderr
)
continue
site_type_pin = site_type.get_site_pin(site_pin.name)
if site_type_pin.direction == prjxray.site_type.SitePinDirection.IN:
add_direct(
interconnect_xml,
input=object_ref(
add_vpr_tile_prefix(tile_name), site_pin.wire
),
output=object_ref(site_name, **port)
)
elif site_type_pin.direction == prjxray.site_type.SitePinDirection.OUT:
pass
else:
if site.type != "PS7":
assert False, site_type_pin.direction
interconnect_xml.append(ET.Comment(" Site->Tile "))
for site_pin in sorted(site.site_pins,
key=lambda site_pin: site_pin.name):
if site_pin.wire is None:
continue
port = find_port(site_pin.name, site_type_ports[site_instance])
if port is None:
continue
site_type_pin = site_type.get_site_pin(site_pin.name)
if site_type_pin.direction == prjxray.site_type.SitePinDirection.IN:
pass
elif site_type_pin.direction == prjxray.site_type.SitePinDirection.OUT:
add_direct(
interconnect_xml,
input=object_ref(site_name, **port),
output=object_ref(
add_vpr_tile_prefix(tile_name), site_pin.wire
),
)
else:
if site.type != "PS7":
assert False, site_type_pin.direction
else:
site_type_ports = {}
site_type_path = site_pbtype.format(fused_site_name, fused_site_name)
cell_pb_type = ET.ElementTree()
root_element = cell_pb_type.parse(site_type_path)
ports = {}
for inputs in root_element.iter('input'):
ports[inputs.attrib['name']] = int(inputs.attrib['num_pins'])
for clocks in root_element.iter('clock'):
ports[clocks.attrib['name']] = int(clocks.attrib['num_pins'])
for outputs in root_element.iter('output'):
ports[outputs.attrib['name']] = int(outputs.attrib['num_pins'])
attrib = dict(root_element.attrib)
include_xml = ET.SubElement(pb_type_xml, 'pb_type', attrib)
ET.SubElement(
include_xml, XI_INCLUDE, {
'href': site_type_path,
'xpointer': "xpointer(pb_type/child::node())",
}
)
site_name = root_element.attrib['name']
def fused_port_name(site, site_pin):
return '{}_{}_{}'.format(site.prefix, site.name, site_pin.name)
for site in tile.get_sites():
site_type = db.get_site_type(site.type)
interconnect_xml.append(ET.Comment(" Tile->Site "))
for site_pin in sorted(site.site_pins,
key=lambda site_pin: site_pin.name):
if site_pin.wire is None:
continue
port_name = fused_port_name(site, site_pin)
port = find_port(port_name, ports)
if port is None:
print(
"*** WARNING *** Didn't find port for name {} for site type {}"
.format(port_name, site.type),
file=sys.stderr
)
continue
site_type_pin = site_type.get_site_pin(site_pin.name)
if site_type_pin.direction == prjxray.site_type.SitePinDirection.IN:
add_direct(
interconnect_xml,
input=object_ref(
add_vpr_tile_prefix(tile_name), site_pin.wire
),
output=object_ref(site_name, **port)
)
elif site_type_pin.direction == prjxray.site_type.SitePinDirection.OUT:
pass
else:
if site.type != "PS7":
assert False, site_type_pin.direction
interconnect_xml.append(ET.Comment(" Site->Tile "))
for site_pin in sorted(site.site_pins,
key=lambda site_pin: site_pin.name):
if site_pin.wire is None:
continue
port = find_port(fused_port_name(site, site_pin), ports)
if port is None:
# Already warned above
continue
site_type_pin = site_type.get_site_pin(site_pin.name)
if site_type_pin.direction == prjxray.site_type.SitePinDirection.IN:
pass
elif site_type_pin.direction == prjxray.site_type.SitePinDirection.OUT:
add_direct(
interconnect_xml,
input=object_ref(site_name, **port),
output=object_ref(
add_vpr_tile_prefix(tile_name), site_pin.wire
),
)
else:
if site.type != "PS7":
assert False, site_type_pin.direction
pb_type_xml.append(interconnect_xml)
model.write_model()
write_xml(args.output_pb_type, pb_type_xml)
def import_site_as_tile(db, args):
""" Create a root-level pb_type with the same pin names as a site type.
"""
site_type = db.get_site_type(args.tile)
# Wires sink to a site within the tile are input wires.
input_wires = set()
# Wires source from a site within the tile are output wires.
output_wires = set()
# Wires unused for this tile (arch specific)
unused_wires = list()
drop_wires = list()
if args.unused_wires:
unused_wires = args.unused_wires.split(",")
site_type_instances = parse_site_type_instance(args.site_types)
assert len(site_type_instances) == 1
assert args.tile in site_type_instances
assert len(site_type_instances[args.tile]) == 1
for site_pin in site_type.get_site_pins():
site_type_pin = site_type.get_site_pin(site_pin)
if site_type_pin.name in unused_wires:
drop_wires.append(site_pin)
continue
if site_type_pin.direction == prjxray.site_type.SitePinDirection.IN:
input_wires.add(site_type_pin.name)
elif site_type_pin.direction == prjxray.site_type.SitePinDirection.OUT:
output_wires.add(site_type_pin.name)
else:
assert False, site_type_pin.direction
for wire in drop_wires:
del site_type.site_pins[wire]
##########################################################################
# Generate the model.xml file #
##########################################################################
model = ModelXml(f=args.output_model, site_directory=args.site_directory)
model.add_model_include(args.tile, site_type_instances[args.tile][0])
model.write_model()
##########################################################################
# Generate the pb_type.xml file #
##########################################################################
tile_name = args.tile
pb_type_xml = start_pb_type(
tile_name, args.pin_assignments, input_wires, output_wires
)
site = args.tile
site_instance = site_type_instances[args.tile][0]
site_pbtype = args.site_directory + "/{0}/{1}.pb_type.xml"
site_type_path = site_pbtype.format(site.lower(), site_instance.lower())
ET.SubElement(pb_type_xml, XI_INCLUDE, {
'href': site_type_path,
})
cell_pb_type = ET.ElementTree()
root_element = cell_pb_type.parse(site_type_path)
site_name = root_element.attrib['name']
ports = {}
for inputs in root_element.iter('input'):
ports[inputs.attrib['name']] = int(inputs.attrib['num_pins'])
for clocks in root_element.iter('clock'):
ports[clocks.attrib['name']] = int(clocks.attrib['num_pins'])
for outputs in root_element.iter('output'):
ports[outputs.attrib['name']] = int(outputs.attrib['num_pins'])
interconnect_xml = ET.Element('interconnect')
interconnect_xml.append(ET.Comment(" Tile->Site "))
for site_pin in sorted(site_type.get_site_pins()):
site_type_pin = site_type.get_site_pin(site_pin)
port = find_port(site_type_pin.name, ports)
if port is None:
print(
"*** WARNING *** Didn't find port for name {} for site type {}"
.format(site_type_pin.name, site_type.type),
file=sys.stderr
)
continue
if site_type_pin.direction == prjxray.site_type.SitePinDirection.IN:
add_direct(
interconnect_xml,
input=object_ref(add_vpr_tile_prefix(tile_name), site_pin),
output=object_ref(site_name, **port)
)
elif site_type_pin.direction == prjxray.site_type.SitePinDirection.OUT:
pass
else:
assert False, site_type_pin.direction
interconnect_xml.append(ET.Comment(" Site->Tile "))
for site_pin in sorted(site_type.get_site_pins()):
site_type_pin = site_type.get_site_pin(site_pin)
port = find_port(site_type_pin.name, ports)
if port is None:
print(
"*** WARNING *** Didn't find port for name {} for site type {}"
.format(site_type_pin.name, site_type),
file=sys.stderr
)
continue
if site_type_pin.direction == prjxray.site_type.SitePinDirection.IN:
pass
elif site_type_pin.direction == prjxray.site_type.SitePinDirection.OUT:
add_direct(
interconnect_xml,
input=object_ref(site_name, **port),
output=object_ref(add_vpr_tile_prefix(tile_name), site_pin),
)
else:
assert False, site_type_pin.direction
pb_type_xml.append(interconnect_xml)
write_xml(args.output_pb_type, pb_type_xml)
def expand_nodes_in_tile_type(conn, tile_type_pkey):
cur = conn.cursor()
cur.execute(
"""
CREATE TEMP TABLE phy_tiles AS
SELECT tile_map.phy_tile_pkey
FROM tile_map
INNER JOIN phy_tile
ON phy_tile.pkey = tile_map.phy_tile_pkey
WHERE tile_map.tile_pkey IN (
SELECT pkey FROM tile WHERE tile_type_pkey = ?
)
GROUP BY phy_tile.tile_type_pkey;""", (tile_type_pkey, )
)
# Set of node pkeys to expand
cur.execute(
"""
SELECT DISTINCT node_pkey
FROM wire
WHERE phy_tile_pkey IN (SELECT phy_tile_pkey FROM phy_tiles);"""
)
nodes_to_expand = set(node_pkey for (node_pkey, ) in cur)
nodes_to_set = {}
while len(nodes_to_expand) > 0:
node_pkey = nodes_to_expand.pop()
node_set = nodes_to_set.get(node_pkey, set((node_pkey, )))
nodes_to_set[node_pkey] = node_set
cur.execute(
"""
WITH
other_wires(phy_tile_pkey, pip_in_tile_pkey, other_wire_in_tile_pkey) AS (
SELECT
wire.phy_tile_pkey, undirected_pips.pip_in_tile_pkey, undirected_pips.other_wire_in_tile_pkey
FROM undirected_pips
INNER JOIN wire
ON wire.wire_in_tile_pkey = undirected_pips.wire_in_tile_pkey
INNER JOIN pip_in_tile
ON pip_in_tile.pkey = undirected_pips.pip_in_tile_pkey
WHERE
wire.node_pkey = ?
AND
NOT pip_in_tile.is_pseudo
)
SELECT wire.node_pkey
FROM wire
INNER JOIN other_wires
ON
wire.wire_in_tile_pkey = other_wires.other_wire_in_tile_pkey
AND
wire.phy_tile_pkey = other_wires.phy_tile_pkey
WHERE
wire.phy_tile_pkey IN (SELECT phy_tile_pkey FROM phy_tiles);""",
(node_pkey, )
)
# Update node sets
for (other_node_pkey, ) in cur:
node_set |= nodes_to_set.get(
other_node_pkey, set((other_node_pkey, ))
)
# Merge node sets
for node_pkey in node_set:
nodes_to_set[node_pkey] = node_set
unique_node_sets = {}
for node_set in nodes_to_set.values():
if id(node_set) not in unique_node_sets:
unique_node_sets[id(node_set)] = node_set
if False:
# Print node sets for debugging and inspection.
for node_set in unique_node_sets.values():
print()
print(id(node_set), len(node_set))
for node_pkey in node_set:
cur.execute(
"""
SELECT name
FROM wire_in_tile
WHERE pkey IN (
SELECT wire.wire_in_tile_pkey
FROM wire
WHERE node_pkey = ?
)""", (node_pkey, )
)
print(node_pkey, ' '.join(name for (name, ) in cur))
cur.execute("""DROP TABLE phy_tiles""")
return unique_node_sets.values()
def find_connections(conn, input_wires, output_wires, tile_type_pkey):
""" Remove top-level ports only connected to internal sources and generates
tile internal connections."""
# Create connected node sets
node_sets = list(expand_nodes_in_tile_type(conn, tile_type_pkey))
# Number of site external connections for ports
is_top_level_pin_external = {}
for wire in input_wires:
is_top_level_pin_external[wire] = False
for wire in output_wires:
is_top_level_pin_external[wire] = False
internal_connections = {}
top_level_connections = {}
wire_to_site_pin = {}
cur = conn.cursor()
for node_set in node_sets:
ipin_count = 0
opin_count = 0
node_set_has_external = False
pins_used_in_node_sets = set()
input_pins = set()
output_pins = set()
for node_pkey in node_set:
cur.execute(
"""
SELECT
tile.tile_type_pkey, wire_in_tile.name, site_type.name, site_pin.name, site_pin.direction, count()
FROM wire
INNER JOIN wire_in_tile
ON wire.wire_in_tile_pkey = wire_in_tile.pkey
INNER JOIN site_pin
ON site_pin.pkey = wire_in_tile.site_pin_pkey
INNER JOIN tile
ON wire.tile_pkey = tile.pkey
INNER JOIN site_type
ON site_type.pkey = site_pin.site_type_pkey
WHERE
wire.node_pkey = ?
AND
wire_in_tile.site_pin_pkey IS NOT NULL
GROUP BY site_pin.direction;
""", (node_pkey, )
)
for wire_tile_type_pkey, wire_name, site_type_name, site_pin_name, dir, count in cur:
if wire_tile_type_pkey == tile_type_pkey:
value = (site_type_name, site_pin_name)
if wire_name in wire_to_site_pin:
assert value == wire_to_site_pin[wire_name], (
wire_name, value, wire_to_site_pin[wire_name]
)
else:
wire_to_site_pin[wire_name] = value
pins_used_in_node_sets.add(wire_name)
direction = prjxray.site_type.SitePinDirection(dir)
if direction == prjxray.site_type.SitePinDirection.IN:
output_pins.add(wire_name)
opin_count += count
elif direction == prjxray.site_type.SitePinDirection.OUT:
input_pins.add(wire_name)
ipin_count += count
else:
assert False, (node_pkey, direction)
else:
node_set_has_external = True
assert len(input_pins) in [0, 1], input_pins
if ipin_count == 0 or opin_count == 0 or node_set_has_external:
# This node set is connected externally, mark as such
for wire_name in pins_used_in_node_sets:
is_top_level_pin_external[wire_name] = True
if ipin_count > 0 and opin_count > 0:
# TODO: Add check that pips and site pins on these internal
# connections are 0 delay.
assert len(input_pins) == 1
input_wire = input_pins.pop()
for wire_name in output_pins:
if wire_name in internal_connections:
assert input_wire == internal_connections[wire_name]
else:
internal_connections[wire_name] = input_wire
for wire in sorted(is_top_level_pin_external):
if not is_top_level_pin_external[wire]:
if wire in input_wires:
input_wires.remove(wire)
elif wire in output_wires:
output_wires.remove(wire)
else:
assert False, wire
else:
top_level_connections[wire] = wire_to_site_pin[wire]
output_internal_connections = {}
for output_wire in internal_connections:
output_internal_connections[wire_to_site_pin[output_wire]
] = wire_to_site_pin[
internal_connections[output_wire]]
return top_level_connections, output_internal_connections
def get_tile_prefix(conn, tile_type_pkey, site_type):
cur = conn.cursor()
cur.execute(
"""
SELECT tile_type.name
FROM tile_type
WHERE pkey IN (
SELECT tile_type.pkey
FROM tile_type
INNER JOIN site
ON site.tile_type_pkey = tile_type.pkey
INNER JOIN site_type
ON site_type.pkey = site.site_type_pkey
WHERE
site_type.name = ?
AND
tile_type.pkey IN (
SELECT DISTINCT phy_tile.tile_type_pkey
FROM phy_tile
INNER JOIN tile_map
ON phy_tile.pkey = tile_map.phy_tile_pkey
WHERE tile_map.tile_pkey IN (
SELECT tile.pkey
FROM tile
WHERE tile.tile_type_pkey = ?
)
)
);""", (site_type, tile_type_pkey)
)
results = list(cur.fetchall())
assert len(results) > 0, (tile_type_pkey, site_type)
# Use shortest variant of tile type, under the assumption that this is
# the most common.
#
# Example:
#
# Between RIOI3, RIOI3_TBYTESRC, and RIOI3_TBYTETERM, use RIOI3.
min_tile_type = min(tile_type for (tile_type, ) in results)
return '{' + min_tile_type + '}'
# prjxray segbits use site names without the version suffix.
NORMALIZED_SITE_TYPES = {
"IOB33M": "IOB",
"IOB33S": "IOB",
"IOB33": "IOB",
"ILOGICE3": "ILOGIC",
"OLOGICE3": "OLOGIC",
"IDELAYE2": "IDELAY",
}
def normalize_site_type(site_type):
return NORMALIZED_SITE_TYPES.get(site_type, site_type)
def import_tile_from_database(conn, args):
""" Create a root-level pb_type using the site pins and sites from the database.
"""
# Wires sink to a site within the tile are input wires.
input_wires = set()
# Wires source from a site within the tile are output wires.
output_wires = set()
cur = conn.cursor()
cur2 = conn.cursor()
cur.execute("SELECT pkey FROM tile_type WHERE name = ?", (args.tile, ))
tile_type_pkey = cur.fetchone()[0]
# Find instances of sites, sorted by their original tile type.
# Then choice the first of each site type as the wire set.
# This ensures a unique and internally consistent site set for analyzing
# connectivity.
#
# Note: This does assume that only one instance of each site is present
# in each tile, which is checked.
sites = {}
sites_in_tiles = set()
cur.execute(
"""
SELECT
site.site_type_pkey, wire_in_tile.site_pkey, wire_in_tile.phy_tile_type_pkey
FROM wire_in_tile
INNER JOIN site
ON site.pkey = wire_in_tile.site_pkey
WHERE
wire_in_tile.tile_type_pkey = ?
GROUP BY site.site_type_pkey, wire_in_tile.phy_tile_type_pkey
ORDER BY wire_in_tile.phy_tile_type_pkey;""", (tile_type_pkey, )
)
for site_type_pkey, site_pkey, phy_tile_type_pkey in cur:
if site_type_pkey not in sites:
sites[site_type_pkey] = site_pkey
# Verify that assumption that each site type is only used once per tile
# is true.
key = (site_type_pkey, phy_tile_type_pkey)
assert key not in sites_in_tiles, key
sites_in_tiles.add(key)
# Retrieve initial top-level port names
top_level_pins = {}
for site_pkey in sites.values():
for wire_in_tile_pkey, wire_name, direction in cur.execute("""
SELECT
wire_in_tile.pkey, wire_in_tile.name, site_pin.direction
FROM wire_in_tile
INNER JOIN site_pin
ON wire_in_tile.site_pin_pkey = site_pin.pkey
WHERE
site_pkey = ?""", (site_pkey, )):
direction = prjxray.site_type.SitePinDirection(direction)
assert wire_name not in top_level_pins
top_level_pins[wire_name] = (site_pkey, wire_in_tile_pkey)
if direction == prjxray.site_type.SitePinDirection.IN:
assert wire_name not in input_wires
input_wires.add(wire_name)
elif direction == prjxray.site_type.SitePinDirection.OUT:
assert wire_name not in output_wires
output_wires.add(wire_name)
else:
assert False, wire_name
##########################################################################
# Generate the model.xml file #
##########################################################################
model = ModelXml(f=args.output_model, site_directory=args.site_directory)
site_type_instances = parse_site_type_instance(args.site_types)
for site_type_pkey in sites:
cur.execute(
"SELECT name FROM site_type WHERE pkey = ?", (site_type_pkey, )
)
site_type = cur.fetchone()[0]
for instance in site_type_instances[site_type]:
model.add_model_include(site_type, instance)
model.write_model()
# Determine which input/output wires connection to other site pins, and no
# others.
top_level_connections, internal_connections = find_connections(
conn, input_wires, output_wires, tile_type_pkey
)
##########################################################################
# Generate the pb_type.xml file #
##########################################################################
tile_name = args.tile
pb_type_xml = start_pb_type(
tile_name, args.pin_assignments, input_wires, output_wires
)
cell_names = {}
interconnect_xml = ET.Element('interconnect')
site_pbtype = args.site_directory + "/{0}/{1}.pb_type.xml"
site_type_count = {}
site_prefixes = {}
cells_idx = []
ignored_site_types = set()
cur.execute(
"""
WITH tiles_per_tile(num_tiles) AS (
SELECT count()
FROM tile_map
INNER JOIN tile
ON tile_map.tile_pkey = tile.pkey
WHERE tile.tile_type_pkey = ?
GROUP BY tile_map.tile_pkey
)
SELECT max(num_tiles) FROM tiles_per_tile;
""", (tile_type_pkey, )
)
max_tile_count = cur.fetchone()[0]
need_tile_prefixs = max_tile_count > 1
site_type_ports = {}
cur.execute(
"""
SELECT DISTINCT
site_type.name, site_instance.y_coord % 2
FROM
site
INNER JOIN
site_type
ON site.site_type_pkey = site_type.pkey
INNER JOIN
site_instance
ON site.pkey = site_instance.site_pkey
WHERE
site.pkey IN (
SELECT
DISTINCT site_pkey
FROM
wire_in_tile
WHERE
tile_type_pkey = ?
AND site_pin_pkey IS NOT NULL
)
""", (tile_type_pkey, )
)
for idx, (site_type, site_y) in enumerate(cur):
if site_type in ignored_site_types:
continue
if site_type not in site_type_count:
site_type_count[site_type] = 0
site_prefixes[site_type] = []
cells_idx.append(site_type_count[site_type])
site_type_count[site_type] += 1
site_prefix = '{}_Y{}'.format(normalize_site_type(site_type), site_y)
# When tiles are merged, additional tile prefixes are required here
# to disambiguate which physical tile this site belongs too
if need_tile_prefixs:
tile_prefix = get_tile_prefix(conn, tile_type_pkey, site_type)
site_prefix = '{}.{}'.format(tile_prefix, site_prefix)
site_instance = site_type_instances[site_type][cells_idx[idx]]
site_type_path = site_pbtype.format(
site_type.lower(), site_instance.lower()
)
cell_pb_type = ET.ElementTree()
root_element = cell_pb_type.parse(site_type_path)
cell_names[site_instance] = root_element.attrib['name']
ports = {}
for inputs in root_element.iter('input'):
ports[inputs.attrib['name']] = int(inputs.attrib['num_pins'])
for clocks in root_element.iter('clock'):
ports[clocks.attrib['name']] = int(clocks.attrib['num_pins'])
for outputs in root_element.iter('output'):
ports[outputs.attrib['name']] = int(outputs.attrib['num_pins'])
assert site_instance not in site_type_ports, (
site_instance, site_type_ports.keys()
)
site_type_ports[site_instance] = ports
attrib = dict(root_element.attrib)
include_xml = ET.SubElement(pb_type_xml, 'pb_type', attrib)
ET.SubElement(
include_xml, XI_INCLUDE, {
'href':
site_type_path,
'xpointer':
"xpointer(pb_type/child::node()[local-name()!='metadata'])",
}
)
metadata_xml = ET.SubElement(include_xml, 'metadata')
if not args.no_fasm_prefix:
ET.SubElement(
metadata_xml, 'meta', {
'name': 'fasm_prefix',
}
).text = site_prefix
# Import pb_type metadata if it exists.
if any(child.tag == 'metadata' for child in root_element):
ET.SubElement(
metadata_xml, XI_INCLUDE, {
'href': site_type_path,
'xpointer': "xpointer(pb_type/metadata/child::node())",
}
)
# Prevent emitting empty metadata
if not any(child.tag == 'meta' for child in metadata_xml):
include_xml.remove(metadata_xml)
# Iterate over sites in tile
cur.execute(
"""
SELECT
site_type.name,
site.pkey
FROM
site
INNER JOIN site_type ON site.site_type_pkey = site_type.pkey
WHERE
site.pkey IN (
SELECT
DISTINCT site_pkey
FROM
wire_in_tile
WHERE
tile_type_pkey = ?
AND site_pin_pkey IS NOT NULL
)
GROUP BY
site.site_type_pkey,
site.x_coord,
site.y_coord
""", (tile_type_pkey, )
)
for idx, (site_type, site_pkey) in enumerate(cur):
if site_type in ignored_site_types:
continue
site_idx = cells_idx[idx]
site_instance = site_type_instances[site_type][site_idx]
site_name = cell_names[site_instance]
interconnect_xml.append(ET.Comment(" Tile->Site "))
# Iterate over pins in site
cur2.execute(
"""
SELECT
site_pin.name,
site_pin.direction,
wire_in_tile.name
FROM
wire_in_tile
INNER JOIN site_pin ON wire_in_tile.site_pin_pkey = site_pin.pkey
WHERE
wire_in_tile.tile_type_pkey = ?
AND wire_in_tile.site_pkey = ?
AND wire_in_tile.site_pin_pkey IS NOT NULL;
""", (tile_type_pkey, site_pkey)
)
site_pins = list(cur2)
for site_pin_name, site_pin_direction, site_pin_wire in site_pins:
site_pin_direction = prjxray.site_type.SitePinDirection(
site_pin_direction
)
port = find_port(site_pin_name, site_type_ports[site_instance])
if port is None:
print(
"*** WARNING *** Didn't find port for name {} for site type {}"
.format(site_pin_name, site_type),
file=sys.stderr
)
continue
# Sanity check top_level_connections
if site_pin_wire not in top_level_connections:
continue
assert top_level_connections[site_pin_wire] == (
site_type, site_pin_name
)
if site_pin_direction == prjxray.site_type.SitePinDirection.IN:
add_direct(
interconnect_xml,
input=object_ref(
add_vpr_tile_prefix(tile_name), site_pin_wire
),
output=object_ref(site_name, **port)
)
elif site_pin_direction == prjxray.site_type.SitePinDirection.OUT:
pass
else:
assert False, site_pin_direction
interconnect_xml.append(ET.Comment(" Site->Tile "))
# Iterate over pins in site
cur2.execute("")
for site_pin_name, site_pin_direction, site_pin_wire in site_pins:
site_pin_direction = prjxray.site_type.SitePinDirection(
site_pin_direction
)
port = find_port(site_pin_name, site_type_ports[site_instance])
if port is None:
continue
# Sanity check top_level_connections
if site_pin_wire not in top_level_connections:
continue
assert top_level_connections[site_pin_wire] == (
site_type, site_pin_name
)
if site_pin_direction == prjxray.site_type.SitePinDirection.IN:
pass
elif site_pin_direction == prjxray.site_type.SitePinDirection.OUT:
add_direct(
interconnect_xml,
input=object_ref(site_name, **port),
output=object_ref(
add_vpr_tile_prefix(tile_name), site_pin_wire
),
)
else:
assert False, site_pin_direction
interconnect_xml.append(ET.Comment(" Site->Site "))
for (dest_site_type, dest_site_pin_name), (src_site_type, src_site_pin_name) in \
sorted(internal_connections.items(), key=lambda x: (x[1], x[0])):
# Only handling single instance per site type right now
assert len(site_type_instances[src_site_type]) == 1
assert len(site_type_instances[dest_site_type]) == 1
src_site_instance = site_type_instances[src_site_type][0]
src_port = find_port(
src_site_pin_name, site_type_ports[src_site_instance]
)
src_site_name = cell_names[src_site_instance]
if src_port is None:
print(
"*** WARNING *** Didn't find port for name {} for site type {}"
.format(src_site_pin_name, src_site_type),
file=sys.stderr
)
continue
dest_site_instance = site_type_instances[dest_site_type][0]
dest_port = find_port(
dest_site_pin_name, site_type_ports[dest_site_instance]
)
dest_site_name = cell_names[dest_site_instance]
if dest_port is None:
print(
"*** WARNING *** Didn't find port for name {} for site type {}"
.format(dest_site_pin_name, dest_site_type),
file=sys.stderr
)
continue
add_direct(
interconnect_xml,
input=object_ref(src_site_name, **src_port),
output=object_ref(dest_site_name, **dest_port),
)
pb_type_xml.append(interconnect_xml)
write_xml(args.output_pb_type, pb_type_xml)
def main():
parser = argparse.ArgumentParser(
description=__doc__, fromfile_prefix_chars='@', prefix_chars='-~'
)
parser.add_argument('--db_root', help="""Project X-Ray database to use.""")
parser.add_argument('--part', help="""FPGA part to use.""")
parser.add_argument('--tile', help="""Tile to generate for""")
parser.add_argument(
'--site_directory', help="""Diretory where sites are defined"""
)
parser.add_argument(
'--site_coords',
type=str,
default='X',
help="""Specify which site coords to use ('X', 'Y' or 'XY')"""
)
parser.add_argument(
'--output-pb-type',
nargs='?',
type=argparse.FileType('w'),
default=sys.stdout,
help="""File to write the output too."""
)
parser.add_argument(
'--output-model',
nargs='?',
type=argparse.FileType('w'),
default=sys.stdout,
help="""File to write the output too."""
)
parser.add_argument(
'--pin_assignments', required=True, type=argparse.FileType('r')
)
parser.add_argument(
'--site_as_tile',
action='store_true',
)
parser.add_argument(
'--site_types',
required=True,
help="Comma seperated list of site types to include in this tile."
)
parser.add_argument(
'--fused_sites',
action='store_true',
help="""
Typically a tile can treat the sites within the tile as independent.
For tiles where this is not true, fused sites only imports 1 primatative
for the entire tile, which should be named the same as the tile type."""
)
parser.add_argument(
'--connection_database',
help="""
Location of connection database to define this tile type.
The tile will be defined by the sites and wires from the
connection database in lue of Project X-Ray."""
)
parser.add_argument(
'--filter_x', help="Filter imported sites by their x coordinate."
)
parser.add_argument(
'--no_fasm_prefix',
action="store_true",
help="""Do not insert fasm prefix to the metadata."""
)
parser.add_argument(
'--unused_wires',
help="Comma seperated list of site wires to exclude in this tile."
)
args = parser.parse_args()
db = prjxray.db.Database(args.db_root, args.part)
ET.register_namespace('xi', XI_URL)
if args.site_as_tile:
assert not args.fused_sites
import_site_as_tile(db, args)
elif args.connection_database:
with sqlite3.connect("file:{}?mode=ro".format(
args.connection_database), uri=True) as conn:
import_tile_from_database(conn, args)
else:
import_tile(db, args)
if __name__ == '__main__':
main()