/* -*- c++ -*- */

/***************************************************************************
                         CacheWBNBGHB.h  -  description
                             -------------------
    begin                : mon jul 28 2003
    copyright            : (C) 2003 by Daniel Gracia P�rez
    email                : gracia@lri.fr
***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef __MICROLIB_CACHEWBNBGHB_H__
#define __MICROLIB_CACHEWBNBGHB_H__

#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "mem_common.h"
#include "common.h"
#include "base/utility.h"
#include "system/CacheContainer.h"
#include "system/memory/cache/CacheCommon.h"
#include "system/memory/cache/MSHRContainerPrefetcher.h"
#include "system/memory/cache/microlib/GHB.h"

/* A SystemC module implementing the cache L1. It is a template class. */
/* T, nSources, nDestinations: data type manipulated by the instructions */
/* replacementPolicy: replacement policy to apply */
/* nPorts: the total number of ports that can be used to service requests, should be bigger than 1*/
/* nCPUtoCacheDataPathSize: maximum size in bytes of the data that the cache can receive from the CPU */
/* nCachetoCPUDataPathSize: maximum size in bytes of the data that the cache can send towards the CPU */
/* nCPULineSize: the minimum size of the data that has to be sent towards the CPU,
 *               if 0 then the value is ignored (e.g. connected to the CPU)
 *               else the size of the cache line in level -1.
 *               it is used to set the base address of the requested data */
/* nMemtoCacheDataPathSize: maximum size in bytes of the data that the cache can be received from the memory system */
/* nCachetoMemDataPathSize: maximum size in bytes of the data that the cache can send towards the memory system,
 *                          used in the case of write back policy,
 *                          it should be the same than nCPUtoCacheDataPathSize if write through */
/* nLineSize: size of a cache line in bytes */
/* nCacheLines: number of cache lines */
/* nAssociativity: number of sets in the cache,
 *        if nAssociativity==nCacheLine complete associative,
 *        if nAssociativity==1 direct cache */
/* nStages: number of pipe stages, number of cycles */
template <class T,
int nSources,
ReplacementPolicyType replacementPolicy,
int nCPUtoCacheDataPathSize,
int nCachetoCPUDataPathSize,
int nCPULineSize,
int nMemtoCacheDataPathSize,
int nCachetoMemDataPathSize,
int nLineSize,
int nCacheLines,
int nAssociativity,
int nStages,
int nDelay,
int nPorts,
int nMSHR,
int nMSHRRead,
int nGHBITEntries,
int nGHBEntries,
int nGHBPCShift,
int nGHBDepth>
class MicrolibCacheWBNBGHB:public sc_module
{
public:
  /* cache: it is the structure that will keep the data of the cache */
  CacheContainer<nCacheLines, nLineSize, nAssociativity, replacementPolicy, 1> cache;
private:
  /* cacheQueue: implements the cache pipeline */
  Queue<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheQueue;
  /* MSHRQueue: implements the miss address file */
  MSHRContainerPrefetcher<T, nSources, nLineSize, (nCPULineSize > 0) ? nCPULineSize : nLineSize, nMSHR, nMSHRRead> MSHR;
  /* writeBuffer: it is used as delayed write buffer of the cache */
  DelayedWriteBuffer<nLineSize> writeBuffer;
  /* returnBuffer: it is used to stock temporaly the data sent towards the CPU */
  ReturnData<T, /*nSources,*/ nLineSize, nCachetoCPUDataPathSize> returnBuffer;
  /* requestBuffer: it is used to stock temporaly the data sent towards the memory system */
  RequestData<T, /*nSources,*/ nMemtoCacheDataPathSize, nLineSize> requestBuffer;

  /* requestQueue: implements a request queue from the misses produced in the cacheQueue */
  Queue<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nMSHR * 2> requestQueue;

  /* ghb: implements the global history buffer prefetcher */
  GHB<nGHBITEntries, nGHBEntries, nGHBPCShift, nGHBDepth> ghb;


  /*******************************************************************************/
  /*******************************************************************************/
  /******************** External Control Functions (start) ***********************/
  /*******************************************************************************/
  /*******************************************************************************/

  /* if an accept is received from the CPU enable the output to the CPU */
  void CheckCPUAccept()
  {
    int port, index;

    for (port = -1, index = 0; index < nPorts; index++)
    {
      if (port == -1)
      {
        if (inCPUtoCacheAccept[index])
        {
          outCachetoCPUEnable[index] = true;
          port = index;
        }
        else
        {
          outCachetoCPUEnable[index] = false;
        }
      }
      else
        outCachetoCPUEnable[index] = false;
    }
  }

  /* if an accept is received from the memory system enable the output to the memory system */
  void CheckMemAccept()
  {
    outCachetoMemEnable = inMemtoCacheAccept;
  }

  void CheckCPURequests()
  {
    QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit;
    int port, index;

    for (port = -1, index = 0; index < nPorts; index++)
    {
      if (port == -1)
      {
        if (inCPUtoCacheValid[index])
        {
          port = index;
        }
        else
        {
          outCachetoCPUAccept[index] = false;
        }
      }
      else
        outCachetoCPUAccept[index] = false;
    }
    if (port != -1)
    {
      /* if the cache is not full, the request can be accepted */
      if (cacheQueue.Empty())
      {
        outCachetoCPUAccept[port] = true;
      }
      else
      {
        /* check if there will be place in the cache pipeline */
        int stage = 0;
        bool cont = true;

        for (cacheit = cacheQueue.SeekAtTail();
             cont && cacheit;
             cacheit--, stage++)
        {
          if (cacheit->stage > stage)
          {
            outCachetoCPUAccept[port] = true;
            cont = false;
          }
          else
          {
            if (stage != nStages - 1)
            {
              if (cacheit->delay[cacheit->stage] > 1)
              {
                outCachetoCPUAccept[port] = false;
                cont = false;
              }
            }
            else
            {
              if (cacheit->delay[stage] > 0)
              {
                outCachetoCPUAccept[port] = false;
                cont = false;
              }
              else
              {
                if (cacheit->write)
                {
                  switch (cacheit->state)
                  {
                    case WRITE:
                      outCachetoCPUAccept[port] = cache.hit(cacheit->address);
                      break;
                    case WRITE_MISS_NO_READ:
                      outCachetoCPUAccept[port] = inMemtoCacheAccept;
                      break;
                    default:
                      outCachetoCPUAccept[port] = false;
                      break;
                  }
                }
                else
                {
                  outCachetoCPUAccept[port] = false;
                }
              }
            }
          }
        }
      }
    }
  }

  /* if the memory system replies to a miss */
  void CheckMemValid()
  {
    if (inMemtoCacheValid)
    {
      /* nothing has to be checked, the system never blocks the memory inputs */
      outCachetoMemAccept = true;
    }
    else
    {
      outCachetoMemAccept = false;
    }
  }

  /*******************************************************************************/
  /*******************************************************************************/
  /********************** External Control Functions (end) ***********************/
  /*******************************************************************************/
  /*******************************************************************************/

  /*******************************************************************************/
  /*******************************************************************************/
  /******************** Internal Control Functions (start) ***********************/
  /*******************************************************************************/
  /*******************************************************************************/

