#include<systemc.h>


#define SC_WAIT_UNTIL(i) do { wait() ; }while( !(i) )  
#define mem_length  256 

//bool verbose = false;

/**
 * Bus protocol : full handshake with inout data + address
 **/

template <class A,class T>
SC_PROTOCOL(MemHndshk) {
  public:
  SC_OUTMASTER_P {
    sc_terminal_out<T,TT_DATA> data;
    sc_terminal_out<A,TT_ADDRESS> addr;
    sc_terminal_out<bool,TT_CONTROL> req;
    sc_terminal_in<bool,TT_CONTROL> ack;
  };
  SC_INMASTER_P {
    sc_terminal_in<T,TT_DATA> data;
    sc_terminal_out<A,TT_ADDRESS> adr;
    sc_terminal_out<bool,TT_CONTROL> req;
    sc_terminal_in<bool,TT_CONTROL> ack;
  };
  SC_INOUTMASTER_P {
    sc_terminal_inout<T,TT_DATA> data;
    sc_terminal_out<A,TT_ADDRESS> addr;
    sc_terminal_out<bool,TT_CONTROL> req;
    sc_terminal_in<bool,TT_CONTROL> ack;
    sc_terminal_out<bool,TT_CONTROL> RnW;
  };
  SC_OUTSLAVE_P {
    sc_terminal_out<T,TT_DATA> data;
    sc_terminal_in<A,TT_ADDRESS> addr;
    sc_terminal_in<bool,TT_CONTROL> req;
    sc_terminal_out<bool,TT_CONTROL> ack;
  };
  SC_INSLAVE_P {
    sc_terminal_in<T,TT_DATA> data;
    sc_terminal_in<A,TT_ADDRESS> addr;
    sc_terminal_in<bool,TT_CONTROL> req;
    sc_terminal_out<bool,TT_CONTROL> ack;
  };
  SC_INOUTSLAVE_P {
    sc_terminal_inout<T,TT_DATA> data;
    sc_terminal_in<A,TT_ADDRESS> addr;
    sc_terminal_in<bool,TT_CONTROL> req;
    sc_terminal_out<bool,TT_CONTROL> ack;
    sc_terminal_in<bool,TT_CONTROL> RnW;
  };
};
/**
 * channel : implements the functionality of original msg_read,
 * msg_write and msg_data 
 * proc_port is the port to the procesor and is still at the untimed
 * level ( using sc_indexed for passing the address )
 * mem_port is at the Bus Cycle Accurate level : uses the MemHndshk
 * bus protocol
 **/
template <class A,class T>
SC_MODULE(Bus_BCASH){
    sc_inoutslave<T,sc_indexed<T,mem_length> > proc_port;
    sc_inoutmaster<T,MemHndshk<A,T> > mem_port;
    void proc_slave_process() {
      A addr = proc_port.get_address();
      mem_port.addr = addr ;
      if (!proc_port.input()) {
        // reading
        mem_port.RnW = true ;
        mem_port.req = true ;
	SC_WAIT_UNTIL( mem_port.ack == true ) ;
        mem_port.req = false ;
	proc_port = mem_port.data;
      } else { // writing
        mem_port.RnW = false ;
	mem_port.data = proc_port ;
	mem_port.req = true ;
	SC_WAIT_UNTIL( mem_port.ack == true ) ;
        mem_port.req = false ;
      }
    }
    SC_CTOR(Bus_BCASH) {
      SC_SLAVE(proc_slave_process,proc_port);
    }
};


/**
 * processor model : at the untimed level
 * uses sc_indexed to address thenbus
 **/

template <class A,class T>
SC_MODULE( proc ) {
   sc_inoutmaster<T,sc_indexed<T,mem_length> > mem_port;
   sc_in_clk clk;
   void body( void ) {
      T data ;

	cout << "send message Write( 10, 5 )" << endl ;
      mem_port[10] = 5;
      
	cout << "send message Write( 12, 3 )" << endl ;
      mem_port[12] = 3;
      
	cout << "send message Read( 10 ) and receive data" << endl ;
      data = mem_port[10];
      
	cout << "value of data at 10th address is " << data << endl ;
      
	cout << "send message Read( 12 ) and receive data" << endl ;
      data = mem_port[12];
      
	cout << "value of data at 12th address is " << data << endl ;
      
   }
   SC_CTOR( proc ) {
     SC_THREAD( body)
       sensitive_pos <<  clk ;
   }
};


/**
 * Memory model at the Bus Cycle Accurate level
 **/


template <class A, class T>
SC_MODULE( mem_BCA ) {
  sc_inoutslave<T,MemHndshk<A,T> > mem_port;
  sc_in_clk clk ;

  T M[mem_length] ;

  void body( void ) {
    int d ;

    while( true ) {

      SC_WAIT_UNTIL( mem_port.req == true ) ;

      if ( mem_port.RnW ) { // READ request 
	  cout << "MEM_READ( " << mem_port.addr << " ) is called" << endl ;
        d = M [mem_port.addr] ;
        mem_port.data = d ;
      } else { // WRITE request
	  cout << "MEM_WRITE( " << mem_port.addr 
             << ", " << mem_port.data << " ) is called" << endl ;
	M[ mem_port.addr] = mem_port.data ;
      }
      mem_port.ack = true ;
 
      wait() ;
      mem_port.ack = false ;
    }
  }

  SC_CTOR( mem_BCA ) {
    SC_THREAD( body ) ;
    sensitive_pos << clk;
    for (int i=0; i<mem_length; i++ ) 
      M[i] =0 ;
  } ;
} ;

/**
 * top level model using a untimed version of the processor and 
 * a BCA level memory
 **/

void UT_BCASH() {

  /*
  sc_link_mp<int> proc_to_bus;
  sc_link_mp<int> mem_to_bus;
  Bus_BCASH<int,int> BUS("BUS");
  BUS.proc_port(proc_to_bus);
  BUS.mem_port(mem_to_bus);

 
  // PROCESSOR ( Behavior model and communication is at message level ) 
  proc<int, int> PROC("PROC") ;
  PROC.clk(clk) ;
  PROC.mem_port(proc_to_bus);

  // MEMORY ( RTL model and communication is at signal level )
  mem_BCA<int, int> MEM("MEM") ;
  MEM.clk(clk) ;
  MEM.mem_port(mem_to_bus) ;
  */
}

int sc_main( int argc, char** argv ) 
{
   sc_time dftime(1.0,sc_ns);          //added 
   sc_set_default_time_unit(dftime);  

   sc_time ct(5.0, sc_ns);
   sc_clock clk( "clock", ct) ;

  sc_link_mp<int> proc_to_bus;
  sc_link_mp<int> mem_to_bus;
  Bus_BCASH<int,int> BUS("BUS");
  BUS.proc_port(proc_to_bus);
  BUS.mem_port(mem_to_bus);

 
  // PROCESSOR ( Behavior model and communication is at message level ) 
  proc<int, int> PROC("PROC") ;
  PROC.clk(clk) ;
  PROC.mem_port(proc_to_bus);

  // MEMORY ( RTL model and communication is at signal level )
  mem_BCA<int, int> MEM("MEM") ;
  MEM.clk(clk) ;
  MEM.mem_port(mem_to_bus) ;

  // UT_BCASH();
   sc_time simt(10000, sc_ns);
   sc_start( simt ) ;
   return 0 ;
}


