#!/usr/bin/env python3

import os
import re

from edalize.edatool import get_edatool

packages = {
    "Artix": {
        "xc7a12t"  : ["cpg238", "csg325"],
        "xc7a15t"  : ["cpg236", "csg324", "csg325", "ftg256", "fgg484"],
        "xc7a25t"  : ["cpg238", "csg325"],
        "xc7a35t"  : ["cpg236", "csg324", "csg325", "ftg256", "fgg484"],
        "xc7a50t"  : ["cpg236", "csg324", "csg325", "ftg256", "fgg484"],
        "xc7a75t"  : ["csg324", "ftg256", "fgg484", "fgg676"],
        "xc7a100t" : ["csg324", "ftg256", "fgg484", "fgg676"],
        "xc7a200t" : ["sbg484", "fbg484", "fbg676", "ffg1156"],
    },
    # Added but seems not possible to use same bitstream
    # for all Kintex with the same size but different package.
    "Kintex 7": {
        "xc7k70t"  : ["fbg484", "fbg676"],
        "xc7k160t" : ["fbg484", "fbg676", "ffg676"],
        "xc7k325t" : ["fbg676", "ffg676", "fbg900", "ffg900"],
        "xc7k355t" : ["ffg901"],
        "xc7k410t" : ["fbg676", "ffg676", "fbg900", "ffg900"],
        "xc7k420t" : ["ffg901", "ffg1156"],
        "xc7k480t" : ["ffg901", "ffg1156"],
    },
    "Spartan 7": {
        "xc7s6"   : ["ftgb196", "cpga196", "csga225"],
        "xc7s15"  : ["ftgb196", "cpga196", "csga225"],
        "xc7s25"  : ["ftgb196", "csga225", "csga324"],
        "xc7s50"  : ["ftgb196", "csga324", "fgga484"],
        "xc7s75"  : ["fgga484", "fgga676"],
        "xc7s100" : ["fgga484", "fgga676"],
    },
}

if len(os.sys.argv) != 3 :
    print("missing board flash type params")
    os.sys.exit()
part       = os.sys.argv[1]
flash_type = os.sys.argv[2]

# Check file type keyword
assert flash_type in ["spi", "bpi"]

build_dir="tmp_" + part
if not os.path.isdir(build_dir):
    try:
        os.mkdir(build_dir)
    except OSError:
        print ("Creation of the directory %s failed" % build_dir)
    else:
        print ("Successfully created the directory %s " % build_dir)

currDir    = os.path.abspath(os.path.curdir) + '/'
files      = []
parameters = {}
pkg_name   = None
pkg        = None
model      = ""

subpart = part[0:4].lower()
if subpart == '10cl':
    family = "Cyclone 10 LP"
    tool = "quartus"
elif subpart == 'ep4c':
    family = "Cyclone IV E"
    tool = "quartus"
elif subpart[0:2] == '5c':
    family = "Cyclone V"
    tool = "quartus"
    files.append({'name': currDir + 'constr_cycloneV.tcl',
                  'file_type': 'tclSource'})
elif subpart[0:2] == '5s':
    family = "Stratix V"
    tool = "quartus"
    files.append({'name': currDir + 'constr_cycloneV.tcl',
                  'file_type': 'tclSource'})
elif subpart == "xc7a":
    family = "Artix"
    tool   = "vivado"
    model  = subpart
elif subpart == "xc7v":
    family = "Virtex 7"
    tool = "vivado"
elif subpart == "xc7k":
    device_size = int(part.split('k')[1].split('t')[0])
    if flash_type == "bpi" or device_size <= 160:
        family = "Kintex 7"
        tool = "vivado"
    else:
        family = "Kintex7"
        tool = "ise"
    speed = -2
    model = subpart
elif subpart == "xc7s":
    family = "Spartan 7"
    tool   = "vivado"
    model  = subpart
elif subpart == "xc6s":
    family = "Spartan6"
    tool = "ise"
    speed = -3
elif subpart == "xc3s":
    family = "Spartan3E"
    tool = "ise"
    speed = -4
elif subpart == "xc6v":
    family = "Virtex6"
    tool = "ise"
    speed = -1
elif subpart in ["xcvu", "xcku", "xcau"]:
    family = "Xilinx UltraScale"
    tool = "vivado"
