/*
 * 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 "mscache_ctrl.h"

MSCache_Ctrl :: MSCache_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 Cache(parameters);

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

void MSCache_Ctrl::pc()
{	
  out_ack.rack			= false;
  out_ack.wack			= false;
  out_cmd.cmdval		= false;
  out_cmd.direct_write          = false;
  out_wdata.enable              = false;

  switch (read_state) 
  {
  case R_IDLE:
    out_ack.hit	        	= hit_s;
    out_ack.rack		= true;
    out_rdata.data[0]		= block_s[0];
    out_rdata.data[1]		= block_s[1];
    break;

  case R_START_READ_BLOCK :
    out_cmd.cmdval              = true;
    out_cmd.id		        = 16;
    out_cmd.cmd		        = READ;
    out_cmd.start_address	= address_in_s;
    out_cmd.plen		= SIZE_64_IN_BYTES;
    break;

  case R_START_WRITE_BACK_BLOCK : 
    //cache enabled : write victim block_s to memory
    out_cmd.cmdval              = true;
    out_cmd.direct_write        = true;
    out_cmd.cmd		        = WRITE;
    out_cmd.start_address	= victim_block->getBaseAddress();
    out_cmd.plen		= cache_param.block_size * 4;
    out_wdata.enable            = true;
    out_wdata.data[0]		= victim_block->getData()[0];
    out_wdata.data[1]		= victim_block->getData()[1];
    break;

  default : 
    break;
  }

  switch (write_state) 
  {

  case W_IDLE :
    out_ack.hit 		= hit_s;
    out_ack.wack 		= true;
    break;

  case W_START_WRITE_BLOCK :
    //cache disabled : write input block to memory
    out_cmd.direct_write        = true;
    out_cmd.cmdval              = true;
    out_cmd.cmd	         	= WRITE;
    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;

  case W_START_WRITE_THROUGH_BLOCK :
    //cache enabled : write input block to memory
    out_cmd.direct_write        = true;
    out_cmd.cmdval		= true;
    out_cmd.cmd 		= WRITE;
    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;

  case W_START_WRITE_BACK_BLOCK :
    //cache enabled : write victim block to memory
    out_cmd.direct_write       	= true;
    out_cmd.cmdval		= true;
    out_cmd.cmd		        = WRITE;
    out_cmd.start_address	= victim_block->getBaseAddress();
    out_cmd.plen		= cache_param.block_size * 4;
    out_wdata.enable            = true;
    out_wdata.data[0]		= victim_block->getData()[0];
    out_wdata.data[1]		= victim_block->getData()[1];
    break;

  default :
    break;
  }


}

void MSCache_Ctrl::ps()
{
  DEBUG(TRACE, FSM, "[%s]\tFSM RD\t%u\n", basename(), (uint32_t)read_state.read());
  DEBUG(TRACE, FSM, "[%s]\tFSM WR\t%u\n", basename(), (uint32_t)write_state.read());

  if (!p_resetn) 
  {
    read_misses = 0;
    write_misses = 0;
    write_back = 0;
    read_accesses = 0;
    write_accesses = 0;
    address_in_s = 0xFFFFFFFF;
    address_256_s = 0xFFFFFFFF;
    memset(block_s, 0, cache_param.block_size * sizeof(int));
    read_state = R_IDLE;
    write_state = W_IDLE;
    return;
  }

  switch (read_state) 
  {
  case R_IDLE ://0
    if(in_cmd.enable && in_cmd.cmd == MTC_READ)
    {
      address_in_s    = in_cmd.address;
      index_s         = index(in_cmd.address, MS_SIZE_IN_BYTES);
      address_256_s   = aligned(in_cmd.address, MS_SIZE_IN_BYTES);

      if (cache_param.enable)
      {
	//cache statistics
	read_accesses++;

	hit_s = cache0->lookup(in_cmd.address, victim_block);
	read_state = R_TEST_HIT_SIGNAL;
      }
      else
      {
	if (register_2_in.address == aligned(in_cmd.address, MS_SIZE_IN_BYTES) && fill_64(register_2_in.be, index(in_cmd.address, MS_SIZE_IN_BYTES)))
	{
	  block_s[0] = register_2_in.data[index(in_cmd.address, MS_SIZE_IN_BYTES)];
	  block_s[1] = register_2_in.data[1 + index(in_cmd.address, MS_SIZE_IN_BYTES)];
	  read_state = R_IDLE;
	}
	else
	  read_state = R_START_READ_BLOCK;
      }
    }
    break;

  case R_TEST_HIT_SIGNAL :
    if (hit_s)
    {	
      memcpy(block_s, cache0->readCache(address_in_s), cache_param.block_size*sizeof(int));
      read_state = R_IDLE;
    }
    else
    {
      //cache statistics
      read_misses++;

      if (cache_param.write_policy == WRITE_THROUGH)
	//No need to check dirty bit before reading memory
      {
	if ((register_2_in.address == address_256_s) && fill_64(register_2_in.be, index_s))
	{
	  block_s[0] = register_2_in.data[index_s];
	  block_s[1] = register_2_in.data[1 + index_s];
	  //Write into cache
	  cache0->replace(address_in_s, block_s, false);
	  read_state = R_IDLE;
	}
	else
	  read_state = R_START_READ_BLOCK;
      }
      else
	//Write back
      {
	if (!victim_block->isDirty())
	  //clean
	{
	  if ((register_2_in.address == address_256_s) && fill_64(register_2_in.be, index_s))
	  {
	    block_s[0] = register_2_in.data[index_s];
	    block_s[1] = register_2_in.data[1 + index_s];
	    //Write into cache
	    cache0->replace(address_in_s, block_s, false);
	    read_state = R_IDLE;
	  }
	  else
	    read_state = R_START_READ_BLOCK;

	}
	else
	{
	  //cache statistics
	  write_back++;
	  read_state = R_START_WRITE_BACK_BLOCK;
	}
      }
    }
    break;

  case R_START_READ_BLOCK:
    if (out_cmd.cmdack.read())
      read_state = R_WAIT_READ_BLOCK;
    break;

  case R_START_WRITE_BACK_BLOCK: 
    if (out_cmd.cmdack.read())
      read_state = R_WAIT_WRITE_BACK_BLOCK;
    break;

  case R_WAIT_WRITE_BACK_BLOCK:
    if (out_cmd.rspval.read())
      //Ram is updated
      //Now read missing block
      read_state = R_START_READ_BLOCK;
    break;

  case R_WAIT_READ_BLOCK :
    if (out_cmd.rspval.read())
    {
      block_s[0] = register_2_in.data[index_s];
      block_s[1] = register_2_in.data[1 + index_s];
      //Copy to cache
      if (cache_param.enable)
	//Write into cache
	cache0->replace(address_in_s, block_s, false);
      read_state = R_IDLE;
    }
    break;

  default : 
    break;
  }

  switch (write_state) 
  {
  case W_IDLE :
    if(in_cmd.enable && in_cmd.cmd == MTC_WRITE)
    {
      address_in_s = in_cmd.address;
      block_s[0] = in_wdata.data[0];
      block_s[1] = in_wdata.data[1];
      index_s = index(in_cmd.address, MS_SIZE_IN_BYTES);
      address_256_s = aligned(in_cmd.address, MS_SIZE_IN_BYTES);

      if (cache_param.enable)
      {
	//cache statistics
	write_accesses++;

	hit_s = cache0->lookup(in_cmd.address, victim_block);

	if (cache_param.write_policy == WRITE_THROUGH)
	  //Write in Ram
	  write_state =  W_START_WRITE_THROUGH_BLOCK;
	else
	  //WRITE-BACK
	  write_state = W_TEST_HIT_SIGNAL;
      }
      else
      {
	write_state = W_START_WRITE_BLOCK;
      }
    }
    break;

  case W_START_WRITE_BLOCK :
    if (out_cmd.cmdack)
      write_state = W_WAIT_WRITE_BLOCK;
    break;

  case W_START_WRITE_THROUGH_BLOCK :
    //Write or replace cache
    if (hit_s)
      cache0->writeCache(address_in_s, block_s, cache_param.block_size);
    else 
    {
      write_misses++;
      cache0->replace(address_in_s, block_s, true);
    }
    write_state = W_WAIT_WRITE_THROUGH_BLOCK;
    break;

  case W_START_WRITE_BACK_BLOCK :
    if (out_cmd.cmdack)
      write_state = W_WAIT_WRITE_BACK_BLOCK;
    break;

  case W_TEST_HIT_SIGNAL :
    if (hit_s)
    {	
      cache0->writeCache(address_in_s, block_s, cache_param.block_size);
      write_state = W_IDLE;
    }
    else
    {
      //cache statistics
      write_misses++;

      if (!victim_block->isDirty())
	//clean
      {
	cache0->replace(address_in_s, block_s, true);
	write_state = W_IDLE;
      }
      else
      {
	index_s = index(victim_block->getBaseAddress(), MS_SIZE_IN_BYTES);
	address_256_s = aligned(victim_block->getBaseAddress(), MS_SIZE_IN_BYTES);
	//cache statistics
	write_back++;
	write_state = W_START_WRITE_BACK_BLOCK;
      }
    }		
    break;

  case W_WAIT_WRITE_BLOCK :
    if (out_cmd.rspval)
      write_state = W_IDLE;
    break;

  case W_WAIT_WRITE_THROUGH_BLOCK :
    if (out_cmd.rspval)
      write_state = W_IDLE;
    break;

  case W_WAIT_WRITE_BACK_BLOCK :
    if (out_cmd.rspval)
    {
      cache0->replace(address_in_s, block_s, true);
      write_state = W_IDLE;
    }
    break;

  default :
    break;
  }

}

void MSCache_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 MISS NUMBER		= %u\n", write_misses+read_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 MISS RATE		= %f\n", (float)(write_misses+read_misses)/(read_accesses + write_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 WRITE BACK NUMBER	= %u\n", write_back);
}

void MSCache_Ctrl::reset_stats()
{
  read_misses = 0;
  write_misses = 0;
  write_back = 0;
  read_accesses = 0;
  write_accesses = 0;
}
