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

#define mem_ctrl(x) template<typename vci_param> x VciMemCtrl<vci_param>

mem_ctrl(/**/)::VciMemCtrl(sc_module_name name, size_t nreg):
	   sc_module(name),
	   p_clk("clk"),
	   p_resetn("resetn"),
	   p_vci_initiator("p_vci_initiator")
{
  m_nreg = nreg;

  in = new DATA_REGISTER_WRITE_OUT[m_nreg];
  out = new DATA_REGISTER_READ_IN[m_nreg];

  SC_METHOD(transition);
  dont_initialize();
  sensitive << p_clk.pos();

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

  DEBUG(NONE, STD, "[SECBUS] Building VciMemCtrl (%s) with %u registers\n", basename(), nreg);
}

mem_ctrl(/**/)::~VciMemCtrl()
{
  delete in;
  delete out;
}

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

  if (!p_resetn.read())
  {
    fsm_cmd_s = VCI_CMD_IDLE;
    fsm_rsp_s = VCI_RSP_IDLE;

    m_cycle = 0;

    return;
  }

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

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

  write_in_reg_s = false;
  flush_reg_s = false;

  if (wr_data.enable)
  {
    wr_data_s[0] = wr_data.data[0].read();
    wr_data_s[1] = wr_data.data[1].read();
  }

  /**
     FSM: MEMORY COMMAND FROM SECBUS TO THE MEMORY CONTROLLER

     3 states :
       IDLE - waiting for valid command from SecBus
       READ - handle a read command 
       WRITE - handle a write command

  */
  switch (fsm_cmd_s)
  {
  case VCI_CMD_IDLE:
    if (!mem_rq.cmdval.read())
      break;

    address_s = mem_rq.start_address.read();
    cmd_s = mem_rq.cmd.read();
    id_s = mem_rq.id.read();
    plen_s = mem_rq.plen.read();
    reg_idx_s = index(mem_rq.start_address.read(), SIZE_256_IN_BYTES);
    cmd_cpt_s = 0;

    if (mem_rq.direct_write.read() || mem_rq.cmd.read() == READ)
    {
      cells_to_come_s = mem_rq.plen.read()/vci_param::B;
    }
    else
    {
      cells_to_come_s = (mem_rq.plen.read() + _ctz(out[_ctz(mem_rq.id.read(), 32)].be.read(), vci_param::B) + vci_param::B - 1)/vci_param::B;
    }

    if (fsm_rsp_s.read() == VCI_RSP_IDLE)
    {
      switch(mem_rq.cmd.read())
      {
      case READ:
	/* can read simultaneous on multiple registers */
	if (mem_rq.id.read() < (1 << m_nreg) && mem_rq.id.read() != 0)
	{
	  fsm_cmd_s = VCI_CMD_READ;
//	  flush_reg_s = true;
	}
	else
	{
	  DEBUG(ERROR, OUT, "Try to reach a non existing register (register mask = %08x)", 
		(uint32_t)mem_rq.id.read());
	}
	break;

      case WRITE:
	// direct write
	if (mem_rq.direct_write.read())
	{
	  fsm_cmd_s = VCI_CMD_WRITE;
	  break;
	}
	/* only one register can be used as source to write in the memory */
	if (mem_rq.id.read() <= (1 << (m_nreg-1)) && isPow2(mem_rq.id.read()))
	{
	  fsm_cmd_s = VCI_CMD_WRITE;
	}
	else
	{
	  DEBUG(ERROR, OUT, "Try to write data from a non existing register (register %u - limit %u)", 
		(uint32_t)mem_rq.id.read(), m_nreg);
	}
	break;

      default:
	DEBUG(ERROR, OUT, "Unknown command from memory arbiter (cmd = %u)", 
	      (uint32_t)mem_rq.cmd.read());
	break;
      }
    }
    break;

  case VCI_CMD_READ:
    if (p_vci_initiator.cmdack.read())
    {
      fsm_cmd_s = VCI_CMD_IDLE;
    }
    break;

  case VCI_CMD_WRITE:
    if (p_vci_initiator.cmdack.read())
    {
      if (mem_rq.direct_write.read())
      {
	DEBUG(TRACE, OUT, "DW0x%08x %08x 0xF (cycle %u)\n", 
	      (uint32_t)(address_s.read()),
	      (uint32_t)wr_data_s[cmd_cpt_s.read()].read(),
	      m_cycle);
      }
      else
      {
	DEBUG(TRACE, OUT, "W0x%08x %08x %x (cycle %u)\n", 
	      (uint32_t)(address_s.read()),
	      (uint32_t)out[_ctz(id_s.read(), 32)].data[reg_idx_s.read()].read(),
	      (uint32_t)(out[_ctz(id_s.read(), 32)].be >> ((reg_idx_s.read())*4)) & 0xF,
	      m_cycle);
      }
      reg_idx_s = reg_idx_s.read() + 1;
      address_s = address_s.read() + vci_param::B;
      cmd_cpt_s = cmd_cpt_s + 1;

      if (cmd_cpt_s + 1 >= cells_to_come_s)
	fsm_cmd_s = VCI_CMD_IDLE;
    }
    break;

  default:
    break;
  }

  /**
     FSM: MEMORY RESPONSES FROM MEMORY CONTROLLER

     4 states :
       IDLE - waiting for responses
       READ - write the data from the memory to the correct register
       LAST_READ - write the last data from the memory to the correct register
       WRITE - just for acknowlegment

  */
  switch (fsm_rsp_s)
  {
  case VCI_RSP_IDLE:
    if (!mem_rq.cmdval.read())
      break;

    cmd_cpt_s = 0;

    switch (mem_rq.cmd.read())
    {
    case READ:
      fsm_rsp_s = VCI_RSP_READ;
      break;

    case WRITE:
      fsm_rsp_s = VCI_RSP_WRITE;
      break;

    default:
      break;
    }
    break;

  case VCI_RSP_READ:
    if (!p_vci_initiator.rspval.read())
      break;

    data_s = p_vci_initiator.rdata.read();
    cmd_cpt_s = cmd_cpt_s.read() + vci_param::B;
    address_s = address_s.read() + vci_param::B;
    reg_idx_s = index(address_s.read(), SIZE_256_IN_BYTES);

    write_in_reg_s = true;

    DEBUG(TRACE, OUT, "R0x%08x %08x %x (cycle %u)\n",
	  (uint32_t)(address_s.read()),
	  (uint32_t)(p_vci_initiator.rdata.read()),
	  (uint32_t)(0xF),
	  m_cycle);

    if (p_vci_initiator.reop.read())
      fsm_rsp_s = VCI_RSP_IDLE;

    if (p_vci_initiator.rerror.read() != vci_param::ERR_NORMAL)
      DEBUG(ERROR, OUT, "Unexpected RERROR from memory (addr = %x err = %u)\n", 
	    (addr_t)mem_rq.start_address.read(), 
	    (uint32_t)p_vci_initiator.rerror.read()); 
    break;

  case VCI_RSP_WRITE:
    if (!p_vci_initiator.rspval.read())
      break;

    if (p_vci_initiator.reop.read())
      fsm_rsp_s = VCI_RSP_IDLE;

    if (p_vci_initiator.rerror.read() != vci_param::ERR_NORMAL)
      DEBUG(ERROR, OUT, "Unexpected WERROR from memory (addr = %x err = %u)\n", 
	    (addr_t)mem_rq.start_address.read(), 
	    (uint32_t)p_vci_initiator.rerror.read()); 
    break;

  default:
    break;
  }
}

