#ifndef _SOCKET_IFC_HPP_
#define _SOCKET_IFC_HPP_

#include <systemc.h>
#include <esl.h>

// ----------------
// Requests across a socket

enum ReqOp { Req_NOP, WR, RD };

typedef sc_uint <10>  ReqInfo;
typedef sc_uint <24>  ReqAddr;
typedef sc_uint <32>  ReqData;

struct Socket_Req {
  ReqOp    reqOp;
  ReqInfo  reqInfo;
  ReqAddr  reqAddr;
  ReqData  reqData;
};

// ================================================================
// Socket Responses

enum RespOp { Rsp_NOP, OK };

typedef sc_uint <10>    RespInfo;
typedef sc_uint <1>     RespAddr;
typedef sc_uint <32>    RespData;

struct Socket_Resp {
  RespOp    respOp;
  RespInfo  respInfo;
  RespAddr  respAddr;
  RespData  respData;
};

// Define an inequality operator on responses, for comparing
// an actual response to an expected response, in the testbench

bool differs( const Socket_Resp  &resp1 ,
	      const Socket_Resp  &resp2 )
{
  return
    (resp1.respOp   != resp2.respOp)   ||
    (resp1.respInfo != resp2.respInfo) ||
    (resp1.respAddr != resp2.respAddr) ||
    (resp1.respData != resp2.respData);
}

// ================================================================
// Help functions for displaying requests and responses

void
display_ReqOp (string s1, ReqOp op, string s2)
{
  cout << s1;
  switch (op) {
  case Req_NOP : cout << "Req_NOP"; break;
  case WR      : cout << "WR";   break;
  case RD      : cout << "RD";   break;
  }
  cout << s2;
}

void
display_Socket_Req (string s1, Socket_Req req, string s2)
{
  cout << s1;
  display_ReqOp ("Socket_Req{", req.reqOp, "");
  if (req.reqOp != Req_NOP)
    cout << hex << ", " << req.reqInfo.to_uint () << ", "
	 << req.reqAddr.to_uint () << ", " << req.reqData.to_uint ();
  cout << "}" << s2;
}

void
display_RespOp (string s1, RespOp op, string s2)
{
  cout << s1;
  switch (op) {
  case Rsp_NOP: cout <<  "Rsp_NOP"; break;
  case OK     : cout <<  "OK";    break;
  }
  cout << s2;
}

void
display_Socket_Resp (string s1, Socket_Resp resp, string s2)
{
  cout << s1;
  display_RespOp ("Socket_Resp{", resp.respOp, "");
  if (resp.respOp != Rsp_NOP)
    cout << hex << ", " << resp.respInfo.to_uint () << ", "
	 << resp.respAddr.to_uint () << ", "
	 << resp.respData.to_uint () <<  "}" << s2;
}

// ================================================================
// Definitions of master and slave interfaces according to the
// wire-level socket definition
// These interfaces will be "always ready, always enabled"
// so that there are no extra signal wires beyond what is implied by
// the argument and result types of the methods

ESL_INTERFACE (Socket_master_req_ifc) {
  ESL_METHOD_VALUE_INTERFACE  ( getReqOp,   ReqOp   );
  ESL_METHOD_VALUE_INTERFACE  ( getReqInfo, ReqInfo );
  ESL_METHOD_VALUE_INTERFACE  ( getReqAddr, ReqAddr );
  ESL_METHOD_VALUE_INTERFACE  ( getReqData, ReqData );

  ESL_METHOD_ACTION_INTERFACE ( reqAccept );
}; // Socket_master_req_ifc

ESL_INTERFACE (Socket_master_resp_ifc) {
  ESL_METHOD_ACTION_INTERFACE ( putResp,
				RespOp    respOp,
				RespInfo  respInfo,
				RespAddr  respAddr,
				RespData  respData );

  ESL_METHOD_VALUE_INTERFACE ( respAccept, bool );
}; // Socket_master_resp_ifc

