#ifndef _TB_HPP_
#define _TB_HPP_

// ----------------------------------------------------------------
// A simple testbench for the Switch

#include <stdlib.h>

// ----------------
// Standard imports

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

// ----------------
// Design imports

#include "edge_fifos.hpp"
#include "socket_ifc.hpp"
#include "switch.hpp"

// ----------------------------------------------------------------
// Initiator module

ESL_MODULE (mkInitiatorModel, Socket_master_ifc) {

 private:
  unsigned int id;
  unsigned int reqnum;

  FIFO<Socket_Req>   *reqs;    // Outgoing requests
  FIFO<Socket_Resp>  *resps;   // Incoming responses

  // We use two separate fifos for hoding expected responses from the
  // two targets because responses may be returned out of order; we
  // always need to know the next expected response from each target

  FIFO<Socket_Resp>  *expected_resps_0; // Expected responses to RD reqs to 0
  FIFO<Socket_Resp>  *expected_resps_1; // Expected responses to RD reqs to 1

  // ----------------
  // Generating requests

  ESL_RULE (gen_reqs, true) {
    // Choose random op, addr and data
    unsigned int randx = random();
    ReqOp op   = (((randx & 7) > 5) ? WR : RD);
    unsigned int addr = (randx & 0xFFF0) >> 4;
    unsigned int data = (randx >> 16);
    // Send this initiator's id and the request num in the info field
    unsigned int info = ((id << 8) | (reqnum & 0xFF));  // {2b id, 8b reqnum}

    Socket_Req  req = { op, info, addr, data };
    reqs->enq (req);

    // record expected response
    if (op == RD) {
      Socket_Resp resp = { OK, 0, id, ((req.reqAddr << 16) | req.reqData) };
      if ((req.reqAddr & 0x1) == 0) {
	resp.respInfo = ((0 << 8) | (reqnum & 0xFF));  // {2b id, 8b reqnum}
	expected_resps_0->enq (resp);
      }
      else {
	resp.respInfo = ((1 << 8) | (reqnum & 0xFF));  // {2b id, 8b reqnum}
	expected_resps_1->enq (resp);
      }
    }

    cout << esl_cycle() << ": Tb: initiator" << id;
    display_Socket_Req (" sent request ", req, "\n");
    reqnum++;
  }

  // ----------------
  // Fielding responses

  ESL_RULE (accept_resps_from_0, ((resps->first ().respInfo >> 8) == 0)) {
    Socket_Resp resp = resps->first();
    resps->deq ();

    cout << esl_cycle() << ": Tb: initiator" << id;
    display_Socket_Resp (" received response ", resp, "\n");

    Socket_Resp resp1 = expected_resps_0->first ();
    expected_resps_0->deq();

    if (resp1 != resp) {
      display_Socket_Resp ("ERROR: does not match expected response ", resp1, "\n");
      exit (1);
    }
    else
      cout << "    Response is ok" << endl;
  }

  ESL_RULE (accept_resps_from_1, ((resps->first ().respInfo >> 8) == 1)) {
    Socket_Resp resp = resps->first();
    resps->deq ();

    cout << esl_cycle() << ": Tb: initiator" << id;
    display_Socket_Resp (" received response ", resp, "\n");

    Socket_Resp resp1 = expected_resps_1->first ();
    expected_resps_1->deq();

    if (resp1 != resp) {
      display_Socket_Resp ("ERROR: does not match expected response ", resp1, "\n");
      exit (1);
    }
    else
      cout << "    Response is ok" << endl;
  }

  // ================================================================
  // CONSTRUCTOR

 public:
  ESL_CTOR(mkInitiatorModel, unsigned int id_) :
    id (id_),
    reqnum (0)
    {
      srandom (id_);
      reqs  = new mkPipelineFIFO1_ug_deq <Socket_Req>  ("initiator_model_reqs");
      resps = new mkPipelineFIFO1_ug_enq <Socket_Resp> ("initiator_model_resps");

      // We hold expected responses in these FIFOs.
      // To allow the initiator to run ahead and pipeline requests into the targets,
      // the following FIFOs need to hold expected responses for all requests in flight
      // Hence we use FIFOs sized to account for the round-trip time

      expected_resps_0 = new mkSizedFIFO<Socket_Resp> ("initiator_model_expected_resps_0", 10);
      expected_resps_1 = new mkSizedFIFO<Socket_Resp> ("initiator_model_expected_resps_1", 10);

      ESL_RETURN_INTERFACE (new fifos_to_master_ifc ("initiator_ifc", reqs, resps));

      ESL_END_CTOR;
    }
}; // mkInitiatorModel

