/*
 * 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 "cpu/inorder/resources/resource_list.hh"
#include "cpu/inorder/cpu.hh"
#include "cpu/inorder/first_stage.hh"
#include "cpu/inorder/resource_pool.hh"
#include "debug/InOrderStage.hh"
#include "params/InOrderTrace.hh"

using namespace std;
using namespace ThePipeline;

FirstStage::FirstStage(Params *params, unsigned stage_num)
    : PipelineStage(params, stage_num), numFetchingThreads(1),
      fetchPolicy(FirstStage::RoundRobin)
{
    for(ThreadID tid = 0; tid < this->numThreads; tid++) {
        stageStatus[tid] = Running;
    }
}

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

    fetchPriorityList = &cpu->fetchPriorityList;

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


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

    // Clear the instruction list and skid buffer in case they have any
    // insts in them.
    DPRINTF(InOrderStage, "Removing instructions from stage instruction "
            "list.\n");
    while (!skidBuffer[tid].empty()) {
        if (skidBuffer[tid].front()->seqNum <= squash_seq_num) {
            DPRINTF(InOrderStage,"[tid:%i]: Cannot remove [sn:%i] because "
                    "it's <= squashing seqNum %i.\n",
                    tid,
                    skidBuffer[tid].front()->seqNum,
                    squash_seq_num);

            DPRINTF(InOrderStage, "[tid:%i]: Cannot remove incoming "
                    "instructions before delay slot [sn:%i]. %i insts"
                    "left.\n", tid, squash_seq_num,
                    skidBuffer[tid].size());
            break;
        }
        DPRINTF(InOrderStage, "[tid:%i]: Removing instruction, [sn:%i] "
                "PC %s.\n", tid, skidBuffer[tid].front()->seqNum,
                skidBuffer[tid].front()->pc);
        skidBuffer[tid].pop_front();
    }

    // Now that squash has propagated to the first stage,
    // Alert CPU to remove instructions from the CPU instruction list.
    // @todo: Move this to the CPU object.
    cpu->removeInstsUntil(squash_seq_num, tid);
}

void 
FirstStage::squashDueToMemStall(InstSeqNum seq_num, ThreadID tid)
{
    // Need to preserve the stalling instruction in first-stage
    // since the squash() from first stage also removes
    // the instruction from the CPU (removeInstsUntil). If that
    // functionality gets changed then you can move this offset.
    // (stalling instruction = seq_num + 1)
    squash(seq_num+1, tid);
}


void
FirstStage::processStage(bool &status_change)
{
    list<ThreadID>::iterator threads = activeThreads->begin();

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

    while (instsProcessed < stageWidth)  {
        ThreadID tid = getFetchingThread(fetchPolicy);

        if (tid >= 0) {
            DPRINTF(InOrderStage, "Processing [tid:%i]\n",tid);
            processThread(status_change, tid);
            DPRINTF(InOrderStage, "Done Processing [tid:%i]\n",tid);
        } else {
            DPRINTF(InOrderStage, "No more threads to fetch from.\n");
            break;
        }
    }

    if (instsProcessed > 0) {
        ++runCycles;
        idle = false;        
    } else {
        ++idleCycles;
        idle = true;        
    }

}

//@TODO: Note in documentation, that when you make a pipeline stage change, 
//then make sure you change the first stage too
void
FirstStage::processInsts(ThreadID tid)
{
    bool all_reqs_completed = true;

    for (int insts_fetched = instsProcessed;
         insts_fetched < stageWidth;
         insts_fetched++) {

        DynInstPtr inst;
        bool new_inst = false;

        if (!skidBuffer[tid].empty()) {
            inst = skidBuffer[tid].front();
        } else {
            // Get new instruction.
            new_inst = true;

            inst = new InOrderDynInst(cpu,
                                      cpu->thread[tid],
                                      cpu->nextInstSeqNum(tid),
                                      tid,
                                      tid);

#if TRACING_ON
            inst->traceData =
                tracer->getInstRecord(ThePipeline::NumStages,
                                      cpu->stageTracing,
                                      cpu->thread[tid]->getTC());

#else
            inst->traceData = NULL;
#endif      // TRACING_ON

            // Add instruction to the CPU's list of instructions.
            inst->setInstListIt(cpu->addInst(inst));

            // Create Front-End Resource Schedule For Instruction
            inst->setFrontSked(cpu->frontEndSked);
        }

        int reqs_processed = 0;            
        all_reqs_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++;

        if (!all_reqs_completed || !sendInstToNextStage(inst)) {
            if (new_inst) {
                DPRINTF(InOrderStage, "[tid:%u]: [sn:%u] Did not finish all "
                        "requests for this stage. Keep in stage inst. "
                        "list.\n", tid, inst->seqNum);
                skidBuffer[tid].push_back(inst);
            }
            block(tid);
            break;
        } else if (!skidBuffer[tid].empty()){
            DPRINTF(InOrderStage, "[tid:%u]: [sn:%u] Finished all "
                    "requests for this stage.\n", tid, inst->seqNum);
            skidBuffer[tid].pop_front();
        }

    }

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

ThreadID
FirstStage::getFetchingThread(FetchPriority &fetch_priority)
{
    ThreadID num_active_threads = cpu->numActiveThreads();

    if (num_active_threads > 1) {
        switch (fetch_priority) {
          case SingleThread:
            return cpu->activeThreadId();

          case RoundRobin:
            return roundRobin();

          default:
            return InvalidThreadID;
        }
    } else if (num_active_threads == 1) {
        ThreadID tid = *activeThreads->begin();

        if (stageStatus[tid] == Running ||
            stageStatus[tid] == Idle ||
            stageStatus[tid] == Unblocking) {
            return tid;
        } else {
            return InvalidThreadID;
        }
    } else {
        return InvalidThreadID;
    }    
}

ThreadID
FirstStage::roundRobin()
{
    list<ThreadID>::iterator pri_iter = fetchPriorityList->begin();
    list<ThreadID>::iterator end      = fetchPriorityList->end();

    ThreadID high_pri;

    while (pri_iter != end) {
        high_pri = *pri_iter;

        assert(high_pri <= numThreads);

        if (stageStatus[high_pri] == Running ||
            stageStatus[high_pri] == Idle ||
            stageStatus[high_pri] == Unblocking){

            fetchPriorityList->erase(pri_iter);
            fetchPriorityList->push_back(high_pri);

            return high_pri;
        }

        pri_iter++;
    }

    return InvalidThreadID;
}
