/*
 * Copyright (c) 2007 MIPS Technologies, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Korey Sewell
 *
 */

#include "base/str.hh"
#include "config/the_isa.hh"
#include "cpu/inorder/cpu.hh"
#include "cpu/inorder/pipeline_stage.hh"
#include "cpu/inorder/resource_pool.hh"
#include "debug/Activity.hh"
#include "debug/InOrderStage.hh"
#include "debug/InOrderStall.hh"
#include "debug/Resource.hh"
#include "debug/ThreadModel.hh"

using namespace std;
using namespace ThePipeline;

PipelineStage::PipelineStage(Params *params, unsigned stage_num)
    : stageNum(stage_num), stageWidth(params->stageWidth),
      numThreads(ThePipeline::MaxThreads), _status(Inactive),
      stageBufferMax(params->stageWidth),
      prevStageValid(false), nextStageValid(false), idle(false)
{
    init(params);
}

PipelineStage::~PipelineStage()
{
   for(ThreadID tid = 0; tid < numThreads; tid++) {
       skidBuffer[tid].clear();
       stalls[tid].resources.clear();
   }
}

void
PipelineStage::init(Params *params)
{
    for(ThreadID tid = 0; tid < numThreads; tid++) {
        stageStatus[tid] = Idle;

        for (int stNum = 0; stNum < NumStages; stNum++) {
            stalls[tid].stage[stNum] = false;
        }
        stalls[tid].resources.clear();

        if (stageNum < BackEndStartStage)
            lastStallingStage[tid] = BackEndStartStage - 1;
        else
            lastStallingStage[tid] = NumStages - 1;
    }

    if ((InOrderCPU::ThreadModel) params->threadModel ==
        InOrderCPU::SwitchOnCacheMiss) {
        switchedOutBuffer.resize(ThePipeline::MaxThreads);
        switchedOutValid.resize(ThePipeline::MaxThreads);
    }
}


std::string
PipelineStage::name() const
{
     return cpu->name() + ".stage" + to_string(stageNum);
}


void
PipelineStage::regStats()
{
   idleCycles
        .name(name() + ".idleCycles")
       .desc("Number of cycles 0 instructions are processed.");
   
    runCycles
        .name(name() + ".runCycles")
        .desc("Number of cycles 1+ instructions are processed.");

    utilization
        .name(name() + ".utilization")
        .desc("Percentage of cycles stage was utilized (processing insts).")
        .precision(6);
    utilization = (runCycles / cpu->numCycles) * 100;
    
}


void
PipelineStage::setCPU(InOrderCPU *cpu_ptr)
{
    cpu = cpu_ptr;

    DPRINTF(InOrderStage, "Set CPU pointer.\n");

    tracer = dynamic_cast<Trace::InOrderTrace *>(cpu->getTracer());
}


void
PipelineStage::setTimeBuffer(TimeBuffer<TimeStruct> *tb_ptr)
{
    DPRINTF(InOrderStage, "Setting time buffer pointer.\n");
    timeBuffer = tb_ptr;

    // Setup wire to write information back to fetch.
    // @todo: should this be writing to the next stage => -1 and reading from is (0)???
    toPrevStages = timeBuffer->getWire(0);

    // Create wires to get information from proper places in time buffer.
    fromNextStages = timeBuffer->getWire(-1);
}


void
PipelineStage::setPrevStageQueue(TimeBuffer<InterStageStruct> *prev_stage_ptr)
{
    DPRINTF(InOrderStage, "Setting previous stage queue pointer.\n");
    prevStageQueue = prev_stage_ptr;

    // Setup wire to read information from fetch queue.
    prevStage = prevStageQueue->getWire(-1);

    prevStageValid = true;
}



void
PipelineStage::setNextStageQueue(TimeBuffer<InterStageStruct> *next_stage_ptr)
{
    DPRINTF(InOrderStage, "Setting next stage pointer.\n");
    nextStageQueue = next_stage_ptr;

    // Setup wire to write information to proper place in stage queue.
    nextStage = nextStageQueue->getWire(0);
    nextStageValid = true;
}



void
PipelineStage::setActiveThreads(list<ThreadID> *at_ptr)
{
    DPRINTF(InOrderStage, "Setting active threads list pointer.\n");
    activeThreads = at_ptr;
}

