
* ng-ultra: new architecture * Implementation as in D2 deliverable * Support for nxdesignsuite-24.0.0.0-20240429T102300 * Save memory by directly outputing json * Add support for bidirectional IOs * cleanup * Create BFRs properly * Add IOM insertion * Cleanup * Block certain pips depending of DDFR mode * Add LUT bypass to improve routability * Add bypass for CSC mode of GCK * Fix IOM case * Initial memory support * Better RF/XRF handling * fix * RF placement and legalization * Disconnect non available ports for NX_RAM * cleanup * Add RFB/RAM context support for latest release * Remove ports that must not be used * Proper port used only on RFB * Add structure for clock sinks * Use cell type where applicable * Add clock sinks for other cell types * Validation check fixes * Commented too restrictive placement * Added more crossbar wire type * Hande IO termination input * Fail early due to NX tools limitation for now * Validations and fixes for RAM I/Os * Fix for latest version of tools * Use ctx->idf where applicable * warn if RAM ports are not actually used * Fix IOM packing * Fix CY packing * Change how constants are handled on CY * Post placement optimization for CY * Address comments for PR * pack and export GCK, WFG and PLL * Cover more global routing cases * Constraing to location if provided * Place at LOC * Pack and export DSP * wip * wip * notes * wip * wip * Validate DSPs * DSP cascading * Check mandatory parameters for DSP * existing gck * wip * export all the rest for bitstream * CDC packing * add more sinks * place FIFO * map rest of FIFO ports * enable pll by default * cleanup * Initial XLUT support * Fix statistics * Properly duplicate GCKs * RRSTO and WRSTO are not used on XFIFO * Fix for latest version of JSON format * Implement GCK limitations * cleanup * cleanup * Add more signals and use lowskew name * cleanup code a bit * Fix wfb * detect cascaded GCKs * Handle DFR * Route dfr clock properly * Cleanup * Cleanup bitstream code * Review issues addressed * Move helper routines * Expose private members for unit tests * cleanup * remove scale factor * make all location helper arrays static * Addressed review comments * Support post-routing CSC and SCC * Support NX_BFF * Place CSS and SCC only on allowed locations * Support latest Impulse * ng_ultra: Expand bounding box further for left-edge IO Signed-off-by: gatecat <gatecat@ds0.me> * Export all IO parameters in bitstream * Handle new CSV order or parameters and additional validation * Add some more undocumented values for CSV * Support for old and new CSV formats * Initial DDFR support * Display warning message once per file * Address review issues * Fix crash on memory access * Make boundbox fit NG-Ultra internal design * Update attributes after dff rewrite * Implement basic NG-Ultra LUT-DFF unit tests * Always use first seen xbar input Signed-off-by: gatecat <gatecat@ds0.me> * Simplified crossbar pip detection * Change order to prevent issues with some unconnected constants * Pack LUT and multiple DFF in stripe * Place DFF chains * Improve large DFF chains * Rename to pack_dff_chains * Better use XLUTs when possible * pack output DFF together with XLUT * option to disable XLUT optimiziations * Make more optimizations optional * fix to use pre-increment * GCK for lowskew signals * Bugfix for nets that are not part of lowskew network * Fix bitstream export for PLL cell * Remove separate route lowskew * Allow WFG mode 2 * Merge inverter into GCK * Add CSC per TILE when needed * Improve reusage of existing cell for CSC * Take preferred CSC * Cleanup * When in place CSC size not important * Cleanup * Reset and Load restriction * make csc optimisation optional * Proper count for IO resources * Detect when there is no next cell for DSP chain * Do not incorporate loops in XLUT * Check if output exists * Update copyright for delivery * Make building NG-Ultra chip database optional, follow filename convention * Ported drawing code to new API * Update expandBoundingBox for NG-Ultra * Copyright and license update * Add README information * cleanup and constids * Using ctx->idf where applicable * remove if_using_basecluster * refactor extra data usage * refactor to use create_cell_ptr only * optimized getCSC * optimize critical path a bit * clangformat * disable clangformat where applicable --------- Signed-off-by: gatecat <gatecat@ds0.me> Co-authored-by: Lofty <dan.ravensloft@gmail.com> Co-authored-by: gatecat <gatecat@ds0.me>
727 lines
28 KiB
Python
727 lines
28 KiB
Python
# nextpnr -- Next Generation Place and Route
|
|
#
|
|
# Copyright (C) 2024 The Project Beyond Authors.
|
|
#
|
|
# Permission to use, copy, modify, and/or distribute this software for any
|
|
# purpose with or without fee is hereby granted, provided that the above
|
|
# copyright notice and this permission notice appear in all copies.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
#
|
|
|
|
import json
|
|
from os import path
|
|
import sys
|
|
import argparse
|
|
import gzip
|
|
|
|
# Configuration flags
|
|
USE_LUT_PERMUTATION = True
|
|
|
|
sys.path.append(path.join(path.dirname(__file__), "../../.."))
|
|
from himbaechel_dbgen.chip import *
|
|
|
|
@dataclass
|
|
class TileExtraData(BBAStruct):
|
|
name: IdString
|
|
lobe: int = 0
|
|
tile_type: int = 0
|
|
|
|
def serialise_lists(self, context: str, bba: BBAWriter):
|
|
pass
|
|
def serialise(self, context: str, bba: BBAWriter):
|
|
bba.u32(self.name.index)
|
|
bba.u8(self.lobe)
|
|
bba.u8(self.tile_type)
|
|
bba.u16(0) # dummy
|
|
|
|
@dataclass
|
|
class PipExtraData(BBAStruct):
|
|
name: IdString = field(default_factory=IdString)
|
|
data_type: int = 0
|
|
input: int = 0
|
|
output: int = 0
|
|
|
|
def serialise_lists(self, context: str, bba: BBAWriter):
|
|
pass
|
|
def serialise(self, context: str, bba: BBAWriter):
|
|
bba.u32(self.name.index)
|
|
bba.u16(self.data_type)
|
|
bba.u8(self.input)
|
|
bba.u8(self.output)
|
|
|
|
@dataclass
|
|
class BelExtraData(BBAStruct):
|
|
flags: int = 0
|
|
|
|
def serialise_lists(self, context: str, bba: BBAWriter):
|
|
pass
|
|
def serialise(self, context: str, bba: BBAWriter):
|
|
bba.u32(self.flags)
|
|
|
|
PLL_Z = 0
|
|
WFG_C_Z = 1
|
|
WFG_R_Z = 11
|
|
|
|
IOM_Z = 136
|
|
|
|
RAM_Z = 0
|
|
DSP1_Z = 1
|
|
DSP2_Z = 2
|
|
|
|
SOCIF_Z = 0
|
|
SERVICE_Z = 1
|
|
|
|
LUT_Z = 0
|
|
CY_Z = 32
|
|
XLUT_Z = CY_Z + 4
|
|
RF_Z = XLUT_Z + 8
|
|
XRF_Z = RF_Z + 2
|
|
FIFO_Z = XRF_Z + 1
|
|
XFIFO_Z = FIFO_Z + 2
|
|
CDC_Z = XFIFO_Z + 1
|
|
XCDC_Z = CDC_Z + 2
|
|
|
|
PIP_EXTRA_CROSSBAR = 1
|
|
PIP_EXTRA_MUX = 2
|
|
PIP_EXTRA_BYPASS = 3
|
|
PIP_EXTRA_LUT_PERMUTATION = 4
|
|
PIP_EXTRA_INTERCONNECT = 5
|
|
PIP_EXTRA_VIRTUAL = 6
|
|
|
|
BEL_EXTRA_FE_CSC = 1
|
|
BEL_EXTRA_FE_SCC = 2
|
|
|
|
TILE_EXTRA_FABRIC = 0
|
|
TILE_EXTRA_TUBE = 1
|
|
TILE_EXTRA_SOC = 2
|
|
TILE_EXTRA_RING = 3
|
|
TILE_EXTRA_FENCE = 4
|
|
|
|
def bel_z(tile_type, bel_name, bel_type):
|
|
if tile_type.startswith("CKG"):
|
|
if (bel_type=="PLL"):
|
|
return PLL_Z
|
|
else:
|
|
if bel_name.startswith("WFG.WFG_C"):
|
|
return int(bel_name.replace("WFG.WFG_C","")) + WFG_C_Z -1
|
|
else:
|
|
return int(bel_name.replace("WFG.WFG_R","")) + WFG_R_Z -1
|
|
elif tile_type.startswith("HSSL"):
|
|
if bel_type=="PMA":
|
|
return 0
|
|
elif bel_type=="CRX":
|
|
return int(bel_name.split(".")[0][7:]) * 2 - 1
|
|
else:
|
|
return int(bel_name.split(".")[0][7:]) * 2
|
|
elif tile_type.startswith("IOB") and tile_type in ["IOB0","IOB1","IOB6","IOB7"]: #direct
|
|
sp = bel_name.split(".")
|
|
if bel_type=="IOP":
|
|
return int(sp[0][1:]) * 4
|
|
else:
|
|
return (int(sp[0][3:])-1) * 4 + (1 if sp[1][0]=='I' else 2 if sp[1][0]=='O' else 3)
|
|
elif tile_type.startswith("IOB"): # complex
|
|
sp = bel_name.split(".")
|
|
if bel_type=="IOTP":
|
|
return (int(sp[0][1:3]) *2 + (1 if sp[0][3]=='N' else 0)) * 4
|
|
elif bel_type=="IOM":
|
|
return IOM_Z
|
|
else:
|
|
return (int(sp[0][3:])-1) * 4 + (1 if sp[1][0]=='I' else 2 if sp[1][0]=='O' else 3)
|
|
elif tile_type == "CGB":
|
|
if bel_type=="RAM":
|
|
return RAM_Z
|
|
elif bel_name=="S2.DSP1":
|
|
return DSP1_Z
|
|
else:
|
|
return DSP2_Z
|
|
elif tile_type == "SOCBank":
|
|
if bel_type=="SOCIF":
|
|
return SOCIF_Z
|
|
else:
|
|
return SERVICE_Z
|
|
elif tile_type == "TILE":
|
|
sp = bel_name.split(".")
|
|
if bel_type=="BEYOND_FE":
|
|
return (int(sp[1][2:])-1) % 32
|
|
elif bel_type=="CY":
|
|
pos = int(sp[1][2:])-1
|
|
# in S1, S5 and S9 they are ordered other way arround
|
|
return ((pos % 4) if pos < 12 else 3 - (pos % 4)) + CY_Z
|
|
elif bel_type=="XLUT":
|
|
return (int(sp[1][4:])-1) % 8 + XLUT_Z
|
|
elif bel_type=="RF":
|
|
return (int(sp[1][2:])-1) + RF_Z
|
|
elif bel_type=="XRF":
|
|
return (int(sp[1][3:])-1) + XRF_Z
|
|
elif bel_type=="FIFO":
|
|
return (int(sp[1][4:])-1) + FIFO_Z
|
|
elif bel_type=="XFIFO":
|
|
return (int(sp[1][5:])-1) + XFIFO_Z
|
|
elif bel_type=="CDC":
|
|
return (int(sp[1][3:])-1) + CDC_Z
|
|
elif bel_type=="XCDC":
|
|
return (int(sp[1][4:])-1) + XCDC_Z
|
|
else:
|
|
raise Exception(f"Unknown bel type {bel_type}")
|
|
elif tile_type == "TUBE":
|
|
sp = bel_name.split(".")
|
|
return (int(sp[0][1:])-1)*20 + (int(sp[1][1:])-1)
|
|
else:
|
|
raise Exception(f"Unknown type {tile_type}")
|
|
|
|
if USE_LUT_PERMUTATION:
|
|
# Note PI1-PI4 are not real inputs but will appear
|
|
# before actual BEL input to enable LUT permutation
|
|
lut_to_beyond_fe = {
|
|
"I1" : "PI1",
|
|
"I2" : "PI2",
|
|
"I3" : "PI3",
|
|
"I4" : "PI4",
|
|
"O" : "LO",
|
|
"J" : "LJ",
|
|
"K" : "LK",
|
|
"D" : "LD",
|
|
"X" : "LX",
|
|
}
|
|
else:
|
|
lut_to_beyond_fe = {
|
|
"I1" : "I1",
|
|
"I2" : "I2",
|
|
"I3" : "I3",
|
|
"I4" : "I4",
|
|
"O" : "LO",
|
|
"J" : "LJ",
|
|
"K" : "LK",
|
|
"D" : "LD",
|
|
"X" : "LX",
|
|
}
|
|
|
|
dff_to_beyond_fe = {
|
|
"I" : "DI",
|
|
"O" : "DO",
|
|
"L" : "L",
|
|
"CK" : "CK",
|
|
"R" : "R",
|
|
"J" : "DJ",
|
|
"P" : "DP",
|
|
"C" : "DC",
|
|
"S" : "DS",
|
|
"K" : "DK",
|
|
}
|
|
|
|
def is_complex(tile_type):
|
|
return tile_type not in ["IOB0","IOB1","IOB6","IOB7"]
|
|
|
|
def map_to_beyond(tile_type,bel_name,bel_type=None):
|
|
if not tile_type in ["TILE"]:
|
|
return (bel_name,bel_type)
|
|
s = bel_name.split(".")
|
|
if tile_type in ["TILE"]:
|
|
# BEYOND_FE
|
|
if not (s[1].startswith("LUT") or s[1].startswith("DFF")):
|
|
return (bel_name,bel_type)
|
|
if len(s)==3:
|
|
# BEL and PORT
|
|
if s[1].startswith("LUT"):
|
|
pin = lut_to_beyond_fe[s[2]]
|
|
else:
|
|
pin = dff_to_beyond_fe[s[2]]
|
|
if pin is None:
|
|
return (None,bel_type)
|
|
return (s[0]+".FE"+s[1][3:]+"."+pin, bel_type if type(bel_type) is int else "BEYOND_FE")
|
|
else:
|
|
# just BEL
|
|
return (s[0]+".FE"+s[1][3:], "BEYOND_FE")
|
|
|
|
split_map = ["TILE", "CGB"]
|
|
|
|
def split_tilegrid(tilegrid):
|
|
new_tilegrid = dict()
|
|
for k,v in tilegrid.items():
|
|
if v["type"] not in split_map:
|
|
new_tilegrid[k] = dict()
|
|
data = dict(v)
|
|
data["orig"] = data["type"]
|
|
data["x"]=data["x"]*4
|
|
data["y"]=data["y"]*4
|
|
new_tilegrid[k][0] = data
|
|
else:
|
|
new_tilegrid[k] = dict()
|
|
for i in range(4*4):
|
|
data = dict(v)
|
|
data["orig"] = data["type"]
|
|
data["type"] = data["type"]+"_"+str(i)
|
|
data["x"]=data["x"]*4 + i // 4
|
|
data["y"]=data["y"]*4 + i % 4
|
|
new_tilegrid[k][i] = data
|
|
return new_tilegrid
|
|
|
|
def determine_subtile(tile,bel):
|
|
if tile=="TILE":
|
|
if bel.startswith("SYSTEM"):
|
|
s = 3
|
|
elif bel.startswith("RE") or bel.startswith("RS") or bel.startswith("RI"):
|
|
s = int(bel.split(".")[0][2:])-1
|
|
elif bel.startswith("S"):
|
|
s = int(bel.split(".")[0][1:])+3
|
|
else:
|
|
return 0
|
|
return s
|
|
elif tile=="CGB":
|
|
if bel.startswith("SYSTEM"):
|
|
s = 6
|
|
elif bel.startswith("RE"):
|
|
s = 8
|
|
elif bel.startswith("RS"):
|
|
s = int(bel.split(".")[0][2:])
|
|
elif bel.startswith("RI"):
|
|
s = 5
|
|
elif bel.startswith("S1"):
|
|
s = 0
|
|
elif bel.startswith("S2.DSP1"):
|
|
s = 7
|
|
else:
|
|
return 15
|
|
return s
|
|
else:
|
|
return 0
|
|
|
|
def split_per_bels(bels):
|
|
new_bels = dict()
|
|
for k,v in bels.items():
|
|
if k not in split_map:
|
|
new_bels[k] = dict()
|
|
data = dict(v)
|
|
new_bels[k][0] = data
|
|
else:
|
|
new_bels[k] = dict()
|
|
for i in range(4*4):
|
|
new_bels[k][i] = dict()
|
|
for item,bel in v.items():
|
|
(item, bel) = map_to_beyond(k,item,bel)
|
|
if item is not None:
|
|
num = determine_subtile(k,item)
|
|
new_bels[k][num][item] = bel
|
|
return new_bels
|
|
|
|
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 create_pips(tt, tile_type, muxes, num, args):
|
|
file_path = path.join(args.db, args.device, tile_type + ".txt")
|
|
if not path.isfile(file_path):
|
|
return
|
|
with open(file_path) as f:
|
|
for item in f:
|
|
line = item.strip().split(" ")
|
|
name1,_ = map_to_beyond(tile_type,line[0])
|
|
name2,_ = map_to_beyond(tile_type,line[1])
|
|
if name1 is None or name2 is None:
|
|
continue
|
|
num1 = determine_subtile(tile_type,name1)
|
|
num2 = determine_subtile(tile_type,name2)
|
|
|
|
if name2 in muxes:
|
|
name2 = name2 + "." + line[2]
|
|
if num1==num and not tt.has_wire(name1):
|
|
tt.create_wire(name=name1, type=tile_type + "_WIRE")
|
|
if num2==num and not tt.has_wire(name2):
|
|
tt.create_wire(name=name2, type=tile_type + "_WIRE")
|
|
timing_class = line[3]
|
|
# Only create PIP if both ends are in same subtile
|
|
if num1==num and num2==num:
|
|
tt.create_pip(name1,name2,timing_class)
|
|
|
|
def create_tile_types(ch: Chip, bels, bel_pins, crossbars, interconnects, muxes, args):
|
|
for tile_type,items in bels.items():
|
|
for num in items.keys():
|
|
if len(items)==1:
|
|
sub_type = tile_type
|
|
else:
|
|
sub_type = f"{tile_type}_{num}"
|
|
tt = ch.create_tile_type(sub_type)
|
|
|
|
def lookup_site_wire(canon_name):
|
|
if not tt.has_wire(canon_name):
|
|
tt.create_wire(name=canon_name, type="BEL_PIN_WIRE")
|
|
return canon_name
|
|
|
|
# Create BELs inside tile
|
|
for name,bel in bels[tile_type][num].items():
|
|
nb = tt.create_bel(name, bel, z=bel_z(tile_type,name,bel))
|
|
# Create wires for each BEL port
|
|
for index in bel_pins[bel].keys():
|
|
pin = bel_pins[bel][index]
|
|
tt.add_bel_pin(nb, pin["name"], lookup_site_wire(f"{name}."+pin["name"]), lookup_port_type(pin["direction"]))
|
|
if (tile_type.startswith("TILE") and bel=="BEYOND_FE"):
|
|
flag = 0
|
|
fe_nxd = int(name[name.index(".")+3:])
|
|
if (((fe_nxd-1) & 127) < 64):
|
|
if ((fe_nxd-1)&31) < 16:
|
|
if (fe_nxd & 1)==1:
|
|
flag |= BEL_EXTRA_FE_SCC
|
|
else:
|
|
if (fe_nxd & 1)==0:
|
|
flag |= BEL_EXTRA_FE_SCC
|
|
|
|
if fe_nxd in (65, 80, 81, 96, 225,240,241,256, 321,336,337,352):
|
|
flag |= BEL_EXTRA_FE_CSC
|
|
nb.extra_data = BelExtraData(flag)
|
|
|
|
# LUT drawers, LO already connected to LJ
|
|
vi = tt.create_pip(f"{name}.LJ",f"{name}.LK","Virtual")
|
|
vi.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_VIRTUAL,0,0)
|
|
vi = tt.create_pip(f"{name}.LJ",f"{name}.LD","Virtual")
|
|
vi.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_VIRTUAL,0,0)
|
|
vi = tt.create_pip(f"{name}.LJ",f"{name}.LX","Virtual")
|
|
vi.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_VIRTUAL,0,0)
|
|
# DFF drawers, DO already connected to DJ
|
|
vi = tt.create_pip(f"{name}.DJ",f"{name}.DP","Virtual")
|
|
vi.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_VIRTUAL,0,0)
|
|
vi = tt.create_pip(f"{name}.DJ",f"{name}.DC","Virtual")
|
|
vi.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_VIRTUAL,0,0)
|
|
vi = tt.create_pip(f"{name}.DJ",f"{name}.DS","Virtual")
|
|
vi.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_VIRTUAL,0,0)
|
|
vi = tt.create_pip(f"{name}.DJ",f"{name}.DK","Virtual")
|
|
vi.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_VIRTUAL,0,0)
|
|
# DFF bypass
|
|
by = tt.create_pip(f"{name}.DI",f"{name}.DO","BYPASS")
|
|
by.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_BYPASS,0,0)
|
|
# LUT bypass
|
|
by = tt.create_pip(f"{name}.I1",f"{name}.LO","BYPASS")
|
|
by.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_BYPASS,1,0)
|
|
elif (tile_type.startswith("TILE") and bel=="XLUT"):
|
|
for out in ["G1","G2","G3","G4"]:
|
|
vi = tt.create_pip(f"{name}.J",f"{name}.{out}","Virtual")
|
|
vi.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_VIRTUAL,0,0)
|
|
elif (tile_type.startswith("TILE") and bel=="CY"):
|
|
# matrix of each input to each output combination
|
|
# crossbar but use mux placeholder for convenience
|
|
for inp in ["I1","I2","I3","I4"]:
|
|
for out in ["O1","O2","O3","O4"]:
|
|
pd = tt.create_pip(f"{name}."+inp,f"{name}."+out,"MATRIX_PIP")
|
|
pd.extra_data = PipExtraData(ch.strs.id(f"{name}."+inp),PIP_EXTRA_MUX,int(inp[1:])-1,int(out[1:])-1)
|
|
|
|
elif (tile_type.startswith("CKG") and bel=="WFG"):
|
|
by = tt.create_pip(f"{name}.ZI",f"{name}.ZO","BYPASS")
|
|
by.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_BYPASS,0,0)
|
|
elif (tile_type.startswith("TUBE") and bel=="GCK"):
|
|
# 20 clock signals comming to 20 GCK, SI1 is bypass
|
|
by = tt.create_pip(f"{name}.SI1",f"{name}.SO","BYPASS")
|
|
by.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_BYPASS,0,0)
|
|
# there are CMD signals that can be bypassed as well
|
|
by = tt.create_pip(f"{name}.CMD",f"{name}.SO","BYPASS")
|
|
by.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_BYPASS,1,0)
|
|
|
|
|
|
# Add LUT permutation
|
|
if USE_LUT_PERMUTATION and tile_type=="TILE":
|
|
for name,bel in bels[tile_type][num].items():
|
|
if bel=="BEYOND_FE":
|
|
for inp in ["PI1","PI2","PI3","PI4"]:
|
|
tt.create_wire(name=f"{name}."+inp, type="LUT_PERMUTATION_WIRE")
|
|
for out in ["I1","I2","I3","I4"]:
|
|
pd = tt.create_pip(f"{name}."+inp,f"{name}."+out,"LUT_PERMUTATION")
|
|
pd.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_LUT_PERMUTATION,int(inp[2:])-1,int(out[1:])-1)
|
|
|
|
# Create crossbars as multiple PIPs
|
|
for name,xb in crossbars[tile_type][num].items():
|
|
inputs = list()
|
|
outputs = list()
|
|
for index in bel_pins[xb].keys():
|
|
pin = bel_pins[xb][index]
|
|
tt.create_wire(name=f"{name}."+pin["name"], type="CROSSBAR_"+xb+"_INPUT_WIRE" if pin["direction"] == "Input" else "CROSSBAR_"+xb+"_OUTPUT_WIRE")
|
|
for index in bel_pins[xb].keys():
|
|
pin = bel_pins[xb][index]
|
|
if pin["direction"] == "Input":
|
|
inputs.append(pin["name"])
|
|
else:
|
|
outputs.append(pin["name"])
|
|
for inp in inputs:
|
|
for out in outputs:
|
|
pd = tt.create_pip(f"{name}."+inp,f"{name}."+out,"CROSSBAR_"+xb)
|
|
pd.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_CROSSBAR,int(inp[1:])-1,int(out[1:])-1)
|
|
|
|
# Interconnects are just PIPs with one I and one O
|
|
for name,xb in interconnects[tile_type][num].items():
|
|
for index in bel_pins[xb].keys():
|
|
pin = bel_pins[xb][index]
|
|
tt.create_wire(name=f"{name}."+pin["name"], type="INTERCONNECT_INPUT" if pin["direction"] == "Input" else "INTERCONNECT_OUTPUT")
|
|
inp = f"{name}."+bel_pins[xb]["I"]["name"]
|
|
out = f"{name}."+bel_pins[xb]["O"]["name"]
|
|
pd = tt.create_pip(inp,out,"INTERCONNECT")
|
|
pd.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_INTERCONNECT,0,0)
|
|
|
|
# Create MUXes as many to one connections
|
|
if tile_type in muxes:
|
|
for (name, n) in muxes[tile_type][num].items():
|
|
for i in range(0,n+1):
|
|
new_name = name + "." + str(i)
|
|
if not tt.has_wire(new_name):
|
|
tt.create_wire(name=new_name, type="MUX_WIRE")
|
|
pd = tt.create_pip(new_name,name,"MUX_PIP")
|
|
pd.extra_data = PipExtraData(ch.strs.id(name),PIP_EXTRA_MUX,i,0)
|
|
|
|
m = muxes[tile_type] if tile_type in muxes else dict()
|
|
mux = muxes[tile_type][num] if num in m else dict()
|
|
create_pips(tt, tile_type, mux, num, args)
|
|
|
|
def create_null(ch: Chip):
|
|
tt = ch.create_tile_type("NULL")
|
|
|
|
def set_timings(ch, pip_timings, bel_timings):
|
|
speed = "DEFAULT"
|
|
tmg = ch.set_speed_grades([speed])
|
|
for k,v in pip_timings.items():
|
|
tmg.set_pip_class(grade=speed, name=k, delay=TimingValue(v[0]))
|
|
tmg.set_node_class(grade=speed, name=k, delay=TimingValue(v[0]))
|
|
tmg.set_pip_class(grade=speed, name="INTERCONNECT", delay=TimingValue(0))
|
|
tmg.set_pip_class(grade=speed, name="MUX_PIP", delay=TimingValue(0))
|
|
tmg.set_pip_class(grade=speed, name="MATRIX_PIP", delay=TimingValue(0))
|
|
tmg.set_pip_class(grade=speed, name="LUT_PERMUTATION", delay=TimingValue(0))
|
|
tmg.set_pip_class(grade=speed, name="BYPASS", delay=TimingValue(142))
|
|
for k,g in bel_timings.items():
|
|
primitive = ch.timing.add_cell_variant(speed, k)
|
|
for t,v in g.items():
|
|
if t=="IOPath":
|
|
for from_port,values in v.items():
|
|
for to_port,data in values.items():
|
|
primitive.add_comb_arc(from_port, to_port, TimingValue(data[0], data[1]))
|
|
elif t=="SetupHold":
|
|
for from_port,values in v.items():
|
|
for to_port,data in values.items():
|
|
primitive.add_setup_hold(from_port, to_port, ClockEdge.RISING, TimingValue(data[0]), TimingValue(data[1]))
|
|
elif t=="ClockOut":
|
|
for from_port,values in v.items():
|
|
for to_port,data in values.items():
|
|
primitive.add_clock_out(from_port, to_port, ClockEdge.RISING, TimingValue(data[0],data[1]))
|
|
|
|
def get_pos(tilegrid,name,bel):
|
|
tile = tilegrid[name][0]["orig"]
|
|
num = determine_subtile(tile,bel)
|
|
item = tilegrid[name][num]
|
|
x = item["x"]
|
|
y = item["y"]
|
|
return (tile,num,x,y)
|
|
|
|
|
|
global_connections = dict()
|
|
def load_globals(args):
|
|
print("Load global connections...")
|
|
with gzip.open(path.join(args.db, args.device, "GLOBAL.txt.gz"),"rt") as f:
|
|
for item in f:
|
|
line = item.strip().split(" ")
|
|
tile_name = line[0].split(":")[0]
|
|
if tile_name not in global_connections:
|
|
global_connections[tile_name] = dict()
|
|
if line[0] not in global_connections[tile_name]:
|
|
global_connections[tile_name][line[0]] = list()
|
|
global_connections[tile_name][line[0]].append(line)
|
|
|
|
def create_nodes(ch, tile_name, tilegrid, muxes, pip_timings):
|
|
if tile_name not in global_connections:
|
|
return
|
|
connections = global_connections[tile_name]
|
|
for key,val in connections.items():
|
|
name1 = key.split(":")
|
|
t1,_,x1,y1 = get_pos(tilegrid,name1[0],name1[1])
|
|
name1[1],_ = map_to_beyond(t1,name1[1])
|
|
if name1[1] is None:
|
|
continue
|
|
node = [NodeWire(x1, y1, name1[1])]
|
|
timing = None
|
|
timing_val = -1
|
|
for v in val:
|
|
name2 = v[1].split(":")
|
|
name2[1],_ = map_to_beyond(tilegrid[name2[0]][0]["orig"],name2[1])
|
|
t2,num,x2,y2 = get_pos(tilegrid,name2[0],name2[1])
|
|
if name2[1] is None:
|
|
continue
|
|
if pip_timings[v[3]][0] > timing_val:
|
|
timing_val = pip_timings[v[3]][0]
|
|
timing = v[3]
|
|
if t2 in muxes and num in muxes[t2] and name2[1] in muxes[t2][num]:
|
|
node.append(NodeWire(x2, y2, name2[1]+"."+v[2]))
|
|
else:
|
|
node.append(NodeWire(x2, y2, name2[1]))
|
|
|
|
ch.add_node(node,timing_class=timing)
|
|
|
|
|
|
subtile_connections = dict()
|
|
def create_nodes_subtiles(ch, tilegrid, name, tile_type, muxes, pip_timings, args):
|
|
if tile_type not in subtile_connections:
|
|
subtile_connections[tile_type] = dict()
|
|
|
|
file_path = path.join(args.db, args.device, tile_type + ".txt")
|
|
if not path.isfile(file_path):
|
|
return
|
|
with open(file_path) as f:
|
|
for item in f:
|
|
line = item.strip().split(" ")
|
|
name1 = line[0]
|
|
name2 = line[1]
|
|
name1,_ = map_to_beyond(tile_type,line[0])
|
|
name2,_ = map_to_beyond(tile_type,line[1])
|
|
if name1 is None or name2 is None:
|
|
continue
|
|
num1 = determine_subtile(tile_type,name1)
|
|
num2 = determine_subtile(tile_type,name2)
|
|
# Only create WIRE if ends are NOT in same subtile
|
|
if num1!=num2:
|
|
if name1 not in subtile_connections[tile_type] :
|
|
subtile_connections[tile_type][name1] = list()
|
|
subtile_connections[tile_type][name1].append([name1, name2, line[2], line[3]])
|
|
|
|
for name1,val in subtile_connections[tile_type].items():
|
|
_,_,x1,y1 = get_pos(tilegrid,name,name1)
|
|
node = [NodeWire(x1, y1, name1)]
|
|
timing_val = -1
|
|
timing = None
|
|
for v in val:
|
|
name2 = v[1]
|
|
t2,num,x2,y2 = get_pos(tilegrid,name,name2)
|
|
if pip_timings[v[3]][0] > timing_val:
|
|
timing_val = pip_timings[v[3]][0]
|
|
timing = v[3]
|
|
if t2 in muxes and num in muxes[t2] and name2 in muxes[t2][num]:
|
|
node.append(NodeWire(x2, y2, name2+"."+v[2]))
|
|
else:
|
|
node.append(NodeWire(x2, y2, name2))
|
|
|
|
ch.add_node(node,timing_class=timing)
|
|
|
|
def import_package(ch, package, bels, tilegrid):
|
|
pkg = ch.create_package(package)
|
|
for name, data in tilegrid.items():
|
|
for key, item in data.items():
|
|
ty = item["orig"]
|
|
x = item["x"]
|
|
y = item["y"]
|
|
if ty.startswith("IOB"):
|
|
for bel,ty in bels[ty][key].items():
|
|
# Support for native primitives
|
|
if ty =="IOTP":
|
|
pin = name+"_"+bel[:4]
|
|
elif ty =="IOP":
|
|
pin = name+"_"+bel[:3]
|
|
else:
|
|
continue
|
|
pkg.create_pad(pin, f"X{x}Y{y}", bel, "", int(name[3:]))
|
|
|
|
def main():
|
|
xlbase = path.join(path.dirname(path.realpath(__file__)), "..")
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--db", help="Project Beyond device database path (e.g. ../prjbeyond/database)", type=str, required=True)
|
|
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()
|
|
|
|
with open(path.join(args.db, "devices.json")) as f:
|
|
devices = json.load(f)
|
|
|
|
width = (devices["families"][args.device]["max_col"] + 1) * 4
|
|
height = (devices["families"][args.device]["max_row"] + 1) * 4
|
|
packages = devices["families"][args.device]["packages"]
|
|
|
|
ch = Chip("ng-ultra",args.device, width, height)
|
|
ch.strs.read_constids(path.join(path.dirname(__file__), "..", "constids.inc"))
|
|
|
|
# Data that is depending of location
|
|
with open(path.join(args.db, args.device, "tilegrid.json")) as f:
|
|
tilegrid = split_tilegrid(json.load(f))
|
|
|
|
with open(path.join(args.db, args.device, "bels.json")) as f:
|
|
bels = split_per_bels(json.load(f))
|
|
|
|
with open(path.join(args.db, args.device, "crossbars.json")) as f:
|
|
crossbars = split_per_bels(json.load(f))
|
|
|
|
with open(path.join(args.db, args.device, "interconnects.json")) as f:
|
|
interconnects = split_per_bels(json.load(f))
|
|
|
|
with open(path.join(args.db, args.device, "muxes.json")) as f:
|
|
muxes = split_per_bels(json.load(f))
|
|
|
|
# Data that is not related to position
|
|
with open(path.join(args.db, args.device, "bel_pins.json")) as f:
|
|
bel_pins = json.load(f)
|
|
bel_pins["IOP"]["IO"] = { "direction": "Bidir", "name": "IO" }
|
|
bel_pins["IOTP"]["IO"] = { "direction": "Bidir", "name": "IO" }
|
|
|
|
with open(path.join(args.db, args.device, "pip_timings.json")) as f:
|
|
pip_timings = json.load(f)
|
|
|
|
with open(path.join(args.db, args.device, "bel_timings.json")) as f:
|
|
bel_timings = json.load(f)
|
|
|
|
create_tile_types(ch, bels, bel_pins, crossbars, interconnects, muxes, args)
|
|
create_null(ch)
|
|
set_timings(ch, pip_timings, bel_timings)
|
|
|
|
for x in range(width):
|
|
for y in range(height):
|
|
ch.set_tile_type(x,y,"NULL")
|
|
|
|
load_globals(args)
|
|
for name, data in tilegrid.items():
|
|
for item in data.values():
|
|
ti = ch.set_tile_type(item["x"],item["y"],item["type"])
|
|
lobe = 0
|
|
if item["orig"] in ["TILE","CGB"]:
|
|
tmp = name.replace("TILE[","").replace("CGB[","").replace("]","")
|
|
x,y = tmp.split("x")
|
|
lobe = ((int(y)-1) // 12)*2 + (1 if int(x)>46 else 0) + 1
|
|
elif item["orig"].startswith("IOB") or item["orig"].startswith("HSSL"):
|
|
match item["orig"]:
|
|
case "IOB0" | "IOB1":
|
|
lobe = 5
|
|
case "IOB6" | "IOB7":
|
|
lobe = 6
|
|
case "IOB8" | "IOB9" | "IOB10":
|
|
lobe = 2
|
|
case "IOB11" | "IOB12" | "IOB13":
|
|
lobe = 1
|
|
case "IOB2" | "IOB3" | "HSSL0" | "HSSL1" | "HSSL2" | "HSSL3":
|
|
lobe = 7
|
|
case "IOB4" | "IOB5" | "HSSL4" | "HSSL5" | "HSSL6" | "HSSL7":
|
|
lobe = 8
|
|
tile_type = 0
|
|
if item["orig"] in ["TILE","CGB","MESH"]:
|
|
tile_type = TILE_EXTRA_FABRIC
|
|
elif item["orig"] in ["TUBE"]:
|
|
tile_type = TILE_EXTRA_TUBE
|
|
elif item["orig"] in ["SOCBank"]:
|
|
tile_type = TILE_EXTRA_SOC
|
|
elif item["orig"].startswith("IOB") or item["orig"].startswith("HSSL") or item["orig"].startswith("CKG"):
|
|
tile_type = TILE_EXTRA_RING
|
|
elif item["orig"].startswith("FENCE"):
|
|
tile_type = TILE_EXTRA_FENCE
|
|
|
|
ti.extra_data = TileExtraData(ch.strs.id(name),lobe, tile_type)
|
|
|
|
for name, data in tilegrid.items():
|
|
print(f"Generate nodes for {name}...")
|
|
create_nodes_subtiles(ch, tilegrid, name, data[0]["orig"], muxes, pip_timings, args)
|
|
create_nodes(ch, name, tilegrid, muxes, pip_timings)
|
|
|
|
for package in packages:
|
|
import_package(ch, package, bels, tilegrid)
|
|
|
|
ch.write_bba(args.bba)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|