nextpnr/himbaechel/uarch/xilinx/gen/xilinx_gen.py
gatecat 7516b8950a xilinx: Few more stub timings
Signed-off-by: gatecat <gatecat@ds0.me>
2024-09-26 17:30:36 +02:00

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()