#!/usr/bin/env python3

# Based on Xilinx cells_xtra.py; modified for Radiant's structure

from argparse import ArgumentParser
from io import StringIO
from enum import Enum, auto
import os.path
import sys
import re


class Cell:
    def __init__(self, name, keep=False, port_attrs={}):
        self.name = name
        self.keep = keep
        self.port_attrs = port_attrs
        self.found = False

class State(Enum):
    OUTSIDE = auto()
    IN_MODULE = auto()
    IN_OTHER_MODULE = auto()
    IN_FUNCTION = auto()
    IN_TASK = auto()

devices = [
    ("lifcl", [
        Cell("ACC54"),
        Cell("ADC"),
        Cell("ALUREG"),
        Cell("BB_ADC", keep=True),
        Cell("BB_CDR", keep=True),
        Cell("BB_I3C_A", keep=True),
        Cell("BFD1P3KX"),
        Cell("BFD1P3LX"),
        Cell("BNKREF18", keep=True),
        Cell("CONFIG_LMMI", keep=True),
        Cell("DCC"),
        Cell("DCS"),
        Cell("DDRDLL"),
        Cell("DELAYA"),
        Cell("DELAYB"),
        Cell("DIFFIO18", keep=True),
        Cell("DLLDEL"),
        Cell("DP16K_MODE"),
        Cell("DP16K"),
        Cell("DPHY", keep=True),
        Cell("DPSC512K"),
        Cell("DQSBUF"),
        Cell("EBR_CORE"),
        Cell("EBR"),
        Cell("ECLKDIV"),
        Cell("ECLKSYNC"),
        Cell("FBMUX"),
        Cell("FIFO16K_MODE"),
        Cell("FIFO16K"),
        Cell("GSR"),
        Cell("HSE"),
        Cell("I2CFIFO"),
        Cell("IDDR71"),
        Cell("IDDRX1"),
        Cell("IDDRX2DQ"),
        Cell("IDDRX2"),
        Cell("IDDRX4DQ"),
        Cell("IDDRX4"),
        Cell("IDDRX5"),
        Cell("IFD1P3BX"),
        Cell("IFD1P3DX"),
        Cell("IFD1P3IX"),
        Cell("IFD1P3JX"),
        Cell("JTAG", keep=True),
        Cell("LRAM"),
        Cell("M18X36"),
        Cell("MIPI"),
        Cell("MULT18"),
#        Cell("MULT18X18"),
#        Cell("MULT18X36"),
        Cell("MULT36"),
#        Cell("MULT36X36"),
        Cell("MULT9"),
#        Cell("MULT9X9"),
#        Cell("MULTADDSUB18X18"),
        Cell("MULTADDSUB18X18WIDE"),
#        Cell("MULTADDSUB18X36"),
#        Cell("MULTADDSUB36X36"),
        Cell("MULTADDSUB9X9WIDE"),
        Cell("MULTIBOOT", keep=True),
#        Cell("MULTPREADD18X18"),
#        Cell("MULTPREADD9X9"),
        Cell("ODDR71"),
        Cell("ODDRX1"),
        Cell("ODDRX2DQS"),
        Cell("ODDRX2DQ"),
        Cell("ODDRX2"),
        Cell("ODDRX4DQS"),
        Cell("ODDRX4DQ"),
        Cell("ODDRX4"),
        Cell("ODDRX5"),
        Cell("OFD1P3BX"),
        Cell("OFD1P3DX"),
        Cell("OFD1P3IX"),
        Cell("OFD1P3JX"),
        Cell("OSC"),
        Cell("OSCA"),
        Cell("OSHX2"),
        Cell("OSHX4"),
        Cell("PCIE"),
        Cell("PCLKDIV"),
        Cell("PCLKDIVSP"),
        Cell("PDP16K_MODE"),
        Cell("PDP16K"),
        Cell("PDPSC16K_MODE"),
        Cell("PDPSC16K"),
        Cell("PDPSC512K"),
        Cell("PLL"),
        Cell("PREADD9"),
        Cell("PUR", keep=True),
        Cell("REFMUX"),
        Cell("REG18"),
        Cell("SEDC", keep=True),
        Cell("SEIO18"),
        Cell("SEIO33"),
        Cell("SGMIICDR"),
        Cell("SP16K_MODE"),
        Cell("SP16K"),
        Cell("SP512K"),
        Cell("TSALLA"),
        Cell("TSHX2DQS"),
        Cell("TSHX2DQ"),
        Cell("TSHX4DQS"),
        Cell("TSHX4DQ"),
        Cell("WDT", keep=True),

        Cell("ACC54_CORE"),
        Cell("ADC_CORE"),
        Cell("ALUREG_CORE"),
        Cell("BNKREF18_CORE"),
        Cell("BNKREF33_CORE"),
        Cell("DIFFIO18_CORE"),
        Cell("CONFIG_CLKRST_CORE", keep=True),
        Cell("CONFIG_HSE_CORE", keep=True),
        Cell("CONFIG_IP_CORE", keep=True),
        Cell("CONFIG_JTAG_CORE", keep=True),
        Cell("CONFIG_LMMI_CORE", keep=True),
        Cell("CONFIG_MULTIBOOT_CORE", keep=True),
        Cell("CONFIG_SEDC_CORE", keep=True),
        Cell("CONFIG_WDT_CORE", keep=True),
        Cell("DDRDLL_CORE"),
        Cell("DLLDEL_CORE"),
        Cell("DPHY_CORE"),
        Cell("DQSBUF_CORE"),
        Cell("ECLKDIV_CORE"),
        Cell("ECLKSYNC_CORE"),
        Cell("FBMUX_CORE"),
        Cell("GSR_CORE"),
        Cell("I2CFIFO_CORE"),
        Cell("LRAM_CORE"),
        Cell("MULT18_CORE"),
        Cell("MULT18X36_CORE"),
        Cell("MULT36_CORE"),
        Cell("MULT9_CORE"),
        Cell("OSC_CORE"),
        Cell("PCIE_CORE"),
        Cell("PLL_CORE"),
        Cell("PREADD9_CORE"),
        Cell("REFMUX_CORE"),
        Cell("REG18_CORE"),
        Cell("SEIO18_CORE"),
        Cell("SEIO33_CORE"),
        Cell("SGMIICDR_CORE"),
    ])
]