  bool CheckCPUAcceptedData()
  {
    int index, port;
    bool changed = false;

//boveerle
    /* check if an accept from the cpu is being waited */
    if(returnBuffer.ready)
    {
//eoveerle
    for (index = 0, port = -1; index < nPorts; index++)
    {
      if (port == -1)
      {
        if (inCPUtoCacheAccept[index])
        {
          port = index;
        }
      }
      else
      {
        if (inCPUtoCacheAccept[index])
        {
          cerr << "Error(" << name() << "): more than one module accepted data from the cache." << endl;
          cerr << *this;
          exit(-1);
        }
      }
    }
    if (port != -1)
    {
      /* if there was nothing in the return buffer,
       * there should have not been an accept signal from the CPU */
      if (!returnBuffer.ready)
      {
        cerr << "Error(" << name() << "): data accepted by the cpu when nothing has been sent" << endl;;
        cerr << *this;
        exit(-1);
      }
      /* check how much has still to be sent */
      returnBuffer.size = returnBuffer.size - nCachetoCPUDataPathSize;
      if (returnBuffer.size > 0)
      {
        /* the returnBuffer is not empty, so it continues servicing the data */
        returnBuffer.index = returnBuffer.index + nCachetoCPUDataPathSize;
        if (returnBuffer.index == nCPULineSize) returnBuffer.index = 0;
        returnBuffer.address = returnBuffer.base + returnBuffer.index;
      }
      else
      {
        /* the returnBuffer is empty */
        if (returnBuffer.id == ID_CACHE)
        {
          /* the cache entry that made the request can be
           * removed, it must be the head the entry to remove */
          returnBuffer.ready = false;
        }
        else
        {
          /* it is a data generated by the MSHR, so it can be removed
           */
          returnBuffer.ready = false;
        }
      }
    }
    else
    {
      /* the last request to the cpu has not been serviced */
    }
//boveerle
      } //if(returnBuffer.ready)
//eoveerle

    return changed;
  }

  bool CheckMemAcceptedDatafromCache()
  {
    QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nMSHR * 2> cacheit;
    bool changed = false;

    if (inMemtoCacheAccept)
    {
      if (!requestBuffer.ready)
      {
        cerr << "Error(" << name() << "): data accepted by the memory system when nothing has been sent from the cache" << endl;
        cerr << *this;
        exit(-1);
      }
      /* check if the request was a read or a write */
      if (requestBuffer.write)
      {
        /* this request is a write */
        /* if last request was finished from the cache pipeline (a write request)
         * remove the request from the cache pipeline */
        requestBuffer.size -= nCachetoMemDataPathSize;
        if (requestBuffer.size > 0)
        {
          /* the request has not bben finished, the request has to be keeped,
           * the address has to be increased,
           * and the cache pipeline is not notified */
          requestBuffer.index += nCachetoMemDataPathSize;
          requestBuffer.address = requestBuffer.base + requestBuffer.index;
        }
        else
        {
          /* the request has been finished, the cache pipeline has to be updated */
          // 					cacheit = cacheQueue.SeekAtHead();
          cacheit = requestQueue.SeekAtHead();
          switch (cacheit->state)
          {
            case WRITE_MISS_EVICTION:
              cacheit->state = WRITE_MISS_EVICTION_WRITE_DONE;
              break;
            case WRITE_MISS_NO_READ:
              // 						cacheQueue.RemoveHead();
              requestQueue.RemoveHead();
              changed = true;
              break;
            case READ_MISS_EVICTION:
              cacheit->state = READ_MISS_EVICTION_WRITE_DONE;
              break;
            case PREFETCH_MISS_EVICTION:
              cacheit->state = PREFETCH_MISS_EVICTION_WRITE_DONE;
              break;
            case EVICT_READY:
              // 						cacheQueue.RemoveHead();
              requestQueue.RemoveHead();
              changed = true;
              break;
          }
          requestBuffer.ready = false;
        }
      }
      else
      {
        /* the request is a read */
        /* the access is a cache miss
         * the cache entry (the head of the cache queue)
         * is not removed from the cache pipeline (this is a blocking cache) */
        /* the request buffer is marked as not ready, to be used with the next memory request */
        // 				cacheit = cacheQueue.SeekAtHead();
        cacheit = requestQueue.SeekAtHead();
        switch (cacheit->state)
        {
          case WRITE_MISS:
            // 					cacheQueue.RemoveHead();
            requestQueue.RemoveHead();
            changed = true;
            break;
          case WRITE_MISS_EVICTION_WRITE_DONE:
            // 					cacheQueue.RemoveHead();
            requestQueue.RemoveHead();
            changed = true;
            break;
          case READ_MISS:
            // 					cacheQueue.RemoveHead();
            requestQueue.RemoveHead();
            changed = true;
            break;
          case READ_MISS_EVICTION_WRITE_DONE:
            // 					cacheQueue.RemoveHead();
            requestQueue.RemoveHead();
            changed = true;
            break;
          case PREFETCH_MISS:
          case PREFETCH_MISS_EVICTION_WRITE_DONE:
            // 					cacheQueue.RemoveHead();
            requestQueue.RemoveHead();
            changed = true;
            break;
        }
        requestBuffer.ready = false;
      }
    }

    return changed;
  }

  bool CheckAcceptedData()
  {
    bool changed = false; /* indicates if the state of the cache changed */

    changed = CheckCPUAcceptedData();

    changed = CheckMemAcceptedDatafromCache() || changed;

    return changed;
  }

  bool UpdateWriteBuffer()
  {
    bool changed = false;

    /* check that data was received from the memory system */
    if (inMemtoCacheEnable)
    {
      /* check that it is not an incorrect line */
      if (writeBuffer.address != (inMemtoCacheAddress & ~((address_t)nLineSize-1)) &&
          writeBuffer.size != 0)
      {
        if (writeBuffer.size == nLineSize)
        {
          cerr << "Error(" << name() << "): writeBuffer was full and a memory access was received" << endl;
          cerr << *this;
          exit(-1);
        }
        else
        {
          cerr << "Error(" << name() << "): memory system has not finished actual write buffer" << endl;
          cerr << *this;
          exit(-1);
        }
      }
      else
      {
        /* update bytes that are received */
        for (int i = 0; i < nMemtoCacheDataPathSize; i++)
        {
          if (writeBuffer.ready[(int)(i + (inMemtoCacheAddress &
                                           ((address_t)(nLineSize - 1))))])
          {
            cerr << "Error(" << name() << "): writeBuffer has received two times the same address" << endl;
            cerr << *this;
            exit(-1);
          }
          writeBuffer.ready[i + (inMemtoCacheAddress
                                 & ((address_t)(nLineSize - 1)))] = true;
        }
        memcpy(&(writeBuffer.buffer[inMemtoCacheAddress&((address_t)(nLineSize - 1))]),
               inMemtoCacheData.Read(),
               nMemtoCacheDataPathSize);
        writeBuffer.size += nMemtoCacheDataPathSize;
        writeBuffer.address = inMemtoCacheAddress & (~((address_t)(nLineSize - 1)));
        writeBuffer.uid = inMemtoCacheTag;
      }
    }

    return changed;
  }

