
/* -*- c++ -*-
 * File 	: vci_synthetic_initiator.cpp
 * Date 	: 23/12/2010
 * Copyright 	: UPMC / LIP6
 * Authors 	: Christophe Choichillon
 * Version	: 2.1
 *
 * SOCLIB_LGPL_HEADER_BEGIN
 *
 * This file is part of SoCLib, GNU LGPLv2.1.
 *
 * SoCLib is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; version 2.1 of the License.
 *
 * SoCLib is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with SoCLib; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * SOCLIB_LGPL_HEADER_END
 *
 * Maintainers: christophe.choichillon@lip6.fr
 */

#include "../include/vci_synthetic_initiator.h"
#include <iostream>


//#define DETERMINISTIC
#define RANDOM_PERIOD

namespace soclib { namespace caba {


#define tmpl(x) template<typename vci_param> x VciSyntheticInitiator<vci_param>

  //using soclib::common::uint32_log2;	
  
  ////////////////////////////////
  // 	Constructor 
  ////////////////////////////////

  tmpl(/**/)::VciSyntheticInitiator( 
      sc_module_name name,
      const soclib::common::MappingTable &mt,
      const soclib::common::IntTab       &vci_index,
      const uint32_t length,    // Packet length (flit numbers)
      const uint32_t rho,       // Offered load * 1000
      const uint32_t depth,     // Fifo depth
      const uint32_t xmesh,	
      const uint32_t ymesh,
      const uint32_t bc_period, // Broadcast period, if no broadcast => 0
      const uint32_t xmin, 
      const uint32_t xmax,
      const uint32_t ymin,
      const uint32_t ymax
      )

    : soclib::caba::BaseModule(name),

    p_clk("clk"),
    p_resetn("resetn"),
    p_vci("vci_ini"),
    //  FIFOs 
    m_srcid( mt.indexForId(vci_index) ),
    m_length(length),
    m_rho(rho),
    m_depth(depth),
    m_xmesh(xmesh),
    m_ymesh(ymesh),
    m_bc_period(bc_period),
    m_xmin(xmin),
    m_xmax(xmax),
    m_ymin(ymin),
    m_ymax(ymax),
    r_date_fifo("r_date_fifo", m_depth),
    r_bc_fifo("r_bc_fifo", m_depth),
    r_cmd_fsm("r_cmd_fsm"),
    r_cmd_address("r_cmd_address"),  		
    r_cmd_trdid("r_cmd_trdid"),	
    r_cmd_count("r_cmd_count"),  	
    r_cmd_seed("r_cmd_seed"),	
    r_bc_nrsp("r_bc_nrsp"),	
    r_cpt_cycles("r_cpt_cycles"),  		
    r_cpt_period("r_cpt_period"),  		
    r_nb_single("r_nb_single"),	
    r_latency_single("r_latency_single"),	
    r_nb_bc("r_nb_bc"),	
    r_latency_bc("r_latency_bc"),
    r_time_to_next_bc("r_time_to_next_bc")
{

      r_pending_fsm = new sc_signal<bool>[m_tab_size];
      r_pending_date = new sc_signal<uint64_t>[m_tab_size];

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

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

    } // end constructor


  /////////////////////////////////
  tmpl(/**/)::~VciSyntheticInitiator()
    /////////////////////////////////
  {
	delete r_pending_fsm;
	delete r_pending_date;
  }

  ///////////////////////////////////
  tmpl(uint32_t)::destAdress()
  ///////////////////////////////////
  {
    return (uint32_t) (rand() % (m_xmesh * m_ymesh)) ;
  }


  ///////////////////////////////////
  tmpl(uint32_t)::destAdress(uint32_t *rand_seed)
  ///////////////////////////////////
  {
    return (uint32_t) (rand_r(rand_seed) % (m_xmesh * m_ymesh)) ;
  }

