/*******************************************************************************
 * 
 E *urecom OpenAirInterface
 Copyright(c) 1999 - 2011 Eurecom
 
 This program is free software; you can redistribute it and/or modify it
 under the terms and conditions of the GNU General Public License,
 version 2, as published by the Free Software Foundation.
 
 This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
 
 The full GNU General Public License is included in this distribution in
 the file called "COPYING".
 
 Contact Information
 Openair Admin: openair_admin@eurecom.fr
 Openair Tech : openair_tech@eurecom.fr
 Forums       : http://forums.eurecom.fsr/openairinterface
 Address      : Eurecom, 2229, route des crĂȘtes, 06560 Valbonne Sophia Antipolis, France
 
 *******************************************************************************/

/*! \file vcd_signal_dumper.c
 * \brief Dump functions calls and variables to VCD file. Use GTKWave to display this file.
 * \author S. Roux
 * \date 2012
 * \version 0.1
 * \company Eurecom
 * \email: roux@eurecom.fr
 * \note
 * \warning
 */

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <error.h>
#include <time.h>
#include <unistd.h>
#include <assert.h>

#include "signals.h"
#if defined(ENABLE_VCD_FIFO)
# include "liblfds611.h"
#endif

#include "vcd_signal_dumper.h"

#if defined(ENABLE_RTAI_CLOCK)
#include "rtai_lxrt.h"
#endif

#define VCDSIGNALDUMPER_VERSION_MAJOR 0
#define VCDSIGNALDUMPER_VERSION_MINOR 1

// Global variable. If the VCD option is set at execution time, output VCD trace. Otherwise this module has no effect.
int ouput_vcd = 0;

struct vcd_module_s {
    const char     *name;
    int             number_of_signals;
    const char    **signals_names;
    vcd_signal_type signal_type;
    int             signal_size;
} vcd_module_s;
 
const char* eurecomVariablesNames[] = {
    "frame_number_eNB",
    "slot_number_eNB",
    "frame_number_UE",
    "slot_number_UE",
    "daq_mbox",
    "diff2",
    "itti_send_msg",
    "itti_poll_msg",
    "itti_recv_msg",
    "itti_alloc_msg"
};
 
const char* eurecomFunctionsNames[] = {
    "macxface_macphy_init",
    "macxface_macphy_exit",
    "macxface_eNB_dlsch_ulsch_scheduler",
    "macxface_fill_rar",
    "macxface_terminate_ra_proc",
    "macxface_initiate_ra_proc",
    "macxface_cancel_ra_proc",
    "macxface_get_dci_sdu",
    "macxface_get_dlsch_sdu",
    "macxface_rx_sdu",
    "macxface_mrbch_phy_sync_failure",
    "macxface_SR_indication",
    "macxface_phy_config_sib1_eNB",
    "macxface_phy_config_sib2_eNB",
    "macxface_phy_config_dedicated_eNB",
    "macxface_out_of_sync_ind",
    "macxface_ue_decode_si",
    "macxface_ue_decode_ccch",
    "macxface_ue_decode_bcch",
    "macxface_ue_send_sdu",
    "macxface_ue_get_sdu",
    "macxface_ue_get_rach",
    "macxface_ue_process_rar",
    "macxface_ue_scheduler",
    "macxface_ue_get_sr",
    "lte_ue_measurement_procedures",
    "lte_ue_pdcch_procedures",
    "lte_ue_pbch_procedures",
    "phy_procedures_eNb_tx",
    "phy_procedures_eNb_rx",
    "phy_procedures_ue_tx",
    "phy_procedures_ue_rx",
    "phy_procedures_eNB_lte",
    "phy_procedures_UE_lte",
    "emu_transport",
    "log_record",
    "pdcp_run",
    "rt_sleep",
    "pdsch_thread",
    "dlsch_thread0",
    "dlsch_thread1",
    "dlsch_thread2",
    "dlsch_thread3",
    "dlsch_thread4",
    "dlsch_thread5",
    "dlsch_thread6",
    "dlsch_thread7",
    "dlsch_decoding0",
    "dlsch_decoding1",
    "dlsch_decoding2",
    "dlsch_decoding3",
    "dlsch_decoding4",
    "dlsch_decoding5",
    "dlsch_decoding6",
    "dlsch_decoding7",
    "rx_pdcch",
    "dci_decoding",
    "rrc_mac_config_req",
    "rrc_ue_decode_sib1",
    "rrc_ue_decode_si",
    "phy_ue_config_sib2",
    "phy_ue_compute_prach",
    "phy_enb_ulsch_decoding",
    "phy_enb_sfgen",
    "phy_enb_prach_rx",
    "phy_enb_pdcch_tx",
    "phy_enb_rs_tx",
    "phy_ue_ulsch_modulation",
    "phy_ue_ulsch_encoding",
    "phy_ue_ulsch_scrambling",
    "phy_eNB_dlsch_modulation",
    "phy_eNB_dlsch_encoding",
    "phy_eNB_dlsch_scramblig",
    "pdcp_apply_security",
    "pdcp_validate_security",
    "itti_enqueue_message",
    "itti_dump_enqueue_message",
    "test"
};

