/*
 * $Id$
 *
 * [ Description ]
 * 
 * Test
 */

#include "Behavioural/Core/Multi_Front_end/Front_end/Ifetch_unit/Ifetch_queue/SelfTest/include/test.h"
#include "Common/include/Test.h"
#include "Behavioural/include/Allocation.h"
#include <list>
#include <set>

#define NB_ITERATION  1
#define CYCLE_MAX     (2048*NB_ITERATION)

#define LABEL(str...)							\
  {									\
    msg (_("{%d} "),static_cast<uint32_t>(sc_simulation_time()));	\
    msg (str);								\
    msg (_("\n"));							\
  } while(0)

#define SC_START(cycle_offset)                                                       \
  do									             \
    {									             \
      /*cout << "SC_START (begin)" << endl;*/				             \
									             \
      uint32_t cycle_current = static_cast<uint32_t>(sc_simulation_time());          \
      if (cycle_offset != 0)						             \
	{								             \
	  cout << "##########[ cycle "<< cycle_current+cycle_offset << " ]" << endl; \
	}								             \
									             \
      if (cycle_current > CYCLE_MAX)					             \
	{								             \
	  TEST_KO("Maximal cycles Reached");				             \
	}								             \
									             \
      sc_start(cycle_offset);						             \
									             \
      /*cout << "SC_START (end  )" << endl;*/				             \
    } while(0)


class entry_t
{
public : uint32_t           _cycle;
public : Tpacket_t          _packet;
public : Tgeneral_address_t _addr;

public : entry_t (uint32_t           cycle,
		  Tpacket_t          packet,
		  Tgeneral_address_t addr  )
  {
    _cycle  = cycle ;
    _packet = packet;
    _addr   = addr  ;
  }
};

