#include <iostream>
#include "../include/Cache.h"

using namespace std;
using namespace environnement;
using namespace environnement::cache;

#define TEST(x,y)				\
  {						\
    cout << "Line " << __LINE__ << " : ";	\
    if (x==y)					\
      {						\
	cout << "Test OK" << endl;		\
      }						\
    else					\
      {						\
	cout << "Test KO" << endl;		\
	exit (EXIT_FAILURE);			\
      }						\
  } while (0)
    


int main (void)
{
  cout << "<main> Begin" << endl;

  {
    cache_onelevel::Parameters * param = new cache_onelevel::Parameters 
      (
       4, // nb_port      
       32, // nb_line      
       8, // size_line    
       4,// size_word    
       4, // associativity
       2, // hit_latence  
       3 // miss_penality
       );

    cout << *param << endl;

    cache_onelevel::Cache_OneLevel * cache = new cache_onelevel::Cache_OneLevel ("my_cache",param);
    cache->reset();
    
    cout << *cache << endl;
    
    cache->transition();
    cache->transition();

    cout << *cache << endl;

    TEST(cache->access(0, 0x100, 0, CACHED, WRITE), MISS);
    TEST(cache->latence(0), 5);
    cache->transition(); // miss cycle 1

    TEST(cache->access(0, 0x100, 0, CACHED, WRITE), HIT_WRITE_BUFFER);
    TEST(cache->latence(0), 4);
    cache->transition(); // miss cycle 2

    TEST(cache->access(0, 0x100, 0, CACHED, WRITE), HIT_WRITE_BUFFER);
    TEST(cache->latence(0), 3);
    cache->transition(); // miss cycle 3

    TEST(cache->access(0, 0x100, 0, CACHED, WRITE), HIT_CACHE); //word 0
    TEST(cache->latence(0), 2);

    TEST(cache->access(0, 0x104, 0, CACHED, WRITE), HIT_CACHE); //word 1
    TEST(cache->access(0, 0x108, 0, CACHED, WRITE), HIT_CACHE); //word 2
    TEST(cache->access(0, 0x10c, 0, CACHED, WRITE), HIT_CACHE); //word 3
    TEST(cache->access(0, 0x110, 0, CACHED, WRITE), HIT_CACHE); //word 4
    TEST(cache->access(0, 0x114, 0, CACHED, WRITE), HIT_CACHE); //word 5
    TEST(cache->access(0, 0x118, 0, CACHED, WRITE), HIT_CACHE); //word 6
    TEST(cache->access(0, 0x11c, 0, CACHED, WRITE), HIT_CACHE); //word 7
    TEST(cache->access(0, 0x120, 0, CACHED, WRITE), MISS      );
    TEST(cache->access(1, 0x140, 0, CACHED, WRITE), MISS      );
    TEST(cache->access(2, 0x160, 0, CACHED, WRITE), MISS      );
    TEST(cache->access(3, 0x144, 0, CACHED, WRITE), HIT_BYPASS);

    TEST(cache->latence(0), 5);
    TEST(cache->latence(1), 5);
    TEST(cache->latence(2), 5);
    TEST(cache->latence(3), 5);

    cache->transition(); // miss access    
    TEST(cache->access(0, 0x180, 0, CACHED, WRITE), MISS      );
    TEST(cache->access(1, 0x1a0, 0, CACHED, WRITE), MISS      );
    TEST(cache->access(2, 0x1c0, 0, CACHED, WRITE), MISS      );
    TEST(cache->access(3, 0x1e0, 0, CACHED, WRITE), MISS      );

    cache->transition(); // miss cycle 1
    cout << *cache << endl;

    TEST(cache->access(0, 0x200, 0, CACHED, WRITE), MISS      );
    TEST(cache->access(1, 0x300, 0, CACHED, WRITE), MISS      );
    TEST(cache->access(2, 0x400, 0, CACHED, WRITE), MISS      );

    cache->transition(); // miss cycle 0
    cache->transition(); // miss cycle 1
    cache->transition(); // miss cycle 2
    cache->transition(); // miss cycle 3
    cout << *cache << endl;

    // line 0 : all way is use
    TEST(cache->access(0, 0x118, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(1, 0x204, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(2, 0x100, 0,UNCACHED, WRITE), MISS      );
    TEST(cache->access(3, 0x118, 0,  CACHED, WRITE), HIT_BYPASS);

    TEST(cache->latence(0), 2);
    TEST(cache->latence(1), 2);
    TEST(cache->latence(2), 5);
    TEST(cache->latence(3), 2);

    cache->transition();
    cout << *cache << endl;
    
    TEST(cache->access(0, 0x500, 0,  CACHED, WRITE), MISS      );

    cache->transition(); // miss cycle 1
    TEST(cache->access(0, 0x100, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(1, 0x200, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(2, 0x300, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(3, 0x400, 0,  CACHED, WRITE), HIT_CACHE );

    cache->transition(); // miss cycle 2

    TEST(cache->access(0, 0x100, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(1, 0x200, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(2, 0x300, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(3, 0x400, 0,  CACHED, WRITE), HIT_CACHE );

    cache->transition(); // miss cycle 3
    TEST(cache->access(0, 0x100, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(1, 0x200, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(2, 0x300, 0,  CACHED, WRITE), HIT_CACHE );
    TEST(cache->access(3, 0x400, 0,  CACHED, WRITE), MISS );

    TEST(cache->latence(0), 2);
    TEST(cache->latence(1), 2);
    TEST(cache->latence(2), 2);
    TEST(cache->latence(3), 5);
    
    cout << *cache << endl;

    delete cache;
    delete param;
  }

  {
    uint32_t * nb_line       = new uint32_t [3];
    uint32_t * size_line     = new uint32_t [3];
    uint32_t * size_word     = new uint32_t [3];
    uint32_t * associativity = new uint32_t [3];
    uint32_t * hit_latence   = new uint32_t [3];
    uint32_t * miss_penality = new uint32_t [3];

    nb_line       [0] = 4 ; nb_line       [1] = 4 ; nb_line       [2] = 4 ;
    size_line     [0] = 8 ; size_line     [1] = 8 ; size_line     [2] = 8 ;
    size_word     [0] = 4 ; size_word     [1] = 4 ; size_word     [2] = 4 ;
    associativity [0] = 1 ; associativity [1] = 2 ; associativity [2] = 4 ;
    hit_latence   [0] = 1 ; hit_latence   [1] = 2 ; hit_latence   [2] = 2 ;
    miss_penality [0] = 3 ; miss_penality [1] = 5 ; miss_penality [2] = 7 ;

    cache_multilevel::Parameters * param = new cache_multilevel::Parameters 
      (
       3,  // nb_level
       4,  // nb_port      
       nb_line      ,
       size_line    ,
       size_word    ,
       associativity,
       hit_latence  ,
       miss_penality
       );

    cout << *param << endl;
  
    cache_multilevel::Cache_MultiLevel * cache = new cache_multilevel::Cache_MultiLevel ("my_cache",param);

    cout << *cache << endl;
    cache->reset();
    cout << *cache << endl;
  
    cache_multilevel::Access access;

    access = cache->access (0, 0x100, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 0);
    TEST(access.hit          , MISS);
    TEST(access.latence      , 20);
    TEST(access.last_nb_level, 2);

    cache->transition(); //19
    cache->transition(); //18
    cache->transition(); //17
    cache->transition(); //16
    cache->transition(); //15
    cache->transition(); //14
    cache->transition(); //13
    cache->transition(); //12
    cache->transition(); //11
    cache->transition(); //10
    cout << *cache << endl;

    access = cache->access (0, 0x100, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 0);
    TEST(access.hit          , HIT_WRITE_BUFFER);
    TEST(access.latence      , 10);
    TEST(access.last_nb_level, 0);

    cache->transition(); // 9
    cache->transition(); // 8
    cache->transition(); // 7
    cache->transition(); // 6
    cache->transition(); // 5
    cache->transition(); // 4
    cache->transition(); // 3
    cache->transition(); // 2

    access = cache->access (0, 0x100, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 0);
    TEST(access.hit          , HIT_WRITE_BUFFER);
    TEST(access.latence      , 2);
    TEST(access.last_nb_level, 0);

    cache->transition(); // 1

    access = cache->access (0, 0x100, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 0);
    TEST(access.hit          , HIT_CACHE);
    TEST(access.latence      , 1);
    TEST(access.last_nb_level, 0);
    cout << *cache << endl;

    cache->transition();

    access = cache->access (0, 0x100, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 0);
    TEST(access.hit          , HIT_CACHE);
    TEST(access.latence      , 1);
    TEST(access.last_nb_level, 0);


    access = cache->access (0, 0x100, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 0);
    TEST(access.hit          , HIT_CACHE);
    TEST(access.latence      , 1);
    TEST(access.last_nb_level, 0);

    access = cache->access (1, 0x200, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 1);
    TEST(access.hit          , MISS);
    TEST(access.latence      , 20);
    TEST(access.last_nb_level, 2);

    access = cache->access (2, 0x100, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 2);
    TEST(access.hit          , HIT_BYPASS);
    TEST(access.latence      , 1);
    TEST(access.last_nb_level, 0);

    access = cache->access (3, 0x200, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 3);
    TEST(access.hit          , HIT_BYPASS);
    TEST(access.latence      , 20);
    TEST(access.last_nb_level, 0);

    cache->transition(); //19
    cache->transition(); //18
    cache->transition(); //17
    cache->transition(); //16
    cache->transition(); //15


//     access = cache->access (1, 0x300, 0, CACHED, WRITE);
//     cache->update_access(access);

//     TEST(access.num_port     , 1);
//     TEST(access.hit          , MISS);
//     TEST(access.latence      , 20);
//     TEST(access.last_nb_level, 2);

    cache->transition(); //14
    cache->transition(); //13
    cache->transition(); //12
    cache->transition(); //11
    cache->transition(); //10
    cout << *cache << endl;

    access = cache->access (0, 0x100, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 0);
    TEST(access.hit          , HIT_CACHE);
    TEST(access.latence      , 1);
    TEST(access.last_nb_level, 0);

    access = cache->access (1, 0x200, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 1);
    TEST(access.hit          , HIT_WRITE_BUFFER);
    TEST(access.latence      , 10);
    TEST(access.last_nb_level, 0);

    access = cache->access (2, 0x100, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 2);
    TEST(access.hit          , HIT_BYPASS);
    TEST(access.latence      , 1);
    TEST(access.last_nb_level, 0);

    access = cache->access (3, 0x200, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 3);
    TEST(access.hit          , HIT_WRITE_BUFFER);
    TEST(access.latence      , 10);
    TEST(access.last_nb_level, 0);

    cache->transition(); //9

    access = cache->access (1, 0x400, 0, CACHED, WRITE);
    cache->update_access(access);

    TEST(access.num_port     , 1);
    TEST(access.hit          , MISS);
    TEST(access.latence      , 20);
    TEST(access.last_nb_level, 2);

    cache->transition(); //8
    cache->transition(); //7
    cache->transition(); //6
    cache->transition(); //5
    cache->transition(); //4
    cache->transition(); //3
    cache->transition(); //2
    cache->transition(); //1
    cache->transition(); //0

    access = cache->access (1, 0x100, 0, CACHED, WRITE);
    cout << access<< endl;
    
    cache->update_access(access);

    TEST(access.num_port     , 1);
    TEST(access.hit          , HIT_CACHE);
    TEST(access.latence      , 6 );
    TEST(access.last_nb_level, 1);

    cout << *cache << endl;
    delete cache;
    delete param;
    delete [] nb_line      ;
    delete [] size_line    ;
    delete [] size_word    ;
    delete [] associativity;
    delete [] hit_latence  ;
    delete [] miss_penality;
  }

  {
    uint32_t * cache_shared_nb_line       = new uint32_t [1];
    uint32_t * cache_shared_size_line     = new uint32_t [1];
    uint32_t * cache_shared_size_word     = new uint32_t [1];
    uint32_t * cache_shared_associativity = new uint32_t [1];
    uint32_t * cache_shared_hit_latence   = new uint32_t [1];
    uint32_t * cache_shared_miss_penality = new uint32_t [1];

    cache_shared_nb_line       [0] = 8 ;
    cache_shared_size_line     [0] = 8 ;
    cache_shared_size_word     [0] = 4 ;
    cache_shared_associativity [0] = 4 ;
    cache_shared_hit_latence   [0] = 2 ;
    cache_shared_miss_penality [0] = 5 ;

    uint32_t  * icache_nb_level      = new uint32_t   [2];
    uint32_t  * icache_nb_port       = new uint32_t   [2];
    uint32_t ** icache_nb_line       = new uint32_t * [2];
    uint32_t ** icache_size_line     = new uint32_t * [2];
    uint32_t ** icache_size_word     = new uint32_t * [2];
    uint32_t ** icache_associativity = new uint32_t * [2];
    uint32_t ** icache_hit_latence   = new uint32_t * [2];
    uint32_t ** icache_miss_penality = new uint32_t * [2];

    icache_nb_level      [0]    = 1;
    icache_nb_port       [0]    = 2;
    icache_nb_line       [0]    = new uint32_t [1];
    icache_size_line     [0]    = new uint32_t [1];
    icache_size_word     [0]    = new uint32_t [1];
    icache_associativity [0]    = new uint32_t [1];
    icache_hit_latence   [0]    = new uint32_t [1];
    icache_miss_penality [0]    = new uint32_t [1];

    icache_nb_line       [0][0] = 8;
    icache_size_line     [0][0] = 8;
    icache_size_word     [0][0] = 4;
    icache_associativity [0][0] = 1;
    icache_hit_latence   [0][0] = 1;
    icache_miss_penality [0][0] = 3;

    icache_nb_level      [1]    = 2;
    icache_nb_port       [1]    = 2;
    icache_nb_line       [1]    = new uint32_t [2];
    icache_size_line     [1]    = new uint32_t [2];
    icache_size_word     [1]    = new uint32_t [2];
    icache_associativity [1]    = new uint32_t [2];
    icache_hit_latence   [1]    = new uint32_t [2];
    icache_miss_penality [1]    = new uint32_t [2];
    
    icache_nb_line       [1][0] = 4;
    icache_size_line     [1][0] = 8;
    icache_size_word     [1][0] = 4;
    icache_associativity [1][0] = 1;
    icache_hit_latence   [1][0] = 1;
    icache_miss_penality [1][0] = 3;

    icache_nb_line       [1][1] = 4;
    icache_size_line     [1][1] = 8;
    icache_size_word     [1][1] = 4;
    icache_associativity [1][1] = 4;
    icache_hit_latence   [1][1] = 1;
    icache_miss_penality [1][1] = 4;

    uint32_t  * dcache_nb_level      = new uint32_t   [2];
    uint32_t  * dcache_nb_port       = new uint32_t   [2];
    uint32_t ** dcache_nb_line       = new uint32_t * [2];
    uint32_t ** dcache_size_line     = new uint32_t * [2];
    uint32_t ** dcache_size_word     = new uint32_t * [2];
    uint32_t ** dcache_associativity = new uint32_t * [2];
    uint32_t ** dcache_hit_latence   = new uint32_t * [2];
    uint32_t ** dcache_miss_penality = new uint32_t * [2];

    dcache_nb_level      [0]    = 1;
    dcache_nb_port       [0]    = 2;
    dcache_nb_line       [0]    = new uint32_t [1];
    dcache_size_line     [0]    = new uint32_t [1];
    dcache_size_word     [0]    = new uint32_t [1];
    dcache_associativity [0]    = new uint32_t [1];
    dcache_hit_latence   [0]    = new uint32_t [1];
    dcache_miss_penality [0]    = new uint32_t [1];

    dcache_nb_line       [0][0] = 8;
    dcache_size_line     [0][0] = 8;
    dcache_size_word     [0][0] = 4;
    dcache_associativity [0][0] = 1;
    dcache_hit_latence   [0][0] = 1;
    dcache_miss_penality [0][0] = 3;

    dcache_nb_level      [1]    = 2;
    dcache_nb_port       [1]    = 2;
    dcache_nb_line       [1]    = new uint32_t [2];
    dcache_size_line     [1]    = new uint32_t [2];
    dcache_size_word     [1]    = new uint32_t [2];
    dcache_associativity [1]    = new uint32_t [2];
    dcache_hit_latence   [1]    = new uint32_t [2];
    dcache_miss_penality [1]    = new uint32_t [2];
    
    dcache_nb_line       [1][0] = 4;
    dcache_size_line     [1][0] = 8;
    dcache_size_word     [1][0] = 4;
    dcache_associativity [1][0] = 1;
    dcache_hit_latence   [1][0] = 1;
    dcache_miss_penality [1][0] = 3;

    dcache_nb_line       [1][1] = 4;
    dcache_size_line     [1][1] = 8;
    dcache_size_word     [1][1] = 4;
    dcache_associativity [1][1] = 4;
    dcache_hit_latence   [1][1] = 1;
    dcache_miss_penality [1][1] = 4;

    Parameters * param = new Parameters 
      (2,//nb_cache_dedicated
       
       icache_nb_level      ,
       icache_nb_port       ,
       icache_nb_line       ,
       icache_size_line     ,
       icache_size_word     ,
       icache_associativity ,
       icache_hit_latence   ,
       icache_miss_penality ,
       
       dcache_nb_level      ,
       dcache_nb_port       ,
       dcache_nb_line       ,
       dcache_size_line     ,
       dcache_size_word     ,
       dcache_associativity ,
       dcache_hit_latence   ,
       dcache_miss_penality ,
     
       1,  // nb_level
       cache_shared_nb_line      ,
       cache_shared_size_line    ,
       cache_shared_size_word    ,
       cache_shared_associativity,
       cache_shared_hit_latence  ,
       cache_shared_miss_penality
       );

    cout << *param << endl;

    cache::Cache * cache = new cache::Cache ("my_cache",param);

    cout << *cache << endl;
    cache->reset();
    cout << *cache << endl;

    TEST(cache->latence (DATA_CACHE, 1, 0, 0x100, 0, CACHED, WRITE), 16);
    cache->transition();
    TEST(cache->latence (DATA_CACHE, 1, 0, 0x100, 0, CACHED, WRITE), 15);
    cache->transition();

    cout << *cache << endl;


    delete    cache;
    delete    param;
    delete [] cache_shared_nb_line      ;
    delete [] cache_shared_size_line    ;
    delete [] cache_shared_size_word    ;
    delete [] cache_shared_associativity;
    delete [] cache_shared_hit_latence  ;
    delete [] cache_shared_miss_penality;
    delete [] icache_nb_level      ;
    delete [] icache_nb_port       ;
    delete [] icache_nb_line       [0];
    delete [] icache_size_line     [0];
    delete [] icache_size_word     [0];
    delete [] icache_associativity [0];
    delete [] icache_hit_latence   [0];
    delete [] icache_miss_penality [0];
    delete [] icache_nb_line       [1];
    delete [] icache_size_line     [1];
    delete [] icache_size_word     [1];
    delete [] icache_associativity [1];
    delete [] icache_hit_latence   [1];
    delete [] icache_miss_penality [1];
    delete [] icache_nb_line       ;
    delete [] icache_size_line     ;
    delete [] icache_size_word     ;
    delete [] icache_associativity ;
    delete [] icache_hit_latence   ;
    delete [] icache_miss_penality ;
    delete [] dcache_nb_level      ;
    delete [] dcache_nb_port       ;
    delete [] dcache_nb_line       [0];
    delete [] dcache_size_line     [0];
    delete [] dcache_size_word     [0];
    delete [] dcache_associativity [0];
    delete [] dcache_hit_latence   [0];
    delete [] dcache_miss_penality [0];
    delete [] dcache_nb_line       [1];
    delete [] dcache_size_line     [1];
    delete [] dcache_size_word     [1];
    delete [] dcache_associativity [1];
    delete [] dcache_hit_latence   [1];
    delete [] dcache_miss_penality [1];
    delete [] dcache_nb_line       ;
    delete [] dcache_size_line     ;
    delete [] dcache_size_word     ;
    delete [] dcache_associativity ;
    delete [] dcache_hit_latence   ;
    delete [] dcache_miss_penality ;
  }

  cout << "<main> End" << endl;
  
  return EXIT_SUCCESS;
}
