from collections import defaultdict

from fuzzconfig import FuzzConfig
import interconnect
import nets
import pytrellis
import re
import argparse
import fuzzloops
import nonrouting
import os

jobs = [
        {
            "cfg": FuzzConfig(job="PICB0_AB", family="MachXO2", device="LCMXO2-1200HC",
                        ncl="empty.ncl", tiles=["PB11:PIC_B0"]),
            "side": "B",
            "pins": [("13", "A"), ("14", "B")]
        },

        # Split into multiple jobs, because Diamond chokes if the I/Os don't
        # actually physically exist (QFN32 is default).
        {
            "cfg": FuzzConfig(job="PICB0_CD", family="MachXO2", device="LCMXO2-1200HC",
                        ncl="empty.ncl", tiles=["PB6:PIC_B0"]),
            "side": "B",
            "pins": [("9", "C"), ("10", "D")]
        },

        {
            "cfg": FuzzConfig(job="PICL0_IO", family="MachXO2", device="LCMXO2-1200HC",
                        ncl="empty.ncl", tiles=["PL5:PIC_L0"]),
            "side": "L",
            "pins": [("12", "A"), ("13", "B"), ("14", "C"), ("15", "D")],
            "package": "TQFP100"
        },

        {
            "cfg": FuzzConfig(job="PICR0_IO", family="MachXO2", device="LCMXO2-1200HC",
                        ncl="empty.ncl", tiles=["PR5:PIC_R0"]),
            "side": "R",
            "pins": [("65", "A"), ("64", "B"), ("63", "C"), ("62", "D")],
            "package": "TQFP100"
        },

        {
            "cfg": FuzzConfig(job="PICT0_IO", family="MachXO2", device="LCMXO2-1200HC",
                        ncl="empty.ncl", tiles=["PT10:PIC_T0"]),
            "side": "T",
            "pins": [("97", "A"), ("96", "B")],
            "package": "TQFP100"
        },

        # FIXME: WARNING - map: In "LOCATE COMP "pad" SITE "PT10{C,D} pin" ;":
        # Current SYS_CONFIG setting prohibits pin be used as user IO. This
        # preference has been disabled. Why?
        {
            "cfg": FuzzConfig(job="PICT0_IO", family="MachXO2", device="LCMXO2-1200HC",
                        ncl="empty.ncl", tiles=["PT12:PIC_T0"]),
            "side": "T",
            "pins": [("28", "C"), ("27", "D")]
        },

        {
            "cfg": FuzzConfig(job="PICRS0_IO", family="MachXO2", device="LCMXO2-1200HC",
                        ncl="empty.ncl", tiles=["PR3:PIC_RS0"]),
            "side": "R",
            "pins": [("71", "A"), ("70", "B")],
            "package": "TQFP100"
        },

        {
            "cfg": FuzzConfig(job="PICLS0_IO", family="MachXO2", device="LCMXO2-1200HC",
                        ncl="empty.ncl", tiles=["PL9:PIC_LS0"]),
            "side": "L",
            "pins": [("20", "A"), ("21", "B")],
            "package": "TQFP100"
        },

        {
            "cfg": FuzzConfig(job="PICL0VREF_IO", family="MachXO2", device="LCMXO2-1200HC",
                        ncl="empty.ncl", tiles=["PL4:PIC_L0_VREF3"]),
            "side": "L",
            "pins": [("11", "A"), ("12", "B"), ("13", "C"), ("14", "D")],
            "package": "TQFP144"
        },
]

# Function constructed from reading the MachXO2 sysIO Usage Guide.
# Diamond is very sensitive to invalid I/O combinations, and will happily
# change the I/O type out from under you if you give it a bad combination.
# This can lead to further errors when the Diamond-assigned I/O type is
# invalid for the pin (for the complementary pair, especially).
def get_io_types(dir, pio, side):
    # Singled-ended I/O types.
    types = [
        "LVTTL33",
        "LVCMOS33",
        "LVCMOS25",
        "LVCMOS18",
        "LVCMOS15",
        "LVCMOS12",
        "SSTL25_I",
        "SSTL18_I",
        "HSTL18_I"
    ]

    if dir == "INPUT":
        types += [
            "SSTL25_II",
            "SSTL18_II",
            "HSTL18_II",
            "LVCMOS25R33",
            "LVCMOS18R33",
            "LVCMOS18R25",
            "LVCMOS15R33",
            "LVCMOS15R25"
        ]

    if dir in ("INPUT", "BIDIR"):
        types += [
            "LVCMOS12R33",
            "LVCMOS12R25",
            "LVCMOS10R33",
            "LVCMOS10R25"
        ]

    if side == "B":
        # Only bottom bank supports PCI33.
        types += [
            "PCI33"
        ]

    # Differential I/O types.
    if pio in ("A", "C"):
        types += [
            "SSTL25D_I",
            "SSTL18D_I",
            "HSTL18D_I",
            "MIPI",
            "LVCMOS33D",
            "LVCMOS25D",
            "LVCMOS18D",
            "LVCMOS15D",
            "LVCMOS12D",
        ]

        if dir == "INPUT":
            # True differential inputs.
            # FIXME: Also supported in bidir?
            # map_impl.mrp suggests no (warning: violates legal combination
            # and is ignored.)
            types += [
                "SSTL25D_II",
                "SSTL18D_II",
                "HSTL18D_II",
                "LVDS25",
                "LVPECL33",
                "MLVDS25",
                "BLVDS25",
                "RSDS25"
            ]

        if dir == "OUTPUT":
            # Emulated differential output.
            # FIXME: Also supported in bidir?
            types += [
                "LVDS25E",
                "LVPECL33E",
                "MLVDS25E",
                "BLVDS25E",
                "RSDS25E"
            ]

            # True differential output. Only supported on Top and primary pair.
            if pio == "A" and side == "T":
                types += [
                    "LVDS25"
                ]
    return types