/*inline void
PipelineStage::switchToActive()
{
   if (_status == Inactive) {
        DPRINTF(Activity, "Activating stage.\n");

        cpu->activateStage(stageNum);

        _status = Active;
    }
}*/

void
PipelineStage::switchOut()
{
    // Stage can immediately switch out.
    panic("Switching Out of Stages Unimplemented");
}


void
PipelineStage::takeOverFrom()
{
    _status = Inactive;

    // Be sure to reset state and clear out any old instructions.
    for (ThreadID tid = 0; tid < numThreads; ++tid) {
        stageStatus[tid] = Idle;

        for (int stNum = 0; stNum < NumStages; stNum++) {
            stalls[tid].stage[stNum] = false;
        }

        stalls[tid].resources.clear();

        skidBuffer[tid].clear();
    }
    wroteToTimeBuffer = false;
}



bool
PipelineStage::checkStall(ThreadID tid) const
{
    bool ret_val = false;

    // Only check pipeline stall from stage directly following this stage
    if (nextStageValid && stalls[tid].stage[stageNum + 1]) {
        DPRINTF(InOrderStage,"[tid:%i]: Stall fom Stage %i detected.\n",
                tid, stageNum + 1);
        ret_val = true;
    }

    if (!stalls[tid].resources.empty()) {
#if TRACING_ON
        string stall_src;

        for (int i=0; i < stalls[tid].resources.size(); i++) {
            stall_src += stalls[tid].resources[i]->res->name() + ":";
        }

        DPRINTF(InOrderStage,"[tid:%i]: Stall fom resources (%s) detected.\n",
                tid, stall_src);
#endif
        ret_val = true;
    }

    return ret_val;
}


void
PipelineStage::removeStalls(ThreadID tid)
{
    for (int st_num = 0; st_num < NumStages; st_num++) {
        if (stalls[tid].stage[st_num] == true) {
            DPRINTF(InOrderStage, "Removing stall from stage %i.\n",
                    st_num);
            stalls[tid].stage[st_num] = false;
        }

        if (toPrevStages->stageBlock[st_num][tid] == true) {
            DPRINTF(InOrderStage, "Removing pending block from stage %i.\n",
                    st_num);
            toPrevStages->stageBlock[st_num][tid] = false;
        }

        if (fromNextStages->stageBlock[st_num][tid] == true) {
            DPRINTF(InOrderStage, "Removing pending block from stage %i.\n",
                    st_num);
            fromNextStages->stageBlock[st_num][tid] = false;
        }
    }
    stalls[tid].resources.clear();
}

inline bool
PipelineStage::prevStageInstsValid()
{
    return prevStage->insts.size() > 0;
}

bool
PipelineStage::isBlocked(ThreadID tid)
{
    return stageStatus[tid] == Blocked;
}

bool
PipelineStage::block(ThreadID tid)
{
    DPRINTF(InOrderStage, "[tid:%d]: Blocking, sending block signal back to "
            "previous stages.\n", tid);

    // If the stage status is blocked or unblocking then stage has not yet
    // signalled fetch to unblock. In that case, there is no need to tell
    // fetch to block.
    if (stageStatus[tid] != Blocked) {
        if (stageStatus[tid] != Unblocking) {
            wroteToTimeBuffer = true;
        }

        stageStatus[tid] = Blocked;

        if (prevStageValid) {
            DPRINTF(InOrderStage, "[tid:%d]: Stage %i setting block signal.\n",
                    tid, stageNum);
            toPrevStages->stageBlock[stageNum][tid] = true;
        }

        return true;
    }


    return false;
}

void
PipelineStage::blockDueToBuffer(ThreadID tid)
{
    DPRINTF(InOrderStage, "[tid:%d]: Blocking instructions from passing to "
            "next stage.\n", tid);

    if (stageStatus[tid] != Blocked) {
        if (stageStatus[tid] != Unblocking) {
            wroteToTimeBuffer = true;
        }

        // Set the status to Blocked.
        stageStatus[tid] = Blocked;
    }
}

