390 lines
17 KiB
Python
390 lines
17 KiB
Python
from os import path
|
|
import sys
|
|
import argparse
|
|
import xilinx_device
|
|
import filters
|
|
import struct
|
|
import parse_sdf
|
|
sys.path.append(path.join(path.dirname(__file__), "../../.."))
|
|
from himbaechel_dbgen.chip import *
|
|
|
|
def lookup_port_type(t):
|
|
if t == "INPUT": return PinType.INPUT
|
|
elif t == "OUTPUT": return PinType.OUTPUT
|
|
elif t == "BIDIR": return PinType.INOUT
|
|
else: assert False
|
|
|
|
def gen_bel_name(site, bel_name):
|
|
prim_st = site.primary.site_type()
|
|
if prim_st in ("IOB33M", "IOB33S", "IOB33", "IOB18M", "IOB18S", "IOB18"):
|
|
return f"{site.site_type()}.{bel_name}"
|
|
else:
|
|
return f"{bel_name}"
|
|
|
|
@dataclass
|
|
class PipClass(Enum):
|
|
TILE_ROUTING = 0
|
|
SITE_ENTRANCE = 1
|
|
SITE_EXIT = 2
|
|
SITE_INTERNAL = 3
|
|
LUT_PERMUTATION = 4
|
|
LUT_ROUTETHRU = 5
|
|
CONST_DRIVER = 6
|
|
|
|
@dataclass
|
|
class PipExtraData(BBAStruct):
|
|
site_key: int = -1
|
|
bel_name: IdString = field(default_factory=IdString)
|
|
pip_config: int = 0
|
|
|
|
def serialise_lists(self, context: str, bba: BBAWriter):
|
|
pass
|
|
def serialise(self, context: str, bba: BBAWriter):
|
|
bba.u32(self.site_key)
|
|
bba.u32(self.bel_name.index)
|
|
bba.u32(self.pip_config)
|
|
|
|
@dataclass
|
|
class BelExtraData(BBAStruct):
|
|
name_in_site: IdString
|
|
|
|
def serialise_lists(self, context: str, bba: BBAWriter):
|
|
pass
|
|
def serialise(self, context: str, bba: BBAWriter):
|
|
bba.u32(self.name_in_site.index)
|
|
|
|
@dataclass
|
|
class SiteInst(BBAStruct):
|
|
name_prefix: IdString
|
|
site_x: int
|
|
site_y: int
|
|
rel_x: int
|
|
rel_y: int
|
|
int_x: int
|
|
int_y: int
|
|
variants: list[IdString] = field(default_factory=list)
|
|
|
|
def serialise_lists(self, context: str, bba: BBAWriter):
|
|
bba.label(f"{context}_variants")
|
|
for i, variant in enumerate(self.variants):
|
|
bba.u32(variant.index)
|
|
def serialise(self, context: str, bba: BBAWriter):
|
|
bba.u32(self.name_prefix.index)
|
|
bba.u16(self.site_x)
|
|
bba.u16(self.site_y)
|
|
bba.u16(self.rel_x)
|
|
bba.u16(self.rel_y)
|
|
bba.u16(self.int_x)
|
|
bba.u16(self.int_y)
|
|
bba.slice(f"{context}_variants", len(self.variants))
|
|
|
|
@dataclass
|
|
class TileExtraData(BBAStruct):
|
|
name_prefix: IdString
|
|
tile_x: int
|
|
tile_y: int
|
|
sites: list[SiteInst] = field(default_factory=list)
|
|
|
|
def serialise_lists(self, context: str, bba: BBAWriter):
|
|
for i, site in enumerate(self.sites):
|
|
site.serialise_lists(f"{context}_si{i}", bba)
|
|
bba.label(f"{context}_sites")
|
|
for i, site in enumerate(self.sites):
|
|
site.serialise(f"{context}_si{i}", bba)
|
|
def serialise(self, context: str, bba: BBAWriter):
|
|
bba.u32(self.name_prefix.index)
|
|
bba.u16(self.tile_x)
|
|
bba.u16(self.tile_y)
|
|
bba.slice(f"{context}_sites", len(self.sites))
|
|
|
|
def timing_pip_classs(pip: xilinx_device.PIP):
|
|
return f"{'buf' if pip.is_buffered() else 'sw'}_d{pip.max_delay()}_r{pip.resistance()}_c{pip.capacitance()}"
|
|
|
|
seen_pip_timings = set()
|
|
seen_node_timings = set()
|
|
|
|
def import_tiletype(ch: Chip, tile: xilinx_device.Tile):
|
|
tile_type = tile.tile_type()
|
|
if tile.x == 0 and tile.y == 0:
|
|
assert tile_type == "NULL"
|
|
tile_type = "NULL_CORNER"
|
|
tt = ch.create_tile_type(tile_type)
|
|
# import tile wires
|
|
for wire in tile.wires():
|
|
nw = tt.create_wire(wire.name(), wire.intent())
|
|
nw.flags = -1 # not a site wire
|
|
if wire.is_gnd():
|
|
nw.const_value = ch.strs.id("GND")
|
|
if wire.is_vcc():
|
|
nw.const_value = ch.strs.id("VCC")
|
|
if tile_type == "NULL_CORNER":
|
|
# pseudo ground bels
|
|
tt.create_wire(f"GND", "GND", const_value="GND")
|
|
tt.create_wire(f"VCC", "VCC", const_value="VCC")
|
|
|
|
gnd = tt.create_bel(f"PSEUDO_GND", f"PSEUDO_GND", z=0)
|
|
gnd.site = -1
|
|
gnd.extra_data = BelExtraData(name_in_site=ch.strs.id("PSEUDO_GND"))
|
|
tt.add_bel_pin(gnd, "Y", "GND", PinType.OUTPUT)
|
|
|
|
vcc = tt.create_bel(f"PSEUDO_VCC", f"PSEUDO_VCC", z=1)
|
|
vcc.site = -1
|
|
vcc.extra_data = BelExtraData(name_in_site=ch.strs.id("PSEUDO_VCC"))
|
|
tt.add_bel_pin(vcc, "Y", "VCC", PinType.OUTPUT)
|
|
def add_pip(src_wire, dst_wire, pip_class=PipClass.TILE_ROUTING, timing="",
|
|
site_key=-1, bel_name="", pip_config=0):
|
|
np = tt.create_pip(src_wire, dst_wire, timing)
|
|
np.flags = pip_class.value
|
|
np.extra_data = PipExtraData(site_key=site_key, bel_name=ch.strs.id(bel_name),
|
|
pip_config=pip_config)
|
|
|
|
def lookup_site_wire(sw):
|
|
canon_name = f"{sw.site.rel_name()}.{sw.name()}"
|
|
if not tt.has_wire(canon_name):
|
|
nw = tt.create_wire(name=canon_name,
|
|
type="INTENT_SITE_GND" if sw.name() == "GND_WIRE" else "INTENT_SITE_WIRE")
|
|
nw.flags = sw.site.primary.index
|
|
return canon_name
|
|
|
|
def add_site_io_pip(pin):
|
|
pn = pin.name()
|
|
s = pin.site
|
|
site_key = (s.index << 8)
|
|
if pin.tile_wire() is None:
|
|
return None
|
|
# TODO: timing class
|
|
if pin.dir() in ("OUTPUT", "BIDIR"):
|
|
if s.primary.site_type() == "IPAD" and pn == "O":
|
|
return None
|
|
# if s.rel_xy()[0] == 0 and s.primary.site_type() == "SLICEL" and pin.site_wire().name() == "A":
|
|
# # Add ground pip
|
|
# # self.add_pseudo_pip(self.row_gnd_wire_index, self.sitewire_to_tilewire(pin.site_wire()),
|
|
# # pip_type=NextpnrPipType.CONST_DRIVER)
|
|
# pass
|
|
add_pip(lookup_site_wire(pin.site_wire()), pin.tile_wire().name(),
|
|
pip_class=PipClass.SITE_EXIT, timing="SITE_NULL")
|
|
else:
|
|
if s.site_type() in ("SLICEL", "SLICEM"):
|
|
# Add permuation pseudo-pips for LUT inputs
|
|
swn = pin.site_wire().name()
|
|
if len(swn) == 2 and swn[0] in "ABCDEFGH" and swn[1] in "123456":
|
|
i = int(swn[1])
|
|
for j in range(1, 7):
|
|
if (i == 6) != (j == 6):
|
|
continue # don't allow permutation of input 6
|
|
pip_config = ("ABCDEFGH".index(swn[0]) << 8) | ((j - 1) << 4) | (i - 1)
|
|
if s.rel_xy()[0] == 1:
|
|
pip_config |= (4 << 8)
|
|
add_pip(s.pin(f"{swn[0]}{j}").tile_wire().name(), lookup_site_wire(pin.site_wire()),
|
|
pip_class=PipClass.LUT_PERMUTATION,
|
|
pip_config=pip_config,
|
|
site_key=(s.index << 8),
|
|
timing="SITE_NULL"
|
|
)
|
|
return
|
|
add_pip(pin.tile_wire().name(), lookup_site_wire(pin.site_wire()),
|
|
pip_class=PipClass.SITE_ENTRANCE, timing="SITE_NULL")
|
|
|
|
# TODO: ground/vcc
|
|
tile_wire_count = len(tt.wires)
|
|
for site in tile.sites():
|
|
seen_pins = set()
|
|
for variant_idx, variant in enumerate(site.available_variants()):
|
|
if variant in ("FIFO36E1", ): #unsupported atm
|
|
continue
|
|
sv = site.variant(variant)
|
|
variant_key = (site.index << 8) | (variant_idx & 0xFF)
|
|
# Import site bels
|
|
for bel in sv.bels():
|
|
z = filters.get_bel_z_override(bel, len(tt.bels))
|
|
# Overriden z of -1 means we skip this bel
|
|
if z == -1:
|
|
continue
|
|
bel_name = gen_bel_name(sv, bel.name())
|
|
nb = tt.create_bel(name=f"{site.rel_name()}.{bel_name}", type=filters.get_bel_type_override(bel.bel_type()), z=z)
|
|
nb.site = variant_key
|
|
nb.extra_data = BelExtraData(name_in_site=ch.strs.id(bel_name))
|
|
if bel.bel_class() == "RBEL": nb.flags |= 2
|
|
# TODO: extra data (site etc)
|
|
for pin in bel.pins():
|
|
tt.add_bel_pin(nb, pin.name, lookup_site_wire(pin.site_wire()), lookup_port_type(pin.dir()))
|
|
# Import site pins
|
|
for pin in sv.pins():
|
|
pin_key = (pin.name(), pin.site_wire().name())
|
|
if pin_key not in seen_pins:
|
|
add_site_io_pip(pin)
|
|
seen_pins.add(pin_key)
|
|
# Import site pips
|
|
for site_pip in sv.pips():
|
|
if "LUT" in site_pip.bel().bel_type():
|
|
continue # ignore site LUT route-throughs
|
|
bel_name = site_pip.bel().name()
|
|
bel_pin = site_pip.bel_input()
|
|
if (bel_name == "ADI1MUX" and bel_pin == "BDI1") or \
|
|
(bel_name == "BDI1MUX" and bel_pin == "DI") or \
|
|
(bel_name == "CDI1MUX" and bel_pin == "DI") or \
|
|
(bel_name.startswith("TFBUSED")) or \
|
|
(bel_name == "OMUX"):
|
|
continue
|
|
add_pip(lookup_site_wire(site_pip.src_wire()),
|
|
lookup_site_wire(site_pip.dst_wire()),
|
|
pip_class=PipClass.SITE_INTERNAL,
|
|
site_key=variant_key,
|
|
bel_name=bel_name,
|
|
pip_config=ch.strs.id(bel_pin).index,
|
|
timing="SITE_NULL"
|
|
)
|
|
# Import tile pips
|
|
for pip in tile.pips():
|
|
if not filters.include_pip(tile.tile_type(), pip):
|
|
continue
|
|
tcls = timing_pip_classs(pip)
|
|
if tcls not in seen_pip_timings:
|
|
ch.timing.set_pip_class(f"DEFAULT", tcls,
|
|
delay=TimingValue(int(pip.min_delay()*1000), int(pip.max_delay()*1000)), # ps
|
|
in_cap=TimingValue(int(pip.capacitance()*1000)), # fF
|
|
out_res=TimingValue(int(pip.resistance())), # mohm
|
|
is_buffered=pip.is_buffered())
|
|
seen_pip_timings.add(tcls)
|
|
add_pip(pip.src_wire().name(), pip.dst_wire().name(), pip_class=PipClass.TILE_ROUTING, timing=tcls,
|
|
pip_config=1 if pip.is_route_thru() else 0)
|
|
# TODO: extra data, route-through flag
|
|
if pip.is_bidi():
|
|
add_pip(pip.dst_wire().name(), pip.src_wire().name(), pip_class=PipClass.TILE_ROUTING, timing=tcls,
|
|
pip_config=1 if pip.is_route_thru() else 0)
|
|
|
|
def import_sdf_timings(variant, sdfcell):
|
|
# TODO: anything other than comb
|
|
for entry in sdfcell.entries:
|
|
if isinstance(entry, parse_sdf.IOPath):
|
|
variant.add_comb_arc(entry.from_pin, entry.to_pin,
|
|
TimingValue(int(min(entry.rising.minv, entry.falling.minv)*1000),
|
|
int(max(entry.rising.maxv, entry.falling.maxv)*1000)))
|
|
|
|
def main():
|
|
xlbase = path.join(path.dirname(path.realpath(__file__)), "..")
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--xray", help="Project X-Ray device database path for current family (e.g. ../prjxray-db/artix7)", type=str, required=True)
|
|
parser.add_argument("--metadata", help="nextpnr-xilinx site metadata root", type=str, default=path.join(xlbase, "meta", "artix7"))
|
|
parser.add_argument("--device", help="name of device to export", type=str, required=True)
|
|
parser.add_argument("--constids", help="name of nextpnr constids file to read", type=str, default=path.join(xlbase, "constids.inc"))
|
|
parser.add_argument("--bba", help="bba file to write", type=str, required=True)
|
|
args = parser.parse_args()
|
|
|
|
# Init database paths
|
|
metadata_root = args.metadata
|
|
xraydb_root = args.xray
|
|
if "xc7z" in args.device:
|
|
metadata_root = metadata_root.replace("artix7", "zynq7")
|
|
xraydb_root = xraydb_root.replace("artix7", "zynq7")
|
|
if "xc7k" in args.device:
|
|
metadata_root = metadata_root.replace("artix7", "kintex7")
|
|
xraydb_root = xraydb_root.replace("artix7", "kintex7")
|
|
if "xc7s" in args.device:
|
|
metadata_root = metadata_root.replace("artix7", "spartan7")
|
|
xraydb_root = xraydb_root.replace("artix7", "spartan7")
|
|
# Load prjxray device data
|
|
d = xilinx_device.import_device(args.device, xraydb_root, metadata_root)
|
|
# Init constant ids
|
|
ch = Chip("xilinx", args.device, d.width, d.height)
|
|
ch.strs.read_constids(path.join(path.dirname(__file__), args.constids))
|
|
ch.set_speed_grades(["DEFAULT", ]) # TODO: figure out how speed grades are supposed to work in prjxray
|
|
# Import tile types
|
|
for tile in d.tiles:
|
|
tile_type = tile.tile_type()
|
|
if tile.x == 0 and tile.y == 0:
|
|
assert tile_type == "NULL"
|
|
# location for pseudo gnd/vcc bels
|
|
tile_type = "NULL_CORNER"
|
|
if tile_type not in ch.tile_type_idx:
|
|
import_tiletype(ch, tile)
|
|
ti = ch.set_tile_type(tile.x, tile.y, tile_type)
|
|
prefix, tx, ty = tile.split_name()
|
|
ti.extra_data = TileExtraData(ch.strs.id(prefix), tx, ty)
|
|
for site in tile.sites():
|
|
ti.extra_data.sites.append(SiteInst(
|
|
name_prefix=ch.strs.id(site.prefix),
|
|
site_x=site.grid_xy[0], site_y=site.grid_xy[1],
|
|
rel_x=site.rel_xy()[0], rel_y=site.rel_xy()[1],
|
|
int_x=tile.interconn_xy[0], int_y=tile.interconn_xy[1],
|
|
variants=[ch.strs.id(v) for v in site.available_variants()]
|
|
))
|
|
# Import nodes
|
|
seen_nodes = set()
|
|
tt_used_wires = {}
|
|
print("Processing nodes...")
|
|
for row in range(d.height):
|
|
# TODO: GND and VCC row connectivity...
|
|
for col in range(d.width):
|
|
t = d.tiles_by_xy[col, row]
|
|
for w in t.wires():
|
|
n = w.node()
|
|
uid = n.unique_index()
|
|
if uid in seen_nodes:
|
|
continue
|
|
if len(n.wires) > 1:
|
|
node_wires = []
|
|
node_cap = 0
|
|
node_res = 0
|
|
# Add interconnect tiles first for better delay estimates in nextpnr
|
|
for j in range(2):
|
|
for w in n.wires:
|
|
if (w.tile.tile_type() in ("INT", "INT_L", "INT_R")) != (j == 0):
|
|
continue
|
|
if w.tile.tile_type() not in tt_used_wires:
|
|
tt_used_wires[w.tile.tile_type()] = w.tile.used_wire_indices()
|
|
node_cap += int(w.capacitance() * 1000)
|
|
node_res += int(w.resistance())
|
|
# prune wires from nodes that just pass through a tile without connecting to
|
|
# bels/pips, because nextpnr doesn't care about these
|
|
if w.index not in tt_used_wires[w.tile.tile_type()]:
|
|
continue
|
|
node_wires.append(NodeWire(w.tile.x, w.tile.y, w.index))
|
|
if len(node_wires) > 1:
|
|
timing_class = f"node_c{node_cap}_c{node_res}"
|
|
ch.add_node(node_wires, timing_class=timing_class)
|
|
if timing_class not in seen_node_timings:
|
|
ch.timing.set_node_class("DEFAULT", timing_class, delay=TimingValue(0), cap=TimingValue(node_cap), res=TimingValue(node_res))
|
|
seen_node_timings.add(timing_class)
|
|
del node_wires
|
|
seen_nodes.add(uid)
|
|
# Stub timing class
|
|
ch.timing.set_pip_class("DEFAULT", "SITE_NULL", delay=TimingValue(20))
|
|
# Stub bel timings
|
|
lut = ch.timing.add_cell_variant("DEFAULT", "SLICE_LUTX")
|
|
for i in range(1, 6+1):
|
|
lut.add_comb_arc(f"A{i}", "O6", TimingValue(100, 125))
|
|
if i <= 5: lut.add_comb_arc(f"A{i}", "O5", TimingValue(120, 150))
|
|
for i in range(1, 8+1):
|
|
lut.add_setup_hold("CLK", f"WA{i}", ClockEdge.RISING, TimingValue(100, 500), TimingValue(100, 200))
|
|
lut.add_setup_hold("CLK", "WE", ClockEdge.RISING, TimingValue(100, 600), TimingValue(100, 100))
|
|
lut.add_setup_hold("CLK", "DI1", ClockEdge.RISING, TimingValue(100, 100), TimingValue(100, 100))
|
|
ff = ch.timing.add_cell_variant("DEFAULT", "SLICE_FFX")
|
|
ff.add_setup_hold("CK", "CE", ClockEdge.RISING, TimingValue(100, 100), TimingValue(0, 0))
|
|
ff.add_setup_hold("CK", "SR", ClockEdge.RISING, TimingValue(100, 100), TimingValue(0, 0))
|
|
ff.add_setup_hold("CK", "D", ClockEdge.RISING, TimingValue(100, 100), TimingValue(200, 200))
|
|
ff.add_clock_out("CK", "Q", ClockEdge.RISING, TimingValue(300, 350))
|
|
|
|
# Load SDF for carry and mux
|
|
slicem_sdf = parse_sdf.parse_sdf_file(path.join(xraydb_root, "timings", "slicem.sdf"))
|
|
mux = ch.timing.add_cell_variant("DEFAULT", "SELMUX2_1")
|
|
import_sdf_timings(mux, slicem_sdf.cells[("SELMUX2_1", "SLICEM/F7BMUX")])
|
|
carry = ch.timing.add_cell_variant("DEFAULT", "CARRY4")
|
|
import_sdf_timings(carry, slicem_sdf.cells[("CARRY4", "SLICEM")])
|
|
|
|
# Import package pins
|
|
for package_name, package in sorted(d.packages.items(), key=lambda x:x[0]):
|
|
pkg = ch.create_package(package_name)
|
|
for pin, site in sorted(package.pin_map.items(), key=lambda x:x[0]):
|
|
site_data = d.sites_by_name[site]
|
|
bel_name = gen_bel_name(site_data, "PAD")
|
|
tile_type = ch.tile_type_at(site_data.tile.x, site_data.tile.y)
|
|
pkg.create_pad(pin, f"X{site_data.tile.x}Y{site_data.tile.y}",
|
|
f"{site_data.rel_name()}.{bel_name}",
|
|
"", 0) # TODO: bank
|
|
ch.write_bba(args.bba)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|