ESL_INTERFACE (Socket_master_ifc) {
  ESL_SUBINTERFACE ( master_req, Socket_master_req_ifc );
  ESL_SUBINTERFACE ( master_resp, Socket_master_resp_ifc );
}; // Socket_master_ifc

// The following module produces an Socket master interface from the
// interfaces of two fifos containing requests and responses

ESL_MODULE ( fifos_to_master_ifc, Socket_master_ifc ) {
 private:
  FIFO<Socket_Req>  *reqs;
  FIFO<Socket_Resp> *resps;

  ESL_SUBINTERFACE_DEF ( master_req, Socket_master_req_ifc ) {
    ESL_METHOD_VALUE ( getReqOp, true, ReqOp) {
      return (esl_this->reqs->ok_to_deq() ? esl_this->reqs->first().reqOp : Req_NOP) ;
    }
    ESL_METHOD_VALUE ( getReqInfo, true, ReqInfo ) {
      if (esl_this->reqs->ok_to_deq())
	return esl_this->reqs->first().reqInfo;
      else
	return 0;
    }
    ESL_METHOD_VALUE ( getReqAddr, true, ReqAddr ) {
      if (esl_this->reqs->ok_to_deq())
	return esl_this->reqs->first().reqAddr;
      else
	return 0;
    }
    ESL_METHOD_VALUE ( getReqData, true,  ReqData) {
      if (esl_this->reqs->ok_to_deq())
	return esl_this->reqs->first().reqData;
      else
	return 0;
    }
    ESL_METHOD_ACTION ( reqAccept, true ) {
      if (esl_this->reqs->ok_to_deq())
	esl_this->reqs->deq ();
    }
  };

  ESL_SUBINTERFACE_DEF ( master_resp, Socket_master_resp_ifc ) {
    ESL_METHOD_ACTION ( putResp, true,
			RespOp    respOp,
			RespInfo  respInfo,
			RespAddr  respAddr,
			RespData  respData ) {
      if ((respOp != Rsp_NOP) && esl_this->resps->ok_to_enq()) {
	Socket_Resp resp;
	resp.respOp = respOp;
	resp.respInfo = respInfo;
	resp.respAddr = respAddr;
	resp.respData = respData;
	esl_this->resps->enq (resp);
      }
    }

    ESL_METHOD_VALUE ( respAccept, true, bool ) {
      return esl_this->resps->ok_to_enq();
    }
  };

 public:
  ESL_CTOR ( fifos_to_master_ifc,
	     FIFO<Socket_Req>  *reqs_,
	     FIFO<Socket_Resp> *resps_ )
    {
      reqs  = reqs_;
      resps = resps_;
      ESL_PROVIDE (master_req);
      ESL_PROVIDE (master_resp);
      ESL_END_CTOR;
    }
}; // fifos_to_master_ifc

// ----------------
// A slave interface
//   exists on an Interconnect (facing a master interface on an Initiator)
//      and on a  Target       (facing a master interface on an Interconnect)

ESL_INTERFACE ( Socket_slave_req_ifc ) {
  ESL_METHOD_ACTION_INTERFACE ( putReq,
				ReqOp    reqOp,
				ReqInfo  reqInfo,
				ReqAddr  reqAddr,
				ReqData  reqData );

  ESL_METHOD_VALUE_INTERFACE ( reqAccept, bool );
}; // Socket_slave_req_ifc

ESL_INTERFACE ( Socket_slave_resp_ifc ) {
  ESL_METHOD_VALUE_INTERFACE ( getRespOp,   RespOp   );
  ESL_METHOD_VALUE_INTERFACE ( getRespInfo, RespInfo );
  ESL_METHOD_VALUE_INTERFACE ( getRespAddr, RespAddr );
  ESL_METHOD_VALUE_INTERFACE ( getRespData, RespData );

  ESL_METHOD_ACTION_INTERFACE ( respAccept );
}; // Socket_slave_resp_ifc

ESL_INTERFACE ( Socket_slave_ifc ) {
  ESL_SUBINTERFACE ( slave_req, Socket_slave_req_ifc );
  ESL_SUBINTERFACE ( slave_resp, Socket_slave_resp_ifc );
}; // Socket_slave_ifc