mem_ctrl(void)::genMoore()
{
  for (size_t i=0;i<m_nreg;i++)
  {
    in[i].enable = false;
    in[i].flush = false;
  }

  p_vci_initiator.rspack = true;
  
  if (fsm_rsp_s == VCI_RSP_IDLE)
    mem_rq.rspval = true;
  else
    mem_rq.rspval = false;

  switch (fsm_cmd_s.read())
  {
  case VCI_CMD_IDLE:
    p_vci_initiator.cmdval  = false;
    p_vci_initiator.address = 0;
    p_vci_initiator.wdata   = 0;
    p_vci_initiator.be      = 0;
    p_vci_initiator.plen    = 0;
    p_vci_initiator.cmd     = vci_param::CMD_WRITE;
    p_vci_initiator.trdid   = 0;
    p_vci_initiator.pktid   = 0;
    p_vci_initiator.srcid   = 0;
    p_vci_initiator.cons    = false;
    p_vci_initiator.wrap    = false;
    p_vci_initiator.contig  = false;
    p_vci_initiator.clen    = 0;
    p_vci_initiator.cfixed  = false;
    p_vci_initiator.eop     = false;
    break;

  case VCI_CMD_READ:
    p_vci_initiator.cmdval  = true;
    p_vci_initiator.address = mem_rq.start_address.read();
    p_vci_initiator.be      = 0xF;
    p_vci_initiator.plen    = mem_rq.plen.read();
    p_vci_initiator.cmd     = vci_param::CMD_READ;
    p_vci_initiator.trdid   = 0;
    p_vci_initiator.pktid   = 0;
#warning need to fix the SRC ID
    p_vci_initiator.srcid   = 0; //srcid_s;
    p_vci_initiator.cons    = false;
    p_vci_initiator.wrap    = false;
    p_vci_initiator.contig  = true;
    p_vci_initiator.clen    = 0;
    p_vci_initiator.cfixed  = false;
    p_vci_initiator.eop     = true;

    mem_rq.cmdack           = true;
    break;

  case VCI_CMD_WRITE:
    p_vci_initiator.cmdval  = true;
    p_vci_initiator.address = address_s.read();
    p_vci_initiator.plen    = plen_s.read();
    p_vci_initiator.cmd     = vci_param::CMD_WRITE;
#warning need to fix the TRDID_S
    p_vci_initiator.trdid   = 0; //trdid_s;
    p_vci_initiator.pktid   = 0;
#warning need to fix the SRCID_S
    p_vci_initiator.srcid   = 0; //srdid_s;
    p_vci_initiator.cons    = false;
    p_vci_initiator.wrap    = false;
    p_vci_initiator.contig  = true;
    p_vci_initiator.clen    = 0;
    p_vci_initiator.cfixed  = false;
    p_vci_initiator.eop     = (cmd_cpt_s.read() + 1 >= cells_to_come_s.read());
    if (mem_rq.direct_write.read())
    {
      p_vci_initiator.be      = 0xF;
      p_vci_initiator.wdata   = wr_data_s[cmd_cpt_s.read()].read();
    }
    else
    {
      p_vci_initiator.be      = (out[_ctz(id_s.read(), 32)].be >> (reg_idx_s.read()*4)) & 0xF;
      p_vci_initiator.wdata   = out[_ctz(id_s.read(), 32)].data[reg_idx_s.read()].read();
    }
    mem_rq.cmdack           = true;
    break;

  default:
    break;
  }

  if (flush_reg_s)
  {
    for (size_t i=0;i<m_nreg;i++)
    {
      if ((id_s.read() >> i) & 0x1)
      {
	in[i].enable = true;
	in[i].flush  = true;
      }
    }
  }

  if (write_in_reg_s)
  {
    for (size_t i=0;i<m_nreg;i++)
    {
      // write into each register given by the mask id
      if ((id_s>>i) & 0x1)
      {
	in[i].enable            = true;
	in[i].be                = 0xF << ((reg_idx_s.read()) * vci_param::B);
	in[i].address           = address_s.read() - 4;
	in[i].data[reg_idx_s]   = data_s.read();
      }    
    }
  }
}