struct vcd_module_s vcd_modules[VCD_SIGNAL_DUMPER_MODULE_END] = {
    { "variables", VCD_SIGNAL_DUMPER_VARIABLES_END, eurecomVariablesNames, VCD_WIRE, 64 },
    { "functions", VCD_SIGNAL_DUMPER_FUNCTIONS_END, eurecomFunctionsNames, VCD_WIRE, 1 },
//    { "ue_procedures_functions", VCD_SIGNAL_DUMPER_UE_PROCEDURES_FUNCTIONS_END, eurecomUEFunctionsNames, VCD_WIRE, 1 },
};

FILE *vcd_fd = NULL;
static inline unsigned long long int vcd_get_time(void);

#if defined(ENABLE_USE_CPU_EXECUTION_TIME)
struct timespec     g_time_start;
#elif defined(ENABLE_RTAI_CLOCK)
RTIME start;
#endif


#if defined(ENABLE_VCD_FIFO)

# define VCD_STACK_NB_ELEMENTS (2 * 1024 * 1024)

typedef struct {
    uint32_t log_id;
    vcd_signal_dumper_modules module;
    union {
        struct {
            vcd_signal_dump_functions function_name;
            vcd_signal_dump_in_out    in_out;
        } function;
        struct {
            vcd_signal_dump_variables variable_name;
            unsigned long value;
        } variable;
    } data;

    long long unsigned int time;
} vcd_queue_user_data_t;

struct lfds611_queue_state *vcd_queue = NULL;
pthread_t vcd_dumper_thread;
#endif

#define BYTE_SIZE   8
#define NIBBLE_SIZE 4
static void uint64_to_binary(uint64_t value, char *binary)
{
    static const char * const nibbles_start[] =
            {
                 "",    "1",   "10",   "11",
              "100",  "101",  "110",  "111",
             "1000", "1001", "1010", "1011",
             "1100", "1101", "1110", "1111",
            };
    static const char * const nibbles[] =
            {
             "0000", "0001", "0010", "0011",
             "0100", "0101", "0110", "0111",
             "1000", "1001", "1010", "1011",
             "1100", "1101", "1110", "1111",
            };
    int nibble;
    int nibble_value;
    int nibble_size;
    int zero = 1;

    for (nibble = 0; nibble < (sizeof (uint64_t) * (BYTE_SIZE / NIBBLE_SIZE)); nibble++)
    {
        nibble_value = value >> ((sizeof (uint64_t) * BYTE_SIZE) - NIBBLE_SIZE);

        if (zero)
        {
            if (nibble_value > 0)
            {
                zero = 0;
                nibble_size = strlen(nibbles_start[nibble_value]);
                memcpy (binary, nibbles_start[nibble_value], nibble_size);
                binary += nibble_size;
            }
        }
        else
        {
            memcpy (binary, nibbles[nibble_value], NIBBLE_SIZE);
            binary += NIBBLE_SIZE;
        }
        value <<= NIBBLE_SIZE;
    }
    /* Add a '0' if the value was null */
    if (zero)
    {
        binary[0] = '0';
        binary ++;
    }
    /* Add a null value at the end of the string */
    binary[0] = '\0';
}

#if defined(ENABLE_VCD_FIFO)
void *vcd_dumper_thread_rt(void *args)
{
    vcd_queue_user_data_t *data;
    char binary_string[(sizeof (uint64_t) * BYTE_SIZE) + 1];

# if defined(ENABLE_ITTI)
    signal_mask();
# endif

    while(1) {
        if (lfds611_queue_dequeue(vcd_queue, (void **) &data) == 0) {
            /* No element -> sleep a while */
            usleep(1);
        } else {
            switch (data->module) {
                case VCD_SIGNAL_DUMPER_MODULE_VARIABLES:
                    if (vcd_fd != NULL)
                    {
                        int variable_name;
                        variable_name = (int)data->data.variable.variable_name;
                        fprintf(vcd_fd, "#%llu\n", data->time);
                        /* Set variable to value */
                        uint64_to_binary(data->data.variable.value, binary_string);
                        fprintf(vcd_fd, "b%s %s_w\n", binary_string,
                                eurecomVariablesNames[variable_name]);
                    }
                    break;
                case VCD_SIGNAL_DUMPER_MODULE_FUNCTIONS:
                    if (vcd_fd != NULL)
                    {
                        int function_name;

                        function_name = (int)data->data.function.function_name;
                        fprintf(vcd_fd, "#%llu\n", data->time);

                        /* Check if we are entering or leaving the function ( 0 = leaving, 1 = entering) */
                        if (data->data.function.in_out == VCD_FUNCTION_IN)
                            /* Set event to 1 */
                            fprintf(vcd_fd, "1%s_w\n", eurecomFunctionsNames[function_name]);
                        else
                            fprintf(vcd_fd, "0%s_w\n", eurecomFunctionsNames[function_name]);
                        fflush(vcd_fd);
                    }
                    break;
                default:
                    break;
            }
            free(data);
        }
    }
    return NULL;
}
#endif