bool
PipelineStage::unblock(ThreadID tid)
{
    // @todo: Shouldnt this be if any available slots are open???
    // Stage is done unblocking only if the skid buffer is empty.
    if (skidBuffer[tid].empty()) {
        DPRINTF(InOrderStage, "[tid:%u]: Done unblocking.\n", tid);

        if (prevStageValid)
            toPrevStages->stageUnblock[stageNum][tid] = true;

        wroteToTimeBuffer = true;

        stageStatus[tid] = Running;

        return true;
    }

    DPRINTF(InOrderStage, "[tid:%u]: Currently unblocking.\n", tid);
    return false;
}

void
PipelineStage::setupSquash(DynInstPtr inst, ThreadID tid)
{
    if (cpu->lastSquashCycle[tid] == curTick() &&
        cpu->squashSeqNum[tid] < inst->seqNum){
        DPRINTF(Resource, "Ignoring [sn:%i] branch squash signal due to "
                "another stage's squash signal for after [sn:%i].\n", 
                inst->seqNum, cpu->squashSeqNum[tid]);
    } else {
        InstSeqNum squash_seq_num = inst->squashSeqNum;
        unsigned squash_stage = (nextStageValid) ? stageNum + 1
            : stageNum;

        toPrevStages->stageInfo[squash_stage][tid].squash = true;
        toPrevStages->stageInfo[squash_stage][tid].doneSeqNum =
            squash_seq_num;

        DPRINTF(InOrderStage, "[tid:%i]: Setting up squashing after "
                "[sn:%i], due to [sn:%i] %s. Squash-Start-Stage:%i\n",
                tid, squash_seq_num, inst->seqNum, inst->instName(),
                squash_stage);

        // Save squash num for later stage use
        cpu->lastSquashCycle[tid] = curTick();
        cpu->squashSeqNum[tid] = squash_seq_num;
    }
}

void
PipelineStage::squashDueToMemStall(InstSeqNum seq_num, ThreadID tid)
{
    squash(seq_num, tid);    
}

void
PipelineStage::squashPrevStageInsts(InstSeqNum squash_seq_num, ThreadID tid)
{
    DPRINTF(InOrderStage, "[tid:%i]: Removing instructions from "
            "incoming stage queue.\n", tid);

    int insts_from_prev_stage = prevStage->insts.size();
    for (int i=0; i < insts_from_prev_stage; i++) {
        if (prevStage->insts[i]->threadNumber == tid &&
            prevStage->insts[i]->seqNum > squash_seq_num) {
            DPRINTF(InOrderStage, "[tid:%i]: Squashing instruction, "
                    "[sn:%i] PC %s.\n",
                    tid,
                    prevStage->insts[i]->seqNum,
                    prevStage->insts[i]->pcState());
            prevStage->insts[i]->setSquashed();

            prevStage->insts[i] = cpu->dummyBufferInst;
        }
    }
}

void
PipelineStage::squash(InstSeqNum squash_seq_num, ThreadID tid)
{
    // Set status to squashing.
    stageStatus[tid] = Squashing;

    squashPrevStageInsts(squash_seq_num, tid);

    DPRINTF(InOrderStage, "[tid:%i]: Removing instructions from incoming stage"
            " skidbuffer.\n", tid);
    //@TODO: Walk Through List Using iterator and remove
    //       all instructions over the value
    std::list<DynInstPtr>::iterator cur_it = skidBuffer[tid].begin();
    std::list<DynInstPtr>::iterator end_it = skidBuffer[tid].end();

    while (cur_it != end_it) {
        if ((*cur_it)->seqNum <= squash_seq_num) {
            DPRINTF(InOrderStage, "[tid:%i]: Cannot remove skidBuffer "
                    "instructions (starting w/[sn:%i]) before "
                    "[sn:%i]. %i insts left.\n", tid, 
                    (*cur_it)->seqNum, squash_seq_num,
                    skidBuffer[tid].size());
            cur_it++;
        } else {
            DPRINTF(InOrderStage, "[tid:%i]: Removing instruction, [sn:%i] "
                    " PC %s.\n", tid, (*cur_it)->seqNum, (*cur_it)->pc);
            (*cur_it)->setSquashed();
            cur_it = skidBuffer[tid].erase(cur_it);
        }

    }

}

int
PipelineStage::stageBufferAvail()
{
    unsigned total = 0;

    for (int i=0; i < ThePipeline::MaxThreads; i++) {
        total += skidBuffer[i].size();
    }

    int avail = stageBufferMax - total;
    assert(avail >= 0);

    return avail;
}