def xtract_cells_decl(device, cells, dirs, outf):
    fname = os.path.join(dir, device + '.v')
    with open(fname) as f:
        state = State.OUTSIDE
        # Probably the most horrible Verilog "parser" ever written.
        cell = None
        for l in f:
            l, _, comment = l.partition('//')
            l = l.strip()
            if l.startswith("module "):
                cell_name = l[7:l.find('(')].strip()
                cell = None
                module_ports = []
                iopad_pin = set()
                if state != State.OUTSIDE:
                    print('Nested modules in {}.'.format(fname))
                    sys.exit(1)
                for c in cells:
                    if c.name != cell_name:
                        continue
                    cell = c
                    state = State.IN_MODULE
                    if cell.keep:
                        outf.write('(* keep *)\n')
                    outf.write('module {} (...);\n'.format(cell.name))
                    cell.found = True

                    m = re.search(r'synthesis .*black_box_pad_pin="([^"]*)"', comment)
                    if m:
                        iopad_pin = set(m.group(1).split(","))

                if cell is None:
                    state = State.IN_OTHER_MODULE
            elif l.startswith('task '):
                if state == State.IN_MODULE:
                    state = State.IN_TASK
            elif l.startswith('function '):
                if state == State.IN_MODULE:
                    state = State.IN_FUNCTION
            elif l == 'endtask':
                if state == State.IN_TASK:
                    state = State.IN_MODULE
            elif l == 'endfunction':
                if state == State.IN_FUNCTION:
                    state = State.IN_MODULE
            elif l == 'endmodule':
                if state == State.IN_MODULE:
                    for kind, rng, port in module_ports:
                        for attr in cell.port_attrs.get(port, []):
                            outf.write('    (* {} *)\n'.format(attr))
                        if port in iopad_pin:
                            outf.write('    (* iopad_external_pin *)\n')
                        if rng is None:
                            outf.write('    {} {};\n'.format(kind, port))
                        else:
                            outf.write('    {} {} {};\n'.format(kind, rng, port))
                    outf.write(l + '\n')
                    outf.write('\n')
                elif state != State.IN_OTHER_MODULE:
                    print('endmodule in weird place in {}.'.format(cell.name, fname))
                    sys.exit(1)
                state = State.OUTSIDE
            elif l.startswith(('input ', 'output ', 'inout ')) and state == State.IN_MODULE:
                if l.endswith((';', ',')):
                    l = l[:-1]
                if ';' in l:
                    print('Weird port line in {} [{}].'.format(fname, l))
                    sys.exit(1)
                kind, _, ports = l.partition(' ')
                for port in ports.split(','):
                    port = port.strip()
                    if port.startswith('['):
                        rng, port = port.split()
                    else:
                        rng = None
                    module_ports.append((kind, rng, port))
            elif l.startswith('parameter ') and state == State.IN_MODULE:
                if l.endswith((';', ',')):
                    l = l[:-1]
                while '  ' in l:
                    l = l.replace('  ', ' ')
                if ';' in l:
                    print('Weird parameter line in {} [{}].'.format(fname, l))
                    sys.exit(1)
                outf.write('    {};\n'.format(l))

        if state != State.OUTSIDE:
            print('endmodule not found in {}.'.format(fname))
            sys.exit(1)
        for cell in cells:
            if not cell.found:
                print('cell {} not found in {}.'.format(cell.name, fname))
if __name__ == '__main__':
    parser = ArgumentParser(description='Extract Lattice blackbox cell definitions from Radiant.')
    parser.add_argument('radiant_dir', nargs='?', default='/opt/lscc/radiant/2.0/')
    args = parser.parse_args()

    dirs = [
        os.path.join(args.radiant_dir, 'cae_library/synthesis/verilog/'),
    ]
    for dir in dirs:
        if not os.path.isdir(dir):
            print('{} is not a directory'.format(dir))

    out = StringIO()
    for device, cells in devices:
        xtract_cells_decl(device, cells, dirs, out)

    with open('cells_xtra.v', 'w') as f:
        f.write('// Created by cells_xtra.py from Lattice models\n')
        f.write('\n')
        f.write(out.getvalue())
