545 lines
20 KiB
Python
545 lines
20 KiB
Python
import json
|
|
import os
|
|
import re
|
|
from enum import Enum
|
|
from tileconn import apply_tileconn
|
|
from parse_sdf import parse_sdf_file
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
# Represents Xilinx device data from PrjXray etc
|
|
|
|
@dataclass
|
|
class WireData:
|
|
index: int
|
|
name: str
|
|
intent: str = ""
|
|
tied_value: Optional[int] = None
|
|
resistance: float = 0
|
|
capacitance: float = 0
|
|
|
|
@dataclass
|
|
class PIPData:
|
|
index: int
|
|
from_wire: int
|
|
to_wire: int
|
|
is_bidi: bool = False
|
|
is_route_thru: bool = False
|
|
is_buffered: bool = False
|
|
min_delay: float = 0
|
|
max_delay: float = 0
|
|
resistance: float = 0
|
|
capacitance: float = 0
|
|
|
|
@dataclass
|
|
class SiteWireData:
|
|
name: str
|
|
is_pin: bool = False
|
|
|
|
@dataclass
|
|
class SiteBELPinData:
|
|
name: str
|
|
pindir: str
|
|
site_wire_idx: int
|
|
|
|
@dataclass
|
|
class SiteBELData:
|
|
name: str
|
|
bel_type: str
|
|
bel_class: str
|
|
pins: list[SiteBELPinData]
|
|
|
|
@dataclass
|
|
class SitePIPData:
|
|
bel_idx: int
|
|
bel_input: str
|
|
from_wire_idx: int
|
|
to_wire_idx: int
|
|
|
|
@dataclass
|
|
class SitePinData:
|
|
name: str
|
|
pindir: str
|
|
site_wire_idx: int
|
|
prim_pin_name: str
|
|
|
|
class SiteData:
|
|
def __init__(self, site_type):
|
|
self.site_type = site_type
|
|
self.wires = []
|
|
self.bels = []
|
|
self.pips = []
|
|
self.pins = []
|
|
self.variants = {}
|
|
|
|
class TileSitePinData:
|
|
def __init__(self, wire_idx):
|
|
self.wire_idx = wire_idx
|
|
self.min_delay = 0
|
|
self.max_delay = 0
|
|
self.resistance = 0
|
|
self.capacitance = 0
|
|
|
|
class TileData:
|
|
def __init__(self, tile_type):
|
|
self.tile_type = tile_type
|
|
self.wires = []
|
|
self.wires_by_name = {}
|
|
self.pips = []
|
|
self.sitepin_data = {} # (type, relxy, pin) -> TileSitePinData
|
|
self.cell_timing = None
|
|
|
|
class PIP:
|
|
def __init__(self, tile, index):
|
|
self.tile = tile
|
|
self.index = index
|
|
self.data = tile.get_pip_data(index)
|
|
def src_wire(self):
|
|
return Wire(self.tile, self.data.from_wire)
|
|
def dst_wire(self):
|
|
return Wire(self.tile, self.data.to_wire)
|
|
def is_route_thru(self):
|
|
return self.data.is_route_thru
|
|
def is_bidi(self):
|
|
return self.data.is_bidi
|
|
def is_buffered(self):
|
|
return self.data.is_buffered
|
|
def min_delay(self):
|
|
return self.data.min_delay
|
|
def max_delay(self):
|
|
return self.data.max_delay
|
|
def resistance(self):
|
|
return self.data.resistance
|
|
def capacitance(self):
|
|
return self.data.capacitance
|
|
|
|
class Wire:
|
|
def __init__(self, tile, index):
|
|
self.tile = tile
|
|
self.index = index
|
|
self.data = tile.get_wire_data(index)
|
|
def name(self):
|
|
return self.data.name
|
|
def intent(self):
|
|
return self.data.intent
|
|
def node(self):
|
|
if self.index not in self.tile.wire_to_node:
|
|
self.tile.wire_to_node[self.index] = Node(self.tile, [self])
|
|
return self.tile.wire_to_node[self.index]
|
|
def is_gnd(self):
|
|
return "GND_WIRE" in self.name()
|
|
def is_vcc(self):
|
|
return "VCC_WIRE" in self.name()
|
|
def resistance(self):
|
|
return self.data.resistance
|
|
def capacitance(self):
|
|
return self.data.capacitance
|
|
|
|
class SiteWire:
|
|
def __init__(self, site, index):
|
|
self.site = site
|
|
self.index = index
|
|
self.data = self.site.get_wire_data(index)
|
|
def name(self):
|
|
return self.data.name
|
|
|
|
class SiteBELPin:
|
|
def __init__(self, bel, name):
|
|
self.bel = bel
|
|
self.name = name
|
|
self.data = self.bel.data.pins[name]
|
|
def name(self):
|
|
return self.name
|
|
def dir(self):
|
|
return self.data.pindir
|
|
def site_wire(self):
|
|
return SiteWire(self.bel.site, self.data.site_wire_idx)
|
|
|
|
class SiteBEL:
|
|
def __init__(self, site, index):
|
|
self.site = site
|
|
self.index = index
|
|
self.data = site.get_bel_data(index)
|
|
def name(self):
|
|
return self.data.name
|
|
def bel_type(self):
|
|
return self.data.bel_type
|
|
def bel_class(self):
|
|
return self.data.bel_class
|
|
def pins(self):
|
|
return (SiteBELPin(self, n) for n in self.data.pins.keys())
|
|
|
|
class SitePIP:
|
|
def __init__(self, site, index):
|
|
self.site = site
|
|
self.index = index
|
|
self.data = site.get_pip_data(index)
|
|
def bel(self):
|
|
return SiteBEL(self.site, self.data.bel_idx)
|
|
def bel_input(self):
|
|
return self.data.bel_input
|
|
def src_wire(self):
|
|
return SiteWire(self.site, self.data.from_wire_idx)
|
|
def dst_wire(self):
|
|
return SiteWire(self.site, self.data.to_wire_idx)
|
|
|
|
class SitePin:
|
|
def __init__(self, site, index):
|
|
self.site = site
|
|
self.index = index
|
|
self.data = site.data.pins[index]
|
|
def name(self):
|
|
return self.data.name
|
|
def dir(self):
|
|
return self.data.pindir
|
|
def site_wire(self):
|
|
return SiteWire(self.site, self.data.site_wire_idx)
|
|
def tile_wire(self):
|
|
return self.site.tile.site_pin_wire(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name)
|
|
def min_delay(self):
|
|
return self.site.tile.site_pin_timing(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name).min_delay
|
|
def max_delay(self):
|
|
return self.site.tile.site_pin_timing(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name).max_delay
|
|
def resistance(self):
|
|
return self.site.tile.site_pin_timing(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name).resistance
|
|
def capacitance(self):
|
|
return self.site.tile.site_pin_timing(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name).capacitance
|
|
|
|
class Site:
|
|
def __init__(self, tile, name, index, grid_xy, data, primary=None):
|
|
self.tile = tile
|
|
self.name = name
|
|
self.index = index
|
|
self.prefix = name[0:name.rfind('_')]
|
|
self.grid_xy = grid_xy
|
|
self.data = data
|
|
self.primary = primary if primary is not None else self
|
|
self._rel_xy = None # filled later
|
|
self._variants = None #filled later
|
|
def get_bel_data(self, index):
|
|
return self.data.bels[index]
|
|
def get_wire_data(self, index):
|
|
return self.data.wires[index]
|
|
def get_pip_data(self, index):
|
|
return self.data.pips[index]
|
|
def site_type(self):
|
|
return self.data.site_type
|
|
def rel_xy(self):
|
|
if self._rel_xy is None:
|
|
base_x = 999999
|
|
base_y = 999999
|
|
for site in self.tile.sites():
|
|
if site.prefix != self.prefix:
|
|
continue
|
|
base_x = min(base_x, site.grid_xy[0])
|
|
base_y = min(base_y, site.grid_xy[1])
|
|
self._rel_xy = (self.grid_xy[0] - base_x, self.grid_xy[1] - base_y)
|
|
return self._rel_xy
|
|
def bels(self):
|
|
return (SiteBEL(self, i) for i in range(len(self.data.bels)))
|
|
def wires(self):
|
|
return (SiteWire(self, i) for i in range(len(self.data.wires)))
|
|
def pips(self):
|
|
return (SitePIP(self, i) for i in range(len(self.data.pips)))
|
|
def pins(self):
|
|
return (SitePin(self, i) for i in range(len(self.data.pins)))
|
|
def pin(self, p):
|
|
for i in range(len(self.data.pins)):
|
|
if self.data.pins[i].name == p:
|
|
return SitePin(self, i)
|
|
return None
|
|
def available_variants(self):
|
|
# Make sure primary type is first
|
|
if self._variants is None:
|
|
self._variants = []
|
|
self._variants.append(self.site_type())
|
|
for var in sorted(self.data.variants.keys()):
|
|
if var != self.site_type():
|
|
self._variants.append(var)
|
|
return self._variants
|
|
def variant(self, vtype):
|
|
vsite = Site(self.tile, self.name, self.index, self.grid_xy, self.data.variants[vtype], self)
|
|
return vsite
|
|
def rel_name(self):
|
|
x, y = self.rel_xy()
|
|
return f"{self.prefix}_X{x}Y{y}"
|
|
|
|
class Tile:
|
|
def __init__(self, x, y, name, data, interconn_xy, site_insts):
|
|
self.x = x
|
|
self.y = y
|
|
self.name = name
|
|
self.data = data
|
|
self.interconn_xy = interconn_xy
|
|
self.site_insts = site_insts
|
|
self.wire_to_node = {}
|
|
self.node_autoidx = 0
|
|
self.used_wires = None
|
|
def get_pip_data(self, i):
|
|
return self.data.pips[i]
|
|
def get_wire_data(self, i):
|
|
return self.data.wires[i]
|
|
def tile_type(self):
|
|
return self.data.tile_type
|
|
def wires(self):
|
|
return (Wire(self, i) for i in range(len(self.data.wires)))
|
|
def wire(self, name):
|
|
return Wire(self, self.data.wires_by_name[name].index)
|
|
def pips(self):
|
|
return (PIP(self, i) for i in range(len(self.data.pips)))
|
|
def sites(self):
|
|
return self.site_insts
|
|
def site_pin_wire(self, sitetype, rel_xy, pin):
|
|
wire_idx = self.data.sitepin_data[(sitetype, rel_xy, pin)].wire_idx
|
|
return Wire(self, wire_idx) if wire_idx is not None else None
|
|
def site_pin_timing(self, sitetype, rel_xy, pin):
|
|
return self.data.sitepin_data[(sitetype, rel_xy, pin)]
|
|
def cell_timing(self):
|
|
return self.data.cell_timing
|
|
def used_wire_indices(self):
|
|
if self.used_wires is None:
|
|
self.used_wires = set()
|
|
for pip in self.pips():
|
|
self.used_wires.add(pip.src_wire().index)
|
|
self.used_wires.add(pip.dst_wire().index)
|
|
for site in self.sites():
|
|
for v in site.available_variants():
|
|
variant = site.variant(v)
|
|
for pin in variant.pins():
|
|
if pin.tile_wire() is not None:
|
|
self.used_wires.add(pin.tile_wire().index)
|
|
return self.used_wires
|
|
def split_name(self):
|
|
prefix, xy = self.name.rsplit("_", 1)
|
|
xy_m = re.match(r"X(\d+)Y(\d+)", xy)
|
|
return prefix, int(xy_m.group(1)), int(xy_m.group(2))
|
|
|
|
class Node:
|
|
def __init__(self, tile, wires=[]):
|
|
self.tile = tile
|
|
self.index = tile.node_autoidx
|
|
tile.node_autoidx += 1
|
|
self.wires = wires
|
|
def unique_index(self):
|
|
return (self.tile.y << 48) | (self.tile.x << 32) | self.index
|
|
def is_vcc(self):
|
|
for wire in self.wires:
|
|
if wire.is_vcc():
|
|
return True
|
|
return False
|
|
def is_gnd(self):
|
|
for wire in self.wires:
|
|
if wire.is_gnd():
|
|
return True
|
|
return False
|
|
|
|
class Package:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.pin_map = {}
|
|
|
|
class Device:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.tiles = []
|
|
self.tiles_by_name = {}
|
|
self.tiles_by_xy = {}
|
|
self.sites_by_name = {}
|
|
self.width = 0
|
|
self.height = 0
|
|
self.packages = {}
|
|
def tile(self, name):
|
|
return self.tiles_by_name[name]
|
|
def site(self, name):
|
|
return self.sites_by_name[name]
|
|
|
|
def import_device(fabricname, prjxray_root, metadata_root):
|
|
site_type_cache = {}
|
|
tile_type_cache = {}
|
|
tile_json_cache = {}
|
|
def parse_xy(xy):
|
|
xpos = xy.rfind("X")
|
|
ypos = xy.rfind("Y")
|
|
return int(xy[xpos+1:ypos]), int(xy[ypos+1:])
|
|
|
|
def get_site_type_data(sitetype):
|
|
if sitetype not in site_type_cache:
|
|
sd = SiteData(sitetype)
|
|
sp = metadata_root + "/site_type_" + sitetype + ".json"
|
|
if os.path.exists(sp):
|
|
with open(sp, "r") as jf:
|
|
sj = json.load(jf)
|
|
for vtype, vdata in sorted(sj.items()): # Consider all site variants
|
|
if vtype == sitetype:
|
|
vd = sd # primary variant
|
|
else:
|
|
vd = SiteData(vtype)
|
|
site_wire_by_name = {}
|
|
def wire_index(name):
|
|
if name not in site_wire_by_name:
|
|
idx = len(vd.wires)
|
|
vd.wires.append(SiteWireData(name=name))
|
|
site_wire_by_name[name] = idx
|
|
return site_wire_by_name[name]
|
|
# Import bels
|
|
bel_idx_by_name = {}
|
|
for bel, beldata in sorted(vdata["bels"].items()):
|
|
belpins = {}
|
|
for pin, pindata in sorted(beldata["pins"].items()):
|
|
belpins[pin] = SiteBELPinData(name=pin, pindir=pindata["dir"], site_wire_idx=wire_index(pindata["wire"]))
|
|
bd = SiteBELData(name=bel, bel_type=beldata["type"], bel_class=beldata["class"], pins=belpins)
|
|
bel_idx_by_name[bel] = len(vd.bels)
|
|
vd.bels.append(bd)
|
|
# Import pips
|
|
for pipdata in vdata["pips"]:
|
|
bel_idx = bel_idx_by_name[pipdata["bel"]]
|
|
bel_data = vd.bels[bel_idx]
|
|
vd.pips.append(SitePIPData(bel_idx=bel_idx_by_name[pipdata["bel"]], bel_input=pipdata["from_pin"],
|
|
from_wire_idx=bel_data.pins[pipdata["from_pin"]].site_wire_idx,
|
|
to_wire_idx=bel_data.pins[pipdata["to_pin"]].site_wire_idx))
|
|
# Import pins
|
|
for pin, pindata in sorted(vdata["pins"].items()):
|
|
vd.pins.append(SitePinData(name=pin, pindir=pindata["dir"], site_wire_idx=wire_index(pindata["wire"]),
|
|
prim_pin_name=pindata["primary"]))
|
|
sd.variants[vtype] = vd
|
|
else:
|
|
sd.variants[sitetype] = sd
|
|
site_type_cache[sitetype] = sd
|
|
return site_type_cache[sitetype]
|
|
|
|
def read_tile_type_json(tiletype):
|
|
if tiletype not in tile_json_cache:
|
|
if not os.path.exists(prjxray_root + "/tile_type_" + tiletype + ".json"):
|
|
tile_json_cache[tiletype] = dict(wires={}, pips={}, sites=[])
|
|
else:
|
|
with open(prjxray_root + "/tile_type_" + tiletype + ".json", "r") as jf:
|
|
tile_json_cache[tiletype] = json.load(jf)
|
|
return tile_json_cache[tiletype]
|
|
|
|
def get_tile_type_data(tiletype):
|
|
if tiletype not in tile_type_cache:
|
|
td = TileData(tiletype)
|
|
# Import wires and pips
|
|
tj = read_tile_type_json(tiletype)
|
|
for wire, wire_data in sorted(tj["wires"].items()):
|
|
wire_id = len(td.wires)
|
|
wd = WireData(index=wire_id, name=wire, tied_value=None) # FIXME: tied_value
|
|
wd.intent = get_wire_intent(tiletype, wire)
|
|
if wire_data is not None:
|
|
if "res" in wire_data:
|
|
wd.resistance = float(wire_data["res"])
|
|
if "cap" in wire_data:
|
|
wd.capacitance = float(wire_data["cap"])
|
|
td.wires.append(wd)
|
|
td.wires_by_name[wire] = wd
|
|
for pip, pipdata in sorted(tj["pips"].items()):
|
|
# FIXME: pip/wire delays
|
|
pip_id = len(td.pips)
|
|
pd = PIPData(index=pip_id,
|
|
from_wire=td.wires_by_name[pipdata["src_wire"]].index, to_wire=td.wires_by_name[pipdata["dst_wire"]].index,
|
|
is_bidi=(not bool(int(pipdata["is_directional"]))), is_route_thru=bool(int(pipdata["is_pseudo"])))
|
|
if "is_pass_transistor" in pipdata:
|
|
pd.is_buffered = (not bool(int(pipdata["is_pass_transistor"])))
|
|
if "src_to_dst" in pipdata:
|
|
s2d = pipdata["src_to_dst"]
|
|
if "delay" in s2d and s2d["delay"] is not None:
|
|
pd.min_delay = min(float(s2d["delay"][0]), float(s2d["delay"][1]))
|
|
pd.max_delay = max(float(s2d["delay"][2]), float(s2d["delay"][3]))
|
|
if "res" in s2d and s2d["res"] is not None:
|
|
pd.resistance = float(s2d["res"])
|
|
if "in_cap" in s2d and s2d["in_cap"] is not None:
|
|
pd.capacitance = float(s2d["in_cap"])
|
|
td.pips.append(pd)
|
|
for sitedata in tj["sites"]:
|
|
rel_xy = parse_xy(sitedata["name"])
|
|
sitetype = sitedata["type"]
|
|
for sitepin, pindata in sorted(sitedata["site_pins"].items()):
|
|
if pindata is None:
|
|
tspd = TileSitePinData(None)
|
|
else:
|
|
pinwire = td.wires_by_name[pindata["wire"]].index
|
|
tspd = TileSitePinData(pinwire)
|
|
if "delay" in pindata:
|
|
tspd.min_delay = min(float(pindata["delay"][0]), float(pindata["delay"][1]))
|
|
tspd.max_delay = max(float(pindata["delay"][2]), float(pindata["delay"][3]))
|
|
if "res" in pindata:
|
|
tspd.resistance = float(pindata["res"])
|
|
if "cap" in pindata:
|
|
tspd.capacitance = float(pindata["cap"])
|
|
td.sitepin_data[(sitetype, rel_xy, sitepin)] = tspd
|
|
if os.path.exists(prjxray_root + "/timings/" + tiletype + ".sdf"):
|
|
td.cell_timing = parse_sdf_file(prjxray_root + "/timings/" + tiletype + ".sdf")
|
|
|
|
tile_type_cache[tiletype] = td
|
|
|
|
return tile_type_cache[tiletype]
|
|
|
|
def get_wire_intent(tiletype, wirename):
|
|
if tiletype not in ij["tiles"]:
|
|
return "GENERIC"
|
|
if wirename not in ij["tiles"][tiletype]:
|
|
return "GENERIC"
|
|
return ij["intents"][str(ij["tiles"][tiletype][wirename])]
|
|
|
|
d = Device(fabricname)
|
|
|
|
# Load intent JSON
|
|
with open(metadata_root + "/wire_intents.json", "r") as ijf:
|
|
ij = json.load(ijf)
|
|
with open(prjxray_root + "/" + fabricname + "/tilegrid.json") as gf:
|
|
tgj = json.load(gf)
|
|
for tile, tiledata in sorted(tgj.items()):
|
|
x = int(tiledata["grid_x"])
|
|
y = int(tiledata["grid_y"])
|
|
d.width = max(d.width, x + 1)
|
|
d.height = max(d.height, y + 1)
|
|
tiletype = tiledata["type"]
|
|
t = Tile(x, y, tile, get_tile_type_data(tiletype), (-1, -1), [])
|
|
for idx, (site, sitetype) in enumerate(sorted(tiledata["sites"].items())):
|
|
si = Site(t, site, idx, parse_xy(site), get_site_type_data(sitetype))
|
|
t.site_insts.append(si)
|
|
d.sites_by_name[site] = si
|
|
d.tiles_by_name[tile] = t
|
|
d.tiles_by_xy[x, y] = t
|
|
d.tiles.append(t)
|
|
|
|
# Resolve interconnect tile coordinates
|
|
for t in d.tiles:
|
|
for delta in range(0, 30):
|
|
if t.interconn_xy != (-1, -1):
|
|
break # found, done
|
|
for direction in (-1, +1):
|
|
nxy = (t.x + direction * delta, t.y)
|
|
if nxy not in d.tiles_by_xy:
|
|
continue
|
|
if d.tiles_by_xy[nxy].tile_type not in ("INT", "INT_L", "INT_R"):
|
|
continue
|
|
t.interconn_xy = nxy
|
|
break
|
|
# Read package pins
|
|
for entry in os.scandir(prjxray_root):
|
|
if not entry.is_dir() or not entry.name.startswith(fabricname):
|
|
continue
|
|
device_postfix = entry.name[len(fabricname):]
|
|
if len(device_postfix) == 0:
|
|
continue
|
|
package_name = device_postfix.split("-")[0]
|
|
if package_name in d.packages:
|
|
continue # already seen in a different speed grade
|
|
with open(prjxray_root + "/" + entry.name + "/package_pins.csv") as ppf:
|
|
pkg = Package(name=package_name)
|
|
for line in ppf:
|
|
sl = line.strip().split(",")
|
|
if len(sl) < 3:
|
|
continue
|
|
if sl[2] == "site":
|
|
continue # header
|
|
pkg.pin_map[sl[0]] = sl[2]
|
|
d.packages[package_name] = pkg
|
|
with open(prjxray_root + "/" + fabricname + "/tileconn.json", "r") as tcf:
|
|
apply_tileconn(tcf, d)
|
|
return d
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
import_device(*sys.argv[1:])
|