bool
PipelineStage::canSendInstToStage(unsigned stage_num)
{
    bool buffer_avail = false;

    if (cpu->pipelineStage[stage_num]->prevStageValid) {
        buffer_avail = cpu->pipelineStage[stage_num]->stageBufferAvail() -
            cpu->pipelineStage[stage_num-1]->nextStage->insts.size() >= 1;
    }

    if (!buffer_avail && nextStageQueueValid(stage_num)) {
        DPRINTF(InOrderStall, "STALL: No room in stage %i buffer.\n", 
                stageNum + 1);
    }

    return buffer_avail;
}

int
PipelineStage::skidSize()
{
    int total = 0;

    for (int i=0; i < ThePipeline::MaxThreads; i++) {
        total += skidBuffer[i].size();
    }

    return total;
}

bool
PipelineStage::skidsEmpty()
{
    list<ThreadID>::iterator threads = activeThreads->begin();

    while (threads != activeThreads->end()) {
        if (!skidBuffer[*threads++].empty())
            return false;
    }

    return true;
}



void
PipelineStage::updateStatus()
{
    bool any_unblocking = false;

    list<ThreadID>::iterator threads = activeThreads->begin();

    while (threads != activeThreads->end()) {
        ThreadID tid = *threads++;

        if (stageStatus[tid] == Unblocking) {
            any_unblocking = true;
            break;
        }
    }

    // Stage will have activity if it's unblocking.
    if (any_unblocking) {
        if (_status == Inactive) {
            _status = Active;

            DPRINTF(Activity, "Activating stage.\n");

            cpu->activateStage(stageNum);
        }
    } else {
        // If it's not unblocking, then stage will not have any internal
        // activity.  Switch it to inactive.
        if (_status == Active) {
            _status = Inactive;
            DPRINTF(Activity, "Deactivating stage.\n");

            cpu->deactivateStage(stageNum);
        }
    }
}

void 
PipelineStage::activateThread(ThreadID tid)
{    
    if (cpu->threadModel == InOrderCPU::SwitchOnCacheMiss) {
        if (!switchedOutValid[tid]) {
            DPRINTF(InOrderStage, "[tid:%i] No instruction available in "
                    "switch out buffer.\n", tid);        
        } else {
            DynInstPtr inst = switchedOutBuffer[tid];

            DPRINTF(InOrderStage,"[tid:%i]: Re-Inserting [sn:%lli] PC:%s into"
                    " stage skidBuffer %i\n", tid, inst->seqNum,
                    inst->pcState(), inst->threadNumber);

            // Make instruction available for pipeline processing
            skidBuffer[tid].push_back(inst);

            // Update PC so that we start fetching after this instruction to
            // prevent "double"-execution of instructions
            cpu->resPool->scheduleEvent((InOrderCPU::CPUEventType)
                                        ResourcePool::UpdateAfterContextSwitch, 
                                        inst, Cycles(0), 0, tid);

            // Clear switchout buffer
            switchedOutBuffer[tid] = NULL;
            switchedOutValid[tid] = false;            

            // Update any CPU stats based off context switches
            cpu->updateContextSwitchStats();            
        }        
    }
    
}