  bool UpdateMSHRWithWriteBuffer()
  {
    uint8_t buffer[nLineSize];
    int locked_set, locked_line;
    bool dirty;
    bool changed = false;
    bool prefetched = false;

    MSHR.Update(writeBuffer.address, writeBuffer.buffer,
                &locked_set, &locked_line, &dirty, buffer, &prefetched);

    cache.setCacheLine(locked_set, locked_line,
                       writeBuffer.address,
                       (char *)buffer,
                       true, dirty, prefetched);
    cache.Unlock(locked_set, locked_line);

    return changed;
  }

  bool ReadMemData()
  {
    bool changed = false;

    /* update the writeBuffer if data is received from the memory system */
    changed = UpdateWriteBuffer();

    /* if writeBuffer has been completed write the cacheline to the cache */
    if (writeBuffer.size == nLineSize)
    {
      changed = UpdateMSHRWithWriteBuffer();
      writeBuffer.size = 0;
      for (int i = 0; i < nLineSize; i++)
        writeBuffer.ready[i] = false;
    }

    return changed;
  }

  bool RunCachePipeline()
  {
    bool changed = false;
    QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit, cacheitPrev;

    for (cacheit = cacheQueue.SeekAtHead(); cacheit; cacheit++)
    {
      if (cacheit->delay[cacheit->stage] == 0)
      {
        if (cacheitPrev)
        {
          if (cacheitPrev->stage > cacheit->stage + 1)
          {
            cacheit->stage++;
            changed = true;
          }
        }
        else
        {
          if (cacheit->stage < nStages - 1)
          {
            cacheit->stage++;
            changed = true;
          }
        }
      }
      cacheitPrev = cacheit;
    }

    for (cacheit = cacheQueue.SeekAtHead(); cacheit; cacheit++)
    {
      if (cacheit->delay[cacheit->stage] > 0)
      {
        --cacheit->delay[cacheit->stage];
        changed = true;
      }
    }

    return changed;
  }

  bool ReadCPUData()
  {
    CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize> *entry;
    int index,port;
    bool changed = false;

    for (index = 0, port = -1; index < nPorts; index++)
    {
      if (port == -1)
      {
        if (inCPUtoCacheEnable[index])
        {
          port = index;
        }
      }
      else
      {
        if (inCPUtoCacheEnable[index])
        {
          cerr << "Error(" << name() << "): more than one module sent data to the cache (and been accepted)" << endl;
          cerr << *this;
          exit(-1);
        }
      }
    }

    /* if there is a new request from the CPU it is added to the cache pipeline */
    if (port != -1)
    {
      accesses++;
      /* check that the cache queue is not full
       * it should never happen */
      if (cacheQueue.Full())
      {
        cerr << "Error(" << name() << "): CPU request should not have been accepted" << endl;
        cerr << *this;
        exit(-1);
      }

      /* create new entry in the cache pipeline */
      entry = cacheQueue.New();
      entry->stage = 0;
      for (int i = 0; i < nStages; i++)
        entry->delay[i] = nDelay;
      if (entry->delay[0] > 0) entry->delay[0]--;
      changed = true;

      /* copy signals to the cache pipeline entry */
      entry->write = (inCPUtoCacheCommand[port] == WRITE_CACHE_COMMAND);
      switch (inCPUtoCacheCommand[port])
      {
        case WRITE_CACHE_COMMAND:
          accesses_write++;
          entry->state = WRITE;
          break;
        case READ_CACHE_COMMAND:
          accesses_read++;
          entry->state = READ;
          break;
        case PREFETCH_CACHE_COMMAND:
          accesses_prefetch++;
          entry->state = PREFETCH;
          break;
        case EVICT_CACHE_COMMAND:
          accesses_evict++;
          entry->state = EVICT;
          break;
      }

      entry->size = inCPUtoCacheSize[port];
      if (entry->write)
        memcpy(entry->data, inCPUtoCacheData[port].Read(), entry->size);
      entry->address = inCPUtoCacheAddress[port];

      if (nCPULineSize > 0)
      {
        entry->base = entry->address & (~(address_t)(nCPULineSize - 1));
        entry->index = entry->address & ((address_t)(nCPULineSize - 1)) &
                       ~((address_t)(nCachetoCPUDataPathSize - 1));
      }
      else
      {
        entry->base = entry->address;
        entry->index = 0;
      }
      entry->instr = inCPUtoCacheInstruction[port];
      entry->port = port;
      entry->block = false;
      entry->uid = inCPUtoCacheTag[port];
//boveerle
	entry->req_sender = inCPUtoCacheReqSender[port];
	//cerr << "@ReadCPUData req_sender=" << entry->req_sender->name() << "(" << entry->req_sender << ")" << endl;
//eoveerle
    }

    if (!changed && !cacheQueue.Full())
    {
      address_t prefetch_address;

// 			prefetch_address = sp.GetPrediction();
      prefetch_address = ghb.GetPrediction();
      if (prefetch_address)
      {
// 				cerr << "Prefetch address = " << hexa(prefetch_address) << endl;
        /* create new entry in the cache pipeline */
        entry = cacheQueue.New();
        entry->stage = 0;
        for (int i = 0; i < nStages; i++)
          entry->delay[i] = nDelay;
        if (entry->delay[0] > 0) entry->delay[0]--;
        changed = true;

        /* copy signals to the cache pipeline entry */
        entry->write = false;
        accesses_prefetch++;
        entry->state = PREFETCH;

        entry->size = 1;
        entry->address = prefetch_address;

        if (nCPULineSize > 0)
        {
          entry->base = entry->address & (~(address_t)(nCPULineSize - 1));
          entry->index = entry->address & ((address_t)(nCPULineSize - 1)) &
                         ~((address_t)(nCachetoCPUDataPathSize - 1));
        }
        else
        {
          entry->base = entry->address;
          entry->index = 0;
        }
        entry->port = -1;
        entry->block = false;
        entry->uid = -1;
      }
    }

    return changed;
  }