// ----------------------------------------------------------------
// Targets

ESL_MODULE (mkTargetModel, Socket_slave_ifc)
{
 private:
  unsigned int id;
  FIFO<Socket_Req>  *reqs;
  FIFO<Socket_Resp> *resps;

  // ----------------
  // Responding to requests

  ESL_RULE (respond, true) {
    Socket_Req req = reqs->first();
    reqs->deq();

    cout << esl_cycle() << ": Tb: target" << id;
    display_Socket_Req (" received request ", req, "\n");

    if (req.reqOp == RD) {
      // Send this target's id and the request num in the info field
      unsigned int info = ((id << 8) | (req.reqInfo & 0xFF));
      // Send requestor's id in the address field
      unsigned int addr = (req.reqInfo >> 8);
      // Send addr and data in the data field
      unsigned int data = (req.reqAddr << 16 | req.reqData);

      Socket_Resp resp = { OK, info, addr, data };

      resps->enq (resp);
      display_Socket_Resp ("              Sent response ", resp, "\n");
    }
  }

  // ================================================================
  // CONSTRUCTOR

 public:
  ESL_CTOR(mkTargetModel, unsigned int id_) :
    id(id_)
    {
      reqs  = new mkPipelineFIFO1_ug_enq <Socket_Req>  ("target_model_reqs");
      resps = new mkPipelineFIFO1_ug_deq <Socket_Resp> ("target_model_resps");

      ESL_RETURN_INTERFACE (new fifos_to_slave_ifc ("target_ifc", reqs, resps));

      ESL_END_CTOR;
    }
}; // mkTargetModel

// ----------------------------------------------------------------
// The top-level module of the testbench

ESL_MODULE (mkTb, ESL_EMPTY) {

 private:
  // The initiators and targets;
  Socket_master_ifc  *initiator_0;
  Socket_master_ifc  *initiator_1;

  Socket_slave_ifc   *target_0;
  Socket_slave_ifc   *target_1;

  // The switch
  Switch_IFC         *sw;

  // ================================================================
  // CONSTRUCTOR

 public:
  ESL_CTOR(mkTb) {
    initiator_0 = new mkInitiatorModel ("initiator_0", 0);
    initiator_1 = new mkInitiatorModel ("initiator_1", 1);
    target_0    = new mkTargetModel    ("target_0", 0);
    target_1    = new mkTargetModel    ("target_1", 1);

    sw          = new mkSwitch         ("sw");

    // Make the connections
    ESL_EMPTY *i0_sw = (new mkConnection< Socket_master_ifc, Socket_slave_ifc >
			("i0_sw", initiator_0, sw->initiator_0));

    ESL_EMPTY *i1_sw = (new mkConnection< Socket_master_ifc, Socket_slave_ifc >
			("i1_sw", initiator_1, sw->initiator_1));

    ESL_EMPTY *sw_t0 = (new mkConnection< Socket_master_ifc, Socket_slave_ifc >
			("sw_t0",    sw->target_0, target_0));

    ESL_EMPTY *sw_t1 = (new mkConnection< Socket_master_ifc, Socket_slave_ifc >
			("sw_t1",    sw->target_1, target_1));

    ESL_END_CTOR;
  }
};

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

#include "tb.epp"

#endif