void
PipelineStage::sortInsts()
{
    if (prevStageValid) {
        assert(prevStage->insts.size() <= stageWidth);

        int insts_from_prev_stage = prevStage->insts.size();
        int insts_from_cur_stage = skidSize();
        DPRINTF(InOrderStage, "%i insts available from stage buffer %i. Stage "
                "currently has %i insts from last cycle.\n",
                insts_from_prev_stage, prevStageQueue->id(),
                insts_from_cur_stage);

        int inserted_insts = 0;

        for (int i = 0; i < insts_from_prev_stage; i++) {
            if (prevStage->insts[i]->isSquashed()) {
                DPRINTF(InOrderStage, "[tid:%i]: Ignoring squashed [sn:%i], "
                        "not inserting into stage buffer.\n",
                    prevStage->insts[i]->readTid(),
                    prevStage->insts[i]->seqNum);
                continue;
            }

            ThreadID tid = prevStage->insts[i]->threadNumber;

            if (inserted_insts + insts_from_cur_stage == stageWidth) {
               DPRINTF(InOrderStage, "Stage %i has accepted all insts "
                       "possible for this tick. Placing [sn:%i] in stage %i skidBuffer\n",
                       stageNum, prevStage->insts[i]->seqNum, stageNum - 1);
                cpu->pipelineStage[stageNum - 1]->
                    skidBuffer[tid].push_front(prevStage->insts[i]);

                int prev_stage = stageNum - 1;
                if (cpu->pipelineStage[prev_stage]->stageStatus[tid] == Running ||
                    cpu->pipelineStage[prev_stage]->stageStatus[tid] == Idle) {
                    cpu->pipelineStage[prev_stage]->stageStatus[tid] = Unblocking;
                }
            } else {
                DPRINTF(InOrderStage, "[tid:%i]: Inserting [sn:%i] into stage "
                        "buffer.\n", prevStage->insts[i]->readTid(),
                        prevStage->insts[i]->seqNum);

                skidBuffer[tid].push_back(prevStage->insts[i]);
            }

            prevStage->insts[i] = cpu->dummyBufferInst;

            inserted_insts++;
        }
    }
}



void
PipelineStage::readStallSignals(ThreadID tid)
{
    for (int stage_idx = stageNum+1; stage_idx <= lastStallingStage[tid];
         stage_idx++) {

        DPRINTF(InOrderStage, "[tid:%i] Reading stall signals from Stage "
                "%i. Block:%i Unblock:%i.\n",
                tid,
                stage_idx,
                fromNextStages->stageBlock[stage_idx][tid],
                fromNextStages->stageUnblock[stage_idx][tid]);

        // Check for Stage Blocking Signal
        if (fromNextStages->stageBlock[stage_idx][tid]) {
            DPRINTF(InOrderStage, "[tid:%i] Stall from stage %i set.\n", tid,
                    stage_idx);
            stalls[tid].stage[stage_idx] = true;
        }

        // Check for Stage Unblocking Signal
        if (fromNextStages->stageUnblock[stage_idx][tid]) {
            DPRINTF(InOrderStage, "[tid:%i] Stall from stage %i unset.\n", tid,
                    stage_idx);
            stalls[tid].stage[stage_idx] = false;
        }
    }
}



bool
PipelineStage::checkSignalsAndUpdate(ThreadID tid)
{
    // Check if there's a squash signal, squash if there is.
    // Check stall signals, block if necessary.
    // If status was blocked
    //     Check if stall conditions have passed
    //         if so then go to unblocking
    // If status was Squashing
    //     check if squashing is not high.  Switch to running this cycle.

    // Update the per thread stall statuses.
    readStallSignals(tid);

    // Check for squash from later pipeline stages
    for (int stage_idx=stageNum; stage_idx < NumStages; stage_idx++) {
        if (fromNextStages->stageInfo[stage_idx][tid].squash) {
            DPRINTF(InOrderStage, "[tid:%u]: Squashing instructions due to "
                    "squash from stage %u.\n", tid, stage_idx);
            InstSeqNum squash_seq_num = fromNextStages->
                stageInfo[stage_idx][tid].doneSeqNum;
            squash(squash_seq_num, tid);
            break; //return true;
        }
    }

    if (checkStall(tid)) {
        return block(tid);
    }

    if (stageStatus[tid] == Blocked) {
        DPRINTF(InOrderStage, "[tid:%u]: Done blocking, switching to "
                "unblocking.\n", tid);

        stageStatus[tid] = Unblocking;

        unblock(tid);

        return true;
    }

    if (stageStatus[tid] == Squashing) {
        if (!skidBuffer[tid].empty()) {
            DPRINTF(InOrderStage, "[tid:%u]: Done squashing, switching to "
                    "unblocking.\n", tid);

            stageStatus[tid] = Unblocking;
        } else {
            // Switch status to running if stage isn't being told to block or
            // squash this cycle.
            DPRINTF(InOrderStage, "[tid:%u]: Done squashing, switching to "
                    "running.\n", tid);

            stageStatus[tid] = Running;
        }

        return true;
    }

    // If we've reached this point, we have not gotten any signals that
    // cause stage to change its status.  Stage remains the same as before.*/
    return false;
}