  bool SetCacheHeadReadState(QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit)
  {
    bool changed = false;

    cacheit->hit = cache.hit(cacheit->address);
    if (cacheit->hit)
    {
      cache.historyAccess(cache.getSet(cacheit->address), cache.getLine(cacheit->address));
      hits++;
      hits_read++;
      cacheit->state = READ_HIT_READY;
      if (cacheit->instr.cia != 0)  // check that it is not a request from the instruction cache (so it is not a load/store)
      {
        if (cache.getPrefetch(cache.getSet(cacheit->address), cache.getLine(cacheit->address)))
        {
          cache.setPrefetch(cache.getSet(cacheit->address), cache.getLine(cacheit->address), false);
          ghb.Access(cacheit->instr.cia, cacheit->address & (~(address_t)(nLineSize - 1)));
        }
      }
      if (nCPULineSize > 0)
      {
        cache.getCacheLineIndexed(cacheit->data,
                                  cache.getSet(cacheit->address),
                                  cache.getLine(cacheit->address),
                                  (cacheit->address & (~(address_t)(nCPULineSize - 1))) & ((address_t)(nLineSize - 1)),
                                  cacheit->size);
      }
      else
      {
        cache.getCacheLineIndexed(cacheit->data,
                                  cache.getSet(cacheit->address),
                                  cache.getLine(cacheit->address),
                                  (cacheit->address & ((address_t)(nLineSize - 1))),
                                  cacheit->size);
      }
    }
    else
    {
      if (cacheit->state == READ)
      {
        cacheit->set = cache.getReplaceSet(cacheit->address);
        cacheit->line = cache.getReplaceLine(cacheit->address);
        switch (MSHR.AddReadEntry(cacheit->address, cacheit->base, cacheit->index,
                                  cacheit->size, cacheit->port, cacheit->uid,
                                  cacheit->instr, cacheit->set, cacheit->line
//boveerle
, cacheit->req_sender
//eoveerle
))
        {
          case NOT_ACCEPTED_MSHR_ENTRY:
            blocked_mshrs++;
            return changed;
            break;
          case EXISTENT_MSHR_ENTRY:
            cacheQueue.RemoveHead();
            changed = true;
            existent_mshrs++;
            hits_mshr++;
            hits_mshr_read++;
            return changed;
            break;
          case EXISTENT_MSHR_PREFETCH_ENTRY:
            if (cacheit->instr.cia != 0)  // check that it is not a request from the instruction cache (so it is not a load/store)
{
              ghb.Access(cacheit->instr.cia, cacheit->address & (~(address_t)(nLineSize - 1)));
            }
            cacheQueue.RemoveHead();
            changed = true;
            existent_mshrs++;
            hits_mshr++;
            hits_mshr_read++;
            return changed;
            break;
          case NEW_MSHR_ENTRY:
            if (cacheit->instr.cia != 0)  // check that it is not a request from the instruction cache (so it is not a load/store)
{
              ghb.Access(cacheit->instr.cia, cacheit->address & (~(address_t)(nLineSize - 1)));
            }
            cacheit->state = READ_MSHR;
            new_mshrs++;
            break;
        }
      }
      changed = SetCacheHeadReadStateMSHR(cacheit) || changed;
    }

    return changed;
  }

  bool SetCacheHeadReadStateMSHR(QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit)
  {
    bool changed = false;

    if (cache.IsLocked(cacheit->set, cacheit->line))
    {
      return changed;
    }
    cache.Lock(cacheit->set, cacheit->line);
    misses++;
    misses_read++;
    cache.historyAccess(cacheit->set, cacheit->line);
    if (cache.getValid(cacheit->set, cacheit->line))
    {
      cacheit->evictedaddress = cache.getAddress(cacheit->set,
                                cacheit->line);
      cacheit->evicted = cache.getWrite(cacheit->set,
                                        cacheit->line);
      if (cacheit->evicted)
      {
        writebacks++;
        writebacks_read++;
        cache.getCacheLine(cacheit->linedata,
                           cacheit->set,
                           cacheit->line);
        cacheit->state = READ_MISS_EVICTION;
      }
      else
      {
        cacheit->state = READ_MISS;
      }
      cache.setValid(cacheit->set, cacheit->line, false);
    }
    else
    {
      cacheit->evicted = false;
      cacheit->state = READ_MISS;
    }

    return changed;
  }

  bool SetCacheHeadWriteState(QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit)
  {
    bool changed = false;

    cacheit->hit = cache.hit(cacheit->address);
    if (cacheit->hit)
    {
      cache.historyAccess(cache.getSet(cacheit->address), cache.getLine(cacheit->address));
      hits++;
      hits_write++;
      cache.setCacheLineIndexed(cache.getSet(cacheit->address),
                                cache.getLine(cacheit->address),
                                cacheit->data,
                                cacheit->address & ((address_t)(nLineSize - 1)),
                                cacheit->size,
                                true);
      if (cacheit->instr.cia != 0)  // check that it is not a request from the instruction cache (so it is not a load/store)
      {
        if (cache.getPrefetch(cache.getSet(cacheit->address), cache.getLine(cacheit->address)))
        {
          cache.setPrefetch(cache.getSet(cacheit->address), cache.getLine(cacheit->address), false);
          ghb.Access(cacheit->instr.cia, cacheit->address & (~(address_t)(nLineSize - 1)));
        }
      }
      /* remove the cache head (independently of the allocationPolicy
       */
      cacheQueue.RemoveHead();
      changed = true;
    }
    else
    {
      if (cacheit->state == WRITE)
      {
        cacheit->set = cache.getReplaceSet(cacheit->address);
        cacheit->line = cache.getReplaceLine(cacheit->address);
        switch (MSHR.AddWriteEntry(cacheit->address, cacheit->base, cacheit->index,
                                   cacheit->size, (uint8_t *)cacheit->data,
                                   cacheit->instr, cacheit->set, cacheit->line
//boveerle
, cacheit->req_sender
//eoveerle
))
        {
          case NOT_ACCEPTED_MSHR_ENTRY:
            blocked_mshrs++;
            return changed;
            break;
          case EXISTENT_MSHR_ENTRY:
            cacheQueue.RemoveHead();
            changed = true;
            existent_mshrs++;
            hits_mshr++;
            hits_mshr_write++;
            return changed;
            break;
          case EXISTENT_MSHR_PREFETCH_ENTRY:
            if (cacheit->instr.cia != 0)  // check that it is not a request from the instruction cache (so it is not a load/store)
{
              ghb.Access(cacheit->instr.cia, cacheit->address & (~(address_t)(nLineSize - 1)));
            }
            cacheQueue.RemoveHead();
            changed = true;
            existent_mshrs++;
            hits_mshr++;
            hits_mshr_write++;
            return changed;
            break;
          case NEW_MSHR_ENTRY:
            if (cacheit->instr.cia != 0)  // check that it is not a request from the instruction cache (so it is not a load/store)
{
              ghb.Access(cacheit->instr.cia, cacheit->address & (~(address_t)(nLineSize - 1)));
            }
            cacheit->state = WRITE_MSHR;
            new_mshrs++;
            break;
        }
      }
      changed = SetCacheHeadWriteStateMSHR(cacheit) || changed;
    }

    return changed;
  }

  bool SetCacheHeadWriteStateMSHR(QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit)
  {
    bool changed = false;

    if (cache.IsLocked(cacheit->set, cacheit->line))
    {
      return changed;
    }
    cache.Lock(cacheit->set, cacheit->line);
    misses++;
    misses_write++;
    cache.historyAccess(cacheit->set, cacheit->line);
    if (cache.getValid(cacheit->set, cacheit->line))
    {
      cacheit->evictedaddress = cache.getAddress(cacheit->set,
                                cacheit->line);
      cacheit->evicted = cache.getWrite(cacheit->set,
                                        cacheit->line);
      if (cacheit->evicted)
      {
        writebacks++;
        writebacks_write++;

        cache.getCacheLine(cacheit->linedata,
                           cacheit->set,
                           cacheit->line);
        cacheit->state = WRITE_MISS_EVICTION;
      }
      else
      {
        cacheit->state = WRITE_MISS;
      }
      cache.setValid(cacheit->set,
                     cacheit->line, false);
    }
    else
    {
      cacheit->evicted = false;
      cacheit->state = WRITE_MISS;
    }

    return changed;
  }

