/*
 * SecBus - This file is part of SecBus
 * Copyright (C) 2012 - Telecom ParisTech
 * 
 * This file must be used under the terms of the CeCILL.
 * This source file is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at    
 * http://www.cecill.info/licences/Licence_CeCILL_V1.1-US.txt
 *
 */

#include "cache.h"

Cache::Cache(const cache_param_s & parameters)
{
  cache_param = parameters;

  //allocation & init of storage
  storage = new cacheblock_t * [parameters.set_number] ; 

  for (int i=0;i<parameters.set_number;i++)
  {
    storage[i] = new cacheblock_t [parameters.associativity]; 
    for (int j = 0 ; j  < parameters.associativity; j++ )
      storage[i][j].init(parameters.block_size);

  }
  blockIndex = 0;

  printCacheParam(parameters);
}

Cache::~Cache()
{
};

bool Cache::lookup(addr_t address, pCacheblock_t &  victim_block)
{
  int setIndex = getSetIndex(address);

  for (int i = 0 ; i  < cache_param.associativity; i++ )
  {
    if (storage[setIndex][i].lookup(address))
    {	
      // Only for storage n-way associative n>1 else 0
      blockIndex = i ;
      // hit =1
      return true;										
    }
  }
  // hit=0 : provide a victim block
  victim_block = &storage[setIndex][cache_param.associativity-1];

  return false;
}

uint32_t* Cache::readCache(addr_t address)
{
  int setIndex = getSetIndex(address);

  if (cache_param.replacement_policy == LRU)
  {
    //shift block at top of stack
    lru(address);
    return (storage[setIndex][0].rData(address));
  }
  else
    return (storage[setIndex][blockIndex].rData(address));
}

void Cache::writeCache(addr_t address, uint32_t* data, int size)
{
  int setIndex = getSetIndex(address);
  //Write data
  storage[setIndex][blockIndex].wData(data, true);

  //shift block at top of stack if LRU
  if (cache_param.replacement_policy == LRU)
    //shift block at top of stack
    lru(address);
}

void Cache::replace(addr_t address, uint32_t* data, bool dirty)
{
  int setIndex = getSetIndex(address);
  //shift all block of the set
  shift(address);
  //data
  storage[setIndex][0].wData(data, dirty);
  // New address 
  storage[setIndex][0].setBaseAddress(address);
}

int Cache::getSetIndex(addr_t address)
{
  return ((address % (cache_param.set_number * cache_param.block_size * sizeof(int)))/(cache_param.block_size * sizeof(int)));
}

void Cache::lru(addr_t address)
{
  int setIndex = getSetIndex(address);

  cacheblock_t tempblock ;
  tempblock.init(cache_param.block_size);
  copyCacheBlock(tempblock, storage[setIndex][blockIndex]);

  if ( cache_param.associativity > 1)
  {
    for (int i = blockIndex ; i > 0 ; i-- )
    {	
      // We shift all the block
      copyCacheBlock(storage[setIndex][i], storage[setIndex][i-1]);
    }
    //write block go to top of stack
    copyCacheBlock(storage[setIndex][0], tempblock);						
  }		
}


void Cache::shift(addr_t address)
{
  int setIndex = getSetIndex(address);

  if ( cache_param.associativity > 1)
  {
    for ( int i = cache_param.associativity-1 ; i > 0  ; i-- )
      // We shift all the block
      copyCacheBlock(storage[setIndex][i], storage[setIndex][i-1]);
  }	
}

void Cache::copyCacheBlock( cacheblock_t & TargetCacheBlock, cacheblock_t & SourceCacheBlock)
{
  //Copy data
  TargetCacheBlock.wData(SourceCacheBlock.getData(), SourceCacheBlock.isDirty());
  //Copy base address
  TargetCacheBlock.setBaseAddress(SourceCacheBlock.getBaseAddress());
}


//------------------------------- SCCache --------------------------//
/*
#warning MAYBE SOME OPTIMIZATIONS !!!!
bool SCCache::rlookup(addr_t address, pCacheblock_t &  victim_block)
{
  int setIndex = getSetIndex(address);

  for (int i = 0 ; i  < cache_param.associativity; i++ )
  {
    //block data and address
    bdata = storage[setIndex][i].getData();
    baddress = storage[setIndex][i].getBaseAddress();

    if (getSizeBit(bdata) == SIZE_4K)
    {
      if (get4kPageAddress(address) == get4kPageAddress(baddress)) 
      {
	// Only for storage n-way associative n>1 else 0
	blockIndex = i ;
	// hit =1
	return true;
      }
    }
    else
    {
      if (get4mPageAddress(address) == get4mPageAddress(baddress)) 
      {
	// Only for storage n-way associative n>1 else 0
	blockIndex = i ;
	// hit =1
	return true;
      }
    }

  }
  // hit=0 : provide a victim block
  victim_block = &storage[setIndex][cache_param.associativity-1];
  
  return false;
}
*/    
//------------------------ ComplexCache -----------------------//
int ComplexCache::getSetIndex(addr_t address)
  {
    return ((address % (cache_param.set_number * cache_param.block_size * sizeof(int)))
	    /(cache_param.block_size * sizeof(int)));
  }

bool ComplexCache::getFreeblock(addr_t address)
{
  unsigned int setIndex = getSetIndex(address);
  for (int i=0;i<cache_param.associativity;i++)
  {
    //Look for an non valid block
    if (!set[setIndex].block[i].isValid())
    {
      //Index of a free block
      blockIndex = i;
      return true;
    }
  }
  return false;
}