else:
    print("Error: unknown device")
    os.sys.exit()

if model in ["xc7a", "xc7s"]:
    pkg      = packages[family][part][0]
    pkg_name = f"{model}_{pkg}"
if model in ["xc7k"]:
    m        = re.match(r"(xc7k\d+t)(\w+)", part)
    pkg      = m.group(2)
    pkg_name = f"{model}_{pkg}"
    if tool == "ise":
        model = m.group(1)

if tool in ["ise", "vivado"]:
    pkg_name = {
        "xc3s500evq100"    : "xc3s_vq100",
        "xc6slx9tqg144"    : "xc6s_tqg144",
        "xc6slx9csg324"    : "xc6s_csg324",
        "xc6slx16ftg256"   : "xc6s_ftg256",
        "xc6slx16csg324"   : "xc6s_csg324",
        "xc6slx25csg324"   : "xc6s_csg324",
        "xc6slx25tcsg324"  : "xc6s_t_csg324",
        "xc6slx45csg324"   : "xc6s_csg324",
        "xc6slx45tfgg484"  : "xc6s_t_fgg484",
        "xc6slx100fgg484"  : "xc6s_fgg484",
        "xc6slx150tcsg484" : "xc6s_csg484",
        "xc6slx150tfgg484" : "xc6s_t_fgg484",
        "xc6vlx130tff784"  : "xc6v_ff784",
        "xc7vx330tffg1157" : "xc7v_ffg1157",
        "xcku040-ffva1156" : "xcku040_ffva1156",
        "xcku060-ffva1156" : "xcku060_ffva1156",
        "xcvu9p-flga2104"  : "xcvu9p_flga2104",
        "xcvu37p-fsvh2892" : "xcvu37p_fsvh2892",
        "xcku3p-ffva676"   : "xcku3p_ffva676",
        "xcku5p-ffvb676"   : "xcku5p_ffvb676",
        "xcau10p-ffvb676"  : "xcau10p_ffvb676",
        "xcau15p-ffvb676"  : "xcau15p_ffvb676",
    }.get(part, pkg_name)
    if tool == "ise":
        cst_type = "UCF"
        tool_options = {'family': family,
                        'device': {
                            "xc3s500evq100":    "xc3s500e",
                            "xc6slx9tqg144":    "xc6slx9",
                            "xc6slx9csg324":    "xc6slx9",
                            "xc6slx16ftg256":   "xc6slx16",
                            "xc6slx16csg324":   "xc6slx16",
                            "xc6slx25csg324":   "xc6slx25",
                            "xc6slx25tcsg324":  "xc6slx25t",
                            "xc6slx45csg324":   "xc6slx45",
                            "xc6slx45tfgg484":  "xc6slx45t",
                            "xc6slx100fgg484":  "xc6slx100",
                            "xc6slx150tcsg484": "xc6slx150t",
                            "xc6slx150tfgg484": "xc6slx150t",
                            "xc6vlx130tff784":  "xc6vlx130t",
                            }.get(part, model),
                        'package': {
                            "xc3s500evq100":    "vq100",
                            "xc6slx9tqg144":    "tqg144",
                            "xc6slx9csg324":    "csg324",
                            "xc6slx16ftg256":   "ftg256",
                            "xc6slx16csg324":   "csg324",
                            "xc6slx25csg324":   "csg324",
                            "xc6slx25tcsg324":  "csg324",
                            "xc6slx45csg324":   "csg324",
                            "xc6slx45tfgg484":  "fgg484",
                            "xc6slx100fgg484":  "fgg484",
                            "xc6slx150tcsg484": "csg484",
                            "xc6slx150tfgg484": "fgg484",
                            "xc6vlx130tff784":  "ff784",
                            }.get(part, pkg),
                        'speed' : speed
                }
    else:
        cst_type = "xdc"
        # Artix/Spartan 7 Specific use case:
        if family in ["Artix", "Spartan 7"]:
            tool_options = {'part': f"{part}{pkg}-1"}
        elif family == "Xilinx UltraScale":
            if part in ["xcvu9p-flga2104", "xcku5p-ffvb676"]:
                tool_options = {'part': part + '-1-e'}
                parameters["secondaryflash"]= {
                    'datatype': 'int',
                    'paramtype': 'vlogdefine',
                    'description': 'secondary flash',
                    'default': 1}
            elif part == "xcku3p-ffva676":
                tool_options = {'part': part + '-2-e'}
            elif part == "xcvu37p-fsvh2892":
                tool_options = {'part': part + '-2L-e'}
            elif part in ["xcku040-ffva1156", "xcku060-ffva1156"]:
                tool_options = {'part': part + '-2-e'}
                parameters["secondaryflash"]= {
                    'datatype': 'int',
                    'paramtype': 'vlogdefine',
                    'description': 'secondary flash',
                    'default': 1}
            elif part == "xcau10p-ffvb676":
                tool_options = {'part': part + '-1-e'}
            elif part == "xcau15p-ffvb676":
                tool_options = {'part': part + '-2-e'}
        else:
            tool_options = {'part': part + '-1'}

    cst_file = currDir + "constr_" + pkg_name + "." + cst_type.lower()
    files.append({'name': os.path.join(currDir, f"xilinx_{flash_type}OverJtag.v"),
                  'file_type': 'verilogSource'})
    files.append({'name': cst_file, 'file_type': cst_type})