  bool SetCacheHeadPrefetchState(QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit)
  {
    bool changed = false;

    cacheit->hit = cache.hit(cacheit->address);
    if (cacheit->hit)
    {
      cache.historyAccess(cache.getSet(cacheit->address),
                          cache.getLine(cacheit->address));
      hits++;
      hits_prefetch++;
      cacheQueue.RemoveHead();
      changed = true;
    }
    else
    {
      if (cacheit->state == PREFETCH)
      {
        cacheit->set = cache.getReplaceSet(cacheit->address);
        cacheit->line = cache.getReplaceLine(cacheit->address);
        switch (MSHR.AddPrefetchEntry(cacheit->address, cacheit->base, cacheit->index,
                                      cacheit->size, cacheit->port, cacheit->uid,
                                      cacheit->instr, cacheit->set, cacheit->line
//boveerle
, cacheit->req_sender
//eoveerle
))
        {
          case NOT_ACCEPTED_MSHR_ENTRY:
            blocked_mshrs++;
            return changed;
            break;
          case EXISTENT_MSHR_PREFETCH_ENTRY:
          case EXISTENT_MSHR_ENTRY:
            cacheQueue.RemoveHead();
            changed = true;
            existent_mshrs++;
            hits_mshr++;
            hits_mshr_prefetch++;
            return changed;
            break;
          case NEW_MSHR_ENTRY:
            cacheit->state = PREFETCH_MSHR;
            new_mshrs++;
            break;
        }
      }
      changed = SetCacheHeadPrefetchStateMSHR(cacheit) || changed;
    }

    return changed;
  }

  bool SetCacheHeadPrefetchStateMSHR(QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit)
  {
    bool changed = false;

    if (cache.IsLocked(cacheit->set, cacheit->line))
      return changed;
    cache.Lock(cacheit->set, cacheit->line);
    misses++;
    misses_prefetch++;
    cache.historyAccess(cacheit->set, cacheit->line);
    if (cache.getValid(cacheit->set, cacheit->line))
    {
      cacheit->evictedaddress = cache.getAddress(cacheit->set,
                                cacheit->line);
      cacheit->evicted = cache.getWrite(cacheit->set,
                                        cacheit->line);
      if (cacheit->evicted)
      {
        writebacks++;
        writebacks_prefetch++;
        cache.getCacheLine(cacheit->linedata,
                           cacheit->set,
                           cacheit->line);
        cacheit->state = PREFETCH_MISS_EVICTION;
      }
      else
      {
        cacheit->state = PREFETCH_MISS;
      }
      cache.setValid(cacheit->set, cacheit->line, false);
    }
    else
    {
      cacheit->evicted = false;
      cacheit->state = PREFETCH_MISS;
    }

    return changed;
  }

  bool SetCacheHeadEvictState(QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit)
  {
    bool changed = false;

    cacheit->hit = cache.hit(cacheit->address);
    if (cacheit->hit)
    {
      hits++;
      hits_evict++;
      /* no history access, we want to reuse the entry of the evicted line */
      cacheit->set = cache.getSet(cacheit->address);
      cacheit->line = cache.getLine(cacheit->address);
      if (cache.getValid(cacheit->set, cacheit->line))
      {
        cacheit->evictedaddress = cache.getAddress(cacheit->set,
                                  cacheit->line);
        cacheit->evicted = cache.getWrite(cacheit->set,
                                          cacheit->line);
        cache.setValid(cacheit->set, cacheit->line, false);
        cache.setWrite(cacheit->set, cacheit->line, false);
        if (cacheit->evicted)
        {
          writebacks++;
          writebacks_evict++;
          cache.getCacheLine(cacheit->linedata,
                             cacheit->set,
                             cacheit->line);
          cacheit->state = EVICT_READY;
        }
        else
        {
          cacheQueue.RemoveHead();
          changed = true;
        }
      }
      else
      {
        /* why did we get a hit if the line is not valid??? */
        cerr << name() << ": trying to evict a line that is not valid" << endl;
        cerr << *this;
        exit(-1);
      }
    }
    else
    {
      /* do nothing, eviction of a line that does not exist in cache */
      misses++;
      misses_evict++;
      cacheQueue.RemoveHead();
      changed = true;
    }

    return changed;
  }

  bool SetCacheHeadState()
  {
    bool changed = false;
    QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit;

    cacheit = cacheQueue.SeekAtHead();
    if (cacheit)
    {
      if (cacheit->stage == nStages - 1 && cacheit->delay[nStages - 1] == 0)
      {
        switch (cacheit->state)
        {
          case READ:
            changed = SetCacheHeadReadState(cacheit);
            if (cacheit && cacheit->state != READ && cacheit->state != READ_MSHR && cacheit->state != READ_HIT_READY)
            {
              requestQueue << *cacheit;
              cacheQueue.RemoveHead();
            }
            break;
          case READ_MSHR:
            changed = SetCacheHeadReadStateMSHR(cacheit);
            if (cacheit && cacheit->state != READ_MSHR && cacheit->state != READ_HIT_READY)
            {
              requestQueue << *cacheit;
              cacheQueue.RemoveHead();
            }
            break;
          case WRITE:
            changed = SetCacheHeadWriteState(cacheit);
            if (cacheit && cacheit->state != WRITE && cacheit->state != WRITE_MSHR)
            {
              requestQueue << *cacheit;
              cacheQueue.RemoveHead();
            }
            break;
          case WRITE_MSHR:
            changed = SetCacheHeadWriteStateMSHR(cacheit);
            if (cacheit && cacheit->state != WRITE_MSHR)
            {
              requestQueue << *cacheit;
              cacheQueue.RemoveHead();
            }
            break;
          case PREFETCH:
            changed = SetCacheHeadPrefetchState(cacheit);
            if (cacheit && cacheit->state != PREFETCH && cacheit->state != PREFETCH_MSHR)
            {
              requestQueue << *cacheit;
              cacheQueue.RemoveHead();
            }
            break;
          case PREFETCH_MSHR:
            changed = SetCacheHeadPrefetchStateMSHR(cacheit);
            if (cacheit && cacheit->state != PREFETCH_MSHR)
            {
              requestQueue << *cacheit;
              cacheQueue.RemoveHead();
            }
            break;
          case EVICT:
            changed = SetCacheHeadEvictState(cacheit);
            if (cacheit && cacheit->state != EVICT)
            {
              requestQueue << *cacheit;
              cacheQueue.RemoveHead();
            }
            break;
        }
      }
    }

    return changed;
  }

  bool SendCacheDatatoCPU()
  {
    bool changed = false;
    QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit;

    cacheit = cacheQueue.SeekAtHead();
    if (!returnBuffer.ready)
    {
      if (cacheit)
      {
        switch (cacheit->state)
        {
          case READ_HIT_READY:
            returnBuffer.port = cacheit->port;
            returnBuffer.instr = cacheit->instr;
            returnBuffer.base = cacheit->base;
            returnBuffer.index = cacheit->index;
            returnBuffer.address = cacheit->address;
            returnBuffer.size = cacheit->size;
            memcpy(returnBuffer.data, cacheit->data, returnBuffer.size);
            returnBuffer.id = ID_CACHE;
            returnBuffer.uid = cacheit->uid;
            returnBuffer.ready = true;
//boveerle
	returnBuffer.req_sender = cacheit->req_sender;
	//cerr << "@SendCacheDatatoCPU req_sender=" << returnBuffer.req_sender->name() << endl;
//eoveerle
            cacheQueue.RemoveHead();

            changed = true;
            break;
        }
      }
    }

    return changed;
  }