void
PipelineStage::tick()
{
    idle = false;
    
    wroteToTimeBuffer = false;

    bool status_change = false;
    
    sortInsts();

    instsProcessed = 0;

    processStage(status_change);

    if (status_change) {
        updateStatus();
    }

    if (wroteToTimeBuffer) {
        DPRINTF(Activity, "Activity this cycle.\n");
        cpu->activityThisCycle();
    }

    DPRINTF(InOrderStage, "\n\n");
}

void
PipelineStage::setResStall(ResReqPtr res_req, ThreadID tid)
{
    DPRINTF(InOrderStage, "Inserting stall from %s.\n", res_req->res->name());
    stalls[tid].resources.push_back(res_req);
}

void
PipelineStage::unsetResStall(ResReqPtr res_req, ThreadID tid)
{
    // Search through stalls to find stalling request and then
    // remove it
    vector<ResReqPtr>::iterator req_it = stalls[tid].resources.begin();
    vector<ResReqPtr>::iterator req_end = stalls[tid].resources.end();

    while (req_it != req_end) {
        if( (*req_it)->res ==  res_req->res && // Same Resource
            (*req_it)->inst ==  res_req->inst && // Same Instruction
            (*req_it)->getSlot() ==  res_req->getSlot()) {
            DPRINTF(InOrderStage, "[tid:%u]: Clearing stall by %s.\n",
                    tid, res_req->res->name());
            stalls[tid].resources.erase(req_it);
            break;
        }

        req_it++;
    }

    if (stalls[tid].resources.size() == 0) {
        DPRINTF(InOrderStage, "[tid:%u]: There are no remaining resource"
                "stalls.\n", tid);
    }
}

// @TODO: Update How we handled threads in CPU. Maybe threads shouldnt be 
// handled one at a time, but instead first come first serve by instruction?
// Questions are how should a pipeline stage handle thread-specific stalls &
// pipeline squashes
void
PipelineStage::processStage(bool &status_change)
{
    list<ThreadID>::iterator threads = activeThreads->begin();

    //Check stall and squash signals.
    while (threads != activeThreads->end()) {
        ThreadID tid = *threads++;

        DPRINTF(InOrderStage,"Processing [tid:%i]\n",tid);
        status_change =  checkSignalsAndUpdate(tid) || status_change;

        processThread(status_change, tid);
    }

    if (nextStageValid) {
        DPRINTF(InOrderStage, "%i insts now available for stage %i.\n",
                nextStage->insts.size(), stageNum + 1);
    }

    if (instsProcessed > 0) {
        ++runCycles;
        idle = false;        
    } else {
        ++idleCycles;        
        idle = true;        
    }
    
    DPRINTF(InOrderStage, "%i left in stage %i incoming buffer.\n", skidSize(),
            stageNum);

    DPRINTF(InOrderStage, "%i available in stage %i incoming buffer.\n", 
            stageBufferAvail(), stageNum);
}

void
PipelineStage::processThread(bool &status_change, ThreadID tid)
{
    // If status is Running or idle,
    //     call processInsts()
    // If status is Unblocking,
    //     buffer any instructions coming from fetch
   //     continue trying to empty skid buffer
    //     check if stall conditions have passed

    // Stage should try to process as many instructions as its bandwidth
    // will allow, as long as it is not currently blocked.
    if (stageStatus[tid] == Running ||
        stageStatus[tid] == Idle) {
        DPRINTF(InOrderStage, "[tid:%u]: Not blocked, so attempting to run "
                "stage.\n",tid);

        processInsts(tid);
    } else if (stageStatus[tid] == Unblocking) {
        // Make sure that the skid buffer has something in it if the
        // status is unblocking.
        assert(!skidsEmpty());

        // If the status was unblocking, then instructions from the skid
        // buffer were used.  Remove those instructions and handle
        // the rest of unblocking.
        processInsts(tid);

        status_change = unblock(tid) || status_change;
    }
}


