/*
 * 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 "mtcache_ctrl.h"
#include "debug.h"

MTCache_Ctrl :: MTCache_Ctrl(sc_module_name name, const cache_param_s & parameters)
  : sc_module(name)
{
  SC_METHOD(ps);
  sensitive << p_clk.pos();
  SC_METHOD(pc);
  sensitive << p_clk.neg();

  cache_param = parameters;

  block_s = new uint32_t[parameters.block_size];

  if (parameters.enable)
    cache0 = new ComplexCache(parameters);

  DEBUG(NONE, STD, "[SECBUS] Building MTCache_Ctrl (%s)\n",
	basename());
}

void MTCache_Ctrl::pc()
{
  out_ack.rack		   = false;
  out_ack.wack		   = false;
  out_ack.rstack	   = false;
  out_ack.sack		   = false;
  out_ack.victim_address   = 0xFFFFFFFF;
  out_cmd.cmdval	   = false;
  out_cmd.direct_write     = false;
  out_wdata.enable         = false;

  switch (s_state) 
  {
  case S_IDLE:
    out_ack.sack           = true;
    out_ack.full	   = full_s;
    out_ack.hit	           = hit_s;
    out_ack.dirty	   = dirty_s;
    out_ack.victim_address = victim_address_s;
    out_rdata.data[0]	   = block_s[0];
    out_rdata.data[1]	   = block_s[1];
    break;

  case S_START_READ_BLOCK:
    out_cmd.cmdval         = true;
    out_cmd.id		   = 16;
    out_cmd.cmd		   = READ;
    out_cmd.start_address  = address_256_s;
    out_cmd.plen	   = HS_SIZE_IN_BYTES;
    break;

  case S_START_WRITE_BLOCK: 
    out_cmd.cmd		   = WRITE;
    out_cmd.cmdval	   = true;
    out_cmd.direct_write   = true;
    out_cmd.start_address  = address_in_s;
    out_cmd.plen	   = cache_param.block_size * 4;
    out_wdata.enable       = true;
    out_wdata.data[0]	   = block_s[0];
    out_wdata.data[1]	   = block_s[1];
    break;

  default : 
    break;
  }

  //End Synchronize

  switch (wr_state) 
  {

  case WR_IDLE :
    out_ack.wack	   = true;
    out_ack.full	   = full_s;
    out_ack.hit		   = hit_s;
    out_ack.dirty	   = dirty_s;
    out_ack.victim_address = victim_address_s;
    break;

  default :
    break;
  }
  //End write

  switch (rst_state) 
  {
  case RST_IDLE :
    out_ack.rstack	   = true;
    out_ack.full	   = full_s;
    out_ack.hit		   = hit_s;
    out_ack.dirty	   = dirty_s;
    out_ack.victim_address = victim_address_s;
    out_rdata.data[0]	   = block_s[0];
    out_rdata.data[1]	   = block_s[1];
    break;

  case RST_START_RESTORE :
    out_cmd.id		   = 16;
    out_cmd.cmd		   = READ;
    out_cmd.cmdval	   = true;
    out_cmd.start_address  = address_256_s;
    out_cmd.plen	   = HS_SIZE_IN_BYTES;
    break;

  case RST_START_READ_BLOCK :
    out_cmd.id		   = 16;
    out_cmd.cmd		   = READ;
    out_cmd.cmdval	   = true;
    out_cmd.start_address  = address_256_s;
    out_cmd.plen	   = HS_SIZE_IN_BYTES;
    break;


  default :
    break;

  }

  //End Restore 

  switch (rd_state) 
  {
  case RD_IDLE :
    out_ack.rack	   = true;
    out_ack.full	   = full_s;
    out_ack.hit		   = hit_s;
    out_ack.dirty	   = dirty_s;
    out_ack.victim_address = victim_address_s;
    out_rdata.data[0]	   = block_s[0];
    out_rdata.data[1]	   = block_s[1];
    break;

  case RD_START_READ_BLOCK :
    out_cmd.id		   = 16;
    out_cmd.cmd		   = READ;
    out_cmd.cmdval	   = true;
    out_cmd.start_address  = address_256_s;
    out_cmd.plen	   = HS_SIZE_IN_BYTES;
    break;

  default : 
    break;
  }
}

void MTCache_Ctrl::ps()
{
  DEBUG(TRACE, FSM, "[%s]\tFSM RD\t%u\n", basename(), (uint32_t)rd_state.read());
  DEBUG(TRACE, FSM, "[%s]\tFSM I\t%u\n", basename(), (uint32_t)i_state.read());
  DEBUG(TRACE, FSM, "[%s]\tFSM S\t%u\n", basename(), (uint32_t)s_state.read());
  DEBUG(TRACE, FSM, "[%s]\tFSM RST\t%u\n", basename(), (uint32_t)rst_state.read());
  DEBUG(TRACE, FSM, "[%s]\tFSM W\t%u\n", basename(), (uint32_t)wr_state.read());

  if(!p_resetn)
  {
    invalidate_accesses = 0;
    read_accesses = 0;
    write_accesses = 0;
    restore_accesses = 0;
    synchronize_accesses = 0;
    read_misses = 0;
    write_misses = 0;
    restore_misses = 0;
    synchronize_misses = 0;
    address_in_s = 0xFFFFFFFF;
    address_256_s = 0xFFFFFFFF;
    memset(block_s, 0, cache_param.block_size * sizeof(int));
    s_state = S_IDLE;
    i_state = I_IDLE;
    rd_state = RD_IDLE;
    rst_state = rst_state;
    wr_state = WR_IDLE;
    return;
  }

  switch (i_state) 
  {
  case I_IDLE :
    if(in_cmd.enable && in_cmd.cmd == MTC_INVALIDATE)
    {
      invalidate_accesses++;

      address_in_s = in_cmd.address;

      hit_s = cache0->lookup(in_cmd.address, dirty_s);
      i_state = I_TEST_HIT_SIGNAL;
    }
    break;

  case I_TEST_HIT_SIGNAL :
    if (hit_s)
    {	
      cache0->freeBlock(address_in_s);
      if (dirty_s)
	//decrease dirty counter
	cache0->UpdSetCounter(address_in_s, false);
    }
    i_state = I_IDLE;
    break;

  default : 
    break;
  }
  //End invalidate

  switch (s_state) 
  {
  case S_IDLE :
    if(in_cmd.enable && in_cmd.cmd == MTC_SYNCHRONIZE)
    {
      synchronize_accesses++;

      address_in_s = in_cmd.address;
      address_256_s = aligned(in_cmd.address, HS_SIZE_IN_BYTES);
      index_s = index(in_cmd.address, HS_SIZE_IN_BYTES);
      //Lookup in Cache
      hit_s = cache0->lookup(in_cmd.address, dirty_s);
      s_state = S_TEST_HIT_SIGNAL;
    }
    break;

  case S_TEST_HIT_SIGNAL :
    if (hit_s)
    {	
      if (dirty_s)
	//Write RAM
      {
	//provide data from cache 
	memcpy(block_s, cache0->readCache(address_in_s), 
	       cache_param.block_size*sizeof(int));
	//Send selected block to memory
	s_state = S_START_WRITE_BLOCK;
      }
      else
	//Read Cache
      {
	DEBUG(NONE, STD, "WARNING ! SYNCHRONIZATION OF A NON DIRTY HASH VALUE\n");

	memcpy(block_s, cache0->readCache(address_in_s), 
	       cache_param.block_size*sizeof(int));
	s_state = S_IDLE;
      }
    }
    else
      //Block is not in cache : Read RAM
    {
      synchronize_misses++;

      DEBUG(NONE, STD, "WARNING ! SYNCHRONIZATION OF A HASH VALUE NOT IN CACHE\n");

      //Read RAM
/*      if (register_2_in.address == address_256_s)
      {
	block_s[0] = register_2_in.data[index_s];
	block_s[1] = register_2_in.data[1 + index_s];
	//Store into cache
	cache0->storeCache(address_in_s, block_s, false);
	dirty_s = false;
	s_state = S_IDLE;	
      }
      else*/
	s_state = S_START_READ_BLOCK;
    }	
    break;

  case S_START_READ_BLOCK:
    if (out_cmd.cmdack.read())
      s_state = S_WAIT_READ_BLOCK;
    break;

  case S_START_WRITE_BLOCK:
    if (out_cmd.cmdack.read())
    {
      s_state = S_WAIT_WRITE_BLOCK;
    }
    break;

  case S_WAIT_WRITE_BLOCK:
    if (out_cmd.rspval)
    {
      //RAM is synchronized : Clean dirty bit
      cache0->cleanBlock(address_in_s);
      //decrease dirty counter
      cache0->UpdSetCounter(address_in_s, false);
      s_state = S_IDLE;
    }
    break;

  case S_WAIT_READ_BLOCK :
    if (out_cmd.rspval)
    {
      block_s[0] = register_2_in.data[index_s];
      block_s[1] = register_2_in.data[1 + index_s];
      //Store into cache
      cache0->storeCache(address_in_s, block_s, false);
      dirty_s = false;
      s_state = S_IDLE;	
    }
    break;

  default : 
    break;
  }

  //End Synchronize

  switch (wr_state) 
  {
  case WR_IDLE :
    if(in_cmd.enable && in_cmd.cmd == MTC_WRITE)
    {
      write_accesses++;

      address_in_s = in_cmd.address;

      block_s[0] = in_wdata.data[0];
      block_s[1] = in_wdata.data[1];
      address_256_s = aligned(in_cmd.address, HS_SIZE_IN_BYTES);
      index_s = index(in_cmd.address, HS_SIZE_IN_BYTES);
      //Lookup in Cache
      hit_s = cache0->lookup(in_cmd.address, dirty_s);
      wr_state = WR_TEST_HIT_SIGNAL;
    }
    break;


  case WR_TEST_HIT_SIGNAL :
    if (hit_s)
      //Write HV in cache
    {	
      cache0->writeCache(address_in_s, block_s);
      if (dirty_s)
	//Dirty already set
      {
	wr_state = WR_IDLE;
	full_s = false;
      }
      else
      {
	cache0->UpdSetCounter(address_in_s, true);
	wr_state = WR_TEST_FULL_SIGNAL;
      }
    }
    else
      //Store HV in cache
    {
      write_misses++;

      cache0->storeCache(address_in_s, block_s, true);
      //Increase dirty counter
      cache0->UpdSetCounter(address_in_s, true);
      wr_state = WR_WRITE_CACHE;
    }
    break;

  case WR_WRITE_CACHE :
    wr_state = WR_TEST_FULL_SIGNAL;
    break;


  case WR_TEST_FULL_SIGNAL :
    wr_state = WR_IDLE;
    //consult dirty counter value of current cache set
    if (cache0->readNHVdirty(address_in_s, victim_address_s) >= cache_param.threshold)
      //inform HTM that this cache set is currently satured with dirty HV
      full_s = true;
    else
      //informs HTM that cache this set is not currently satured with dirty HV
      full_s = false;
    break;

  default :
    break;
  }
  //End write

  switch (rst_state) 
  {
  case RST_IDLE :
    if(in_cmd.enable && in_cmd.cmd == MTC_RESTORE)
    {
      restore_accesses++;
      address_in_s = in_cmd.address;
      address_256_s = aligned(in_cmd.address, HS_SIZE_IN_BYTES);
      index_s = index(in_cmd.address, HS_SIZE_IN_BYTES);
      //Lookup in Cache
      hit_s = cache0->lookup(in_cmd.address, dirty_s);
      rst_state = RST_TEST_HIT_SIGNAL;
    }
    break;

  case RST_TEST_HIT_SIGNAL:
    if (hit_s)
    {	
      if (dirty_s)
	//Read RAM 
      {
/*	if (register_2_in.address == address_256_s)
	{
	  block_s[0] = register_2_in.data[index_s];
	  block_s[1] = register_2_in.data[1 + index_s];
	  //Do not store into cache
	  rst_state = RST_IDLE;	
	}
	else*/
	  rst_state = RST_START_RESTORE;

      }	
      else
	//Read Cache
      {
	memcpy(block_s, cache0->readCache(address_in_s), 
	       cache_param.block_size*sizeof(int));

	rst_state = RST_IDLE;
      }
    }
    else
    {
      restore_misses++;
/*
      if (register_2_in.address == address_256_s)
      {
	block_s[0] = register_2_in.data[index_s];
	block_s[1] = register_2_in.data[1 + index_s];
	//Store into cache : Select cache block to write
	cache0->storeCache(address_in_s, block_s, false);
	dirty_s		= false;
	rst_state	= RST_IDLE;

      }
      else */
	//Read external memory
	rst_state = RST_START_READ_BLOCK;
    }
    break;


  case RST_START_RESTORE:
    if (out_cmd.cmdack)
      rst_state = RST_WAIT_RESTORE;
    break;

  case RST_WAIT_RESTORE:
    if (out_cmd.rspval)
      //Read block is in temp block
    {
      block_s[0] = register_2_in.data[index_s];
      block_s[1] = register_2_in.data[1 + index_s];
      //Do not store into cache
      rst_state = RST_IDLE;
    }
    break;

  case RST_START_READ_BLOCK:
    if (out_cmd.cmdack.read())
      rst_state = RST_WAIT_READ_BLOCK;
    break;

  case RST_WAIT_READ_BLOCK:
    if (out_cmd.rspval)
    {
      block_s[0] = register_2_in.data[index_s];
      block_s[1] = register_2_in.data[1 + index_s];
      //Store into cache : Select cache block to write
      cache0->storeCache(address_in_s, block_s, false);
      dirty_s = false;
      rst_state = RST_IDLE;
    }
    break;

  default ://5
    break;

  }

  //End Restore 

  switch (rd_state) 
  {
  case RD_IDLE:
    if(in_cmd.enable && in_cmd.cmd == MTC_READ)
    {
      read_accesses++;

      address_in_s = in_cmd.address;
      address_256_s = aligned(in_cmd.address, HS_SIZE_IN_BYTES);
      index_s = index(in_cmd.address, HS_SIZE_IN_BYTES);
      //Lookup in Cache
      hit_s = cache0->lookup(in_cmd.address, dirty_s);
      rd_state = RD_TEST_HIT_SIGNAL;
    }
    break;

  case RD_TEST_HIT_SIGNAL:
    if (hit_s)
    {	
      memcpy(block_s, cache0->readCache(address_in_s), 
	     cache_param.block_size*sizeof(int));
      rd_state = RD_IDLE;
    }
    else
    {
      read_misses++;
/*
      if (register_2_in.address == address_256_s)
      {
	block_s[0] = register_2_in.data[index_s];
	block_s[1] = register_2_in.data[1 + index_s];
	//Store into Cache
	cache0->storeCache(address_in_s, block_s, false);
	dirty_s = false;
	rd_state = RD_IDLE;	
      }
      else */
	//Read RAM
	rd_state = RD_START_READ_BLOCK;
    }
    break;

  case RD_START_READ_BLOCK:
    if (out_cmd.cmdack.read())
      rd_state = RD_WAIT_READ_BLOCK;
    break;

  case RD_WAIT_READ_BLOCK:
    if (out_cmd.rspval)
    {
      block_s[0] = register_2_in.data[index_s];
      block_s[1] = register_2_in.data[1 + index_s];
      //Store into Cache
      cache0->storeCache(address_in_s, block_s, false);
      dirty_s = false;
      rd_state = RD_IDLE;	
    }
    break;;

  default: 
    break;
  }
}

