#!/usr/bin/env python3
import yaml
import argparse
import sys
import os.path
import glob
import shutil
import re

data_dir = None

def error(msg):
    sys.stderr.write(msg + '\n')
    sys.exit(1)


class Slot(object):
    def __init__(self, name, idx):
        # fmc idx on the board
        self.idx = idx
        # fmc name for the slot
        self.fmc_name = name
        self.config = None

    def read_config(self):
        self.config = yaml.load(open(os.path.join(
            data_dir, self.fmc_name, 'config.yaml')))


class Result(object):
    def __init__(self):
        self.mt_config = None


def parse(filename):
    d = yaml.load(open(filename))
    if 'wrtd-board' not in d:
        error('YAML file must have the wrtd-board key')
    return d['wrtd-board']


def build_mt_config(desc, res):
    appid = desc['appid']
    cpus = desc['cpus']
    cfg = """
  constant c_mt_config : t_mt_config :=
    (
      app_id                        => x"{:08x}",
      cpu_count                     => {},
      cpu_config                    => (""".format(
          appid, res.ncpus)
    # Be sure that cpus are defined from 0 to N-1.
    for i in range(res.ncpus):
        cpu = desc['cpus'].get(i, None)
        if cpu == None:
            error('No definition for cpu {}'.format(i))
        cfg += """
        {}                           => (
          memsize                   => {},
          hmq_config                => (""".format(
                  i, cpu['memsize'] * 1024 // 4)

        hmq = cpu.get('hmq', None)
        if hmq is not None:
            cfg += """
            slot_count              => 1,
            slot_config             => (
              0                     => (
                entries_bits        => {},
                width_bits          => 7,
                header_bits         => 2,
                endpoint_id         => x"0000_0000",
                enable_config_space => FALSE),""".format(
                  hmq['log_entries'])
        cfg += """
              others                => c_DUMMY_MT_MQUEUE_SLOT)),
          rmq_config                => ("""

        rmq = cpu.get('rmq', None)
        if rmq is not None:
            cfg += """
            slot_count              => 1,
            slot_config             => (
              0                     => (
                entries_bits        => {},
                width_bits          => 7,
                header_bits         => 4,
                endpoint_id         => x"0000_0000",
                enable_config_space => TRUE),""".format(
                    rmq['log_entries'])
        cfg += """
              others                => c_DUMMY_MT_MQUEUE_SLOT))),"""
    cfg += """
        others                      => (
          0, c_MT_DEFAULT_MQUEUE_CONFIG, c_MT_DEFAULT_MQUEUE_CONFIG)),
      shared_mem_size               => {}
      );""".format(cpus['shared_memsize'] * 1024 // 4)

    res.mt_config = cfg

BOARD_SLOTS = { 'svec': [0, 1], 'spec': [0]}


def maybe_mkdir(path):
    try:
        os.mkdir(path)
    except FileExistsError:
        pass

def compute_fetchto(relfile):
    fetchto_dir = os.path.join(data_dir, "..", "dependencies")
    if os.path.isabs(data_dir):
        return os.path.normpath(fetchto_dir)
    else:
        return os.path.relpath(fetchto_dir, os.path.dirname(relfile))

def gather_slaves(f, fmcs, inout):
    res = []
    for s in fmcs:
        fmc = f.slots.get(s, None)
        if fmc is None:
            error("slot {} doesn't exist in {}".format(s, inout))
        bus_name = fmc.config['bus'].get(inout, None)
        if bus_name is None:
            error("slot {} has no {}".format(s, inout))
        res.append(bus_name.format(n=s))
    return res


def compute_cpu_xbar(f):
    # Read config file for each slots
    for _, s in f.slots.items():
        s.read_config()
    f.slaves = {}
    cpus_desc = f.desc['cpus']
    for k in f.cpus:
        cpu_desc = cpus_desc[k]
        f.slaves[k] = gather_slaves(f, cpu_desc['inputs'], 'input')
        f.slaves[k].extend(gather_slaves(f, cpu_desc['outputs'], 'output'))


def generate_cpu_xbar_hdl(f, templates):
    for k in f.cpus:
        slaves = f.slaves[k]
        if len(slaves) == 0:
            error("no connections for cpu {}".format(k))
        elif len(slaves) == 1:
            # One connection: generate direct assignment.
            txt = "    fmc_dp_wb_in({}) <= {}_out;".format(k, slaves[0])
            txt += "    {}_in <= fmc_dp_wb_out({});".format(slaves[0], k)
            templates['body'] += txt
        else:
            txt = """
  --  Note: g_address/g_mask index direction is to, master_i/master_o is downto
  cpu{}_crossbar : xwb_crossbar
    generic map (
      g_num_masters => 1,
      g_num_slaves  => {},
      g_registered  => False""".format(k, len(slaves))
            for s in range(len(slaves)):
                txt += ',\n      g_address({}) => x"{:08x}"'.format(s, s << 12)
            for s in range(len(slaves)):
                txt += ',\n      g_mask({}) => x"0000_f000"'.format(s)
            txt += """)
    port map (
      clk_sys_i => clk_sys_62m5,
      rst_n_i   => rst_sys_62m5_n,
      slave_i(0) => fmc_dp_wb_out({}),
      slave_o(0) => fmc_dp_wb_in({})""".format(k, k)
            for s in reversed(range(len(slaves))):
                txt += ',\n      master_i({}) => {}_out'.format(s, slaves[s])
            for s in reversed(range(len(slaves))):
                txt += ',\n      master_o({}) => {}_in'.format(s, slaves[s])
            txt += ");\n"
            templates['body'] += txt


def generate_top_hdl(f):
    # Read template file (from slots).
    templates = { 'use': "", 'ports': "", 'decls': "", 'body': "",
                  'sdb-decl': "", 'sdb-layout': ""}
    section_re = re.compile(r"\[(.+)\]$")
    section = None
    for k, s in f.slots.items():
        addr = "{:08x}".format(0x10000 + k * 0x8000)
        tmpl = open(os.path.join(data_dir, s.fmc_name, 'top.vhd')).read()
        for l in tmpl.splitlines():
            m = section_re.match(l)
            if m:
                section = m.group(1)
            else:
                templates[section] += l.format(n=k, addr=addr) + '\n'

    generate_cpu_xbar_hdl(f, templates)

    # Compute number of slaves for the main WB.
    layout_lines = templates['sdb-layout'].splitlines()
    templates['slaves_decl'] = ""
    nbr_slaves = 0
    layout_re = re.compile(r" *(c_[A-Za-z0-9_]*) *=>.*$")
    for l in layout_lines:
        m = layout_re.match(l)
        if m is None:
            error('badly formatted sdb-layout line "{}"'.format(l))
        d = "  constant {}  : integer := c_WB_SLAVE_BASE + {};\n".format(
            m.group(1), nbr_slaves)
        nbr_slaves += 1
        templates['slaves_decl'] += d

    top_template = open(os.path.join(data_dir, f.board, 'top.vhd')).read()

    top_filename = os.path.join("hdl", "top", f.name,
                                "{}_top.vhd".format(f.name))
    topdir = compute_fetchto(top_filename)
    print("Writing {}".format(top_filename))
    top = open(top_filename, "w")
    top.write(top_template.format(name=f.name,
                                  mt_config=f.mt_config,
                                  ncpus=f.ncpus,
                                  topdir=topdir,
                                  nbr_slaves=nbr_slaves,
                                  nslots=len(f.slots),
                                  **templates))
    top.close()

def generate_files(f):
    # Create dirs
    maybe_mkdir("hdl")
    maybe_mkdir(os.path.join("hdl", "top"))
    maybe_mkdir(os.path.join("hdl", "top", f.name))

    # Top vhdl file
    generate_top_hdl(f)

    # Top Manifest
    manifest_filename = os.path.join("hdl", "top", f.name, "Manifest.py")
    manifest_template = open(
        os.path.join(data_dir, f.board, 'Manifest-top.py')).read()
    modules_set = {s.fmc_name for _, s in f.slots.items()}
    modules = ""
    for m in modules_set:
        repo = open(os.path.join(data_dir, m, 'repo.txt')).read().rstrip()
        modules += '        "{}",\n'.format(repo)

    print("Writing {}".format(manifest_filename))
    manifest = open(manifest_filename, "w")
    dependencies = compute_fetchto(manifest_filename)
    manifest.write(manifest_template.format(name=f.name, modules=modules,
                                            fetchto=dependencies))
    manifest.close()

    # syn Manifest
    maybe_mkdir(os.path.join("hdl", "syn"))
    maybe_mkdir(os.path.join("hdl", "syn", f.name))
    manifest_filename = os.path.join("hdl", "syn", f.name, "Manifest.py")
    manifest_template = open(
        os.path.join(data_dir, f.board, 'Manifest-syn.py')).read()
    print("Writing {}".format(manifest_filename))
    manifest = open(manifest_filename, "w")
    dependencies = compute_fetchto(manifest_filename)
    manifest.write(manifest_template.format(name=f.name,
                                            fetchto=dependencies))
    manifest.close()


    # ucf
    ucf_filename = os.path.join("hdl", "syn", f.name, f.name + ".ucf")
    ucf_template = open(os.path.join(data_dir, f.board, "top.ucf")).read()
    for k, s in f.slots.items():
        # Pin assignments for fmc :param v: in slot :param k:
        fmc_filename = os.path.join(
            data_dir, s.fmc_name, "{}-fmc{}.ucf".format(f.board, k))
        ucf_fmc = open(fmc_filename).read()
        ucf_template += ucf_fmc

    print("Writing {}".format(ucf_filename))
    ucf = open(ucf_filename, "w")
    ucf.write(ucf_template)
    ucf.close()

    # Copy tcl files
    for tcl_file in glob.glob(os.path.join(data_dir, f.board, "*.tcl")):
        print("Writing {}".format(os.path.basename(tcl_file)))
        shutil.copy(tcl_file, os.path.join("hdl", "syn", f.name))

def main():
    global data_dir

    # Arguments
    aparser = argparse.ArgumentParser(description='wrtd config generator')
    aparser.add_argument('filename', help='YAML description file')
    args = aparser.parse_args()

    data_dir = os.path.dirname(__file__)

    res = Result()

    desc = parse(args.filename)
    res.desc = desc
    res.name = desc['name']
    res.cpus = [x for x in desc['cpus'] if isinstance(x, int) ]
    res.ncpus = len(res.cpus)
    build_mt_config(desc, res)
    res.slots = {k: Slot(v, k) for k, v in desc['slots'].items()}

    # Get the slots index for the board.
    res.board = desc['board']
    board_slots = BOARD_SLOTS.get(res.board, None)
    if board_slots is None:
        error('unknown board "{}"'.format(res.board))
    # And check the slots used exist.
    for k, _ in res.slots.items():
        if k not in board_slots:
            error('unknown slot "{}"'.format(k))

    compute_cpu_xbar(res)
    generate_files(res)

if __name__ == '__main__':
    main()