void
PipelineStage::processInsts(ThreadID tid)
{
    // Instructions can come either from the skid buffer or the list of
    // instructions coming from fetch, depending on stage's status.
    int insts_available = skidBuffer[tid].size();

    std::list<DynInstPtr> &insts_to_stage = skidBuffer[tid];

    if (insts_available == 0) {
        DPRINTF(InOrderStage, "[tid:%u]: Nothing to do, breaking out"
                " early.\n",tid);
        return;
    }

    DynInstPtr inst;
    bool last_req_completed = true;

    while (insts_available > 0 &&
           instsProcessed < stageWidth &&
           last_req_completed) {
        assert(!insts_to_stage.empty());

        inst = insts_to_stage.front();

        DPRINTF(InOrderStage, "[tid:%u]: Processing instruction [sn:%lli] "
                "%s with PC %s\n", tid, inst->seqNum,
                inst->instName(),
                inst->pcState());

        if (inst->isSquashed()) {
            DPRINTF(InOrderStage, "[tid:%u]: Instruction %i with PC %s is "
                    "squashed, skipping.\n",
                    tid, inst->seqNum, inst->pcState());

            insts_to_stage.pop_front();

            --insts_available;

            continue;
        }

        int reqs_processed = 0;        
        last_req_completed = processInstSchedule(inst, reqs_processed);

        // If the instruction isnt squashed & we've completed one request
        // Then we can officially count this instruction toward the stage's 
        // bandwidth count
        if (reqs_processed > 0)
            instsProcessed++;

        // Don't let instruction pass to next stage if it hasnt completed
        // all of it's requests for this stage.
        if (!last_req_completed)
            continue;

        // Send to Next Stage or Break Loop
        if (nextStageValid && !sendInstToNextStage(inst)) {
            DPRINTF(InOrderStage, "[tid:%i] [sn:%i] unable to proceed to stage"
                    " %i.\n", tid, inst->seqNum,inst->nextStage);
            break;
        }

        insts_to_stage.pop_front();

        --insts_available;
    }

    // If we didn't process all instructions, then we will need to block
    // and put all those instructions into the skid buffer.
    if (!insts_to_stage.empty()) {
        blockDueToBuffer(tid);
    }

    // Record that stage has written to the time buffer for activity
    // tracking.
    if (instsProcessed) {
        wroteToTimeBuffer = true;
    }
}

bool
PipelineStage::processInstSchedule(DynInstPtr inst,int &reqs_processed)
{
    bool last_req_completed = true;
    ThreadID tid = inst->readTid();

    if (inst->nextResStage() == stageNum) {
        int res_stage_num = inst->nextResStage();

        while (res_stage_num == stageNum) {
            int res_num = inst->nextResource();


            DPRINTF(InOrderStage, "[tid:%i]: [sn:%i]: sending request to %s."
                    "\n", tid, inst->seqNum, cpu->resPool->name(res_num));

            ResReqPtr req = cpu->resPool->request(res_num, inst);
            assert(req->valid);

            bool req_completed = req->isCompleted();
            bool done_in_pipeline = false;
            if (req_completed) {
                DPRINTF(InOrderStage, "[tid:%i]: [sn:%i] request to %s "
                        "completed.\n", tid, inst->seqNum, 
                        cpu->resPool->name(res_num));

                reqs_processed++;                

                req->stagePasses++;                

                done_in_pipeline = inst->finishSkedEntry();
                if (done_in_pipeline) {
                    DPRINTF(InOrderDynInst, "[tid:%i]: [sn:%i] finished "
                            "in pipeline.\n", tid, inst->seqNum);
                }
            } else {
                DPRINTF(InOrderStage, "[tid:%i]: [sn:%i] request to %s failed."
                        "\n", tid, inst->seqNum, cpu->resPool->name(res_num));

                last_req_completed = false;

                if (req->isMemStall() && 
                    cpu->threadModel == InOrderCPU::SwitchOnCacheMiss) {
                    // Save Stalling Instruction
                    DPRINTF(ThreadModel, "[tid:%i] [sn:%i] Detected cache "
                            "miss.\n", tid, inst->seqNum);

                    DPRINTF(InOrderStage, "Inserting [tid:%i][sn:%i] into "
                            "switch out buffer.\n", tid, inst->seqNum);

                    switchedOutBuffer[tid] = inst;
                    switchedOutValid[tid] = true;
                    
                    // Remove Thread From Pipeline & Resource Pool
                    inst->squashingStage = stageNum;
                    inst->squashSeqNum = inst->seqNum;
                    cpu->squashFromMemStall(inst, tid);  

                    // Switch On Cache Miss
                    //=====================
                    // Suspend Thread at end of cycle
                    DPRINTF(ThreadModel, "Suspending [tid:%i] due to cache "
                            "miss.\n", tid);
                    cpu->suspendContext(tid);                    

                    // Activate Next Ready Thread at end of cycle
                    DPRINTF(ThreadModel, "Attempting to activate next ready "
                            "thread due to cache miss.\n");
                    cpu->activateNextReadyContext();
                }
            }

            // If this request is no longer needs to take up bandwidth in the
            // resource, go ahead and free that bandwidth up
            if (req->doneInResource) {
                req->freeSlot();
            }

            // No longer need to process this instruction if the last
            // request it had wasn't completed or if there is nothing
            // else for it to do in the pipeline
            if (done_in_pipeline || !req_completed) {
                break;
            }

            res_stage_num = inst->nextResStage();
        }
    } else {
        DPRINTF(InOrderStage, "[tid:%u]: Instruction [sn:%i] with PC %s "
                " needed no resources in stage %i.\n",
                tid, inst->seqNum, inst->pcState(), stageNum);
    }

    return last_req_completed;
}

