#ifdef SYSTEMC
/*
 * $Id: Return_Address_Stack_transition.cpp 111 2009-02-27 18:37:40Z rosiere $
 *
 * [ Description ]
 * 
 */

#include "Behavioural/Core/Multi_Front_end/Front_end/Prediction_unit/Return_Address_Stack/include/Return_Address_Stack.h"

namespace morpheo                    {
namespace behavioural {
namespace core {
namespace multi_front_end {
namespace front_end {
namespace prediction_unit {
namespace return_address_stack {


#undef  FUNCTION
#define FUNCTION "Return_Address_Stack::transition"
  void Return_Address_Stack::transition (void)
  {
    log_begin(Return_Address_Stack,FUNCTION);
    log_function(Return_Address_Stack,FUNCTION,_name.c_str());

    if (PORT_READ(in_NRESET)==0)
      {
        // Reset all structure
	for (uint32_t i=0; i<_param->_nb_context; i++)
	  {
	    reg_TOP    [i] = 0;
// 	    reg_BOTTOM [i] = 0;
	    reg_NB_ELT [i] = 0;

	    reg_PREDICT_TOP    [i] = 0;
// 	    reg_PREDICT_BOTTOM [i] = 0;
	    reg_PREDICT_NB_ELT [i] = 0;
	  }
      }
    else
      {
	// ===================================================================
	// =====[ PREDICT ]===================================================
	// ===================================================================
	for (uint32_t i=0; i<_param->_nb_inst_predict; i++)
	  if (PORT_READ(in_PREDICT_VAL [i]) and internal_PREDICT_ACK [i])
	    {
	      log_printf(TRACE,Return_Address_Stack,FUNCTION,"  * PREDICT [%d] : Transaction",i);
              
              // Read information and pointer
	      Tcontext_t context    = (_param->_have_port_context_id)?PORT_READ(in_PREDICT_CONTEXT_ID [i]):0;
	      Tcontrol_t push       = PORT_READ(in_PREDICT_PUSH [i]);

	      Tptr_t     top_old    = reg_PREDICT_TOP    [context];
	      Tptr_t     top_new    = top_old;
// 	      Tptr_t     bottom_old = reg_PREDICT_BOTTOM [context];
// 	      Tptr_t     bottom_new = bottom_old;
	      Tptr_t     nb_elt_old = reg_PREDICT_NB_ELT [context];
	      Tptr_t     nb_elt_new = nb_elt_old;

	      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * context : %d",context);

	      // Hit  : push or not empty
	      // Miss : ifetch is stall, no update

              // Test if hit
	      if (internal_PREDICT_HIT [i])
		{
		  log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * before");
		  log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_top     : %d",top_old);
// 		  log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_bottom  : %d",bottom_old);
		  log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_nb_elt  : %d",nb_elt_old);

                  // Test if push
		  if (push)
		    {
                      Taddress_t address = PORT_READ(in_PREDICT_ADDRESS_PUSH [i]);

                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * push (call procedure)");
                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * address_push        : 0x%.8x",address);

		      // push : increase the top (circular)
		      top_new = (top_old+1)%_param->_size_queue[context];

                      // Write new value in Queue
		      reg_stack [context][top_new]._address = address;

		      // Test if full
                      //   -> is full, the push erase the oldest value in stack, also nb_elt is the same
                      //   -> is not full, increase nb_elt
// 		      if (nb_elt_old==_param->_size_queue[context])
// 			bottom_new = (bottom_old+1)%_param->_size_queue[context];
//                       else
// 			nb_elt_new ++;
		      if (nb_elt_old!=_param->_size_queue[context])
			nb_elt_new ++;
		    }
		  else
		    {
		      // pop
                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * pop (return procedure)");

		      // Test if the stack is empty
		      if (nb_elt_old>0)
			{
			  top_new = (top_old==0)?(_param->_size_queue[context]-1):(top_old-1);
			  nb_elt_new --;
			}
                      // no else : can't pop
		    }
		  
                  // Write new pointer
		  reg_PREDICT_TOP    [context] = top_new;
// 		  reg_PREDICT_BOTTOM [context] = bottom_new;
		  reg_PREDICT_NB_ELT [context] = nb_elt_new;

		  log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * after");
		  log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_top     : %d",top_new);
// 		  log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_bottom  : %d",bottom_new);
		  log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_nb_elt  : %d",nb_elt_new);
                }
            }

	// ===================================================================
	// =====[ DECOD ]=====================================================
	// ===================================================================
	for (uint32_t i=0; i<_param->_nb_inst_decod; i++)
	  if (PORT_READ(in_DECOD_VAL [i]) and internal_DECOD_ACK [i])
	    {
	      log_printf(TRACE,Return_Address_Stack,FUNCTION,"  * DECOD [%d] : Transaction",i);

              // Read information
	      Tcontext_t context    = (_param->_have_port_context_id)?PORT_READ(in_DECOD_CONTEXT_ID [i]):0;
	      Tcontrol_t push       = PORT_READ(in_DECOD_PUSH [i]);

              // Read pointer
	      Tptr_t     top_old    = reg_TOP    [context];
	      Tptr_t     top_new    = top_old;
// 	      Tptr_t     bottom_old = reg_BOTTOM [context];
// 	      Tptr_t     bottom_new = bottom_old;
	      Tptr_t     nb_elt_old = reg_NB_ELT [context];
	      Tptr_t     nb_elt_new = nb_elt_old;

	      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * context : %d",context);

	      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * before");
              log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_top     : %d",top_old);
//            log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_bottom  : %d",bottom_old);
              log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_nb_elt  : %d",nb_elt_old);

              // Test if push
              if (push)
                {
                  Taddress_t address = PORT_READ(in_DECOD_ADDRESS_PUSH [i]);
                  
                  log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * push (call procedure)");
                  log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * address_push        : 0x%.8x",address);
                  
                  // push : increase the top (circular)
                  top_new = (top_old+1)%_param->_size_queue[context];
                  
                  // Write new value in Queue
                  reg_stack [context][top_new]._address = address;
                  
                  // Test if full
                  //   -> is full, the push erase the oldest value in stack, also nb_elt is the same
                  //   -> is not full, increase nb_elt
//                   if (nb_elt_old==_param->_size_queue[context])
//                     bottom_new = (bottom_old+1)%_param->_size_queue[context];
//                   else
//                     nb_elt_new ++;
                  if (nb_elt_old!=_param->_size_queue[context])
                    nb_elt_new ++;
                }
              else
                {
                  // pop
                  log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * pop (return procedure)");
                  
                  // Test if the stack is empty
                  if (nb_elt_old>0)
                    {
                      top_new = (top_old==0)?(_param->_size_queue[context]-1):(top_old-1);
                      nb_elt_new --;
                    }
                  // no else : can't pop
                }
              
              // Write new pointer
              reg_TOP    [context] = top_new;
//            reg_BOTTOM [context] = bottom_new;
              reg_NB_ELT [context] = nb_elt_new;
              
              log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * after");
              log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_top     : %d",top_new);
//            log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_bottom  : %d",bottom_new);
              log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_nb_elt  : %d",nb_elt_new);
              
	      // have previous miss of ifetch ?
	      // 2 miss : 
	      //   1) miss predict : is very limited (local at context), can be update very quickly
	      //   2) miss decod   : result is in commit stage ... 

              // manage by Update_Fetch_Prediction_Table and Update_Prediction_Table
              // Note : 
              //   if decod miss : ifetch can have predict call and return branchement. Also, the head of decod can be false

	      Tcontrol_t miss       = PORT_READ(in_DECOD_MISS_PREDICTION [i]);

              log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * miss                : %d",miss);

	      if (miss)
		{

// 		  reg_PREDICT_BOTTOM [context] = reg_BOTTOM [context];
		  reg_PREDICT_TOP    [context] = reg_TOP    [context];
		  reg_PREDICT_NB_ELT [context] = reg_NB_ELT [context];
		}
	    }

	// ===================================================================
	// =====[ UPDATE ]===================================================
	// ===================================================================
	for (uint32_t i=0; i<_param->_nb_inst_update; i++)
	  if (PORT_READ(in_UPDATE_VAL [i]) and internal_UPDATE_ACK [i])
	    {
	      log_printf(TRACE,Return_Address_Stack,FUNCTION,"  * UPDATE [%d] : Transaction",i);

              Tcontext_t context_id        = (_param->_have_port_context_id)?PORT_READ(in_UPDATE_CONTEXT_ID [i]):0;
              Tcontrol_t flush             = PORT_READ(in_UPDATE_FLUSH             [i]);

	      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * context_id        : %d",context_id);
	      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * flush             : %d",flush     );
              
              // An miss prediction on call/return = Return Address Stack is corrupted.
              if (flush)
                {
                  Tcontrol_t push  = PORT_READ(in_UPDATE_PUSH [i]);
                  Tptr_t     value = (push)?1:0;

                  // All pointer is set at 0
                  reg_TOP    [context_id] = value;
//                reg_BOTTOM [context_id] = 0;
                  reg_NB_ELT [context_id] = value;
                  
                  reg_PREDICT_TOP    [context_id] = value;
//                reg_PREDICT_BOTTOM [context_id] = 0;
                  reg_PREDICT_NB_ELT [context_id] = value;

                  if (push)
                    {
                      // reinsert push value
                      Taddress_t address = PORT_READ(in_UPDATE_ADDRESS [i]);
                  
                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * flush and push");
                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * address_push        : 0x%.8x",address);

                      reg_stack [context_id][value]._address = address;
                    }
                }
              else
                {
                  // if miss_prediction -> restore queue
                  // else, the prediction is correct
                  Tcontrol_t miss_prediction   = PORT_READ(in_UPDATE_MISS_PREDICTION   [i]);

                  log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * miss_prediction   : %d",miss_prediction);

#ifdef DEBUG_TEST
                  Tptr_t     index             = PORT_READ(in_UPDATE_INDEX             [i]);
                  Tcontrol_t prediction_ifetch = PORT_READ(in_UPDATE_PREDICTION_IFETCH [i]);
                  log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * index             : %d",index);
                  log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * prediction_ifetch : %d",prediction_ifetch);

//                   if (prediction_ifetch)
//                     {
//                       if (index != reg_PREDICT_TOP [context_id])
//                         throw ERRORMORPHEO(FUNCTION,_("Index is different of predict_top"));
//                     }
//                   else
//                     {
//                       if (index != reg_TOP [context_id])
//                         throw ERRORMORPHEO(FUNCTION,_("Index is different of top"));
//                     }
                  
#endif
                  if (miss_prediction)
                    {
                      Tcontrol_t push              = PORT_READ(in_UPDATE_PUSH              [i]);
                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * push              : %d",push);
#ifndef DEBUG_TEST
                      Tptr_t     index             = PORT_READ(in_UPDATE_INDEX             [i]);
                      Tcontrol_t prediction_ifetch = PORT_READ(in_UPDATE_PREDICTION_IFETCH [i]);
                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * index             : %d",index);
                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * prediction_ifetch : %d",prediction_ifetch);
#endif

                      Tptr_t     top_old    = (prediction_ifetch)?reg_PREDICT_TOP    [context_id]:reg_TOP    [context_id];
                      Tptr_t     top_new    = top_old;

//                    Tptr_t     bottom_old = (prediction_ifetch)?reg_PREDICT_BOTTOM [context_id]:reg_BOTTOM [context_id];
//                    Tptr_t     bottom_new = bottom_old;

                      Tptr_t     nb_elt_old = (prediction_ifetch)?reg_PREDICT_NB_ELT [context_id]:reg_NB_ELT [context_id];
                      Tptr_t     nb_elt_new = nb_elt_old;
                      
                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * before");
                      if (prediction_ifetch)
                        {
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_top     : %d",top_old);
//                        log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_bottom  : %d",bottom_old);
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_nb_elt  : %d",nb_elt_old);
                        }
                      else
                        {
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_top     : %d",top_old);
//                        log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_bottom  : %d",bottom_old);
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_nb_elt  : %d",nb_elt_old);
                        }

                      // if previous is push, pop the value
                      // else is previous is pop, push the poped value
                      if (push)
                        {
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * previous is push, now pop");

                          // previous is push, now must be pop

                          // Test if the stack is empty (if previous flush)
                          if (nb_elt_old>0)
                            {
                              top_new = (top_old==0)?(_param->_size_queue[context_id]-1):(top_old-1);
                              nb_elt_new --;
                            }
                        }
                      else
                        {
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * previous is pop, now push");

                          // previous is pop, now must be push
                          Taddress_t address           = PORT_READ(in_UPDATE_ADDRESS           [i]);

                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * address       : 0x%.8x",address);

                          // push : increase the top (circular)
//                        if (nb_elt_old==_param->_size_queue[context_id])
//                          bottom_new = (bottom_old+1)%_param->_size_queue[context_id];
//                        else
//                          nb_elt_new ++;

                          if (nb_elt_old!=_param->_size_queue[context_id])
                            nb_elt_new ++;

                          top_new = index;
                          
                          reg_stack [context_id][top_new]._address = address;
                        }

                      log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * after");

                      if (prediction_ifetch)
                        {
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_top     : %d",reg_TOP    [context_id]);
//                        log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_bottom  : %d",reg_BOTTOM [context_id]);
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_nb_elt  : %d",reg_NB_ELT [context_id]);

                          reg_PREDICT_TOP    [context_id] = reg_TOP    [context_id];
//                        reg_PREDICT_BOTTOM [context_id] = reg_BOTTOM [context_id];
                          reg_PREDICT_NB_ELT [context_id] = reg_NB_ELT [context_id];

//                           log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_top     : %d",top_new   );
// //                        log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_bottom  : %d",bottom_new);
//                           log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_predict_nb_elt  : %d",nb_elt_new);

//                           reg_PREDICT_TOP    [context_id] = top_new   ;
// //                        reg_PREDICT_BOTTOM [context_id] = bottom_new;
//                           reg_PREDICT_NB_ELT [context_id] = nb_elt_new;

                        }
                      else
                        {
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_top     : %d",top_new);
//                        log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_bottom  : %d",bottom_new);
                          log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_nb_elt  : %d",nb_elt_new);

                          reg_TOP            [context_id] = top_new   ;
//                        reg_BOTTOM         [context_id] = bottom_new;
                          reg_NB_ELT         [context_id] = nb_elt_new;
                          reg_PREDICT_TOP    [context_id] = top_new   ;
//                        reg_PREDICT_BOTTOM [context_id] = bottom_new;
                          reg_PREDICT_NB_ELT [context_id] = nb_elt_new;
                        }
                    }
                }
	    }
      }

#if defined(DEBUG_Return_Address_Stack) and DEBUG>=DEBUG_TRACE
    log_printf(TRACE,Return_Address_Stack,FUNCTION,"  * Dump RAS");
    for (uint32_t i=0; i<_param->_nb_context; ++i)
      {
        log_printf(TRACE,Return_Address_Stack,FUNCTION,"    * Return Address Stack [%d]",i);
        log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_TOP            : %d",reg_TOP            [i]);
//      log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_BOTTOM         : %d",reg_BOTTOM         [i]);
        log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_NB_ELT         : %d",reg_NB_ELT         [i]);
        log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_PREDICT_TOP    : %d",reg_PREDICT_TOP    [i]);
//      log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_PREDICT_BOTTOM : %d",reg_PREDICT_BOTTOM [i]);
        log_printf(TRACE,Return_Address_Stack,FUNCTION,"      * reg_PREDICT_NB_ELT : %d",reg_PREDICT_NB_ELT [i]);

        for (uint32_t j=0; j<_param->_size_queue[i]; ++j)
          log_printf(TRACE,Return_Address_Stack,FUNCTION,"      [%d] %.8x (%.8x)",j,reg_stack [i][j]._address,reg_stack [i][j]._address<<2);
      }
#endif

#if defined(STATISTICS) or defined(VHDL_TESTBENCH)
    end_cycle ();
#endif

    log_end(Return_Address_Stack,FUNCTION);
  };

}; // end namespace return_address_stack
}; // end namespace prediction_unit
}; // end namespace front_end
}; // end namespace multi_front_end
}; // end namespace core

}; // end namespace behavioural
}; // end namespace morpheo              
#endif