  //////////////////////////////////
  tmpl(double)::getLatencySingle()
  //////////////////////////////////
  {
	if (m_rho) 
	  return (double)(r_latency_single.read())/(double)(r_nb_single.read());
	else
	  return 0;

  }

  //////////////////////////////////
  tmpl(double)::getLatencyBC()
  //////////////////////////////////
  {
	if(m_bc_period)
	  return ((double)r_latency_bc.read()/(double)r_nb_bc.read());
	else
	  return 0;

  }

  //////////////////////////////////
  tmpl(void)::print_trace()
  //////////////////////////////////
  {
  	const char* state_cmd_str[] = { "IDLE",
				        "SINGLE_SEND",
					"BC_SEND"};

	const char* state_bc_rsp_str[] = {"IDLE",
					  "WAIT_RSP"};

	std::cout << "Vci_Synthetic_Initiator " << name()
		  << " : " << std::dec << r_cpt_cycles.read() << " cycles " 
		  << " : state_cmd_fsm = " << state_cmd_str[r_cmd_fsm] 
		  << " : state_rsp_fsm = " << state_bc_rsp_str[r_pending_fsm[0].read()] 
		  << " Adresse to send : " << std::hex << r_cmd_address.read()
		  << " Number of BC_RSP to receive : " << std::dec << r_bc_nrsp.read() 
		  << " Number of packets sent : " << std::dec << r_nb_single.read() << " " << r_cmd_trdid.read() 
		  << " Number of BC sent : " << r_nb_bc.read()
		  << " Cycles to the next BC : " << r_time_to_next_bc.read() << std::endl;
	for(int i = 0; i < (1<<vci_param::T) ; i++){
	  std::cout << "ID : " << i << " " << (uint64_t)(r_pending_date[i].read()) << std::endl;
	}
  }

  //////////////////////////////////
  tmpl(void)::printStats()
  //////////////////////////////////
  {
  	std::cout << name() << " : "<< std::dec << r_cpt_cycles.read() << " cycles, " << r_nb_single.read() << " packets sent" << std::endl;
	if (m_rho) 
	{
	  std::cout << "Average latency : " << (double)(r_latency_single.read())/(double)(r_nb_single.read()) << std::endl;
	}
	if(m_bc_period)
	{
	  std::cout << "Number of broadcast sent and received : " << r_nb_bc.read() << std::endl;
	  std::cout << "Average latency : " << ((double)r_latency_bc.read()/(double)r_nb_bc.read()) << std::endl;
	}
  }

