#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <libgen.h>
#include <systemc.h>
#include <cmath>

/*********************************************************************
 * Inclusion des modèles
 *********************************************************************/
#include "shared/macro.h"
#include "shared/soclib_caches_interfaces.h"
#include "shared/soclib_segment_table.h"
#include "shared/mapping_memory.h"
/*********************************************************************
 * Inclusion et parametrage du processeur
 *********************************************************************/
#include "m_cpu_configuration.h"
#include "processor/M_CPU/Configuration/m_cpu_configuration.h"
#include "processor/M_CPU/M_CPU.h"

#include "hierarchy_memory/hierarchy_memory.h"

using namespace hierarchy_memory;
using namespace std;
/*********************************************************************
 * Définitions des paramètres des composants
 *********************************************************************/

#define WITH_XTTY                             false
#define NB_CONTEXT                            (M_CPU_NB_CLUSTER * M_CPU_NB_UL * M_CPU_NB_THREAD)

// Parametre du CPU dans un fichier externe
#define ICACHE_TEMPLATE                       LOG2_M_CPU_NB_CLUSTER + LOG2_M_CPU_NB_UL + LOG2_M_CPU_NB_THREAD+1, (LOG2_M_CPU_SIZE_IFETCH_QUEUE)+1    , M_CPU_SIZE_ADDR_INST, M_CPU_SIZE_INST,M_CPU_NB_INST_FETCH
#define DCACHE_TEMPLATE                       LOG2_M_CPU_NB_CLUSTER + LOG2_M_CPU_NB_UL + LOG2_M_CPU_NB_THREAD+1, (LOG2_M_CPU_SIZE_LOAD_STORE_QUEUE)+1, M_CPU_SIZE_ADDR_DATA, M_CPU_SIZE_DATA

//#define OFFSET_NB_LINE                        (power2(NB_CONTEXT)/power2(M_CPU_NB_CLUSTER))
#define OFFSET_NB_LINE                        1

  //                                       nb_line, size_line, size_word, associativity, hit_latence, miss_penality
#define L1_ICACHE "L1_ICACHE",(128*OFFSET_NB_LINE),        16,         4,             4,           2,             4
#define L1_DCACHE "L1_DCACHE",(128*OFFSET_NB_LINE),        16,         4,             4,           2,             4
#define L2_CACHE  "L2_CACHE" ,               16384,        32,         4,             8,           6,           100

#define NB_IPORT                              1 
#define NB_DPORT                              M_CPU_NB_DATA_ACCESS

#define SIZE_BUFFER_IRSP                      4
#define SIZE_BUFFER_DRSP                      4

#define HIERARCHY_MEMORY_TEMPLATE             LOG2_M_CPU_NB_CLUSTER + LOG2_M_CPU_NB_UL + LOG2_M_CPU_NB_THREAD+1, (LOG2_M_CPU_SIZE_IFETCH_QUEUE)+1    , M_CPU_SIZE_ADDR_INST, M_CPU_SIZE_INST,M_CPU_NB_INST_FETCH, (LOG2_M_CPU_SIZE_LOAD_STORE_QUEUE)+1, M_CPU_SIZE_ADDR_DATA, M_CPU_SIZE_DATA
#define MAPPING_TABLE_CLUSTER_TEMPLATE        LOG2_M_CPU_NB_UL, M_CPU_NB_UL, LOG2_M_CPU_NB_INST_SELECT, M_CPU_NB_INST_SELECT
#define MAPPING_TABLE_UE_TEMPLATE             LOG2_M_CPU_NB_CLUSTER, M_CPU_NB_CLUSTER, LOG2_M_CPU_NB_INST_SELECT, M_CPU_NB_INST_SELECT
#define MAPPING_TABLE_DISPATCH_TEMPLATE       LOG2_M_CPU_NB_INST_SELECT, M_CPU_NB_INST_SELECT, LOG2_M_CPU_NB_UE, M_CPU_NB_UE
#define TYPE_TABLE_INST_L_TEMPLATE            LOG2_NB_INST_L, NB_INST_L


/*********************************************************************
 * Variable global
 *********************************************************************/
timeval                                           time_begin;
timeval                                           time_end;
unsigned int                                      nb_cycles_simulated = 0;