void vcd_signal_dumper_init(char *filename)
{
    if (ouput_vcd) {
      //        char filename[] = "/tmp/openair_vcd_dump.vcd";

        if ((vcd_fd = fopen(filename, "w+")) == NULL)
        {
            perror("vcd_signal_dumper_init: cannot open file");
            return;
        }

#if defined(ENABLE_USE_CPU_EXECUTION_TIME)
        clock_gettime(CLOCK_MONOTONIC, &g_time_start);
#elif defined(ENABLE_RTAI_CLOCK)
        start=rt_get_time_ns();
#endif

        vcd_signal_dumper_create_header();

#if defined(ENABLE_VCD_FIFO)
        fprintf(stderr, "[VCD] Creating new stack for inter-thread\n");

        /* Creating wait-free stack between OAI and dumper thread */
        if (lfds611_queue_new(&vcd_queue, VCD_STACK_NB_ELEMENTS) < 0) {
            fprintf(stderr, "vcd_signal_dumper_init: Failed to create stack\n");
            ouput_vcd = 0;
            return;
        }

        if (pthread_create(&vcd_dumper_thread, NULL, vcd_dumper_thread_rt, NULL) < 0)
        {
            fprintf(stderr, "vcd_signal_dumper_init: Failed to create thread: %s\n",
                    strerror(errno));
            ouput_vcd = 0;
            return;
        }
#endif
    }
}

void vcd_signal_dumper_close(void)
{
    if (ouput_vcd) {
#if defined(ENABLE_VCD_FIFO)
        
#else
        if (vcd_fd != NULL)
        {
            fclose(vcd_fd);
            vcd_fd = NULL;
        }
#endif
    }
}

static inline void vcd_signal_dumper_print_time_since_start(void)
{
    if (vcd_fd != NULL)
    {
#if defined(ENABLE_USE_CPU_EXECUTION_TIME)
        struct timespec time;
        long long unsigned int nanosecondsSinceStart;
        long long unsigned int secondsSinceStart;

        clock_gettime(CLOCK_MONOTONIC, &time);

        /* Get current execution time in nanoseconds */
        nanosecondsSinceStart = (long long unsigned int)((time.tv_nsec - g_time_start.tv_nsec));
        secondsSinceStart     = (long long unsigned int)time.tv_sec - (long long unsigned int)g_time_start.tv_sec;
        /* Write time in nanoseconds */
        fprintf(vcd_fd, "#%llu\n", nanosecondsSinceStart + (secondsSinceStart * 1000000000UL));
#elif defined(ENABLE_RTAI_CLOCK)
        /* Write time in nanoseconds */
        fprintf(vcd_fd, "#%llu\n",rt_get_time_ns()-start);
#endif
    }
}

static inline unsigned long long int vcd_get_time(void)
{
#if defined(ENABLE_USE_CPU_EXECUTION_TIME)
    struct timespec time;

    clock_gettime(CLOCK_MONOTONIC, &time);

    return (long long unsigned int)((time.tv_nsec - g_time_start.tv_nsec)) +
    ((long long unsigned int)time.tv_sec - (long long unsigned int)g_time_start.tv_sec) * 1000000000UL;
#elif defined(ENABLE_RTAI_CLOCK)
    return rt_get_time_ns() - start;
#endif
}

