/*
 * Copyright (c) 2019-2021 Martin Whitaker (icarus@martin-whitaker.me.uk)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "compiler.h"
#include "vpi_user.h"
#include "sv_vpi_user.h"
#include "vvp/ivl_dlfcn.h"

using namespace std;

/* The only VPI routines that can be legally called when the functions in
   the vlog_startup_routines[] array are executed are vpi_register_systf()
   and vpi_register_cb(), so we can simply provide stubs for the rest. We
   aren't going to execute any callbacks, so we can just provide a stub for
   vpi_register_cb() too.

   Note that the Icarus system module illegally calls vpi_get_vlog_info()
   during startup, so take care to fill in the data structure for that.
*/

// callback related

vpiHandle   vpi_register_cb(p_cb_data) { return 0; }
PLI_INT32   vpi_remove_cb(vpiHandle) { return 0; }

void        vpi_get_systf_info(vpiHandle, p_vpi_systf_data) { }

// for obtaining handles

vpiHandle   vpi_handle_by_name(const char*, vpiHandle) { return 0; }
vpiHandle   vpi_handle_by_index(vpiHandle, PLI_INT32) { return 0; }

// for traversing relationships

vpiHandle   vpi_handle(PLI_INT32, vpiHandle) { return 0; }
vpiHandle   vpi_iterate(PLI_INT32, vpiHandle) { return 0; }
vpiHandle   vpi_scan(vpiHandle) { return 0; }

// for processing properties

PLI_INT32   vpi_get(int, vpiHandle) { return 0; }
char*       vpi_get_str(PLI_INT32, vpiHandle) { return 0; }

// delay processing

void        vpi_get_delays(vpiHandle, p_vpi_delay) { }
void        vpi_put_delays(vpiHandle, p_vpi_delay) { }

// value processing

void        vpi_get_value(vpiHandle, p_vpi_value) { }
vpiHandle   vpi_put_value(vpiHandle, p_vpi_value, p_vpi_time, PLI_INT32) { return 0; }

// time processing

void        vpi_get_time(vpiHandle, s_vpi_time*) { }

// data processing

void*       vpi_get_userdata(vpiHandle) { return 0; }
PLI_INT32   vpi_put_userdata(vpiHandle, void*) { return 0; }

// I/O routines

PLI_UINT32  vpi_mcd_open(char *) { return 0; }
PLI_UINT32  vpi_mcd_close(PLI_UINT32) { return 0; }
PLI_INT32   vpi_mcd_flush(PLI_UINT32) { return 0; }
char*       vpi_mcd_name(PLI_UINT32) { return 0; }
PLI_INT32   vpi_mcd_printf(PLI_UINT32, const char*, ...) { return 0; }
PLI_INT32   vpi_mcd_vprintf(PLI_UINT32, const char*, va_list) { return 0; }

PLI_INT32   vpi_flush(void) { return 0; }
PLI_INT32   vpi_printf(const char*, ...) { return 0; }
PLI_INT32   vpi_vprintf(const char*, va_list) { return 0; }

// utility routines

PLI_INT32   vpi_chk_error(p_vpi_error_info) { return 0; }
PLI_INT32   vpi_compare_objects(vpiHandle, vpiHandle) { return 0; }
PLI_INT32   vpi_free_object(vpiHandle) { return 0; }
PLI_INT32   vpi_get_vlog_info(p_vpi_vlog_info info)
{
    info->argc = 0;
    info->argv = 0;
    info->product = 0;
    info->version = 0;
    return 0;
}

// control routines

void        vpi_control(PLI_INT32, ...) { }
void        vpi_sim_control(PLI_INT32, ...) { }

// proposed standard extensions

PLI_INT32   vpi_fopen(const char*, const char*) { return 0; }
FILE*       vpi_get_file(PLI_INT32) { return 0; }

// Icarus extensions

s_vpi_vecval vpip_calc_clog2(vpiHandle)
{
    s_vpi_vecval val = { 0, 0 };
    return val;
}
void        vpip_count_drivers(vpiHandle, unsigned, unsigned [4]) { }
void        vpip_format_strength(char*, s_vpi_value*, unsigned) { }
void        vpip_make_systf_system_defined(vpiHandle) { }
void        vpip_mcd_rawwrite(PLI_UINT32, const char*, size_t) { }
void        vpip_set_return_value(int) { }
void        vpi_vcontrol(PLI_INT32, va_list) { }


/* When a module registers a system function, extract and save the return
   type for use during elaboration. */