void MTCache_Ctrl::print_stats()
{
  //Store in file
  DEBUG(STAT, STD, "%s\n", cache_param.name);
  DEBUG(STAT, STD, "- CAHE ENABLED			= %u\n", 
	cache_param.enable);
  DEBUG(STAT, STD, "- ASSOCIATIVITY			= %u\n", 
	cache_param.associativity);
  DEBUG(STAT, STD, "- SET NUMBER			= %u\n",
	cache_param.set_number);
  DEBUG(STAT, STD, "- BLOCK SIZE			= %u\n",
	cache_param.block_size);
  DEBUG(STAT, STD, "- WRITE POLICY			= ");

  if (cache_param.write_policy == WRITE_BACK)	
    DEBUG(STAT, STD, "WRITE-BACK\n");
  else 	
    DEBUG(STAT, STD, "WRITE-THROUGH\n");

  DEBUG(STAT, STD, "- REPLACEMENT POLICY		= ");

  if (cache_param.replacement_policy == LRU)
    DEBUG(STAT, STD, "LRU\n");
  else
    DEBUG(STAT, STD, "FIFO\n");

  DEBUG(STAT, STD, "- CACHE ACCESS NUMBER		= %u\n", 
	(read_accesses + write_accesses));
  DEBUG(STAT, STD, "- CACHE WRITE ACCESS NUMBER	        = %u\n", 
	write_accesses);
  DEBUG(STAT, STD, "- CACHE READ ACCESS NUMBER	        = %u\n", 
	read_accesses);
  DEBUG(STAT, STD, "- CACHE RESTORE ACCESS NUMBER	= %u\n", 
	restore_accesses);
  DEBUG(STAT, STD, "- CACHE SYNCHRONIZE ACCESS NUMBER	= %u\n", 
	synchronize_accesses);
  DEBUG(STAT, STD, "- CACHE MISS NUMBER                 = %u\n", 
	(read_misses+write_misses+restore_misses+synchronize_misses));
  DEBUG(STAT, STD, "- CACHE WRITE MISS NUMBER		= %u\n", 
	write_misses);
  DEBUG(STAT, STD, "- CACHE READ MISS NUMBER		= %u\n", 
	read_misses);
  DEBUG(STAT, STD, "- CACHE RESTORE MISS NUMBER         = %u\n", 
	restore_misses);
  DEBUG(STAT, STD, "- CACHE SYNCHRONIZE MISS NUMBER     = %u\n", 
	synchronize_misses);
  DEBUG(STAT, STD, "- CACHE MISS RATE		        = %f\n", 
	(float)(read_misses+write_misses+restore_misses+synchronize_misses)/
	(read_accesses+write_accesses+restore_accesses+synchronize_accesses));
  DEBUG(STAT, STD, "- CACHE READ MISS RATE		= %f\n", 
	(float)read_misses/read_accesses);
  DEBUG(STAT, STD, "- CACHE WRITE MISS RATE		= %f\n", 
	(float)write_misses/write_accesses);
  DEBUG(STAT, STD, "- CACHE RESTORE MISS RATE	    	= %f\n", 
	(float)restore_misses/restore_accesses);
  DEBUG(STAT, STD, "- CACHE SYNCHORONIZE MISS RATE	= %f\n", 
	(float)synchronize_misses/synchronize_accesses);
}

void MTCache_Ctrl::reset_stats()
{
  invalidate_accesses = 0;
  read_accesses = 0;
  write_accesses = 0;
  restore_accesses = 0;
  synchronize_accesses = 0;
  read_misses = 0;
  write_misses = 0;
  restore_misses = 0;
  synchronize_misses = 0;
}