  bool SendMSHRDatatoCPU()
  {
    int port;
    T instruction;
    address_t address;
    int size;
    uint8_t data[nCPULineSize > 0 ? nCPULineSize : nLineSize];
    int tag;
    address_t base;
    int index;
    bool changed = false;
//boveerle
	module* req_sender;
//eoveerle

    if (!returnBuffer.ready)
    {
      if (MSHR.GetRead(&address, &base, &index, &size, &port, &instruction, &tag, data
//boveerle
, &req_sender
//eoveerle
))
      {
        returnBuffer.port = port;
        returnBuffer.instr = instruction;
        returnBuffer.base = base;
        returnBuffer.index = index;
        returnBuffer.address = address;
        returnBuffer.size = size;
        memcpy(returnBuffer.data, data, returnBuffer.size);
        returnBuffer.id = ID_MSHR;
        returnBuffer.uid = tag;
        returnBuffer.ready = true;
//boveerle
	returnBuffer.req_sender = req_sender;
	//cerr << "@SendMSHRDatatoCPU req_sender=" << returnBuffer.req_sender << endl;//->name() << endl;
//eoveerle
      }
    }
    return changed;
  }

  bool SendCacheDatatoMem()
  {
    bool changed = false;

    /* this will happen when allocate on write is used and a hit is obtained
     * or when non allocate on write is used */
    // 		QueuePointer<CachePipeStage<T, nSources, nLineSize, nStages, nCPUtoCacheDataPathSize>, nStages> cacheit;
    QueuePointer<CachePipeStage<T, /*nSources,*/ nLineSize, nStages, nCPUtoCacheDataPathSize>, nMSHR * 2> cacheit;

    // 		cacheit = cacheQueue.SeekAtHead();
    cacheit = requestQueue.SeekAtHead();
    if (cacheit)
    {
      switch (cacheit->state)
      {
        case WRITE_MISS:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = false;
          requestBuffer.size = nLineSize; /* a full line is requested */
          requestBuffer.address = cacheit->address;
          requestBuffer.base = requestBuffer.address & (~(address_t)(nLineSize - 1));
          requestBuffer.index = 0;
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case WRITE_MISS_EVICTION:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = true;
          requestBuffer.size = nLineSize;
          requestBuffer.address = cacheit->evictedaddress & (~(address_t)(nLineSize - 1));
          requestBuffer.base = requestBuffer.address & (~(address_t)(nLineSize - 1));
          requestBuffer.index = 0;
          memcpy(requestBuffer.data, cacheit->linedata, requestBuffer.size);
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case WRITE_MISS_EVICTION_WRITE_DONE:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = false;
          requestBuffer.size = nLineSize; /* a full line is requested */
          requestBuffer.address = cacheit->address;
          requestBuffer.base = requestBuffer.address & (~(address_t)(nLineSize - 1));
          requestBuffer.index = 0;
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case WRITE_MISS_NO_READ:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = true;
          requestBuffer.size = cacheit->size;
          requestBuffer.address = cacheit->address;
          requestBuffer.base = requestBuffer.address;
          requestBuffer.index = 0;
          memcpy(requestBuffer.data, cacheit->data, requestBuffer.size);
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case READ_MISS:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = false;
          requestBuffer.size = nLineSize; /* a full line is requested, not just the requested data */
          requestBuffer.address = cacheit->address;
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case READ_MISS_EVICTION:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = true;
          requestBuffer.size = nLineSize;
          requestBuffer.address = cacheit->evictedaddress & (~(address_t)(nLineSize - 1));
          requestBuffer.base = requestBuffer.address & (~(address_t)(nLineSize - 1));
          requestBuffer.index = 0;
          memcpy(requestBuffer.data, cacheit->linedata, requestBuffer.size);
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case READ_MISS_EVICTION_WRITE_DONE:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = false;
          requestBuffer.size = nLineSize; /* a full line is requested, not just the requested data */
          requestBuffer.address = cacheit->address;
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case PREFETCH_MISS:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = false;
          requestBuffer.size = nLineSize; /* a full line is requested, not just the requested data */
          requestBuffer.address = cacheit->address;
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case PREFETCH_MISS_EVICTION:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = true;
          requestBuffer.size = nLineSize;
          requestBuffer.address = cacheit->evictedaddress & (~(address_t)(nLineSize - 1));
          requestBuffer.base = requestBuffer.address & (~(address_t)(nLineSize - 1));
          requestBuffer.index = 0;
          memcpy(requestBuffer.data, cacheit->linedata, requestBuffer.size);
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case PREFETCH_MISS_EVICTION_WRITE_DONE:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = false;
          requestBuffer.size = nLineSize; /* a full line is requested, not just the requested data */
          requestBuffer.address = cacheit->address;
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
        case EVICT_READY:
          requestBuffer.instr = cacheit->instr;
          requestBuffer.write = true;
          requestBuffer.size = nLineSize;
          requestBuffer.address = cacheit->evictedaddress & (~(address_t)(nLineSize - 1));
          requestBuffer.base = requestBuffer.address & (~(address_t)(nLineSize - 1));
          requestBuffer.index = 0;
          memcpy(requestBuffer.data, cacheit->linedata, requestBuffer.size);
          requestBuffer.uid = cacheit->uid;
          requestBuffer.ready = true;
          break;
      }
//boveerle
	requestBuffer.req_sender = cacheit->req_sender;
//eoveerle
    }

    return changed;
  }

  bool SendData()
  {
    int index;
    bool changed = false;

    /* if there is nothing in the returnBuffer the cache can try to sent something to
     * the CPU */
    if (!returnBuffer.ready)
    {
      changed = SendMSHRDatatoCPU() || changed;
      if (!returnBuffer.ready)
        changed = SendCacheDatatoCPU() || changed;
    }

    if (!requestBuffer.ready)
    {
      changed = SendCacheDatatoMem() || changed;
    }

    if (returnBuffer.ready)
    {
      outCachetoCPUInstruction[returnBuffer.port] = returnBuffer.instr;
      outCachetoCPUSize[returnBuffer.port] = returnBuffer.size > nCachetoCPUDataPathSize ? nCachetoCPUDataPathSize : returnBuffer.size;
      outCachetoCPUAddress[returnBuffer.port] = returnBuffer.base + returnBuffer.index;
      outCachetoCPUData[returnBuffer.port].Write(&returnBuffer.data[returnBuffer.index],
          (returnBuffer.size > nCachetoCPUDataPathSize) ? nCachetoCPUDataPathSize : returnBuffer.size);
      outCachetoCPUTag[returnBuffer.port] = returnBuffer.uid;
      outCachetoCPUValid[returnBuffer.port] = true;
      for (index = 0; index < nPorts; index++)
      {
        if (index != returnBuffer.port)
          outCachetoCPUValid[index] = false;
      }
//boveerle
	//cerr << "@ReturnBuffer.ready, req_sender=" << returnBuffer.req_sender << endl;
	    outCachetoCPUReqSender[returnBuffer.port] = returnBuffer.req_sender;
//eoveerle
    }
    else
    {
      for (index = 0; index < nPorts; index++)
      {
        outCachetoCPUValid[index] = false;
      }
    }

    if (requestBuffer.ready)
    {
      outCachetoMemInstruction = requestBuffer.instr;
      outCachetoMemAddress = requestBuffer.address & ~(address_t)(nCachetoMemDataPathSize - 1);
      if (requestBuffer.write)
      {
        outCachetoMemCommand = WRITE_CACHE_COMMAND;
      }
      else
      {
        outCachetoMemCommand = READ_CACHE_COMMAND;
      }
      int size = requestBuffer.size;
      if (requestBuffer.write && size > nCachetoMemDataPathSize)
      {
        size = nCachetoMemDataPathSize;
      }
      outCachetoMemSize = size;
      if (requestBuffer.write)
      {
        outCachetoMemData.Write(requestBuffer.data + requestBuffer.index, size);
      }
      outCachetoMemTag = requestBuffer.uid;
      outCachetoMemValid = true;
    }
    else
    {
      outCachetoMemValid = false;
    }

    return changed;
  }