bool
PipelineStage::nextStageQueueValid(int stage_num)
{
    return cpu->pipelineStage[stage_num]->nextStageValid;
}


bool
PipelineStage::sendInstToNextStage(DynInstPtr inst)
{
    // Update Next Stage Variable in Instruction
    // NOTE: Some Resources will update this nextStage var. to
    // for bypassing, so can't always assume nextStage=stageNum+1
    if (inst->nextStage == stageNum)
        inst->nextStage++;

    bool success = false;
    ThreadID tid = inst->readTid();
    int next_stage = inst->nextStage;
    int prev_stage = next_stage - 1;

    assert(next_stage >= 1);
    assert(prev_stage >= 0);

    DPRINTF(InOrderStage, "[tid:%u]: Attempting to send instructions to "
            "stage %u.\n", tid, stageNum+1);

    if (!canSendInstToStage(inst->nextStage)) {
        DPRINTF(InOrderStage, "[tid:%u]: Could not send instruction to "
                "stage %u.\n", tid, stageNum+1);
        return false;
    }


    if (nextStageQueueValid(inst->nextStage - 1)) {
        if (inst->seqNum > cpu->squashSeqNum[tid] &&
            curTick() == cpu->lastSquashCycle[tid]) {
            DPRINTF(InOrderStage, "[tid:%u]: [sn:%i]: squashed, skipping "
                    "insertion into stage %i queue.\n", tid, inst->seqNum, 
                    inst->nextStage);
        } else {
            if (nextStageValid) {
                DPRINTF(InOrderStage, "[tid:%u] %i slots available in next "
                        "stage buffer.\n", tid, 
                        cpu->pipelineStage[next_stage]->stageBufferAvail());
            }

            DPRINTF(InOrderStage, "[tid:%u]: [sn:%i]: being placed into  "
                    "index %i of stage buffer %i queue.\n",
                    tid, inst->seqNum,
                    cpu->pipelineStage[prev_stage]->nextStage->insts.size(),
                    cpu->pipelineStage[prev_stage]->nextStageQueue->id());

            // Place instructions in inter-stage communication struct for next
            // pipeline stage to read next cycle
            cpu->pipelineStage[prev_stage]->nextStage->insts.push_back(inst);

            success = true;

            // Take note of trace data for this inst & stage
            if (inst->traceData) {
                //@todo: exec traces are broke. fix them
                inst->traceData->setStageCycle(stageNum, curTick());
            }

        }
    }

    return success;
}

void
PipelineStage::dumpInsts()
{
    cprintf("Insts in Stage %i skidbuffers\n",stageNum);

    for (ThreadID tid = 0; tid < ThePipeline::MaxThreads; tid++) {
        std::list<DynInstPtr>::iterator cur_it =  skidBuffer[tid].begin();
        std::list<DynInstPtr>::iterator end_it =  skidBuffer[tid].end();

        while (cur_it != end_it) {
            DynInstPtr inst = (*cur_it);

            cprintf("Inst. PC:%s\n[tid:%i]\n[sn:%i]\n\n",
                    inst->pcState(), inst->threadNumber, inst->seqNum);

            cur_it++;
        }
    }

}