vpiHandle vpi_register_systf(const struct t_vpi_systf_data*ss)
{
    if (ss->type != vpiSysFunc)
        return 0;

    struct sfunc_return_type ret_type;
    ret_type.name = ss->tfname;
    switch (ss->sysfunctype) {
      case vpiIntFunc:
        ret_type.type = IVL_VT_LOGIC;
        ret_type.wid  = 32;
        ret_type.signed_flag = true;
        break;
      case vpiRealFunc:
        ret_type.type = IVL_VT_REAL;
        ret_type.wid  = 1;
        ret_type.signed_flag = true;
        break;
      case vpiTimeFunc:
        ret_type.type = IVL_VT_LOGIC;
        ret_type.wid  = 64;
        ret_type.signed_flag = false;
        break;
      case vpiSizedFunc:
        ret_type.type = IVL_VT_LOGIC;
        ret_type.wid  = ss->sizetf ? ss->sizetf(ss->user_data) : 32;
        ret_type.signed_flag = false;
        break;
      case vpiSizedSignedFunc:
        ret_type.type = IVL_VT_LOGIC;
        ret_type.wid  = ss->sizetf ? ss->sizetf(ss->user_data) : 32;
        ret_type.signed_flag = true;
        break;
      case vpiStringFunc:
        ret_type.type = IVL_VT_STRING;
        ret_type.wid  = 0;
        ret_type.signed_flag = false;
        break;
      case vpiOtherFunc:
        ret_type.type = IVL_VT_NO_TYPE;
        ret_type.wid  = 0;
        ret_type.signed_flag = false;
        break;
      default:
        cerr << "warning: " << ss->tfname << " has an unknown return type. "
                "Assuming 32 bit unsigned." << endl;
        ret_type.type = IVL_VT_LOGIC;
        ret_type.wid  = 32;
        ret_type.signed_flag = false;
        break;
    }
    ret_type.override_flag = false;
    add_sys_func(ret_type);
    return 0;
}

#if defined(__MINGW32__) || defined (__CYGWIN__)
vpip_routines_s vpi_routines = {
    .register_cb                = vpi_register_cb,
    .remove_cb                  = vpi_remove_cb,
    .register_systf             = vpi_register_systf,
    .get_systf_info             = vpi_get_systf_info,
    .handle_by_name             = vpi_handle_by_name,
    .handle_by_index            = vpi_handle_by_index,
    .handle                     = vpi_handle,
    .iterate                    = vpi_iterate,
    .scan                       = vpi_scan,
    .get                        = vpi_get,
    .get_str                    = vpi_get_str,
    .get_delays                 = vpi_get_delays,
    .put_delays                 = vpi_put_delays,
    .get_value                  = vpi_get_value,
    .put_value                  = vpi_put_value,
    .get_time                   = vpi_get_time,
    .get_userdata               = vpi_get_userdata,
    .put_userdata               = vpi_put_userdata,
    .mcd_open                   = vpi_mcd_open,
    .mcd_close                  = vpi_mcd_close,
    .mcd_flush                  = vpi_mcd_flush,
    .mcd_name                   = vpi_mcd_name,
    .mcd_vprintf                = vpi_mcd_vprintf,
    .flush                      = vpi_flush,
    .vprintf                    = vpi_vprintf,
    .chk_error                  = vpi_chk_error,
    .compare_objects            = vpi_compare_objects,
    .free_object                = vpi_free_object,
    .get_vlog_info              = vpi_get_vlog_info,
    .vcontrol                   = vpi_vcontrol,
    .fopen                      = vpi_fopen,
    .get_file                   = vpi_get_file,
    .calc_clog2                 = vpip_calc_clog2,
    .count_drivers              = vpip_count_drivers,
    .format_strength            = vpip_format_strength,
    .make_systf_system_defined  = vpip_make_systf_system_defined,
    .mcd_rawwrite               = vpip_mcd_rawwrite,
    .set_return_value           = vpip_set_return_value,
};

typedef PLI_UINT32 (*vpip_set_callback_t)(vpip_routines_s*, PLI_UINT32);
#endif
typedef void (*vlog_startup_routines_t)(void);

bool load_vpi_module(const char*path)
{
    ivl_dll_t dll = ivl_dlopen(path, false);
    if (dll == 0) {
	cerr << "error: Failed to open '" << path << "' because:" << endl;
        cerr << "     : " << dlerror() << endl;
	return false;
    }

#if defined(__MINGW32__) || defined (__CYGWIN__)
    void*function = ivl_dlsym(dll, "vpip_set_callback");
    if (function == 0) {
        cerr << "warning: '" << path << "' has no vpip_set_callback()" << endl;
        ivl_dlclose(dll);
        return true;
    }
    vpip_set_callback_t set_callback = (vpip_set_callback_t)function;
    if (!set_callback(&vpi_routines, vpip_routines_version)) {
        cerr << "error: Failed to link '" << path << "'. "
                "Try rebuilding it with iverilog-vpi." << endl;
        ivl_dlclose(dll);
        return true;
    }
#endif

    void*table = ivl_dlsym(dll, LU "vlog_startup_routines" TU);
    if (table == 0) {
        cerr << "warning: '" << path << "' has no vlog_startup_routines" << endl;
        ivl_dlclose(dll);
        return true;
    }

    vlog_startup_routines_t*routines = (vlog_startup_routines_t*)table;
    for (unsigned idx = 0; routines[idx]; idx += 1) {
        (routines[idx])();
    }

    ivl_dlclose(dll);
    return true;
}