// The following module produces an Socket slave interface from the
// interfaces of two fifos containing requests and responses

ESL_MODULE ( fifos_to_slave_ifc, Socket_slave_ifc ) {
 private:
  FIFO<Socket_Req>  *reqs;
  FIFO<Socket_Resp> *resps;
  
  ESL_SUBINTERFACE_DEF ( slave_req, Socket_slave_req_ifc ) {
    ESL_METHOD_ACTION ( putReq, true,
			ReqOp      reqOp,
			ReqInfo    reqInfo,
			ReqAddr    reqAddr,
			ReqData    reqData ) {
      if ((reqOp != Req_NOP) && (esl_this->reqs->ok_to_enq())) {
	Socket_Req req;
	req.reqOp = reqOp;
	req.reqInfo = reqInfo;
	req.reqAddr = reqAddr;
	req.reqData = reqData;
	esl_this->reqs->enq (req);
      }
    };

    ESL_METHOD_VALUE ( reqAccept, true, bool ) {
      return esl_this->reqs->ok_to_enq();
    }
  };

  ESL_SUBINTERFACE_DEF ( slave_resp, Socket_slave_resp_ifc ) {
    ESL_METHOD_VALUE ( getRespOp, true, RespOp ) {
      return (esl_this->resps->ok_to_deq() ? esl_this->resps->first().respOp : Rsp_NOP);
    }
    ESL_METHOD_VALUE ( getRespInfo, true, RespInfo ) {
      if (esl_this->resps->ok_to_deq())
	return esl_this->resps->first().respInfo;
      else
	return 0;
    }
    ESL_METHOD_VALUE ( getRespAddr, true, RespAddr ) {
      if (esl_this->resps->ok_to_deq())
	return esl_this->resps->first().respAddr;
      else
	return 0;
    }
    ESL_METHOD_VALUE ( getRespData,  true, RespData ) {
      if (esl_this->resps->ok_to_deq())
	return esl_this->resps->first().respData;
      else
	return 0;
    }

    ESL_METHOD_ACTION ( respAccept, true ) {
      if (esl_this->resps->ok_to_deq())
	esl_this->resps->deq();
    }
  };

 public:
  ESL_CTOR ( fifos_to_slave_ifc,
	     FIFO<Socket_Req>  *reqs_,
	     FIFO<Socket_Resp> *resps_ )
    {
      reqs = reqs_;
      resps = resps_;
      ESL_PROVIDE (slave_req);
      ESL_PROVIDE (slave_resp);
      ESL_END_CTOR;
    }
}; // fifos_to_slave_ifc

// ================================================================
// Connections between master and slave interfaces

ESL_MODULE (connectSockets, ESL_EMPTY)
{
  Socket_master_ifc  *m;
  Socket_slave_ifc   *s;

  ESL_RULE ( relay_req, true ) {
    s->slave_req->putReq ( m->master_req->getReqOp (),
			   m->master_req->getReqInfo (),
			   m->master_req->getReqAddr (),
			   m->master_req->getReqData () );
  }

  ESL_RULE ( relay_req_accpt, s->slave_req->reqAccept () ) {
    m->master_req->reqAccept () ;
  }
      
  ESL_RULE ( relay_resp, true ) {
    m->master_resp->putResp ( s->slave_resp->getRespOp (),
			      s->slave_resp->getRespInfo (),
			      s->slave_resp->getRespAddr (),
			      s->slave_resp->getRespData () );
  }

  ESL_RULE ( relay_resp_accpt, m->master_resp->respAccept () ) {
    s->slave_resp->respAccept () ;
  }
      
  ESL_CTOR (connectSockets, Socket_master_ifc *&_m, Socket_slave_ifc *&_s) :
    m(_m),
    s(_s)
    {
      ESL_END_CTOR;
    }
};

// ================================================================

#ifndef ESE_COMP
#include "socket_ifc.epp"
#endif

#endif