  //////////////////////////////////
  tmpl(void)::transition()
  //////////////////////////////////
  {
    //  RESET          
    if ( ! p_resetn.read() ) 
    {
      // Initializing seed for random numbers generation

#ifndef DETERMINISTIC
      srand(time(NULL));
#endif

      // Initializing FSMs
      r_cmd_fsm = VCI_IDLE;
      for(size_t i=0 ; i<m_tab_size ; i++) r_pending_fsm[i] = false;

      // Initializing FIFOs 
      r_date_fifo.init();
      r_bc_fifo.init();

      // Initializing the instrumentation registers
      r_latency_single 		= 0;
      r_nb_single		= 0;
      r_latency_bc 		= 0;
      r_nb_bc			= 0;
      r_cpt_cycles		= 0;
      r_cpt_period		= 0;
      
      r_cmd_seed       		= (uint32_t)m_srcid;

#ifndef RANDOM_PERIOD
      r_time_to_next_bc		= m_bc_period;
#endif

      return;
    }

    bool    fifo_put = false;
    bool    fifo_get = false;
    bool    fifo_bc  = false;


#ifdef DETERMINISTIC
    uint32_t m_local_seed ;
#endif

    //////////////////
    // VCI CMD FSM 
    //////////////////
    switch ( r_cmd_fsm.read() ) {
      case VCI_IDLE:
        {
	  if (r_date_fifo.rok())
          {
	    if ( r_bc_fifo.read() == true )	// its a broadcast request
            {
	      if ( r_pending_fsm[0].read() == false )	// no current broadcast
              {
	        r_cmd_fsm = VCI_BC_SEND ;
		r_cmd_address = 0x3 | (0x7c1f << vci_param::N-20) ;
              }
            }
            else			// its a single request
            {
	      int id = -1;
	      for(size_t i = 1; i < m_tab_size; i++){  	// ID 0 reserved for broadcast transactions
	        if(r_pending_fsm[i].read() == false)
                {
		  id = (int)i;
		  break;
		}
	      }
	      if(id != -1){
	        r_cmd_fsm = VCI_SINGLE_SEND ;
	        r_cmd_count = 0;
                r_cmd_trdid = id;
	      }
#ifdef DETERMINISTIC
	      m_local_seed = r_cmd_seed.read();
	      r_cmd_address = destAdress(&m_local_seed) << (vci_param::N)-(soclib::common::uint32_log2((uint32_t)m_xmesh)+soclib::common::uint32_log2((uint32_t)m_ymesh));
	      r_cmd_seed = m_local_seed;
#else
	      r_cmd_address = destAdress() << (vci_param::N)-(soclib::common::uint32_log2((uint32_t)m_xmesh)+soclib::common::uint32_log2((uint32_t)m_ymesh));
#endif
	    }
	  }
          break;
        }
      case VCI_SINGLE_SEND:
        {
	  if ( p_vci.cmdack.read())
          {
	    r_cmd_count = r_cmd_count.read() + 1;
	    if (r_cmd_count.read() == m_length-1) 
            {
	      //r_nb_single = r_nb_single.read() + 1;
	      r_cmd_fsm = VCI_SINGLE_REGISTER ;
	    }
	  }
          break;
        }
      case VCI_SINGLE_REGISTER:
        {
	  r_pending_date[r_cmd_trdid.read()] = (uint64_t)(r_date_fifo.read());
          r_pending_fsm[r_cmd_trdid.read()] = true;
          fifo_get = true;
	  r_cmd_fsm = VCI_IDLE;
        }
      case VCI_BC_SEND:
        {
	  if (p_vci.cmdack.read()) 
          {
	    r_bc_nrsp = (m_xmax - m_xmin) * (m_ymax - m_ymin) ;
	    r_pending_fsm[0] = true;
	    r_pending_date[0] = (uint64_t)(r_date_fifo.read());
	    fifo_get = true;
	    r_cmd_fsm = VCI_IDLE;
            break;
	  }
        }
    } // end switch vci_fsm

    
    ///////////////////
    // PENDING FSMs
    //////////////////
    if(p_vci.rspval.read())
    {
      if(p_vci.rtrdid.read() == 0)	// not a broadcast
      {
        assert( ( r_pending_fsm[0].read() == true ) && 
                "illegal broadcast response received");
	r_bc_nrsp = r_bc_nrsp.read() - 1 ;
	if(r_bc_nrsp.read() == 1) 
	{
	  r_pending_fsm[0] = false;
          r_latency_bc = r_latency_bc.read() + (r_cpt_cycles.read() - r_pending_date[0].read());
	}
      }
      else
      {
        assert( ( r_pending_fsm[(int)p_vci.rtrdid.read()] == true ) && 
                "illegal single response received");
        r_pending_fsm[p_vci.rtrdid.read()] = false;
	r_latency_single = r_latency_single.read() + 
                           (r_cpt_cycles.read() - r_pending_date[(int)p_vci.rtrdid.read()].read());
      }
    }

    ////////////////////////
    //  traffic regulator
    ////////////////////////
    if ( m_bc_period && (r_cpt_period.read() > r_time_to_next_bc.read()) )
    { 
      fifo_put = true ;
      fifo_bc  = true;
      if (r_date_fifo.wok())   
      {
        r_nb_bc = r_nb_bc.read() + 1;
        r_cpt_period = 0;
      }
    }
    else if( ( (uint64_t)(m_rho*r_cpt_cycles.read()) > (uint64_t)(m_length*r_nb_single.read()*1000)) )
    {
      fifo_put = true ;
      fifo_bc  = false;
      if (r_date_fifo.wok())   r_nb_single = r_nb_single.read() + 1;
    }

    if ( m_bc_period && (r_cpt_period.read() > r_time_to_next_bc.read()) && r_date_fifo.wok() ) 
    {
#ifdef RANDOM_PERIOD
      r_time_to_next_bc = (uint32_t)(rand()%(2*m_bc_period));
#endif
      r_cpt_period = 0;
    }
    else
      r_cpt_period = r_cpt_period.read() + 1;

    ////////////////////////
    //  update fifos
    ////////////////////////
    if (fifo_put){
      if (fifo_get){
        r_date_fifo.put_and_get(r_cpt_cycles.read());
        r_bc_fifo.put_and_get(fifo_bc);
      } else {
        r_date_fifo.simple_put(r_cpt_cycles.read());
        r_bc_fifo.simple_put(fifo_bc);
      }
    } else {
      if (fifo_get){
        r_date_fifo.simple_get();
        r_bc_fifo.simple_get();
      }
    }
   
    ///////////////////////////
    //  increment local time
    ///////////////////////////
    r_cpt_cycles = r_cpt_cycles.read() + 1;

    return;

  } // end transition()

