#ifndef _SWITCH_HPP_
#define _SWITCH_HPP_

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

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

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

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

// ----------------
// Interface for the Switch

ESL_INTERFACE (Switch_IFC)
{
  ESL_SUBINTERFACE ( initiator_0, Socket_slave_ifc );
  ESL_SUBINTERFACE ( initiator_1, Socket_slave_ifc );

  ESL_SUBINTERFACE ( target_0, Socket_master_ifc );
  ESL_SUBINTERFACE ( target_1, Socket_master_ifc );
};

// ================================================================
// Address-decode functions

bool
addr_is_for_target_0 (ReqAddr addr)
{
   return ((addr & 0x1) == 0);
}

bool
addr_is_for_target_1 (ReqAddr addr)
{
   return ((addr & 0x1) == 1);
}

bool
addr_is_for_initiator_0 (RespAddr addr)
{
   return ((addr & 0x1) == 0);
}

bool
addr_is_for_initiator_1 (RespAddr addr)
{
   return ((addr & 0x1) == 1);
}

// ================================================================
// The Switch Module

ESL_MODULE (mkSwitch, Switch_IFC) {

  // ----------------------------------------------------------------
  // INTERNAL STATE

  // INITIATOR FIFOs

 private:
  // ---- for incoming requests (from initiators)
  FIFO<Socket_Req>        *from_initiator_0;
  FIFO<Socket_Req>        *from_initiator_1;

  // ---- FIFOs for outgoing resps (to initiators)
  FIFO<Socket_Resp>       *to_initiator_0;
  FIFO<Socket_Resp>       *to_initiator_1;
  
  // TARGET FIFOs

  // ---- FIFOs for outgoing requests (to targets)
  FIFO<Socket_Req>        *to_target_0;
  FIFO<Socket_Req>        *to_target_1;

  // ---- FIFOs for incoming resps (from targets)
  FIFO<Socket_Resp>       *from_target_0;
  FIFO<Socket_Resp>       *from_target_1;

  // ---- Additional state for round-robin arbitration
  esl_reg<bool>    to_target_0_round_robin;
  esl_reg<bool>    to_target_1_round_robin;

  esl_reg<bool>    to_initiator_0_round_robin;
  esl_reg<bool>    to_initiator_1_round_robin;

  // ================================================================
  // RULES (the interconnect functionality)

  // ----------------------------------------------------------------
  // Requests from initiators to targets

  // Both Initiators have requests
  ESL_RULE (both_initiators, true) {
    Socket_Req rq0 = from_initiator_0->first();
    Socket_Req rq1 = from_initiator_1->first();
    bool i0t0 = addr_is_for_target_0 (rq0.reqAddr);
    bool i1t0 = addr_is_for_target_0 (rq1.reqAddr);

    // Both to target 0; use round-robin
    if (i0t0 && i1t0) {
      if (to_target_0_round_robin) {
	to_target_0->enq (rq0);
	from_initiator_0->deq();
      }
      else {
	to_target_0->enq (rq1);
	from_initiator_1->deq();
      }
      to_target_0_round_robin = ! to_target_0_round_robin;
    }

    // Both to target 1; use round-robin
    else if ((! i0t0) && (! i1t0)) {
      if (to_target_1_round_robin) {
	to_target_1->enq (rq0);
	from_initiator_0->deq();
      }
      else {
	to_target_1->enq (rq1);
	from_initiator_1->deq();
      }
      to_target_1_round_robin = ! to_target_1_round_robin;
    }

    // Initiator 0 to target 0, initiator 1 to target 1; do both
    else if (i0t0 && (! i1t0)) {
      to_target_0->enq (rq0);
      from_initiator_0->deq();
      to_target_1->enq (rq1);
      from_initiator_1->deq();
    }

    // Initiator 0 to target 1, initiator 1 to target 0; do both
    else { // (! i0t0) && i1t0)
      to_target_1->enq (rq0);
      from_initiator_0->deq();
      to_target_0->enq (rq1);
      from_initiator_1->deq();
    }
  }

  // Initiator 0 only
  ESL_RULE (initiator_0_only, (! from_initiator_1->ok_to_deq())) {
    Socket_Req rq0 = from_initiator_0->first();
    if (addr_is_for_target_0 (rq0.reqAddr))
      to_target_0->enq (rq0);
    else // for target 1
      to_target_1->enq(rq0);
    from_initiator_0->deq();
  }

  // Initiator 1 only
  ESL_RULE (initiator_1_only, (! from_initiator_0->ok_to_deq())) {
    Socket_Req rq1 = from_initiator_1->first();
    if (addr_is_for_target_0 (rq1.reqAddr))
      to_target_0->enq (rq1);
    else // for target 1
      to_target_1->enq (rq1);
    from_initiator_1->deq();
  }

  // ----------------------------------------------------------------
  // Responses from targets to initiators

  // Both Targets have responses
  ESL_RULE (both_targets, true) {
    Socket_Resp rsp0 = from_target_0->first();
    Socket_Resp rsp1 = from_target_1->first();
    bool t0i0 = addr_is_for_initiator_0 (rsp0.respAddr);
    bool t1i0 = addr_is_for_initiator_0 (rsp1.respAddr);

    // Both to initator 0; use round-robin
    if (t0i0 && t1i0) {
      if (to_initiator_0_round_robin) {
	to_initiator_0->enq (rsp0);
	from_target_0->deq();
      }
      else {
	to_initiator_0->enq (rsp1);
	from_target_1->deq();
      }
      to_initiator_0_round_robin = ! to_initiator_0_round_robin;
    }

    // Both to initator 1; use round-robin
    else if ((! t0i0) && (! t1i0)) {
      if (to_initiator_1_round_robin) {
	to_initiator_1->enq (rsp0);
	from_target_0->deq();
      }
      else {
	to_initiator_1->enq (rsp1);
	from_target_1->deq();
      }
      to_initiator_1_round_robin = ! to_initiator_1_round_robin;
    }

    // Initiator 0 to target 0, initiator 1 to target 1; do both
    else if (t0i0 && (! t1i0)) {
      to_initiator_0->enq (rsp0);
      from_target_0->deq();
      to_initiator_1->enq (rsp1);
      from_target_1->deq();
    }

    // Initiator 0 to target 0, initiator 1 to target 1; do both
    else { // (! t0i0) && t1i0)
      to_initiator_1->enq (rsp0);
      from_target_0->deq();
      to_initiator_0->enq (rsp1);
      from_target_1->deq();
    }
  }

  // Target 0 only
  ESL_RULE (target_0_only, (! from_target_1->ok_to_deq())) {
    Socket_Resp rsp0 = from_target_0->first();
    if (addr_is_for_initiator_0 (rsp0.respAddr))
      to_initiator_0->enq (rsp0);
    else
      to_initiator_1->enq (rsp0);
    from_target_0->deq();
  }

  // Target 1 only
  ESL_RULE (target_1_only, (! from_target_0->ok_to_deq())) {
    Socket_Resp rsp1 = from_target_1->first();
    if (addr_is_for_initiator_0 (rsp1.respAddr))
      to_initiator_0->enq (rsp1);
    else
      to_initiator_1->enq (rsp1);
    from_target_1->deq();
  }

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

 public:
  ESL_CTOR(mkSwitch)
    {
      ESL_NEW_REG (to_target_0_round_robin, bool, false);
      ESL_NEW_REG (to_target_1_round_robin, bool, false);
      ESL_NEW_REG (to_initiator_0_round_robin, bool, false);
      ESL_NEW_REG (to_initiator_1_round_robin, bool, false);
      from_initiator_0 = new mkPipelineFIFO1_ug_enq <Socket_Req> ("from_initiator_0");
      from_initiator_1 = new mkPipelineFIFO1_ug_enq <Socket_Req> ("from_initiator_1");

      to_initiator_0   = new mkBypassFIFO1_ug_deq <Socket_Resp> ("to_initiator_0");
      to_initiator_1   = new mkBypassFIFO1_ug_deq <Socket_Resp> ("to_initiator_1");

      to_target_0      = new mkBypassFIFO1_ug_deq <Socket_Req>  ("to_target_0");
      to_target_1      = new mkBypassFIFO1_ug_deq <Socket_Req>  ("to_target_1");

      from_target_0    = new mkPipelineFIFO1_ug_enq <Socket_Resp> ("from_target_0");
      from_target_1    = new mkPipelineFIFO1_ug_enq <Socket_Resp> ("from_target_1");

      // ---- Initiators
      initiator_0 = new fifos_to_slave_ifc
	("switch_to_initiator_0", from_initiator_0, to_initiator_0);
      initiator_1 = new fifos_to_slave_ifc
	("switch_to_initiator_1", from_initiator_1, to_initiator_1);

      // ---- Targets
      target_0    = new fifos_to_master_ifc
	("switch_to_target_0", to_target_0, from_target_0);
      target_1    = new fifos_to_master_ifc
	("switch_to_target_1", to_target_1, from_target_1);

      ESL_END_CTOR;
    }
};

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

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

#endif
