/*
 * global config
 */

#define CONFIG_GDB_SERVER


/*
 * headers
 */

#ifdef _OPENMP
#include <omp.h>
#endif

#include <systemc>
#include <iostream>
#include <cstdlib>

#ifdef CONFIG_GDB_SERVER
#include "gdbserver.h"
#endif

#include "mapping_table.h"

#include "mips32.h"
#include "vci_cc_vcache_wrapper_v4.h"
#include "vci_simple_ram.h"
#include "vci_mem_cache_v4.h"

#include "vci_simhelper.h"
#include "vci_icu.h"
#include "vci_multi_tty.h"
#include "vci_timer.h"
#include "vci_block_device_tsar_v4.h"

#include "vci_vgmn.h"

#include "alloc_elems.h"


/*
 * pf global config
 */

using namespace sc_core;
using namespace soclib::caba;
using namespace soclib::common;

#define cell_width      4
#define address_width   32
#define plen_width      8
#define error_width     2
#define clen_width      1
#define rflag_width     1
#define srcid_width     14
#define pktid_width     4
#define trdid_width     4
#define wrplen_width    1

typedef VciParams<cell_width,
        plen_width,
        address_width,
        error_width,
        clen_width,
        rflag_width,
        srcid_width,
        pktid_width,
        trdid_width,
        wrplen_width> vci_param;

/* mapping table for data */
MappingTable maptabd(32, IntTab(6), IntTab(6), 0xf0000000);
/* mapping table for coherence */
MappingTable maptabc(32, IntTab(srcid_width), IntTab(srcid_width), 0xf0000000);


/*
 * segmentation
 */

#include "segmentation.h"


/*
 * default parameters
 */

struct param_s {
    char *rom_path;
    char *dsk_path;
    bool dummy_boot;
    bool trace_enabled;
    size_t trace_start_cycle;
};

#define PARAM_INITIALIZER   \
{                           \
    .rom_path = NULL,      \
    .dsk_path = NULL,        \
    .dummy_boot = false,    \
    .trace_enabled = false, \
    .trace_start_cycle = 0  \
}

static inline void print_param(const struct param_s &param)
{
    std::cout << std::endl;
    std::cout << "simulation parameters:" << std::endl;
    std::cout << "  rom         = " << param.rom_path << std::endl;
    std::cout << "  dummy boot  = " << param.dummy_boot << std::endl;
    std::cout << "  dsk         = " << param.dsk_path << std::endl;
    std::cout << "  trace       = " << param.trace_enabled << std::endl;
    if (param.trace_enabled)
        std::cout << "    start cyc = " << param.trace_start_cycle << std::endl;

    std::cout << std::endl;
}


/*
 * arguments parsing
 */

void args_parse(unsigned int argc, char *argv[], struct param_s &param)
{
    for (size_t n = 1; n < argc; n = n + 2)
    {
        if ((strcmp(argv[n], "--rom") == 0) && ((n + 1) < argc))
        {
            assert((param.rom_path = strdup(argv[n + 1]))
                    && "insufficient memory");
        }
        else if ((strcmp(argv[n], "--dsk") == 0) && ((n + 1) < argc))
        {
            assert((param.dsk_path = strdup(argv[n + 1]))
                    && "insufficient memory");
        }
        else if (strcmp(argv[n], "--dummy-boot") == 0)
        {
            param.dummy_boot = true;
            /* we don't have an extra argument */
            n = n - 1;
        }
        else if ((strcmp(argv[n], "--trace") == 0) && ((n + 1) < argc))
        {
            param.trace_enabled = true;
            param.trace_start_cycle = atoi(argv[n + 1]);
        }
        else
        {
            std::cout << "Error: don't understand option " << argv[n] << std::endl;
            std::cout << "Accepted arguments are :" << std::endl;
            std::cout << "--rom pathname" << std::endl;
            std::cout << "--dsk pathname" << std::endl;
            std::cout << "[--dummy-boot]" << std::endl;
            std::cout << "[--trace trace_start_cycle]" << std::endl;
            exit(0);
        }
    }

    /* check parameters */
    assert(param.rom_path && "--rom is not optional");
    assert(param.dsk_path && "--dsk is not optional");

    print_param(param);
}


/*
 * netlist
 */