  /////////////////////////////
  tmpl(void)::genMoore()
  /////////////////////////////
  {
    ////////////////////////////////////////////////////////////
    // Command signals on the p_vci port
    ////////////////////////////////////////////////////////////
     p_vci.cmd        = vci_param::CMD_WRITE;   
     p_vci.be         = 0xF;                             
     p_vci.srcid      = m_srcid;   
     p_vci.cons       = false;        
     p_vci.wrap       = false;       
     p_vci.contig     = true;       
     p_vci.clen       = 0;         
     p_vci.cfixed     = false;           
     p_vci.rspack     = true;


    switch ( r_cmd_fsm.read() ) {

      //////////////////
      case VCI_IDLE:
        {
	  p_vci.cmdval  = false;                 
	  p_vci.address = 0; 
	  p_vci.plen    = 0;                                         
	  p_vci.wdata   = 0;                                       
	  p_vci.trdid   = 0;                  
	  p_vci.pktid      = 0;      
	  p_vci.eop     = false;                                   
          break;
        }
        //////////////////
      case VCI_SINGLE_SEND:
        {
	  p_vci.cmdval  = true;                 
	  p_vci.address = (addr_t)(r_cmd_address.read() + (r_cmd_count.read()*4)); 
	  p_vci.plen    = m_length*4;                                         
	  p_vci.wdata   = 0;                                       
	  p_vci.trdid   = r_cmd_trdid.read();                  
	  p_vci.pktid   = 0;      
	  if (r_cmd_count.read() == m_length - 1 ) {
	    p_vci.eop     = true;                                   
	  } else {
	    p_vci.eop     = false;                                   
	  }
          break;
        }
        ///////////////////
      case VCI_BC_SEND:
        {
       	  p_vci.cmdval  = true;                 
	  p_vci.address = (addr_t) r_cmd_address.read(); 
	  p_vci.plen    = 4;                                         
	  p_vci.wdata   = 0;                                       
	  p_vci.trdid   = 0;                  
          p_vci.pktid   = 0;      
	  p_vci.eop     = true;                                   
          break;
        }
      //////////////////
      case VCI_SINGLE_REGISTER:
        {
	  p_vci.cmdval  = false;                 
	  p_vci.address = 0; 
	  p_vci.plen    = 0;                                         
	  p_vci.wdata   = 0;                                       
	  p_vci.trdid   = 0;                  
	  p_vci.pktid   = 0;      
	  p_vci.eop     = false;                                   
          break;
        }
    } // end switch vci_cmd_fsm

  } // end genMoore()

}} // end name space