def get_cfg_vccio(iotype):
    m = re.match(r".*(\d)(\d)$", iotype)
    if not m:
        return "3.3"
    return "{}.{}".format(m.group(1), m.group(2))


def main(args):
    pytrellis.load_database("../../../database")
    for job in [jobs[i] for i in args.ids]:
        cfg = job["cfg"]
        side = job["side"]
        pins = job["pins"]

        os.environ['DEV_PACKAGE'] = job.get("package", "QFN32")

        cfg.setup()
        empty_bitfile = cfg.build_design(cfg.ncl, {})
        cfg.ncl = "pio.v"

        def per_pin(pin):
            loc, pio = pin

            def get_substs(iomode, extracfg=None):
                if iomode == "NONE":
                    iodir, type = "NONE", ""
                else:
                    iodir, type = iomode.split("_", 1)
                substs = {
                    "dir": iodir,
                    "io_type": type,
                    "loc": loc,
                    "extra_attrs": "",
                    "cfg_vio": "3.3"
                }
                if extracfg is not None:
                    substs["extra_attrs"] = '(* {}="{}" *)'.format(extracfg[0], extracfg[1])
                if side == "B":
                    substs["cfg_vio"] = get_cfg_vccio(type)
                return substs

            modes = ["NONE"]
            for iodir in ("INPUT", "OUTPUT", "BIDIR"):
                modes += [iodir + "_" + _ for _ in get_io_types(iodir, pio, side)]

            nonrouting.fuzz_enum_setting(cfg, "PIO{}.BASE_TYPE".format(pio), modes,
                                         lambda x: get_substs(iomode=x),
                                         empty_bitfile, False)

            nonrouting.fuzz_enum_setting(cfg, "PIO{}.PULLMODE".format(pio), ["UP", "DOWN", "NONE", "KEEPER", "FAILSAFE"],
                                         lambda x: get_substs(iomode="INPUT_LVCMOS33", extracfg=("PULLMODE", x)),
                                         empty_bitfile)
            nonrouting.fuzz_enum_setting(cfg, "PIO{}.SLEWRATE".format(pio), ["FAST", "SLOW"],
                                         lambda x: get_substs(iomode="OUTPUT_LVCMOS33", extracfg=("SLEWRATE", x)),
                                         empty_bitfile)
            # # FIXME: Do LVCMOS12, which is 2/6mA.
            nonrouting.fuzz_enum_setting(cfg, "PIO{}.DRIVE".format(pio), ["4", "8", "12", "16", "24"],
                                         lambda x: get_substs(iomode="OUTPUT_LVCMOS33", extracfg=("DRIVE", x)),
                                         empty_bitfile)
            nonrouting.fuzz_enum_setting(cfg, "PIO{}.HYSTERESIS".format(pio), ["SMALL", "LARGE"],
                                         lambda x: get_substs(iomode="INPUT_LVCMOS33", extracfg=("HYSTERESIS", x)),
                                         empty_bitfile)
            nonrouting.fuzz_enum_setting(cfg, "PIO{}.OPENDRAIN".format(pio), ["ON", "OFF"],
                                         lambda x: get_substs(iomode="OUTPUT_LVCMOS33", extracfg=("OPENDRAIN", x)),
                                         empty_bitfile)
            if side in "B":
                nonrouting.fuzz_enum_setting(cfg, "PIO{}.CLAMP".format(pio), ["PCI", "OFF"],
                                             lambda x: get_substs(iomode="INPUT_PCI33", extracfg=("CLAMP", x)),
                                             empty_bitfile)
            else:
                nonrouting.fuzz_enum_setting(cfg, "PIO{}.CLAMP".format(pio), ["ON", "OFF"],
                                             lambda x: get_substs(iomode="INPUT_LVCMOS33", extracfg=("CLAMP", x)),
                                             empty_bitfile)
            if side in "T" and pio in "A":
                nonrouting.fuzz_enum_setting(cfg, "PIO{}.DIFFDRIVE".format(pio), ["1.25"],
                                             lambda x: get_substs(iomode="OUTPUT_LVDS25", extracfg=("DIFFDRIVE", x)),
                                             empty_bitfile)

        fuzzloops.parallel_foreach(pins, per_pin)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="PIO Attributes Fuzzer.")
    parser.add_argument(dest="ids", metavar="N", type=int, nargs="*",
                    default=range(0, len(jobs)), help="Job (indices) to run.")
    args = parser.parse_args()
    main(args)