  /*******************************************************************************/
  /*******************************************************************************/
  /********************** Internal Control Functions (end) ***********************/
  /*******************************************************************************/
  /*******************************************************************************/

public:
  /* statistics */
  /* statistics */
  uint64_t accesses;
  uint64_t accesses_read;
  uint64_t accesses_write;
  uint64_t accesses_prefetch;
  uint64_t accesses_evict;
  uint64_t hits;
  uint64_t hits_read;
  uint64_t hits_write;
  uint64_t hits_prefetch;
  uint64_t hits_evict;
  uint64_t hits_mshr;
  uint64_t hits_mshr_read;
  uint64_t hits_mshr_write;
  uint64_t hits_mshr_prefetch;
  uint64_t misses;
  uint64_t misses_read;
  uint64_t misses_write;
  uint64_t misses_prefetch;
  uint64_t misses_evict;
  uint64_t writebacks;
  uint64_t writebacks_read;
  uint64_t writebacks_write;
  uint64_t writebacks_prefetch;
  uint64_t writebacks_evict;
  uint64_t new_mshrs;
  uint64_t existent_mshrs;
  uint64_t blocked_mshrs;

  /* Clock */
  sc_in_clk inClock;

  /* input signals from the CPU */
  ml_in_valid inCPUtoCacheValid[nPorts];
  ml_in_enable inCPUtoCacheEnable[nPorts];
  ml_in_data<T> inCPUtoCacheInstruction[nPorts];
  ml_in_cache_cmd inCPUtoCacheCommand[nPorts];
  ml_in_vector<nCPUtoCacheDataPathSize> inCPUtoCacheData[nPorts];
  ml_in_size inCPUtoCacheSize[nPorts];
  ml_in_addr inCPUtoCacheAddress[nPorts];
  ml_in_data<int> inCPUtoCacheTag[nPorts];
  ml_in_accept inCPUtoCacheAccept[nPorts];
//boveerle
    ml_in_reqsender inCPUtoCacheReqSender[nPorts];
//eoveerle

  /* output signals to the CPU */
  ml_out_valid outCachetoCPUValid[nPorts];
  ml_out_data<T> outCachetoCPUInstruction[nPorts];
  ml_out_vector<nCachetoCPUDataPathSize> outCachetoCPUData[nPorts];
  ml_out_size outCachetoCPUSize[nPorts];
  ml_out_addr outCachetoCPUAddress[nPorts];
  ml_out_data<int> outCachetoCPUTag[nPorts];
  ml_out_accept outCachetoCPUAccept[nPorts];
  ml_out_enable outCachetoCPUEnable[nPorts];
//boveerle
    ml_out_reqsender outCachetoCPUReqSender[nPorts];
//eoveerle

  /* input signals from the memory system */
  ml_in_valid inMemtoCacheValid;
  ml_in_enable inMemtoCacheEnable;
  ml_in_data<T> inMemtoCacheInstruction;
  ml_in_vector<nMemtoCacheDataPathSize> inMemtoCacheData;
  ml_in_addr inMemtoCacheAddress;
  ml_in_size inMemtoCacheSize;
  ml_in_data<int> inMemtoCacheTag;
  ml_in_accept inMemtoCacheAccept;

  /* output signals to the memory system */
  ml_out_valid outCachetoMemValid;
  ml_out_data <T> outCachetoMemInstruction;
  ml_out_cache_cmd outCachetoMemCommand;
  ml_out_vector<nCachetoMemDataPathSize> outCachetoMemData;
  ml_out_size outCachetoMemSize;
  ml_out_addr outCachetoMemAddress;
  ml_out_data<int> outCachetoMemTag;
  ml_out_accept outCachetoMemAccept;
  ml_out_enable outCachetoMemEnable;

  /* signals to know if the external control needs to be waken up because the internal control
   * modified the state of the cache */
  ml_out_data<bool> outStateChanged;
  ml_in_data<bool> inStateChanged;
  /* signal to connect outStateChanged and inStateChanged */
  ml_signal_data<bool> stateChanged;

  MicrolibCacheWBNBGHB(const char *name):sc_module(name)
  {
    int i;

    outStateChanged(stateChanged);
    inStateChanged(stateChanged);

    SC_HAS_PROCESS(MicrolibCacheWBNBGHB);
/*
    // Make the ExternalControl process sensitive to the input instructions and acknowledge
    SC_METHOD(ExternalControl);
    for (i=0;i<nPorts;i++)
      sensitive << inCPUtoCacheValid[i] << inCPUtoCacheAccept[i];
    sensitive << inMemtoCacheValid << inMemtoCacheAccept;
    sensitive << inStateChanged;
    // Make the InternalControl sensitive to the clock front edge
    SC_METHOD(InternalControl);
    sensitive_pos << inClock;
*/
SC_METHOD(CheckCPUAccept);
for (int i=0;i<nPorts;i++) sensitive << inCPUtoCacheAccept[i];
SC_METHOD(CheckMemAccept);
sensitive << inMemtoCacheAccept;
SC_METHOD(CheckCPURequests);
for (int i=0;i<nPorts;i++) sensitive << inCPUtoCacheValid[i];
SC_METHOD(CheckMemValid);
sensitive << inMemtoCacheValid;

SC_METHOD(start_of_cycle);
sensitive_pos << inClock;
SC_METHOD(end_of_cycle);
sensitive_neg << inClock;



    accesses = 0;
    accesses_read = 0;
    accesses_write = 0;
    accesses_prefetch = 0;
    accesses_evict = 0;
    hits = 0;
    hits_read = 0;
    hits_write = 0;
    hits_prefetch = 0;
    hits_evict = 0;
    hits_mshr = 0;
    hits_mshr_read = 0;
    hits_mshr_write = 0;
    hits_mshr_prefetch = 0;
    misses = 0;
    misses_read = 0;
    misses_write = 0;
    misses_prefetch = 0;
    misses_evict = 0;
    writebacks = 0;
    writebacks_read = 0;
    writebacks_write = 0;
    writebacks_prefetch = 0;
    writebacks_evict = 0;
    new_mshrs = 0;
    existent_mshrs = 0;
    blocked_mshrs = 0;

  }