else:
    # Altera only support SPI mode.
    assert flash_type in ["spi"]

    full_part = {
        "10cl016484" : "10CL016YU484C8G",
        "10cl025256" : "10CL025YU256C8G",
        "10cl055484" : "10CL055YU484C8G",
        "10cl006144" : "10CL006YE144C8G",
        "ep4cgx15027": "EP4CGX150DF27I7",
        "ep4ce11523" : "EP4CE115F23C7",
        "ep4ce2217"  : "EP4CE22F17C6",
        "ep4ce1523"  : "EP4CE15F23C8",
        "ep4ce1017"  : "EP4CE10F17C8",
        "ep4ce622"   : "EP4CE6E22C8",
        "5ce215"     : "5CEBA2U15C8",
        "5ce223"     : "5CEFA2F23I7",
        "5ce523"     : "5CEFA5F23I7",
        "5ce423"     : "5CEBA4F23C8",
        "5ce927"     : "5CEBA9F27C7",
        "5cse423"    : "5CSEMA4U23C6",
        "5cse623"    : "5CSEBA6U23I7",
        "5sgsd5"     : "5SGSMD5K2F40I3"}[part]
    files.append({'name': currDir + 'altera_spiOverJtag.v',
                  'file_type': 'verilogSource'})
    files.append({'name': currDir + 'altclkctrl_inst.v',
                  'file_type': 'verilogSource'})
    files.append({'name': currDir + 'altera_spiOverJtag.sdc',
                  'file_type': 'SDC'})
    tool_options = {'device': full_part, 'family':family}

files.append({'name': os.path.join(currDir, f"{flash_type}OverJtag_core.v"),
              'file_type': 'verilogSource'})

parameters[family.lower().replace(' ', '')]= {
    'datatype': 'int',
    'paramtype': 'vlogdefine',
    'description': 'fpga family',
    'default': 1}

edam = {'name' : f"{flash_type}OverJtag",
        'files': files,
        'tool_options': {tool: tool_options},
        'parameters': parameters,
        'toplevel' : f"{flash_type}OverJtag",
}

backend = get_edatool(tool)(edam=edam, work_root=build_dir)
backend.configure()
backend.build()

if tool in ["vivado", "ise"]:
    import shutil
    import subprocess
    import gzip

    # Compress bitstream.
    with open(f"tmp_{part}/{flash_type}OverJtag.bit", 'rb') as bit:
        with gzip.open(f"{flash_type}OverJtag_{part}.bit.gz", 'wb', compresslevel=9) as bit_gz:
            shutil.copyfileobj(bit, bit_gz)

    # Create Symbolic links for all supported packages.
    if family in ["Artix", "Spartan 7"]:
        in_file = f"{flash_type}OverJtag_{part}.bit.gz"
        for pkg in packages[family][part]:
            out_file = f"{flash_type}OverJtag_{part}{pkg}.bit.gz"
            if not os.path.exists(out_file):
                subprocess.run(["ln", "-s", in_file, out_file])