void vcd_signal_dumper_create_header(void)
{
    if (ouput_vcd) {
        struct tm *pDate;
        time_t intps;

        intps = time(NULL);
        pDate = localtime(&intps);

        if (vcd_fd != NULL)
        {
            int i, j;
            fprintf(vcd_fd, "$date\n\t%s$end\n", asctime(pDate));
            // Display version
            fprintf(vcd_fd, "$version\n\tVCD plugin ver%d.%d\n$end\n", VCDSIGNALDUMPER_VERSION_MAJOR, VCDSIGNALDUMPER_VERSION_MINOR);
            // Init timescale, here = 1ns
            fprintf(vcd_fd, "$timescale 1 ns $end\n");

            /* Initialize each module definition */
            for(i = 0; i < VCD_SIGNAL_DUMPER_MODULE_END; i++) {
                struct vcd_module_s *module;
                module = &vcd_modules[i];
                fprintf(vcd_fd, "$scope module %s $end\n", module->name);
                /* Declare each signal as defined in array */
                for (j = 0; j < module->number_of_signals; j++) {
                    const char *signal_name;
                    signal_name = module->signals_names[j];
                    if (VCD_WIRE == module->signal_type) {
                        fprintf(vcd_fd, "$var wire %d %s_w %s $end\n", module->signal_size, signal_name, signal_name);
                    } else  if (VCD_REAL == module->signal_type) {
                        fprintf(vcd_fd, "$var real %d %s_r %s $end\n", module->signal_size, signal_name, signal_name);
                    } else {
                        // Handle error here
                    }
                }
                fprintf(vcd_fd, "$upscope $end\n");
            }

            /* Init variables and functions to 0 */
            fprintf(vcd_fd, "$dumpvars\n");
            for(i = 0; i < VCD_SIGNAL_DUMPER_MODULE_END; i++) {
                struct vcd_module_s *module;
                module = &vcd_modules[i];
                /* Declare each signal as defined in array */
                for (j = 0; j < module->number_of_signals; j++) {
                    const char *signal_name;
                    signal_name = module->signals_names[j];
                    if (VCD_WIRE == module->signal_type) {
                        if (module->signal_size > 1) {
                            fprintf(vcd_fd, "b0 %s_w $end\n", signal_name);
                        }
                        else {
                            fprintf(vcd_fd, "0%s_w $end\n", signal_name);
                        }
                    } else  if (VCD_REAL == module->signal_type) {
                        fprintf(vcd_fd, "r0 %s_r $end\n", signal_name);
                    } else {
                        // Handle error here
                    }
                }
            }
            fprintf(vcd_fd, "$end\n");
            fprintf(vcd_fd, "$enddefinitions $end\n\n");
            //fflush(vcd_fd);
        }
    }
}

void vcd_signal_dumper_dump_variable_by_name(vcd_signal_dump_variables variable_name,
                                             unsigned long             value)
{
    if (ouput_vcd) {
#if defined(ENABLE_VCD_FIFO)
        vcd_queue_user_data_t *new_data;

        assert(variable_name < VCD_SIGNAL_DUMPER_VARIABLES_END);
        assert(variable_name >= 0);

        new_data = malloc(sizeof(vcd_queue_user_data_t));

        new_data->module = VCD_SIGNAL_DUMPER_MODULE_VARIABLES;
        new_data->time = vcd_get_time();
        new_data->data.variable.variable_name = variable_name;
        new_data->data.variable.value = value;
        lfds611_queue_enqueue(vcd_queue, new_data);
#else
        char binary_string[(sizeof (uint64_t) * BYTE_SIZE) + 1];

        assert(variable_name < VCD_SIGNAL_DUMPER_VARIABLES_END);
        assert(variable_name >= 0);

        if (vcd_fd != NULL)
        {
            vcd_signal_dumper_print_time_since_start();

            /* Set variable to value */
            uint64_to_binary(value, binary_string);
            fprintf(vcd_fd, "b%s %s_w\n", binary_string, eurecomVariablesNames[variable_name]);
            //fflush(vcd_fd);
        }
#endif
    }
}

void vcd_signal_dumper_dump_function_by_name(vcd_signal_dump_functions  function_name,
                                             vcd_signal_dump_in_out     in_out)
{
    if (ouput_vcd) {
#if defined(ENABLE_VCD_FIFO)
        vcd_queue_user_data_t *new_data;

        assert(function_name < VCD_SIGNAL_DUMPER_FUNCTIONS_END);
        assert(function_name >= 0);

        new_data = malloc(sizeof(vcd_queue_user_data_t));

        new_data->module = VCD_SIGNAL_DUMPER_MODULE_FUNCTIONS;
        new_data->time = vcd_get_time();
        new_data->data.function.function_name = function_name;
        new_data->data.function.in_out = in_out;
        lfds611_queue_enqueue(vcd_queue, new_data);
#else
        assert(function_name < VCD_SIGNAL_DUMPER_FUNCTIONS_END);
        assert(function_name >= 0);

        if (vcd_fd != NULL)
        {
            vcd_signal_dumper_print_time_since_start();

            /* Check if we are entering or leaving the function ( 0 = leaving, 1 = entering) */
            if (in_out == VCD_FUNCTION_IN)
                /* Set event to 1 */
                fprintf(vcd_fd, "1%s_w\n", eurecomFunctionsNames[function_name]);
            else
                fprintf(vcd_fd, "0%s_w\n", eurecomFunctionsNames[function_name]);
            //fflush(vcd_fd);
        }
#endif
    }
}