bool ComplexCache::getCleanblock(addr_t address)
{
  unsigned int setIndex = getSetIndex(address);

  for (int i=0;i<cache_param.associativity;i++)
  {
    //Look for a clean block
    if (!set[setIndex].block[i].isDirty())
    {
      //Index of a free block
      blockIndex = i;  
      return true;
    }
  }
  return false;
}

void ComplexCache::fifo(addr_t address, int blockIndex)
{
  unsigned int setIndex = getSetIndex(address);
  cacheblock_t tempblock ;

  tempblock.init(cache_param.block_size);

  copyCacheBlock(tempblock, set[setIndex].block[blockIndex]);

  for (int i = (blockIndex+1) ; i < cache_param.associativity ; i++ )
    // We shift all the block
    copyCacheBlock(set[setIndex].block[i-1], set[setIndex].block[i]);

  //write block go to top of stack
  copyCacheBlock(set[setIndex].block[cache_param.associativity-1], tempblock);
}

void ComplexCache::copyCacheBlock(cacheblock_t & TargetCacheBlock, 
				  cacheblock_t & SourceCacheBlock)
//copy an object of type Buffer
{
  //Copy base address
  TargetCacheBlock.setBaseAddress(SourceCacheBlock.getBaseAddress());
  //Copy data
  TargetCacheBlock.wData(SourceCacheBlock.getData(), SourceCacheBlock.isDirty());
  //Copy valid
  TargetCacheBlock.setValid(SourceCacheBlock.isValid());
}

ComplexCache::ComplexCache(const cache_param_s & parameters)
{
  cache_param = parameters;

  //allocation 
  set = new CacheSet_st_t [parameters.set_number] ; 

  for (int i=0;i<parameters.set_number;i++)
  {
    //Allocation of each set
    set[i].block = new cacheblock_t [parameters.associativity];
    //Reset each set counters
    set[i].other = 0;
    for (int j=0;j<parameters.associativity;j++)
      set[i].block[j].init(parameters.block_size);
  }
  //Reset block index
  blockIndex = 0;

  printCacheParam(parameters);
}

ComplexCache::~ComplexCache()
{
};

bool ComplexCache::lookup(addr_t address, bool & dirty)
{
  unsigned int setIndex = getSetIndex(address);

  for (int i=0;i<cache_param.associativity;i++)
  {
    if (set[setIndex].block[i].lookup(address) && set[setIndex].block[i].isValid())
      //Cache hit and valid block
    {	
      // Only for set n-way associative n>1 else 0
      blockIndex = i ;
      // Return dirty information
      dirty = set[setIndex].block[i].isDirty();
      // hit =1
      return true;										
    }
  }

  // hit=0
  dirty = false;

  return false;
}

uint32_t* ComplexCache::readCache(addr_t address)
{
  unsigned int setIndex = getSetIndex(address);

  if (cache_param.replacement_policy == LRU)
  {
    fifo(address, blockIndex);
    blockIndex = cache_param.associativity - 1;
   
    return (set[setIndex].block[cache_param.associativity-1].rData(address));
  }
  else
  {
    return (set[setIndex].block[blockIndex].rData(address));
  }
}

void ComplexCache::writeCache(addr_t address, uint32_t* data)
{
  unsigned int setIndex = getSetIndex(address);

  //Either set dirty bit or not 
  set[setIndex].block[blockIndex].wData(data, true);

  if (cache_param.replacement_policy == LRU)
    fifo(address, blockIndex);
}

void ComplexCache::storeCache(addr_t address, uint32_t* data, bool dirty)
{
  unsigned int setIndex = getSetIndex(address);

  //look for a clean block
  getCleanblock(address);
  //write data 
  set[setIndex].block[blockIndex].wData(data, dirty);
  //set Valid bit
  set[setIndex].block[blockIndex].setValid(true);
  //set Address
  set[setIndex].block[blockIndex].setBaseAddress(address);
  //fifo
  fifo(address, blockIndex);
}

void ComplexCache::cleanBlock(addr_t address)
{
  unsigned int setIndex = getSetIndex(address);

  if (cache_param.replacement_policy == LRU)
    set[setIndex].block[cache_param.associativity-1].rstDirty();
  else
    set[setIndex].block[blockIndex].rstDirty();		
}

void ComplexCache::freeBlock(addr_t address)
{
  unsigned int setIndex = getSetIndex(address);

  set[setIndex].block[blockIndex].setValid(false);
  set[setIndex].block[blockIndex].rstDirty();		
}

void ComplexCache::UpdSetCounter(addr_t address, bool inc)
{
  unsigned int setIndex = getSetIndex(address);

  //Increment or decrement Set counter
  inc ? set[setIndex].other ++ : set[setIndex].other --;
}


int ComplexCache::readNHVdirty(addr_t address, addr_t & victime_address)
//get number of dirty blocks on selected set
{
  unsigned int setIndex = getSetIndex(address);

  for (int i=0;i<cache_param.associativity;i++)
  {
    //Look for a dirty block
    if (set[setIndex].block[i].isDirty())
      //Address of selected block
    {
      victime_address = set[setIndex].block[i].getBaseAddress();
      break;
    }
  }
  //dirty block counter
  return set[setIndex].other;
}
