/*
 * 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 "vci_input_ctrl.h"
#include "secbus_lib.h"
#include "stats.h"

#define vci_input_ctrl(x) template<typename vci_param> x VciInputCtrl<vci_param>

vci_input_ctrl(/**/)::VciInputCtrl(sc_module_name name):
		     sc_module(name),
		     p_clk("clk"),
		     p_resetn("resetn"),
		     p_vci_initiator("vci_initiator")
{
  SC_METHOD(transition);
  dont_initialize();
  sensitive << p_clk.pos();

  SC_METHOD(genMoore);
  dont_initialize();
  sensitive << p_clk.neg();

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

vci_input_ctrl(void)::transition()
{
  m_cycle++;

  if (!p_resetn.read())
  {
    fsm_state_s = FSM_IDLE;
    m_cycle = 0;
    flush = false;
    pending_packet_s = false;
    cmdack_s = false;
    count_s = 0;

    return;
  }

  DEBUG(TRACE, FSM, "[%s]\t\tFSM\t%u\n", 
	basename(), (uint32_t)fsm_state_s.read());

  pspe_t curr_pspe;
  for (size_t i=0;i<PSPE_SIZE_IN_WORDS;i++)
    curr_pspe[i] = ctx_register.m_pspe[i].read();

  cmdack_s = false;
  
  switch(fsm_state_s.read())
  {
  case FSM_IDLE:
    if (!(mt_ack.uack && mt_ack.cack && mt_ack.alap_ack && mt_ack.iack && sc_busy && ms_ack.uack && ms_ack.cack))
    {
      break;
    }

    if (io_handler.cmd.read() != io_cmd_none)
    {
      bool is_mb;
      is_io_s = true;
      address_s = io_handler.addr.read();
      start_address_s = io_handler.addr.read();
      plen_s = 32;
      be_s = 0xFFFFFFFF;
      cmd_s = io_handler.cmd.read();

      if (inclusion(io_handler.addr.read(), 
		    getPSPEBaseAddress(p_mb_address.read(),
				       p_size.read()),
		    getMMacTBaseAddress(p_mb_address.read(),
					p_size.read())))
      {
	io_mb_s = is_mb = true;
      }
      else
      {
	io_mb_s = is_mb = false;
      }

      switch (io_handler.cmd.read())
      {
      case io_cmd_store:
	ready_to_read_s = true;
	fsm_state_s = FSM_FLUSH_REGISTER_0;
	break;
/*	if (is_mb)
	{
	  fsm_state_s = FSM_UPDATE_CTX;
	}
	else
	{
	  if (ctx_register.page_address.read() ==
	      getPSPEAddressFromPageSize(io_handler.addr.read(),
					 pspe_getSize(&curr_pspe),
					 p_mb_address.read(),
					 p_b_address.read()))
	  {
	    fsm_state_s = FSM_IO_WRITE;
	  }
	  else
	  {
	    fsm_state_s = FSM_READ_CTX;
	  }
	}
	break;*/

      case io_cmd_load:
#warning TODO : handle READ ctx with caches
	if (is_mb)
	{
	  fsm_state_s = FSM_IO_READ;
	}
	else
	{
	  if (ctx_register.page_address.read() ==
	      getPSPEAddressFromPageSize(io_handler.addr.read(),
					 pspe_getSize(&curr_pspe),
					 p_mb_address.read(),
					 p_b_address.read()))
	  {
	    fsm_state_s = FSM_IO_READ;
	  }
	  else
	  {
	    fsm_state_s = FSM_READ_CTX;
	  }
	}
	break;

      case io_cmd_init:
	if (is_mb)
	{
	  fsm_state_s = FSM_START_INIT;
	}
	else
	{
	  fsm_state_s = FSM_READ_CTX;
	}
	break;
	
      case io_cmd_continue:
	if (is_mb)
	{
	  fsm_state_s = FSM_START_CONTINUE;
	}
	else
	{
	  fsm_state_s = FSM_READ_CTX;
	}
	break;
	
      default:
	DEBUG(NONE, STD, "IO CMD %u\n", (uint32_t)io_handler.cmd.read());
	break;
      }
      break;
    }
// Michele: is this exclusive from the previous case?
    if (p_vci_initiator.cmdval.read())
    {
      is_io_s = false;
      address_s = p_vci_initiator.address.read();
      start_address_s = p_vci_initiator.address.read();
      plen_s = p_vci_initiator.plen.read();
      be_s = p_vci_initiator.be.read();
      srcid_s = p_vci_initiator.srcid.read();
      trdid_s = p_vci_initiator.trdid.read();
      pktid_s = p_vci_initiator.pktid.read();
      eop_s = p_vci_initiator.eop.read();
      cmdack_s = true;

//      S_START("read");

      flush = true;

      // data in register 0 doesn't match with the request, flush the register
      if (r_register_3.address.read() != aligned(p_vci_initiator.address.read(),
						 SIZE_256_IN_WORDS))
      {
	flush = true;
      }

      if (p_vci_initiator.cmd.read() == vci_param::CMD_READ)
      {
	cells_count_s = ((p_vci_initiator.plen.read() + 
			_ctz(p_vci_initiator.be.read(), vci_param::B) + 
			vci_param::B - 1)/vci_param::B) - 1;

	if (flush)
	{
	  fsm_state_s = FSM_FLUSH_REGISTER_0;
	}
	else
	{
	  if (ctx_register.page_address.read() !=
	      getPSPEAddressFromPageSize(p_vci_initiator.address.read(),
					 pspe_getSize(&curr_pspe),
					 p_mb_address.read(),
					 p_b_address.read()))
	  {
	    //fsm_state_s = FSM_FLUSH_REGISTER_0;
	    
	    fsm_state_s = FSM_CMD_READ;
	  }
	  else
	  {
	    fsm_state_s = FSM_READ_CTX;
	  }
	}
      }
      else if (p_vci_initiator.cmd.read() == vci_param::CMD_WRITE)
      {
	uint32_t cells = ((p_vci_initiator.plen.read() + 
				   _ctz(p_vci_initiator.be.read(), vci_param::B) + 
				   vci_param::B - 1)/vci_param::B); 

	cells_count_s = cells;
	// cells to come * 4 = nb bytes to write (without considering the mask)
	plen_s = cells * 4;
      	data_s = p_vci_initiator.wdata.read();

	if (flush)
	{
	  
	  fsm_state_s = FSM_FLUSH_REGISTER_0;
	}
	else
	{
	  if (ctx_register.page_address.read() !=
	      getPSPEAddressFromPageSize(p_vci_initiator.address.read(),
					 pspe_getSize(&curr_pspe),
					 p_mb_address.read(),
					 p_b_address.read()))
	  {
//	    fsm_state_s = FSM_FLUSH_REGISTER_0;
	    cmdack_s = !p_vci_initiator.eop.read();
	    fsm_state_s = FSM_CMD_WRITE;
	  }
	  else
	  {
	    fsm_state_s = FSM_READ_CTX;
	  }
	}
      }
      else
      {
	DEBUG(ERROR, STD, "[SECBUS] unknown command from VCI\n");
      }
    }
    break;

  case FSM_UPDATE_CTX:
    fsm_state_s = FSM_WUPDATE_CTX;
    break;

  case FSM_WUPDATE_CTX:
    if (ctx_cmd.ack.read())
    {
      fsm_state_s = FSM_IRQ;
    }
    break;

  case FSM_IRQ:
    fsm_state_s = FSM_WIRQ;
    break;
    
  case FSM_WIRQ:
//    DEBUG(NONE, STD, "wait ack %u %u\n", irq.ack.read(), p_vci_initiator.cmdval.read());
//    if (irq.ack.read() || p_vci_initiator.cmdval.read())
    {
      fsm_state_s = FSM_IDLE;
    }
    break;

  case FSM_IO_READ:
    if (sc_cmd.cmdack.read())
    {
      fsm_state_s = FSM_WIO_READ;
    }
    break;

  case FSM_WIO_READ:
    if (sc_cmd.rspval.read())
    {
      fsm_state_s = FSM_IRQ;
    }
    break;

  case FSM_IO_WRITE:
    if (sc_cmd.cmdack.read())
    {
      fsm_state_s = FSM_WIO_WRITE;
    }
    break;

  case FSM_WIO_WRITE:
    if (sc_cmd.rspval.read())
    {
      fsm_state_s = FSM_IRQ;
    }
    break;

  case FSM_READ_CTX: 
    if (sc_busy && mt_ack.uack)
    {
      fsm_state_s = FSM_WAIT_CTX;
    }
    break;

  case FSM_WAIT_CTX:
    if (ctx_cmd.ack.read())
    {
      if (is_io_s)
      {
	if (cmd_s.read() == io_cmd_load)
	{
	  fsm_state_s = FSM_IO_READ;
	}
	else if (cmd_s.read() == io_cmd_init)
	{
	  if (mt_ack.cack)
	    fsm_state_s = FSM_START_INIT;
	}
	else if (cmd_s.read() == io_cmd_continue)
	{
	  if (mt_ack.cack)
	    fsm_state_s = FSM_START_CONTINUE;
	}
	else
	{
	  fsm_state_s = FSM_IO_WRITE;
	}
      }
      else
      {
	if (p_vci_initiator.cmd.read() == vci_param::CMD_READ)
	{
	  fsm_state_s = FSM_CMD_READ;
	}
	else
	{
	  fsm_state_s = FSM_CMD_WRITE;
	}
      }
    }
    break;

  case FSM_START_INIT:
    fsm_state_s = FSM_WAIT_INIT;
    break;

  case FSM_START_CONTINUE:
    fsm_state_s = FSM_WAIT_INIT;
    break;

  case FSM_WAIT_INIT:
    if (mt_ack.iack)
    {
      fsm_state_s = FSM_IRQ;
    }
    break;

  case FSM_FLUSH_REGISTER_0:
    if (ready_to_read_s.read())
    {
      ready_to_read_s = false;
      fsm_state_s = FSM_READ_IO;
      break;
    }

    if (ctx_register.page_address.read() !=
	getPSPEAddressFromPageSize(address_s.read(),
				   pspe_getSize(&curr_pspe),
				   p_mb_address.read(),
				   p_b_address.read()))
    {
      fsm_state_s = FSM_READ_CTX;
      break;
    }
    
    if (p_vci_initiator.cmd.read() == vci_param::CMD_READ)
    {
      fsm_state_s = FSM_CMD_READ;
    }
    else
    {
      cmdack_s = !p_vci_initiator.eop.read();
      fsm_state_s = FSM_CMD_WRITE;
    }
    break;

  case FSM_READ_IO:
    if (io_mb_s)
    {
      fsm_state_s = FSM_UPDATE_CTX;
    }
    else
    {
      if (ctx_register.page_address.read() ==
	  getPSPEAddressFromPageSize(io_handler.addr.read(),
				     pspe_getSize(&curr_pspe),
				     p_mb_address.read(),
				     p_b_address.read()))
      {
	fsm_state_s = FSM_IO_WRITE;
      }
      else
      {
	fsm_state_s = FSM_READ_CTX;
      }
    }
    break;
    
  case FSM_CMD_READ:
    if (sc_cmd.cmdack.read())
    {
      idx_s = 0;
      fsm_state_s = FSM_WREAD;
    }
    break;

  case FSM_CMD_WRITE:
    cmdack_s = !p_vci_initiator.eop.read();
    // last data
    if (cells_count_s.read() == 1)
    {
      fsm_state_s = FSM_CMD_START_WRITE;
    }
    // data is in the next block, we have to write the previous block
    else if (aligned(p_vci_initiator.address.read(), SIZE_256_IN_BYTES) !=
	     aligned(start_address_s.read(), SIZE_256_IN_BYTES))
    {
      pending_packet_s = true;
      fsm_state_s = FSM_CMD_START_WRITE;
    }
    else
    {
      address_s = p_vci_initiator.address.read();
      be_s = p_vci_initiator.be.read();
      srcid_s = p_vci_initiator.srcid.read();
      trdid_s = p_vci_initiator.trdid.read();
      pktid_s = p_vci_initiator.pktid.read();
      eop_s = p_vci_initiator.eop.read();
      data_s = p_vci_initiator.wdata.read();
/*
      DEBUG(TRACE, IN, "W0x%08x %08x %x (cycle %u)\n", 
	    (addr_t)(address_s.read()), 
	    (uint32_t)data_s.read(),
	    (uint32_t)be_s.read(),
	    m_cycle);
*/    
      cells_count_s = cells_count_s.read() - 1;
    }
    break;

  case FSM_CMD_START_WRITE:
    if (sc_cmd.cmdack.read())
    {
      fsm_state_s = FSM_WWRITE;
    }
    break;

  case FSM_WREAD:
    if (sc_cmd.rspval.read())
    {
      idx_s = index(address_s.read(), SIZE_256_IN_BYTES);
      address_s = address_s.read() + vci_param::B;

      fsm_state_s = FSM_RREAD;
    }
    break;

  case FSM_WWRITE:
    if (sc_cmd.rspval.read())
    {
      if (pending_packet_s.read())
      {
	start_address_s = address_s.read();
	fsm_state_s = FSM_FLUSH_REGISTER_0;
	pending_packet_s = false;
      }
      else
      {
/*	S_STOP("read");
	S_PRINT("read", "writing");*/
	fsm_state_s = FSM_IDLE;
      }
    }
    break;

  case FSM_RREAD:
    if (!p_vci_initiator.rspack.read())
      break;

    DEBUG(TRACE, IN, "R0x%08x %08x %x (cycle %u)\n", 
	  (addr_t)(address_s.read() - vci_param::B), 
	  (uint32_t)r_register_0.data[idx_s.read()].read(),
	  (uint32_t)0xF,
	  m_cycle);

    idx_s = index(address_s.read(), SIZE_256_IN_BYTES);
    cells_count_s = cells_count_s.read() - 1;
    address_s = address_s.read() + vci_param::B;

    if (cells_count_s.read() == 0)
    {
      fsm_state_s = FSM_IDLE;
/*      S_STOP("read");
	S_PRINT("read", "reading");*/
    }
    break;

  case FSM_RWRITE:
    break;

  default:
    break;
  }
}

vci_input_ctrl(void)::genMoore()
{
  p_vci_initiator.cmdack = cmdack_s;
  p_vci_initiator.rspval = false;
  mem_rq.cmdval = false;
  ctx_cmd.enable = false;
  ctx_cmd.mt = false;
  sc_cmd.cmdval = false;
  mem_rq.direct_write = false;
  mt_cmd.enable = false;

  busy = true;

  w_register_0.enable = false;
  w_register_0.flush  = false;

  w_register_3.enable = false;
  w_register_3.flush = false;

  io_ack = false;

  if (irq.ack.read())
    irq.irq = false;

  switch(fsm_state_s.read())
  {
  case FSM_IDLE:
    if (mt_ack.uack && mt_ack.cack && mt_ack.alap_ack && mt_ack.iack && sc_busy && ms_ack.uack && ms_ack.cack)
    {
      busy = false;

      io_ack = true;
//      p_vci_initiator.cmdack = true;
    }
    break;

  case FSM_UPDATE_CTX:
    ctx_cmd.enable = true;
    ctx_cmd.cmd = CTX_CMD_UPDATE;
    ctx_cmd.address = io_handler.addr.read();
    break;

  case FSM_IRQ:
    irq.irq = true;
    irq.address = io_handler.addr.read();
    irq.type = io_status_e_none;
    irq.op = io_status_e_store;
    for (size_t i=0;i<SIZE_256_IN_WORDS;i++)
      io_handler_write.data[i] = r_register_0.data[i];
    break;

  case FSM_READ_CTX:
    if (sc_busy && mt_ack.uack)
    {
      ctx_cmd.enable = true;
      ctx_cmd.cmd = CTX_CMD_READ;
      ctx_cmd.address = address_s.read();
    }
    break;

  case FSM_IO_READ:
    sc_cmd.cmdval = true;
    sc_cmd.cmd = READ;
    sc_cmd.be = 0xFFFFFFFF;
    sc_cmd.mb = io_mb_s.read();
    sc_cmd.address = aligned(address_s.read(), SIZE_256_IN_BYTES);    
    break;

  case FSM_IO_WRITE:
    sc_cmd.cmdval = true;
    sc_cmd.cmd = WRITE;
    sc_cmd.be = 0xFFFFFFFF;
    sc_cmd.mb = false;
    sc_cmd.address = aligned(address_s.read(), SIZE_256_IN_BYTES);
    break;

  case FSM_START_INIT:
    mt_cmd.enable = true;
    mt_cmd.address = address_s.read();
    mt_cmd.cmd = INITIALIZE_PAGE;
    break;

  case FSM_START_CONTINUE:
    mt_cmd.enable = true;
    mt_cmd.address = address_s.read();
    mt_cmd.cmd = INITIALIZE;
    break;

  case FSM_FLUSH_REGISTER_0:
    w_register_3.enable = true;
    w_register_3.flush  = true;
    w_register_0.enable = true;
    w_register_0.flush  = true;
    break;

  case FSM_READ_IO:
    if (io_mb_s.read())
    {
      w_register_0.enable = true;
      w_register_0.be = 0xFFFFFFFF;
      w_register_0.address = io_handler.addr.read();
      
      for (size_t i=0;i<SIZE_256_IN_WORDS;i++)
      {
	w_register_0.data[i] = io_handler.data[i].read();
      }
    }
    else
    {
      w_register_3.enable = true;
      w_register_3.be = 0xFFFFFFFF;
      w_register_3.address = io_handler.addr.read();
      
      for (size_t i=0;i<SIZE_256_IN_WORDS;i++)
      {
	w_register_3.data[i] = io_handler.data[i].read();
      }
    }
    break;

  case FSM_CMD_READ:
    sc_cmd.cmdval = true;
    sc_cmd.cmd = READ;
    sc_cmd.be = 0xFFFFFFFF;//plen_s.read();
    sc_cmd.mb = false;
    sc_cmd.address = aligned(address_s.read(), SIZE_256_IN_BYTES);
    break;

  case FSM_CMD_WRITE:
    p_vci_initiator.cmdack = true;

    w_register_3.enable  = true;
    w_register_3.be      = (0xF & be_s.read()) << 
      (index(address_s.read(), SIZE_256_IN_BYTES)*vci_param::B);
    w_register_3.address = aligned(address_s.read(), SIZE_256_IN_BYTES);
    w_register_3.data[index(address_s.read(), SIZE_256_IN_BYTES)] = data_s.read();

    DEBUG(TRACE, IN, "W0x%08x %08x %x (cycle %u) %d\n", 
	  (addr_t)(address_s.read()), 
	  (uint32_t)data_s.read(),
	  (uint32_t)be_s.read(),
	  m_cycle,
	  (uint32_t)p_vci_initiator.rspack.read());
  
    p_vci_initiator.rspWrite(cells_count_s.read() == 1);
    break;

  case FSM_CMD_START_WRITE:
/*    mem_rq.cmdval = true;
    mem_rq.cmd = WRITE;
    mem_rq.id = 1;
    mem_rq.plen = plen_s.read();
    mem_rq.start_address = start_address_s.read();*/
    sc_cmd.cmdval = true;
    sc_cmd.cmd = WRITE;
//    sc_cmd.be = plen_s.read();
    sc_cmd.mb = false;
    sc_cmd.address = start_address_s.read(); //aligned(address_s.read(), SIZE_256_IN_BYTES);
    break;

  case FSM_WREAD:
    break;

  case FSM_WWRITE:
    break;

  case FSM_RREAD:
    p_vci_initiator.rspRead(cells_count_s == 0, r_register_0.data[idx_s.read()].read());
    break;

  case FSM_RWRITE:
    break;

  default:
    break;
  }
}