/********************************************************
 * Déclarations des composants
 ********************************************************/

UE_CONFIGURATION                                  ** ue_configuration;
MAPPING_TABLE   <MAPPING_TABLE_CLUSTER_TEMPLATE>  ** mapping_table_cluster;
MAPPING_TABLE   <MAPPING_TABLE_UE_TEMPLATE>        * mapping_table_ue;
MAPPING_TABLE   <MAPPING_TABLE_DISPATCH_TEMPLATE>  * mapping_table_dispatch;

TYPE_TABLE      <TYPE_TABLE_INST_L_TEMPLATE>       * type_table_inst_l;

HIERARCHY_MEMORY<HIERARCHY_MEMORY_TEMPLATE>        * memory;
M_CPU           <M_CPU_CONFIGURATION>              * m_cpu;

/*********************************************************************
 * print_time
 *********************************************************************/
void print_time (timeval time_begin, timeval time_end, unsigned int nb_cycles_simulated, unsigned int nb_cycles_current)
{
  double average = (double)(nb_cycles_simulated) / (double)(time_end.tv_sec-time_begin.tv_sec);

  cout << nb_cycles_current << "\t(" << average << " cycles / seconds )" << endl;
}

/*********************************************************************
 * clean_exit
 *********************************************************************/
void clean_exit ()
{
  sc_stop();

  gettimeofday(&time_end       ,NULL);
  print_time(time_begin,time_end,(unsigned int)sc_simulation_time()-nb_cycles_simulated,(unsigned int)sc_simulation_time());

  delete memory;

  for (unsigned int i = 0; i < M_CPU_NB_CLUSTER; i ++)
    delete mapping_table_cluster[i];
  for (unsigned int i = 0; i < M_CPU_NB_UE; i ++)
    delete ue_configuration[i];
  
  delete mapping_table_ue;
  delete mapping_table_dispatch;
  delete type_table_inst_l;
  
  delete m_cpu;
}

/*********************************************************************
 * handler_signal
 *********************************************************************/
void handler_signal(int signum)
{
  switch (signum)
    {
    case SIGTERM :
    case SIGINT  :
      {
	perror("Stop of simulation\n");
	clean_exit();
	exit(2);
	break;
      }
    default :
      {
	break;
      }
    }//end switch signum
}

/*********************************************************************
 * simulation
 *********************************************************************/
unsigned int simulation ()
{
  // Test if can continue the simulation (all thread have not send the stop signal)
  if (memory->stop() == true)
    return 0;

  cout << "How many cycle to simulate ? (0 to stop the simulation)" << endl;

  int nb_cycle;
  cin >> nb_cycle;

  cout << "    ... again " << nb_cycle << " cycles" << endl;
  return nb_cycle;
}

/*********************************************************************
 * power2
 *********************************************************************/
// return the Y=2^n with 2^(n-1) < X <= 2^n
// Warning : possible bug if X > 0x80000000
uint32_t power2 (uint32_t x)
{
  uint32_t mask;

  for (mask = 1; mask < x; mask <<= 1);

  return mask;
}

/*********************************************************************
 * usage
 *********************************************************************/

void usage(char * name_fct)
{
  cerr << "usage : " << name_fct << " filename [nb_cycle]" << endl;
  cerr << "      * filename      : name of binary" << endl;
  cerr << "      * nb_cycle      : number of cycle to simulate" << endl;
  exit(1);
}

/*********************************************************************
 * sc_main
 *********************************************************************/

