
#include <stdint.h>
#include <systemc>
#include <sys/time.h>
#include <iostream>
#include <cstdlib>
#include <cstdarg>

#include "arithmetics.h"
#include "mapping_table.h"
#include "alloc_elems.h"
#include "vci_simple_ram.h"
//#include "vci_multi_tty.h"
#include "vci_local_ring_fast.h"
#include "virtual_dspin_router.h"
#include "vci_synthetic_initiator.h"

// MESH SIZE
#define X_MAX           2
#define Y_MAX           2
#define N_CLUSTERS      X_MAX*Y_MAX
// FLIT_WIDTH
#define WIDTH_CMD	40
#define WIDTH_RSP	33
// Face of each DSPIN Router
#define NORTH		0
#define SOUTH		1
#define EAST		2
#define WEST		3
#define LOCAL		4


int _main(int argc, char *argv[])
{
	using namespace sc_core;
	// Avoid repeating these everywhere
	using soclib::common::IntTab;
	using soclib::common::Segment;

	using soclib::common::uint32_log2;

	// Define VCI parameters
	typedef soclib::caba::VciParams<4,8,40,1,1,1,8,4,4,1> vci_param;

	
	soclib::common::Loader loader(NULL);
	// Mapping table primary network
	
	soclib::common::MappingTable maptab0(40, IntTab(2,10), IntTab(2,3), 0x00C00000);
	soclib::common::MappingTable maptab1(40, IntTab(2,10), IntTab(2,3), 0x00C00000);


	//maptab0.add(Segment("mc_r0" , MC0_R_BASE , MC0_R_SIZE , IntTab(0,0), false, true, IntTab(0,0)));
	//maptab0.add(Segment("mc_m0" , MC0_M_BASE , MC0_M_SIZE , IntTab(0,0), true ));
	//maptab0.add(Segment("mc_r1" , MC1_R_BASE , MC1_R_SIZE , IntTab(1,0), false, true, IntTab(1,0)));
	//maptab0.add(Segment("mc_m1" , MC1_M_BASE , MC1_M_SIZE , IntTab(1,0), true ));
	//maptab0.add(Segment("mc_r2" , MC2_R_BASE , MC2_R_SIZE , IntTab(2,0), false, true, IntTab(2,0)));
	//maptab0.add(Segment("mc_m2" , MC2_M_BASE , MC2_M_SIZE , IntTab(2,0), true ));
	//maptab0.add(Segment("mc_r3" , MC3_R_BASE , MC3_R_SIZE , IntTab(3,0), false, true, IntTab(3,0)));
	//maptab0.add(Segment("mc_m3" , MC3_M_BASE , MC3_M_SIZE , IntTab(3,0), true ));

	std::cout << maptab0 << std::endl;
	// Signals

	sc_clock	signal_clk("clk");
	sc_signal<bool> signal_resetn("resetn");
   
	soclib::caba::VciSignals<vci_param> ** signal_vci_ini_synth_c0 = soclib::common::alloc_elems<soclib::caba::VciSignals<vci_param> >("signal_vci_ini_synth", 2, N_CLUSTERS);
	soclib::caba::VciSignals<vci_param> ** signal_vci_ini_synth_c1 = soclib::common::alloc_elems<soclib::caba::VciSignals<vci_param> >("signal_vci_ini_synth", 2, N_CLUSTERS);
	///////////////////////////////////////////////////////////////
	// VDSPIN Signals : one level for in and out, one level for X length in the mesh, 
	//                  one level for Y length in the mesh, last level for each port of the router
	///////////////////////////////////////////////////////////////
	soclib::caba::DspinSignals<WIDTH_CMD> **** dspin_signals_cmd_c0 = soclib::common::alloc_elems<soclib::caba::DspinSignals<WIDTH_CMD> >("Dspin_cmd_signals_channel_0", 2, Y_MAX, X_MAX, 5 );
	soclib::caba::DspinSignals<WIDTH_CMD> **** dspin_signals_cmd_c1 = soclib::common::alloc_elems<soclib::caba::DspinSignals<WIDTH_CMD> >("Dspin_cmd_signals_channel_1", 2, Y_MAX, X_MAX, 5 );
	soclib::caba::DspinSignals<WIDTH_RSP> **** dspin_signals_rsp_c0 = soclib::common::alloc_elems<soclib::caba::DspinSignals<WIDTH_RSP> >("Dspin_rsp_signals_channel_0", 2, Y_MAX, X_MAX, 5 );
	soclib::caba::DspinSignals<WIDTH_RSP> **** dspin_signals_rsp_c1 = soclib::common::alloc_elems<soclib::caba::DspinSignals<WIDTH_RSP> >("Dspin_rsp_signals_channel_2", 2, Y_MAX, X_MAX, 5 );

	//soclib::caba::VciSignals<vci_param> * signal_vci_tgt_proc  = soclib::common::alloc_elems<soclib::caba::VciSignals<vci_param> >("signal_vci_tgt_proc",  N_CLUSTERS);

	// N_CLUSTERS ring.
	soclib::caba::VciLocalRingFast<vci_param, WIDTH_CMD, WIDTH_RSP> * local_ring_c0 = (soclib::caba::VciLocalRingFast<vci_param, WIDTH_CMD, WIDTH_RSP> *) malloc(sizeof(soclib::caba::VciLocalRingFast<vci_param, WIDTH_CMD, WIDTH_RSP> ) * N_CLUSTERS) ;
	soclib::caba::VciLocalRingFast<vci_param, WIDTH_CMD, WIDTH_RSP> * local_ring_c1 = (soclib::caba::VciLocalRingFast<vci_param, WIDTH_CMD, WIDTH_RSP> *) malloc(sizeof(soclib::caba::VciLocalRingFast<vci_param, WIDTH_CMD, WIDTH_RSP> ) * N_CLUSTERS) ;
	for(int i = 0 ; i < N_CLUSTERS ; i++){                                                            // ringid, fifo, fifo, nb_init, nb_tgt
		new(&local_ring_c0[i]) soclib::caba::VciLocalRingFast<vci_param, WIDTH_CMD, WIDTH_RSP> ("cluster_c0" + i,maptab0, IntTab(i), 2, 18, 1, 1);
		new(&local_ring_c1[i]) soclib::caba::VciLocalRingFast<vci_param, WIDTH_CMD, WIDTH_RSP> ("cluster_c1" + i,maptab1, IntTab(i), 2, 18, 1, 1);
	}

	// Virtual dspin routers
	soclib::caba::VirtualDspinRouter<WIDTH_CMD> ** routers_cmd = (soclib::caba::VirtualDspinRouter<WIDTH_CMD> **) malloc(sizeof(soclib::caba::VirtualDspinRouter<WIDTH_CMD> *) * Y_MAX);
	soclib::caba::VirtualDspinRouter<WIDTH_RSP> ** routers_rsp = (soclib::caba::VirtualDspinRouter<WIDTH_RSP> **) malloc(sizeof(soclib::caba::VirtualDspinRouter<WIDTH_RSP> *) * Y_MAX);

	for(int i = 0; i < Y_MAX; i++ ){
	  routers_cmd[i] = (soclib::caba::VirtualDspinRouter<WIDTH_CMD> * ) malloc(sizeof(soclib::caba::VirtualDspinRouter<WIDTH_CMD>) * X_MAX);
	  routers_rsp[i] = (soclib::caba::VirtualDspinRouter<WIDTH_RSP> * ) malloc(sizeof(soclib::caba::VirtualDspinRouter<WIDTH_RSP>) * X_MAX);
	  for(int j = 0; j < X_MAX; i++){
	    new(&routers_cmd[i][j]) soclib::caba::VirtualDspinRouter<WIDTH_CMD> ("VDspinRouterCMD" + i + j, j, i, soclib::common::uint32_log2(X_MAX), soclib::common::uint32_log2(Y_MAX), 4, 4);
	    new(&routers_rsp[i][j]) soclib::caba::VirtualDspinRouter<WIDTH_RSP> ("VDspinRouterRSP" + i + j, j, i, soclib::common::uint32_log2(X_MAX), soclib::common::uint32_log2(Y_MAX), 4, 4);
	  }
	}


	///////////////////////////////////////////////////////////////
	// Components
	///////////////////////////////////////////////////////////////

	// N_CLUSTERS VCI Synthetic Initiator (1 initiator per cluster/network)
	soclib::caba::VciSyntheticInitiator<vci_param> * initiator_c0 = (soclib::caba::VciSyntheticInitiator<vci_param> *) malloc(sizeof(soclib::caba::VciSyntheticInitiator<vci_param> )* N_CLUSTERS);
	soclib::caba::VciSyntheticInitiator<vci_param> * initiator_c1 = (soclib::caba::VciSyntheticInitiator<vci_param> *) malloc(sizeof(soclib::caba::VciSyntheticInitiator<vci_param> )* N_CLUSTERS);
	for(int i = 0 ; i < Y_MAX; i++)
		for(int j = 0 ; j < X_MAX ; j++){
			new(&initiator_c0[X_MAX*i+j]) soclib::caba::VciSyntheticInitiator<vci_param> ("Initiator_c0" + (i*X_MAX+j), maptab0, IntTab(i,j), 16, 0.5, 2, X_MAX, Y_MAX ); //, 0, 0, 0, 0, 0);
			new(&initiator_c1[X_MAX*i+j]) soclib::caba::VciSyntheticInitiator<vci_param> ("Initiator_c1" + (i*X_MAX+j), maptab0, IntTab(i,j), 16, 0.5, 2, X_MAX, Y_MAX ); //, 0, 0, 0, 0, 0);
		}

	soclib::caba::VciSimpleRam<vci_param> * ram_c0 = (soclib::caba::VciSimpleRam<vci_param> *) malloc(sizeof(soclib::caba::VciSimpleRam<vci_param>) * N_CLUSTERS);
	soclib::caba::VciSimpleRam<vci_param> * ram_c1 = (soclib::caba::VciSimpleRam<vci_param> *) malloc(sizeof(soclib::caba::VciSimpleRam<vci_param>) * N_CLUSTERS);
	for(int i = 0 ; i < Y_MAX ; i++)
		for(int j = 0 ; j < X_MAX ; j++){
			new(&ram_c0[X_MAX*i+j]) soclib::caba::VciSimpleRam<vci_param> ("Ram_target_c0" + (i*X_MAX+j), IntTab(i,j), maptab0, loader, 0);
			new(&ram_c1[X_MAX*i+j]) soclib::caba::VciSimpleRam<vci_param> ("Ram_target_c1" + (i*X_MAX+j), IntTab(i,j), maptab1, loader, 0);
		}

	///////////////////////////////////////////////////////////////
	// Connection of Synthetic Initiator to each local ring per cluster
	///////////////////////////////////////////////////////////////
	for(int i = 0 ; i < N_CLUSTERS ; i++){
	  local_ring_c0[i].p_clk(signal_clk);
	  local_ring_c0[i].p_resetn(signal_resetn);
	  local_ring_c0[i].p_to_initiator[0](signal_vci_ini_synth_c0[0][i]);
	  local_ring_c0[i].p_to_target[0](signal_vci_ini_synth_c0[1][i]);
	  initiator_c0[i].p_clk(signal_clk);
	  initiator_c0[i].p_resetn(signal_resetn);
	  initiator_c0[i].p_vci(signal_vci_ini_synth_c0[0][i]);
	  ram_c0[i].p_clk(signal_clk);
	  ram_c0[i].p_resetn(signal_resetn);
	  ram_c0[i].p_vci(signal_vci_ini_synth_c0[1][i]);
	  local_ring_c1[i].p_clk(signal_clk);
	  local_ring_c1[i].p_resetn(signal_resetn);
	  local_ring_c1[i].p_to_initiator[0](signal_vci_ini_synth_c1[0][i]);
	  local_ring_c1[i].p_to_target[0](signal_vci_ini_synth_c1[1][i]);
	  initiator_c1[i].p_clk(signal_clk);
	  initiator_c1[i].p_resetn(signal_resetn);
	  initiator_c1[i].p_vci(signal_vci_ini_synth_c1[0][i]);
	  ram_c1[i].p_clk(signal_clk);
	  ram_c1[i].p_resetn(signal_resetn);
	  ram_c1[i].p_vci(signal_vci_ini_synth_c1[1][i]);
	}
	
	///////////////////////////////////////////////////////////////
	// Connection of each VDspin Router to each local ring and 
	// neighbors VDspin Router
	///////////////////////////////////////////////////////////////
	for(int i = 0; i < Y_MAX ; i++){
	  for(int j = 0; j < X_MAX ; j++){
	    routers_cmd[i][j].p_clk(signal_clk);
	    routers_cmd[i][j].p_resetn(signal_resetn);
	    routers_rsp[i][j].p_clk(signal_clk);
	    routers_rsp[i][j].p_resetn(signal_resetn);
	    local_ring_c0[i*X_MAX+j].p_gate_cmd_in( dspin_signals_cmd_c0[0][i][j][LOCAL]);
	    local_ring_c0[i*X_MAX+j].p_gate_cmd_out(dspin_signals_cmd_c0[1][i][j][LOCAL] );
	    local_ring_c0[i*X_MAX+j].p_gate_rsp_in( dspin_signals_rsp_c0[0][i][j][LOCAL]);
	    local_ring_c0[i*X_MAX+j].p_gate_rsp_out(dspin_signals_rsp_c0[1][i][j][LOCAL] );
	    local_ring_c1[i*X_MAX+j].p_gate_cmd_in( dspin_signals_cmd_c1[0][i][j][LOCAL]);
	    local_ring_c1[i*X_MAX+j].p_gate_cmd_out(dspin_signals_cmd_c1[1][i][j][LOCAL] );
	    local_ring_c1[i*X_MAX+j].p_gate_rsp_in( dspin_signals_rsp_c1[0][i][j][LOCAL]);
	    local_ring_c1[i*X_MAX+j].p_gate_rsp_out(dspin_signals_rsp_c1[1][i][j][LOCAL] );
	    for(int k = 0; k < 5; k++){
	      if(i == 0){
	        if(j == 0){
		  routers_cmd[i][j].p_out[0][k](dspin_signals_cmd_c0[0][i][j][k]);
		  routers_cmd[i][j].p_out[1][k](dspin_signals_cmd_c1[0][i][j][k]);
		  routers_cmd[i][j].p_in[0][k](dspin_signals_cmd_c0[1][i][j][k]);
		  routers_cmd[i][j].p_in[1][k](dspin_signals_cmd_c1[1][i][j][k]);
		  routers_rsp[i][j].p_out[0][k](dspin_signals_rsp_c0[0][i][j][k]);
		  routers_rsp[i][j].p_out[1][k](dspin_signals_rsp_c1[0][i][j][k]);
		  routers_rsp[i][j].p_in[0][k](dspin_signals_rsp_c0[1][i][j][k]);
		  routers_rsp[i][j].p_in[1][k](dspin_signals_rsp_c1[1][i][j][k]);
		} else {
		  if(k == WEST){
		 	routers_cmd[i][j].p_out[0][k](dspin_signals_cmd_c0[1][i][j-1][EAST]);
		 	routers_cmd[i][j].p_out[1][k](dspin_signals_cmd_c1[1][i][j-1][EAST]);
		 	routers_cmd[i][j].p_in[0][k](dspin_signals_cmd_c0[0][i][j-1][EAST]);
		 	routers_cmd[i][j].p_in[1][k](dspin_signals_cmd_c1[0][i][j-1][EAST]);
		 	routers_rsp[i][j].p_out[0][k](dspin_signals_rsp_c0[1][i][j-1][EAST]);
		 	routers_rsp[i][j].p_out[1][k](dspin_signals_rsp_c1[1][i][j-1][EAST]);
		 	routers_rsp[i][j].p_in[0][k](dspin_signals_rsp_c0[0][i][j-1][EAST]);
		 	routers_rsp[i][j].p_in[1][k](dspin_signals_rsp_c1[0][i][j-1][EAST]);
		  } else {
		 	routers_cmd[i][j].p_out[0][k](dspin_signals_cmd_c0[0][i][j][k]);
		 	routers_cmd[i][j].p_out[1][k](dspin_signals_cmd_c1[0][i][j][k]);
		 	routers_cmd[i][j].p_in[0][k](dspin_signals_cmd_c0[1][i][j][k]);
		 	routers_cmd[i][j].p_in[1][k](dspin_signals_cmd_c1[1][i][j][k]);
		 	routers_rsp[i][j].p_out[0][k](dspin_signals_rsp_c0[0][i][j][k]);
		 	routers_rsp[i][j].p_out[1][k](dspin_signals_rsp_c1[0][i][j][k]);
		 	routers_rsp[i][j].p_in[0][k](dspin_signals_rsp_c0[1][i][j][k]);
		 	routers_rsp[i][j].p_in[1][k](dspin_signals_rsp_c1[1][i][j][k]);
		  }
		}
	      } else {
		if(k == SOUTH){
		 	routers_cmd[i][j].p_out[0][k](dspin_signals_cmd_c0[1][i-1][j][NORTH]);
		 	routers_cmd[i][j].p_out[1][k](dspin_signals_cmd_c1[1][i-1][j][NORTH]);
		 	routers_cmd[i][j].p_in[0][k](dspin_signals_cmd_c0[0][i-1][j][NORTH]);
		 	routers_cmd[i][j].p_in[1][k](dspin_signals_cmd_c1[0][i-1][j][NORTH]);
		 	routers_rsp[i][j].p_out[0][k](dspin_signals_rsp_c0[1][i-1][j][NORTH]);
		 	routers_rsp[i][j].p_out[1][k](dspin_signals_rsp_c1[1][i-1][j][NORTH]);
		 	routers_rsp[i][j].p_in[0][k](dspin_signals_rsp_c0[0][i-1][j][NORTH]);
		 	routers_rsp[i][j].p_in[1][k](dspin_signals_rsp_c1[0][i-1][j][NORTH]);
		} else if(k == WEST){
		   if(j == 0){
		 	routers_cmd[i][j].p_out[0][k](dspin_signals_cmd_c0[0][i][j][k]);
		 	routers_cmd[i][j].p_out[1][k](dspin_signals_cmd_c1[0][i][j][k]);
		 	routers_cmd[i][j].p_in[0][k](dspin_signals_cmd_c0[1][i][j][k]);
		 	routers_cmd[i][j].p_in[1][k](dspin_signals_cmd_c1[1][i][j][k]);
		 	routers_rsp[i][j].p_out[0][k](dspin_signals_rsp_c0[0][i][j][k]);
		 	routers_rsp[i][j].p_out[1][k](dspin_signals_rsp_c1[0][i][j][k]);
		 	routers_rsp[i][j].p_in[0][k](dspin_signals_rsp_c0[1][i][j][k]);
		 	routers_rsp[i][j].p_in[1][k](dspin_signals_rsp_c1[1][i][j][k]);
		   } else {
		 	routers_cmd[i][j].p_out[0][k](dspin_signals_cmd_c0[1][i][j-1][EAST]);
		 	routers_cmd[i][j].p_out[1][k](dspin_signals_cmd_c1[1][i][j-1][EAST]);
		 	routers_cmd[i][j].p_in[0][k](dspin_signals_cmd_c0[0][i][j-1][EAST]);
		 	routers_cmd[i][j].p_in[1][k](dspin_signals_cmd_c1[0][i][j-1][EAST]);
		 	routers_rsp[i][j].p_out[0][k](dspin_signals_rsp_c0[1][i][j-1][EAST]);
		 	routers_rsp[i][j].p_out[1][k](dspin_signals_rsp_c1[1][i][j-1][EAST]);
		 	routers_rsp[i][j].p_in[0][k](dspin_signals_rsp_c0[0][i][j-1][EAST]);
		 	routers_rsp[i][j].p_in[1][k](dspin_signals_rsp_c1[0][i][j-1][EAST]);
		   }
		} else {
		 	routers_cmd[i][j].p_out[0][k](dspin_signals_cmd_c0[0][i][j][k]);
		 	routers_cmd[i][j].p_out[1][k](dspin_signals_cmd_c1[0][i][j][k]);
		 	routers_cmd[i][j].p_in[0][k](dspin_signals_cmd_c0[1][i][j][k]);
		 	routers_cmd[i][j].p_in[1][k](dspin_signals_cmd_c1[1][i][j][k]);
		 	routers_rsp[i][j].p_out[0][k](dspin_signals_rsp_c0[0][i][j][k]);
		 	routers_rsp[i][j].p_out[1][k](dspin_signals_rsp_c1[0][i][j][k]);
		 	routers_rsp[i][j].p_in[0][k](dspin_signals_rsp_c0[1][i][j][k]);
		 	routers_rsp[i][j].p_in[1][k](dspin_signals_rsp_c1[1][i][j][k]);
		}
	      }
	    }
	  }
	}

	////////////////////////////////////////////////////////



	////////////////////////////////////////////////
	//             Simulation Loop                //
	////////////////////////////////////////////////
	int ncycles;

	if (argc == 2) {
		ncycles = std::atoi(argv[1]);
	} else {
		std::cerr
			<< std::endl
			<< "The number of simulation cycles must "
			   "be defined in the command line"
			<< std::endl;
		exit(1);
	}

	sc_start(sc_core::sc_time(0, SC_NS));
	signal_resetn = false;

	sc_start(sc_core::sc_time(1, SC_NS));
	signal_resetn = true;

	for (int i = 0; i < ncycles ; i+=100000) {
		sc_start(sc_core::sc_time(100000, SC_NS));
		
	}

        std::cout << "Hit ENTER to end simulation" << std::endl;
        char buf[1];

	std::cin.getline(buf,1);
	return EXIT_SUCCESS;
}

int sc_main (int argc, char *argv[])
{
	try {
		return _main(argc, argv);
	} catch (std::exception &e) {
		std::cout << e.what() << std::endl;
	} catch (...) {
		std::cout << "Unknown exception occured" << std::endl;
		throw;
	}
	return 1;
}