  void ExternalControl()
  {
    /* if an accept is received from the CPU enable the output to the CPU */
    CheckCPUAccept();

    /* if an accept is received from the memory system enable the output to the memory system */
    CheckMemAccept();

    /* if the CPU request a load/store */
    CheckCPURequests();

    /* if the memory system replies to a miss */
    CheckMemValid();
  }

  void InternalControl()
  {
    bool changed;

// 		cerr << *this;

    /* checks that the data sent in the previous cycle has been accepted
     * and update the state of the cache pipeline and the MSHR queue if
     * necessary */
    changed = CheckAcceptedData();

    /* checks that the module has received something from the memory system
     * and update the MSHR queue state and the cache state if necessary */
    changed = ReadMemData() || changed;

    /* run the cache pipeline */
    changed = RunCachePipeline() || changed;

    /* checks that the cpu has sent something to the module an add it to the
     * cache pipeline */
    changed = ReadCPUData() || changed;

    /* set the cache head state */
    changed = SetCacheHeadState() || changed;

    /* checks if there is something to be sent from the cache pipeline and the
     * MSHR queue and send them to the cpu and memory system */
    changed = SendData() || changed;

    /* set the outStateChanged if the state of the cache changed,
     * this will call the external control if necessary */
    if (changed)
    {
      outStateChanged = !inStateChanged;
    }
  }


  void start_of_cycle()
  { SendData();
  }



  void end_of_cycle()
  {
    bool changed;

    /* checks that the data sent in the previous cycle has been accepted
     * and update the state of the cache pipeline and the MSHR queue if
     * necessary */
    changed = CheckAcceptedData();

    /* checks that the module has received something from the memory system
     * and update the MSHR queue state and the cache state if necessary */
    changed = ReadMemData() || changed;

    /* run the cache pipeline */
    changed = RunCachePipeline() || changed;

    /* checks that the cpu has sent something to the module an add it to the
     * cache pipeline */
    changed = ReadCPUData() || changed;

    /* set the cache head state */
    changed = SetCacheHeadState() || changed;
  }





  void Read(void *buffer, address_t address, int size)
  {
    cache.Read(buffer, address, size);
  }

  void Write(address_t address, const void *buffer, int size)
  {
    cache.Write(address, buffer, size);
  }

  void Set(address_t address, UInt8 data, int size)
  {
    cache.Set(address, data, size);
  }

  bool Check()
  {
    return cacheQueue.Check();
  }

  void DumpStats(ostream& os)
  {
    /*
    	os << name() << "_accesses " << accesses << " # " << name() << " accesses" << endl;
    	os << name() << "_accesses_read " << accesses_read << " # " << name() << " accesses_read" << endl;
    	os << name() << "_accesses_write " << accesses_write << " # " << name() << " accesses_write" << endl;
    	os << name() << "_accesses_prefetch " << accesses_prefetch << " # " << name() << " accesses_prefetch" << endl;
    	os << name() << "_accesses_evict " << accesses_evict << " # " << name() << " accesses_evict" << endl;

    	os << name() << "_hits " << hits << " # " << name() << " hits" << endl;
    	os << name() << "_hits_read " << hits_read << " # " << name() << " hits_read" << endl;
    	os << name() << "_hits_write " << hits_write << " # " << name() << " hits_write" << endl;
    	os << name() << "_hits_prefetch " << hits_prefetch << " # " << name() << " hits_prefetch" << endl;
    	os << name() << "_hits_evict " << hits_evict << " # " << name() << " hits_evict" << endl;

    	os << name() << "_misses " << misses << " # " << name() << " misses" << endl;
    	os << name() << "_misses_read " << misses_read << " # " << name() << " misses_read" << endl;
    	os << name() << "_misses_write " << misses_write << " # " << name() << " misses_write" << endl;
    	os << name() << "_misses_prefetch " << misses_prefetch << " # " << name() << " misses_prefetch" << endl;
    	os << name() << "_misses_evict " << misses_evict << " # " << name() << " misses_evict" << endl;
    	os << name() << "_miss_rate " << (double) misses / (double) accesses << " # " << name() << " miss rate" << endl;

    	os << name() << "_writebacks " << writebacks << " # " << name() << " writebacks" << endl;
    	os << name() << "_writebacks_read " << writebacks_read << " # " << name() << " writebacks_read" << endl;
    	os << name() << "_writebacks_write " << writebacks_write << " # " << name() << " writebacks_write" << endl;
    	os << name() << "_writebacks_prefetch " << writebacks_prefetch << " # " << name() << " writebacks_prefetch" << endl;
    	os << name() << "_writebacks_evict " << writebacks_evict << " # " << name() << " writebacks_evict" << endl;

    	os << name() << "_hits_mshr " << hits_mshr << " # " << name() << " hits_mshr" << endl;
    	os << name() << "_hits_mshr_read " << hits_mshr_read << " # " << name() << " hits_mshr_read" << endl;
    	os << name() << "_hits_mshr_write " << hits_mshr_write << " # " << name() << " hits_mshr_write" << endl;
    	os << name() << "_hits_mshr_prefetch " << hits_mshr_prefetch << " # " << name() << " hits_mshr_prefetch" << endl;

    	os << name() << "_new_mshrs " << new_mshrs << " # " << name() << " new mshrs" << endl;
    	os << name() << "_existent_mshrs " << existent_mshrs << " # " << name() << " existent_mshrs" << endl;
    	os << name() << "_blocked_mshrs " << blocked_mshrs << " # " << name() << " blocked_mshrs" << endl;
    */
  }

  friend ostream& operator << (ostream& os, MicrolibCacheWBNBGHB<T, nSources, replacementPolicy, nCPUtoCacheDataPathSize, nCachetoCPUDataPathSize, nCPULineSize, nMemtoCacheDataPathSize, nCachetoMemDataPathSize, nLineSize, nCacheLines, nAssociativity, nStages, nDelay, nPorts, nMSHR, nMSHRRead, nGHBITEntries, nGHBEntries, nGHBPCShift, nGHBDepth>& cache)
  {
    os << "=================" << cache.name() << " ==================" << endl;
    os << "Cache Pipeline:" << endl;
    os << cache.cacheQueue << endl;
    os << "Miss Address File:" << endl;
    os << cache.MSHR << endl;
    os << "Delayed Write Buffer:" << endl;
    os << cache.writeBuffer << endl;
    os << "Return Buffer:" << endl;
    os << cache.returnBuffer << endl;
    os << "Request Buffer:" << endl;
    os << cache.requestBuffer << endl;
    os << "Request Queue:" << endl;
    os << cache.requestQueue << endl;
    os << "==========================================================" << endl;

    return os;
  }

  void PrintState()
  {
    cerr << "=================" << name() << " ==================" << endl;
    cerr << "Cache Pipeline:" << endl;
    cerr << cacheQueue << endl;
    cerr << "Miss Address File:" << endl;
    cerr << MSHR << endl;
    cerr << "Delayed Write Buffer:" << endl;
    cerr << writeBuffer << endl;
    cerr << "Return Buffer:" << endl;
    cerr << returnBuffer << endl;
    cerr << "Request Buffer:" << endl;
    cerr << requestBuffer << endl;
    cerr << "==========================================================" << endl;
  }

};

#endif //__CACHEWBNBGHB_H__