int sc_main(int argc, char* argv[])
{
  // Trap signal
  signal(SIGTERM, handler_signal);
  signal(SIGINT , handler_signal);
 
  if ( (argc != 2) && 
       (argc != 3))
    usage(argv[0]);

  unsigned int nb_cycles        = 0;
  bool         nb_cycles_define = false;
  const char *       filename  = argv[1];

  if (argc >= 3)
    {
      nb_cycles_define = true;
      nb_cycles        = atoi(argv[2]);
    }

  /*********************************************************************
   * Déclarations des variables pour la simulation
   *********************************************************************/

  /*********************************************************************
   * Déclarations des signaux
   *********************************************************************/
  sc_clock                                 CLK ("clock",1,0.5);
  sc_signal<bool>                          NRESET;

  ICACHE_SIGNALS <ICACHE_TEMPLATE>         ** icache_signals;
  DCACHE_SIGNALS <DCACHE_TEMPLATE>         ** dcache_signals;

  /********************************************************
   * Segment table
   ********************************************************/

  log_printf(INFO,"<sc_main> Table des segments\n");
  SOCLIB_SEGMENT_TABLE segtable;
  segtable.setMSBNumber    (8);
  segtable.setDefaultTarget(0,0);
  
  //shared data segment
  
  // Add a segment   :name        , address of base    , size               , global index , local index, uncache
  segtable.addSegment("text"      , TEXT_BASE          , TEXT_SIZE          , 0            ,0           , false);
  segtable.addSegment("data"      , DATA_CACHED_BASE   , DATA_CACHED_SIZE   , 0            ,0           , false);
  segtable.addSegment("data_unc"  , DATA_UNCACHED_BASE , DATA_UNCACHED_SIZE , 0            ,0           , true );
  segtable.addSegment("stack"     , STACK_BASE         , STACK_SIZE         , 0            ,0           , false);
  segtable.addSegment("tty"       , TTY_BASE           , TTY_SIZE           , 0            ,0           , true );
  segtable.addSegment("sim2os"    , SIM2OS_BASE        , SIM2OS_SIZE        , 0            ,0           , true );
  segtable.addSegment("ramlock"   , RAMLOCK_BASE       , RAMLOCK_SIZE       , 0            ,0           , true );
  
  /********************************************************
   * Déclaration des signaux
   ********************************************************/
  log_printf(INFO,"<sc_main> Déclaration des signaux\n");
  icache_signals    = new ICACHE_SIGNALS     <ICACHE_TEMPLATE> * [M_CPU_NB_CLUSTER];
  dcache_signals    = new DCACHE_SIGNALS     <DCACHE_TEMPLATE> * [M_CPU_NB_CLUSTER];
  
  
  for (unsigned int it_m_cpu_nb_cluster = 0; it_m_cpu_nb_cluster < M_CPU_NB_CLUSTER; it_m_cpu_nb_cluster ++)
    {
      icache_signals [it_m_cpu_nb_cluster] = new ICACHE_SIGNALS <ICACHE_TEMPLATE> [NB_IPORT];
      dcache_signals [it_m_cpu_nb_cluster] = new DCACHE_SIGNALS <DCACHE_TEMPLATE> [NB_DPORT];
    }
  
  /********************************************************
   * Déclaration des composants
   ********************************************************/
  
  log_printf(INFO,"<sc_main> Déclaration des composants\n");
  
  // UE_CONFIGURATION
  
  ue_configuration        = new UE_CONFIGURATION * [M_CPU_NB_UE];
  for (unsigned int i = 0; i < M_CPU_NB_UE; i ++)
    {
      std::ostringstream name_ue_configuration;
      name_ue_configuration << "ue_configuration[" << i << "]";
      ue_configuration[i]     = new UE_CONFIGURATION                                       (name_ue_configuration.str().c_str());
    }
  
  // MAPPING_TABLE (CLUSTER)
  
  mapping_table_cluster    = new MAPPING_TABLE <MAPPING_TABLE_CLUSTER_TEMPLATE> * [M_CPU_NB_CLUSTER];
  for (unsigned int i = 0; i < (M_CPU_NB_CLUSTER); i ++)
    {
      std::ostringstream name_mapping_table_cluster;
      name_mapping_table_cluster << "mapping_table_cluster[" << i << "]";
      mapping_table_cluster[i] = new MAPPING_TABLE <MAPPING_TABLE_CLUSTER_TEMPLATE> (name_mapping_table_cluster.str().c_str());
    }
  
  // MAPPING_TABLE (UE)
  std::ostringstream name_mapping_table_ue;
  name_mapping_table_ue << "mapping_table_ue";
  mapping_table_ue = new MAPPING_TABLE <MAPPING_TABLE_UE_TEMPLATE>   (name_mapping_table_ue.str().c_str());
  
  // MAPPING_TABLE (DISPATCH)
  std::ostringstream name_mapping_table_dispatch;
  name_mapping_table_dispatch << "mapping_table_dispatch";
  mapping_table_dispatch = new MAPPING_TABLE <MAPPING_TABLE_DISPATCH_TEMPLATE>   (name_mapping_table_dispatch.str().c_str());
  
  std::ostringstream name_type_table_inst_l;
  name_type_table_inst_l << "type_table_inst_l";
  
  type_table_inst_l = new TYPE_TABLE <TYPE_TABLE_INST_L_TEMPLATE>   (name_type_table_inst_l.str().c_str());
  
  // initialisation of internal structure
  ini_ue_configuration       (ue_configuration);
  ini_mapping_table_cluster  (mapping_table_cluster);
  ini_mapping_table_ue       (mapping_table_ue);
  ini_mapping_table_dispatch (mapping_table_dispatch);
  ini_type_table_inst_l      (type_table_inst_l);

  // ----- SYSTEM MEMORY -----

  char * name_tty [NB_CONTEXT];

  for (uint32_t num_context = 0; num_context < NB_CONTEXT; num_context ++)
    {
      std::ostringstream name_one_tty;
      name_one_tty << "tty_" << num_context;

      unsigned int size_name = strlen(name_one_tty.str().c_str())+1;
      name_tty [num_context] = new char [size_name];
      strncpy(name_tty [num_context],name_one_tty.str().c_str(),size_name);      
    }
  
  param_entity_t<tty::param_t>     param_tty     [1] = {param_entity_t<tty::param_t>     (TTY_BASE    , TTY_SIZE    , tty::param_t ("tty"   , NB_CONTEXT, name_tty,WITH_XTTY))};
  param_entity_t<ramlock::param_t> param_ramlock [1] = {param_entity_t<ramlock::param_t> (RAMLOCK_BASE, RAMLOCK_SIZE, ramlock::param_t("ramlock", RAMLOCK_SIZE))};
  param_entity_t<sim2os::param_t>  param_sim2os      =  param_entity_t<sim2os::param_t>  (SIM2OS_BASE, SIM2OS_SIZE, sim2os::param_t("sim2os",&segtable));

  param_cache_t                    param_icache           [1] = { param_cache_t (L1_ICACHE) };
  param_cache_t                    param_dcache           [1] = { param_cache_t (L1_DCACHE) };
  param_cache_t                    param_scache           [1] = { param_cache_t (L2_CACHE)};

  cache::cache_multilevel::param_t param_icache_dedicated [M_CPU_NB_CLUSTER];
  cache::cache_multilevel::param_t param_dcache_dedicated [M_CPU_NB_CLUSTER];

  for (uint32_t num_cluster = 0; num_cluster < M_CPU_NB_CLUSTER; num_cluster ++)
    {
      std::ostringstream name_icache_dedicated;
      name_icache_dedicated << "icache_dedicated[" << num_cluster << "]";
      
      param_icache_dedicated [num_cluster] = cache::cache_multilevel::param_t (name_icache_dedicated.str().c_str(),1, NB_IPORT, param_icache);
      
      std::ostringstream name_dcache_dedicated;
      name_dcache_dedicated << "dcache_dedicated[" << num_cluster << "]";
      param_dcache_dedicated [num_cluster] = cache::cache_multilevel::param_t (name_dcache_dedicated.str().c_str(),1, NB_DPORT, param_dcache);
    }
  			    
  cache::cache_multilevel::param_t param_cache_shared   ("param_cache_shared",1, M_CPU_NB_CLUSTER*(NB_IPORT+NB_DPORT), param_scache);

  cache::param_t                   param_cache ("cache"               ,
						M_CPU_NB_CLUSTER            ,
						param_icache_dedicated,
						param_dcache_dedicated,
						param_cache_shared    );

  std::ostringstream name_memory;
  name_memory << basename(argv[0]) << "_memory";
  memory   = new HIERARCHY_MEMORY <HIERARCHY_MEMORY_TEMPLATE> (name_memory.str().c_str() ,
							       0               ,
							       0               ,
							       &segtable       ,
							       M_CPU_NB_CLUSTER,
							       NB_CONTEXT      ,
							       SIZE_BUFFER_IRSP,
							       SIZE_BUFFER_DRSP,
							       hierarchy_memory::param_t(1,param_tty,
											 1,param_ramlock,
											 param_sim2os,
											 param_cache)
							       );
  
  std::ostringstream name_m_cpu;
  name_m_cpu << basename(argv[0]) << "_m_cpu";
  m_cpu              = new M_CPU            <M_CPU_CONFIGURATION>       (name_m_cpu.str().c_str() ,
									 ue_configuration         ,
									 mapping_table_cluster    ,
									 mapping_table_ue         ,
									 mapping_table_dispatch   ,
									 type_table_inst_l        ,
									 M_CPU_PARAMETER_USE
									 );
  //////////////////////////////////////////////////////////
  //	Segments Initialisation
  //////////////////////////////////////////////////////////
  
  log_printf(INFO,"<sc_main> Table des segments : initialisation\n");
  
  const char	*sections_text  []  = {".text",NULL}; 
  const char 	*sections_data  []  = {".data",".rodata",".bss",".sdata",".sbss", NULL}; 
  const char	*sections_stack []  = {".stack",NULL}; 

  memory->init("text"   , filename, sections_text);
  memory->init("stack"  , filename, sections_stack);
  memory->init("data"   , filename, sections_data);

  segtable.print();

  /********************************************************
   * Instanciation
   ********************************************************/

  log_printf(INFO,"<sc_main> Instanciation des composants\n");

  log_printf(INFO,"<sc_main> Instanciation de l'élément \"memory\"\n");
  memory->CLK          (CLK);
  memory->NRESET       (NRESET);

  for (uint32_t x = 0; x < M_CPU_NB_CLUSTER; x ++)
    {
      for (uint32_t y = 0; y < NB_IPORT; y ++)
	memory->ICACHE[x][y] (icache_signals [x][y]);
      for (uint32_t y = 0; y < NB_DPORT; y ++)
	memory->DCACHE[x][y] (dcache_signals [x][y]);
    }
  
  log_printf(INFO,"<sc_main> Instanciation de l'élément \"m_cpu\"\n");
  m_cpu->CLK                   (CLK);
  m_cpu->NRESET                (NRESET);
  for (unsigned int it_m_cpu_nb_cluster = 0; it_m_cpu_nb_cluster < M_CPU_NB_CLUSTER; it_m_cpu_nb_cluster++)
    {
      m_cpu->ICACHE[it_m_cpu_nb_cluster] (icache_signals[it_m_cpu_nb_cluster][0]);

      for (unsigned int it_nb_dport = 0; it_nb_dport < NB_DPORT; it_nb_dport++)
	m_cpu->DCACHE[it_m_cpu_nb_cluster][it_nb_dport] (dcache_signals[it_m_cpu_nb_cluster][it_nb_dport]);	
    }//end it_m_cpu_nb_cluster
     
  log_printf(NONE,"<sc_main> Successful Instanciation\n");

  /********************************************************
   * Simulation - Begin
   ********************************************************/

  // Initialisation
  sc_start(0);

  // Reset
  NRESET.write(false);
  sc_start(5);
  NRESET.write(true);

  // Lunch the simulation
  cout << "\n<" << argv[0] << "> Simulation : Begin\n\n" << endl;

  nb_cycles_simulated = 0;

  while (1)
    {
      unsigned int nb_cycles_simulation;

      if (nb_cycles_define == true)
	{
	  nb_cycles_simulation = nb_cycles;
	  nb_cycles            = 0; // to stop at the next loop :D
	}
      else
	nb_cycles_simulation = simulation();

      if (nb_cycles_simulation == 0)
	break;
  
      gettimeofday(&time_begin     ,NULL);
      sc_start    (nb_cycles_simulation);
      gettimeofday(&time_end       ,NULL);

      nb_cycles_simulated += nb_cycles_simulation;
      
      print_time(time_begin,time_end,nb_cycles_simulation,(unsigned int)sc_simulation_time());
    }

  cout << "\n<" << argv[0] << "> Simulation : End\n\n"   << endl;

  /********************************************************
   * Simulation - End
   ********************************************************/
  clean_exit ();

  return 0;
}