int _main(int argc, char *argv[])
{
#ifdef _OPENMP
    omp_set_dynamic(false);
    omp_set_num_threads(1);
    std::cerr << "Built with openmp version " << _OPENMP << std::endl;
#endif

    struct param_s param = PARAM_INITIALIZER;

    /* parse arguments */
    args_parse(argc, argv, param);

    /*
     * mapping table (data and coherence)
     */

    // caches ram bank
    maptabd.add(Segment("memc_d" , MEMC_BASE , MEMC_SIZE , IntTab(0), true));
    maptabd.add(Segment("boot_d" , BOOT_BASE , BOOT_SIZE , IntTab(1), true));

    // uncached peripherals
    maptabd.add(Segment("icu_d"   , ICU_BASE   , ICU_SIZE   , IntTab(2), false));
    maptabd.add(Segment("mtty_d"  , MTTY_BASE  , MTTY_SIZE  , IntTab(3), false));
    maptabd.add(Segment("timer_d" , TIMER_BASE , TIMER_SIZE , IntTab(4), false));
    maptabd.add(Segment("bd_d"    , BD_BASE    , BD_SIZE    , IntTab(5), false));

    std::cout << maptabd << std::endl;

    // procs
    maptabc.add(Segment("proc_c" , 0x0000000 , 0x10 , IntTab(0) , false));
    maptabc.add(Segment("memc_c" , 1 << (address_width - srcid_width) , 0x10 , IntTab(1) , false));

    std::cout << maptabc << std::endl;

    /*
     * components
     */

    Loader loader;
    loader.load_file(param.rom_path);

#ifdef CONFIG_GDB_SERVER
    typedef GdbServer<Mips32ElIss> proc_iss;
    proc_iss::set_loader(loader);
#else
    typedef Mips32ElIss proc_iss;
#endif

    if (param.dummy_boot == true)
    {
        /* boot linux image directly */
        uint64_t entry_addr = loader.get_entry_point_address();
        std::cout << "setResetAdress: " << std::hex << entry_addr << std::endl << std::endl;
        proc_iss::setResetAddress(entry_addr);
    }

    VciCcVCacheWrapperV4<vci_param, proc_iss > proc("ccvcache",
                0,          // proc_id
                maptabd,    // direct space
                maptabc,    // coherence space
                IntTab(0),  // srcid_d
                IntTab(0),  // srcid_c
                IntTab(0),  // tgtid_c
                8, 8,       // itlb size
                8, 8,       // dtlb size
                4, 64, 16,  // icache size
                4, 64, 16,  // dcache size
                4, 4,       // wbuf size
                0, 0,       // x, y Width
                1,          // memory cache local id
                1000,       // max frozen cycles
                param.trace_start_cycle,
                param.trace_enabled);

    VciSimpleRam<vci_param> ram("ram", IntTab(0), maptabd, loader);

    VciSimpleRam<vci_param> rom("rom", IntTab(1), maptabd, loader);

    VciMemCacheV4<vci_param> memc("memc",
            maptabd, maptabc, maptabd,
            IntTab(0), IntTab(1), IntTab(0), IntTab(1), // srcid_d, srcid_c, tgtid_d, tgtid_c
            16, 256, 16,    // cache size
            1024, 4, 4,     // HEAP size, TRT size, UPT size
            param.trace_start_cycle, param.trace_enabled);

    VciIcu<vci_param> icu("icu", IntTab(2), maptabd,
            3); // #input_irqs

    VciMultiTty<vci_param> mtty("mtty", IntTab(3), maptabd, "vcitty0", NULL);

    VciTimer<vci_param> timer("timer", IntTab(4), maptabd,
            1); // #timers

    VciBlockDeviceTsarV4<vci_param> bd("bd", maptabd, IntTab(1), IntTab(5),
            param.dsk_path); // mapped_file[, block_size=512, latency=0]

    VciVgmn<vci_param> vgmnd("vgmnd", maptabd,
            2, 6,       // #initiators, #targets
            2, 8,       // min_latency, FIFO depth
            IntTab(1)); // default target

    VciVgmn<vci_param> vgmnc("vgmnc", maptabc,
            2, 2,   // #initiators, #targets
            2, 8);  // min_latency, FIFO depth

    /*
     * signals
     */

    /* clock and reset */
    sc_clock signal_clk("signal_clk");
    sc_signal<bool> signal_resetn("signal_resetn");

    /* irq lines */
    sc_signal<bool> *signal_proc_irq =
        alloc_elems<sc_signal<bool> >("signal_proc_irq", proc_iss::n_irq);
    sc_signal<bool> signal_mtty_irq("signal_mtty_irq");
    sc_signal<bool> signal_timer_irq("signal_timer_irq");
    sc_signal<bool> signal_bd_irq("signal_bd_irq");

    /* vci */
    VciSignals<vci_param> signal_vci_ini_d_proc("vci_ini_d_proc");
    VciSignals<vci_param> signal_vci_ini_c_proc("vci_ini_c_proc");
    VciSignals<vci_param> signal_vci_tgt_c_proc("vci_tgt_c_proc");

    VciSignals<vci_param> signal_vci_ram("signal_vci_ram");

    VciSignals<vci_param> signal_vci_tgt_d_rom("signal_vci_tgt_d_rom");

    VciSignals<vci_param> signal_vci_ini_c_memc("signal_vci_ini_c_memc");
    VciSignals<vci_param> signal_vci_tgt_d_memc("signal_vci_tgt_d_memc");
    VciSignals<vci_param> signal_vci_tgt_c_memc("signal_vci_tgt_c_memc");

    VciSignals<vci_param> signal_vci_tgt_d_icu("signal_vci_tgt_d_icu");

    VciSignals<vci_param> signal_vci_tgt_d_mtty("signal_vci_tgt_d_mtty");

    VciSignals<vci_param> signal_vci_tgt_d_timer("signal_vci_tgt_d_timer");

    VciSignals<vci_param> signal_vci_ini_d_bd("signal_vci_ini_d_bd");
    VciSignals<vci_param> signal_vci_tgt_d_bd("signal_vci_tgt_d_bd");

    /*
     * netlist
     */

    proc.p_clk(signal_clk);
    proc.p_resetn(signal_resetn);
    for (size_t i = 0; i < proc_iss::n_irq; i++)
        proc.p_irq[i](signal_proc_irq[i]);
    proc.p_vci_ini_d(signal_vci_ini_d_proc);
    proc.p_vci_ini_c(signal_vci_ini_c_proc);
    proc.p_vci_tgt_c(signal_vci_tgt_c_proc);

    ram.p_clk(signal_clk);
    ram.p_resetn(signal_resetn);
    ram.p_vci(signal_vci_ram);

    rom.p_clk(signal_clk);
    rom.p_resetn(signal_resetn);
    rom.p_vci(signal_vci_tgt_d_rom);

    memc.p_clk(signal_clk);
    memc.p_resetn(signal_resetn);
    memc.p_vci_tgt(signal_vci_tgt_d_memc);
    memc.p_vci_tgt_cleanup(signal_vci_tgt_c_memc);
    memc.p_vci_ini(signal_vci_ini_c_memc);
    memc.p_vci_ixr(signal_vci_ram);

    icu.p_resetn(signal_resetn);
    icu.p_clk(signal_clk);
    icu.p_vci(signal_vci_tgt_d_icu);
    icu.p_irq_in[0](signal_mtty_irq);
    icu.p_irq_in[1](signal_timer_irq);
    icu.p_irq_in[2](signal_bd_irq);
    icu.p_irq(signal_proc_irq[0]);

    mtty.p_clk(signal_clk);
    mtty.p_resetn(signal_resetn);
    mtty.p_vci(signal_vci_tgt_d_mtty);
    mtty.p_irq[0](signal_mtty_irq);

    timer.p_clk(signal_clk);
    timer.p_resetn(signal_resetn);
    timer.p_vci(signal_vci_tgt_d_timer);
    timer.p_irq[0](signal_timer_irq);

    bd.p_clk(signal_clk);
    bd.p_resetn(signal_resetn);
    bd.p_vci_target(signal_vci_tgt_d_bd);
    bd.p_vci_initiator(signal_vci_ini_d_bd);
    bd.p_irq(signal_bd_irq);

    vgmnd.p_clk(signal_clk);
    vgmnd.p_resetn(signal_resetn);
    vgmnd.p_to_initiator[0](signal_vci_ini_d_proc);
    vgmnd.p_to_initiator[1](signal_vci_ini_d_bd);
    vgmnd.p_to_target[0](signal_vci_tgt_d_memc);
    vgmnd.p_to_target[1](signal_vci_tgt_d_rom);
    vgmnd.p_to_target[2](signal_vci_tgt_d_icu);
    vgmnd.p_to_target[3](signal_vci_tgt_d_mtty);
    vgmnd.p_to_target[4](signal_vci_tgt_d_timer);
    vgmnd.p_to_target[5](signal_vci_tgt_d_bd);

    vgmnc.p_clk(signal_clk);
    vgmnc.p_resetn(signal_resetn);
    vgmnc.p_to_initiator[0](signal_vci_ini_c_proc);
    vgmnc.p_to_initiator[1](signal_vci_ini_c_memc);
    vgmnc.p_to_target[0](signal_vci_tgt_c_proc);
    vgmnc.p_to_target[1](signal_vci_tgt_c_memc);

    /*
     * simulation
     */

    sc_start(sc_time(0, SC_NS));
    signal_resetn = false;

    sc_start(sc_time(1, SC_NS));
    signal_resetn = true;

    if (param.trace_enabled)
    {
        if (param.trace_start_cycle > 1)
            // simulate without output until trace_start_cycle
            sc_start(sc_time(param.trace_start_cycle, SC_NS));

        // enable debugging output
        for (size_t n = param.trace_start_cycle ;; n++)
        {
            std::cout << "****************** cycle " << std::dec << n
                << " ************************************************" << std::endl;
            proc.print_trace();
            memc.print_trace();
            signal_vci_ini_d_proc.print_trace("proc_ini_d");
            signal_vci_tgt_c_proc.print_trace("proc_tgt_c");
            signal_vci_ini_c_proc.print_trace("proc_ini_c");
            signal_vci_tgt_d_memc.print_trace("memc_tgt_d");
            signal_vci_tgt_c_memc.print_trace("memc_tgt_c");
            signal_vci_ini_c_memc.print_trace("memc_ini_c");
            if (signal_proc_irq[0].read())
                std::cout << "---- IRQ ----" << std::endl;
            sc_start(sc_time(1, SC_NS));
        }
    } else
        sc_start();

    return EXIT_SUCCESS;
}

int sc_main (int argc, char *argv[])
{
    try {
        return _main(argc, argv);
    } catch (std::exception &e) {
        std::cout << e.what() << std::endl;
    } catch (...) {
        std::cout << "Unknown exception occurred" << std::endl;
        throw;
    }
    return EXIT_FAILURE;
}