void test (string name,
	   morpheo::behavioural::core::multi_front_end::front_end::ifetch_unit::ifetch_queue::Parameters * _param)
{
  msg(_("<%s> : Simulation SystemC.\n"),name.c_str());

#ifdef STATISTICS
  morpheo::behavioural::Parameters_Statistics * _parameters_statistics = new morpheo::behavioural::Parameters_Statistics (5,1000);
#endif

  Ifetch_queue * _Ifetch_queue = new Ifetch_queue (name.c_str(),
#ifdef STATISTICS
					     _parameters_statistics,
#endif
					     _param);
  
#ifdef SYSTEMC
  /*********************************************************************
   * Déclarations des signaux
   *********************************************************************/
  string rename;

  sc_clock              *  in_CLOCK  = new sc_clock ("clock", 1.0, 0.5);	 
  sc_signal<Tcontrol_t> *  in_NRESET = new sc_signal<Tcontrol_t> ("NRESET");

  ALLOC_SC_SIGNAL ( in_ADDRESS_VAL                        ," in_ADDRESS_VAL                        ",Tcontrol_t           );
  ALLOC_SC_SIGNAL (out_ADDRESS_ACK                        ,"out_ADDRESS_ACK                        ",Tcontrol_t           );
  ALLOC_SC_SIGNAL (out_ADDRESS_IFETCH_QUEUE_ID            ,"out_ADDRESS_IFETCH_QUEUE_ID            ",Tifetch_queue_ptr_t  );
  ALLOC1_SC_SIGNAL( in_ADDRESS_INSTRUCTION_ENABLE         ," in_ADDRESS_INSTRUCTION_ENABLE         ",Tcontrol_t           ,_param->_nb_instruction);
  ALLOC_SC_SIGNAL ( in_ADDRESS_INSTRUCTION_ADDRESS        ," in_ADDRESS_INSTRUCTION_ADDRESS        ",Tgeneral_address_t   );
  ALLOC_SC_SIGNAL ( in_ADDRESS_INST_IFETCH_PTR            ," in_ADDRESS_INST_IFETCH_PTR            ",Tinst_ifetch_ptr_t   );
  ALLOC_SC_SIGNAL ( in_ADDRESS_BRANCH_STATE               ," in_ADDRESS_BRANCH_STATE               ",Tbranch_state_t      );
  ALLOC_SC_SIGNAL ( in_ADDRESS_BRANCH_UPDATE_PREDICTION_ID," in_ADDRESS_BRANCH_UPDATE_PREDICTION_ID",Tprediction_ptr_t    );
  ALLOC1_SC_SIGNAL(out_DECOD_VAL                          ,"out_DECOD_VAL                          ",Tcontrol_t           ,_param->_nb_instruction);
  ALLOC1_SC_SIGNAL( in_DECOD_ACK                          ," in_DECOD_ACK                          ",Tcontrol_t           ,_param->_nb_instruction);
  ALLOC1_SC_SIGNAL(out_DECOD_INSTRUCTION                  ,"out_DECOD_INSTRUCTION                  ",Tinstruction_t       ,_param->_nb_instruction);
  ALLOC_SC_SIGNAL (out_DECOD_ADDRESS                      ,"out_DECOD_ADDRESS                      ",Tgeneral_address_t   );
  ALLOC_SC_SIGNAL (out_DECOD_INST_IFETCH_PTR              ,"out_DECOD_INST_IFETCH_PTR              ",Tinst_ifetch_ptr_t   );
  ALLOC_SC_SIGNAL (out_DECOD_BRANCH_STATE                 ,"out_DECOD_BRANCH_STATE                 ",Tbranch_state_t      );
  ALLOC_SC_SIGNAL (out_DECOD_BRANCH_UPDATE_PREDICTION_ID  ,"out_DECOD_BRANCH_UPDATE_PREDICTION_ID  ",Tprediction_ptr_t    );
  ALLOC_SC_SIGNAL (out_DECOD_EXCEPTION                    ,"out_DECOD_EXCEPTION                    ",Tprediction_ptr_t    );
  ALLOC_SC_SIGNAL ( in_ICACHE_RSP_VAL                     ," in_ICACHE_RSP_VAL                     ",Tcontrol_t           );
  ALLOC_SC_SIGNAL (out_ICACHE_RSP_ACK                     ,"out_ICACHE_RSP_ACK                     ",Tcontrol_t           );
  ALLOC_SC_SIGNAL ( in_ICACHE_RSP_PACKET_ID               ," in_ICACHE_RSP_PACKET_ID               ",Tpacket_t            );
  ALLOC1_SC_SIGNAL( in_ICACHE_RSP_INSTRUCTION             ," in_ICACHE_RSP_INSTRUCTION             ",Ticache_instruction_t,_param->_nb_instruction);
  ALLOC_SC_SIGNAL ( in_ICACHE_RSP_ERROR                   ," in_ICACHE_RSP_ERROR                   ",Ticache_error_t      );
  ALLOC_SC_SIGNAL ( in_EVENT_RESET_VAL                    ," in_EVENT_RESET_VAL                    ",Tcontrol_t           );
  ALLOC_SC_SIGNAL (out_EVENT_RESET_ACK                    ,"out_EVENT_RESET_ACK                    ",Tcontrol_t           );
  
  /********************************************************
   * Instanciation
   ********************************************************/
  
  msg(_("<%s> : Instanciation of _Ifetch_queue.\n"),name.c_str());

  (*(_Ifetch_queue->in_CLOCK))        (*(in_CLOCK));
  (*(_Ifetch_queue->in_NRESET))       (*(in_NRESET));

  INSTANCE_SC_SIGNAL (_Ifetch_queue, in_ADDRESS_VAL                        );
  INSTANCE_SC_SIGNAL (_Ifetch_queue,out_ADDRESS_ACK                        );
  if (_param->_have_port_queue_ptr)
  INSTANCE_SC_SIGNAL (_Ifetch_queue,out_ADDRESS_IFETCH_QUEUE_ID            );
  INSTANCE1_SC_SIGNAL(_Ifetch_queue, in_ADDRESS_INSTRUCTION_ENABLE         ,_param->_nb_instruction);
  INSTANCE_SC_SIGNAL (_Ifetch_queue, in_ADDRESS_INSTRUCTION_ADDRESS        );
  if (_param->_have_port_instruction_ptr)
  INSTANCE_SC_SIGNAL (_Ifetch_queue, in_ADDRESS_INST_IFETCH_PTR            );
  INSTANCE_SC_SIGNAL (_Ifetch_queue, in_ADDRESS_BRANCH_STATE               );
  if (_param->_have_port_branch_update_prediction_id)
  INSTANCE_SC_SIGNAL (_Ifetch_queue, in_ADDRESS_BRANCH_UPDATE_PREDICTION_ID);
  INSTANCE1_SC_SIGNAL(_Ifetch_queue,out_DECOD_VAL                          ,_param->_nb_instruction);
  INSTANCE1_SC_SIGNAL(_Ifetch_queue, in_DECOD_ACK                          ,_param->_nb_instruction);
  INSTANCE1_SC_SIGNAL(_Ifetch_queue,out_DECOD_INSTRUCTION                  ,_param->_nb_instruction);
  INSTANCE_SC_SIGNAL (_Ifetch_queue,out_DECOD_ADDRESS                      );
  if (_param->_have_port_instruction_ptr)
  INSTANCE_SC_SIGNAL (_Ifetch_queue,out_DECOD_INST_IFETCH_PTR              );
  INSTANCE_SC_SIGNAL (_Ifetch_queue,out_DECOD_BRANCH_STATE                 );
  if (_param->_have_port_branch_update_prediction_id)
  INSTANCE_SC_SIGNAL (_Ifetch_queue,out_DECOD_BRANCH_UPDATE_PREDICTION_ID  );
  INSTANCE_SC_SIGNAL (_Ifetch_queue,out_DECOD_EXCEPTION                    );
  INSTANCE_SC_SIGNAL (_Ifetch_queue, in_ICACHE_RSP_VAL                     );
  INSTANCE_SC_SIGNAL (_Ifetch_queue,out_ICACHE_RSP_ACK                     );
  if (_param->_have_port_queue_ptr)
  INSTANCE_SC_SIGNAL (_Ifetch_queue, in_ICACHE_RSP_PACKET_ID               );
  INSTANCE1_SC_SIGNAL(_Ifetch_queue, in_ICACHE_RSP_INSTRUCTION             ,_param->_nb_instruction);
  INSTANCE_SC_SIGNAL (_Ifetch_queue, in_ICACHE_RSP_ERROR                   );
  INSTANCE_SC_SIGNAL (_Ifetch_queue, in_EVENT_RESET_VAL                    );
  INSTANCE_SC_SIGNAL (_Ifetch_queue,out_EVENT_RESET_ACK                    );

  msg(_("<%s> : Start Simulation ............\n"),name.c_str());
    
  Time * _time = new Time();

  /********************************************************
   * Simulation - Begin
   ********************************************************/

  // Initialisation

  const uint32_t seed = 0;
//const uint32_t seed = static_cast<uint32_t>(time(NULL));

  srand(seed);

  const  int32_t percent_transaction_address     = 75;
  const  int32_t percent_transaction_decod       = 75;
  const  int32_t percent_transaction_event_reset = 10;
  const  int32_t percent_icache_hit              = 100;
  const  int32_t icache_miss_penality            = 10;

  SC_START(0);
  LABEL("Initialisation");

  LABEL("Reset");
  in_NRESET->write(0);
  SC_START(5);
  in_NRESET->write(1);  

  LABEL("Loop of Test");

  list<Tgeneral_data_t> list_wait_decod;
  list<entry_t>         list_req_icache;
  
  Tgeneral_data_t modulo_iberr = 23;
  Tinstruction_t  xor_inst     = 0xdeadbeef;
  Tgeneral_data_t address;
  for (uint32_t iteration=0; iteration<NB_ITERATION; iteration ++)
    {
      LABEL("Iteration %d",iteration);

      int32_t nb_request_in  = 64;
      int32_t nb_request_out = 64;

      address      = 0x100;

      list_wait_decod.clear();

      while ((nb_request_in  > 0) and
	     (nb_request_out > 0))
	{
	  // =====
	  // ===== ADDRESS
	  // =====
	  in_ADDRESS_VAL                         ->write((nb_request_in  > 0) and ((rand()%100)<percent_transaction_address));
	  in_ADDRESS_INSTRUCTION_ADDRESS         ->write(address);
	  uint32_t nb_inst_enable = (rand() % _param->_nb_instruction);
	  for (uint32_t i=0; i<_param->_nb_instruction; i++)
	  in_ADDRESS_INSTRUCTION_ENABLE [i]      ->write(i<=nb_inst_enable);
	  if (_param->_have_port_instruction_ptr)
	  in_ADDRESS_INST_IFETCH_PTR             ->write(address%_param->_nb_instruction);
	  in_ADDRESS_BRANCH_STATE                ->write(address%SIZE_BRANCH_STATE);
	  if (_param->_have_port_branch_update_prediction_id)
	  in_ADDRESS_BRANCH_UPDATE_PREDICTION_ID ->write(address%_param->_size_branch_update_prediction);

	  // =====
	  // ===== DECOD
	  // =====
	  for (uint32_t i=0; i<_param->_nb_instruction; i++)
	    in_DECOD_ACK [i]->write(0);
	  for (uint32_t i=0; i<_param->_nb_instruction; i++)
	    {
	      if ((rand()%100)>percent_transaction_decod)
		break;
	      in_DECOD_ACK [i]->write(1);
	    }

	  // =====
	  // ===== ICACHE_RSP
	  // =====
	  Tcontrol_t icache_rsp_val = not list_req_icache.empty() and (list_req_icache.front()._cycle == 0);

// 	  LABEL("Test ICACHE_RSP :");
// 	  LABEL(" * list_req_icache.empty()        : %d",list_req_icache.empty());
// 	  if (not list_req_icache.empty())
// 	  LABEL(" * list_req_icache.front()._cycle : %d",list_req_icache.front()._cycle);

	  in_ICACHE_RSP_VAL->write(icache_rsp_val);

	  if (icache_rsp_val)
	    {
	      for (uint32_t i=0; i<_param->_nb_instruction; i++)
		in_ICACHE_RSP_INSTRUCTION [i]->write((list_req_icache.front()._addr + 4*i)^xor_inst);
	      in_ICACHE_RSP_PACKET_ID ->write(list_req_icache.front()._packet);
	      in_ICACHE_RSP_ERROR     ->write(((list_req_icache.front()._addr % modulo_iberr)==0)?ICACHE_ERROR_BUS_ERROR:ICACHE_ERROR_NONE);
	    }

	  // =====
	  // ===== EVENT_RESET
	  // =====
	  in_EVENT_RESET_VAL->write((rand()%100)<percent_transaction_event_reset);

	  // ----------------------------------------------
	  // ----------------------------------------------
	  // ----------------------------------------------
	  SC_START(0);
	  // ----------------------------------------------
	  // ----------------------------------------------
	  // ----------------------------------------------

	  // =====
	  // ===== ADDRESS
	  // =====
	  if ( in_ADDRESS_VAL->read() and
	      out_ADDRESS_ACK->read())
	    {
	      LABEL("ADDRESS             : transaction accepted");
	      LABEL("  * address         : %.8x",address);
	      
	      list_wait_decod.push_back(address);
// 	      list_req_icache.insert(, entry_t((_param->_have_port_queue_ptr)?out_ADDRESS_IFETCH_QUEUE_ID->read():0,address));

	      uint32_t cycle = ((rand()%100)<percent_icache_hit)?1:icache_miss_penality;
	      LABEL("  * cycle           : %d",cycle);

	      list<entry_t>::iterator it;
	      for (it=list_req_icache.begin(); it != list_req_icache.end(); it++)
		if (cycle < it->_cycle)
		  break;

	      LABEL("  * list_req_icache : %d",list_req_icache.size());
	      list_req_icache.insert(it,entry_t(cycle,(_param->_have_port_queue_ptr)?out_ADDRESS_IFETCH_QUEUE_ID->read():0,address));
	      LABEL("  * list_req_icache : %d",list_req_icache.size());	      
	      address += 4*_param->_nb_instruction;
	    }

	  // =====
	  // ===== DECOD
	  // =====
	  bool have_decod_transaction = false;
	  uint32_t nb_decod_keep = 0;
	  for (uint32_t i=0; i<_param->_nb_instruction; i++)
	    {
	      if (out_DECOD_VAL [i]->read())
		{
		  LABEL("DECOD_VAL [%d]       ",i);
		  nb_decod_keep ++;
		  
		  if (in_DECOD_ACK [i]->read())
		    {
		      LABEL("DECOD_ACK [%d]       ",i);

		      have_decod_transaction = true;
		      nb_decod_keep --;

		      TEST(bool           , list_wait_decod.empty()  , false);
		      TEST(Tinstruction_t , out_DECOD_INSTRUCTION[i]->read(), (list_wait_decod.front()+4*i)^xor_inst);
		    }
		}
	    }

	  if (have_decod_transaction)
	    {

	      Tgeneral_data_t addr = list_wait_decod.front();

	      LABEL("DECOD               : transaction accepted");
	      LABEL("  * address         : %.8x",addr);
	      
	      TEST(Tgeneral_data_t   ,out_DECOD_ADDRESS                    ->read(), addr);
	      TEST(Tbranch_state_t   ,out_DECOD_BRANCH_STATE               ->read(),addr%SIZE_BRANCH_STATE);
	      if (_param->_have_port_instruction_ptr)
	      TEST(Tinst_ifetch_ptr_t,out_DECOD_INST_IFETCH_PTR            ->read(),addr%_param->_nb_instruction);
	      if (_param->_have_port_branch_update_prediction_id)
	      TEST(Tprediction_ptr_t ,out_DECOD_BRANCH_UPDATE_PREDICTION_ID->read(),addr%_param->_size_branch_update_prediction);
	      if ((addr % modulo_iberr) == 0)
	      TEST(Texception_t      ,out_DECOD_EXCEPTION                  ->read(),EXCEPTION_IFETCH_BUS_ERROR);
	      else
	      TEST(Texception_t      ,out_DECOD_EXCEPTION                  ->read(),EXCEPTION_IFETCH_NONE     );

	      // all is decod
	      LABEL("  * nb_decod_keep   : %d",nb_decod_keep);
	      if (nb_decod_keep == 0)
		{
		  LABEL("  * decod all bundle");
		  list_wait_decod.pop_front();
		  nb_request_out --;
		}
	    }

	  // =====
	  // ===== ICACHE_RSP
	  // =====
	  if (  in_ICACHE_RSP_VAL->read() and
	       out_ICACHE_RSP_ACK->read())
	    {
	      LABEL("ICACHE_RSP          : transaction accepted");
	      LABEL("  * address         : %.8x",list_req_icache.front()._addr);
	      LABEL("  * list_req_icache : %d",list_req_icache.size());
	      list_req_icache.pop_front();
	      LABEL("  * list_req_icache : %d",list_req_icache.size());

	    }

	  // =====
	  // ===== EVENT_RESET
	  // =====
	  if ( in_EVENT_RESET_VAL->read() and
	      out_EVENT_RESET_ACK->read() )
	    {
	      LABEL("EVENT_RESET         : transaction accepted");
	      list_wait_decod.clear();
	    }

	  SC_START(1);

	  for (list<entry_t>::iterator it=list_req_icache.begin(); it != list_req_icache.end(); it++)
	    {
	      LABEL("ICACHE : %d %d %.8x",it->_cycle, it->_packet, it->_addr);
	      if (it->_cycle > 0)
		it->_cycle --;
	    }
	}
    }

  /********************************************************
   * Simulation - End
   ********************************************************/

  TEST_OK ("End of Simulation");
  delete _time;

  msg(_("<%s> : ............ Stop Simulation\n"),name.c_str());

  delete in_CLOCK;
  delete in_NRESET;
  delete     in_ADDRESS_VAL                        ;
  delete    out_ADDRESS_ACK                        ;
  delete    out_ADDRESS_IFETCH_QUEUE_ID            ;
  delete []  in_ADDRESS_INSTRUCTION_ENABLE         ;
  delete     in_ADDRESS_INSTRUCTION_ADDRESS        ;
  delete     in_ADDRESS_INST_IFETCH_PTR            ;
  delete     in_ADDRESS_BRANCH_STATE               ;
  delete     in_ADDRESS_BRANCH_UPDATE_PREDICTION_ID;
  delete [] out_DECOD_VAL                          ;
  delete []  in_DECOD_ACK                          ;
  delete [] out_DECOD_INSTRUCTION                  ;
  delete    out_DECOD_ADDRESS                      ;
  delete    out_DECOD_INST_IFETCH_PTR              ;
  delete    out_DECOD_BRANCH_STATE                 ;
  delete    out_DECOD_BRANCH_UPDATE_PREDICTION_ID  ;
  delete    out_DECOD_EXCEPTION                    ;
  delete     in_ICACHE_RSP_VAL                     ;
  delete    out_ICACHE_RSP_ACK                     ;
  delete     in_ICACHE_RSP_PACKET_ID               ;
  delete []  in_ICACHE_RSP_INSTRUCTION             ;
  delete     in_ICACHE_RSP_ERROR                   ;
  delete     in_EVENT_RESET_VAL                    ;
  delete    out_EVENT_RESET_ACK                    ;

#endif

  delete _Ifetch_queue;
#ifdef STATISTICS
  delete _parameters_statistics;
#endif
}
