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