/*
 * Copyright (c) 2004-2005 The Regents of The University of Michigan
 * 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.
 */

#include "base/timebuf.hh"
#include "cpu/o3/commit.hh"
#include "cpu/exetrace.hh"

template <class Impl>
SimpleCommit<Impl>::SimpleCommit(Params &params)
    : dcacheInterface(params.dcacheInterface),
      iewToCommitDelay(params.iewToCommitDelay),
      renameToROBDelay(params.renameToROBDelay),
      renameWidth(params.renameWidth),
      iewWidth(params.executeWidth),
      commitWidth(params.commitWidth)
{
    _status = Idle;
}

template <class Impl>
void
SimpleCommit<Impl>::regStats()
{
    commitCommittedInsts
        .name(name() + ".commitCommittedInsts")
        .desc("The number of committed instructions")
        .prereq(commitCommittedInsts);
    commitSquashedInsts
        .name(name() + ".commitSquashedInsts")
        .desc("The number of squashed insts skipped by commit")
        .prereq(commitSquashedInsts);
    commitSquashEvents
        .name(name() + ".commitSquashEvents")
        .desc("The number of times commit is told to squash")
        .prereq(commitSquashEvents);
    commitNonSpecStalls
        .name(name() + ".commitNonSpecStalls")
        .desc("The number of times commit has been forced to stall to "
              "communicate backwards")
        .prereq(commitNonSpecStalls);
    commitCommittedBranches
        .name(name() + ".commitCommittedBranches")
        .desc("The number of committed branches")
        .prereq(commitCommittedBranches);
    commitCommittedLoads
        .name(name() + ".commitCommittedLoads")
        .desc("The number of committed loads")
        .prereq(commitCommittedLoads);
    commitCommittedMemRefs
        .name(name() + ".commitCommittedMemRefs")
        .desc("The number of committed memory references")
        .prereq(commitCommittedMemRefs);
    branchMispredicts
        .name(name() + ".branchMispredicts")
        .desc("The number of times a branch was mispredicted")
        .prereq(branchMispredicts);
    n_committed_dist
        .init(0,commitWidth,1)
        .name(name() + ".COM:committed_per_cycle")
        .desc("Number of insts commited each cycle")
        .flags(Stats::pdf)
        ;
}

template <class Impl>
void
SimpleCommit<Impl>::setCPU(FullCPU *cpu_ptr)
{
    DPRINTF(Commit, "Commit: Setting CPU pointer.\n");
    cpu = cpu_ptr;
}

template <class Impl>
void
SimpleCommit<Impl>::setTimeBuffer(TimeBuffer<TimeStruct> *tb_ptr)
{
    DPRINTF(Commit, "Commit: Setting time buffer pointer.\n");
    timeBuffer = tb_ptr;

    // Setup wire to send information back to IEW.
    toIEW = timeBuffer->getWire(0);

    // Setup wire to read data from IEW (for the ROB).
    robInfoFromIEW = timeBuffer->getWire(-iewToCommitDelay);
}

template <class Impl>
void
SimpleCommit<Impl>::setRenameQueue(TimeBuffer<RenameStruct> *rq_ptr)
{
    DPRINTF(Commit, "Commit: Setting rename queue pointer.\n");
    renameQueue = rq_ptr;

    // Setup wire to get instructions from rename (for the ROB).
    fromRename = renameQueue->getWire(-renameToROBDelay);
}

template <class Impl>
void
SimpleCommit<Impl>::setIEWQueue(TimeBuffer<IEWStruct> *iq_ptr)
{
    DPRINTF(Commit, "Commit: Setting IEW queue pointer.\n");
    iewQueue = iq_ptr;

    // Setup wire to get instructions from IEW.
    fromIEW = iewQueue->getWire(-iewToCommitDelay);
}

template <class Impl>
void
SimpleCommit<Impl>::setROB(ROB *rob_ptr)
{
    DPRINTF(Commit, "Commit: Setting ROB pointer.\n");
    rob = rob_ptr;
}

template <class Impl>
void
SimpleCommit<Impl>::tick()
{
    // If the ROB is currently in its squash sequence, then continue
    // to squash.  In this case, commit does not do anything.  Otherwise
    // run commit.
    if (_status == ROBSquashing) {
        if (rob->isDoneSquashing()) {
            _status = Running;
        } else {
            rob->doSquash();

            // Send back sequence number of tail of ROB, so other stages
            // can squash younger instructions.  Note that really the only
            // stage that this is important for is the IEW stage; other
            // stages can just clear all their state as long as selective
            // replay isn't used.
            toIEW->commitInfo.doneSeqNum = rob->readTailSeqNum();
            toIEW->commitInfo.robSquashing = true;
        }
    } else {
        commit();
    }

    markCompletedInsts();

    // Writeback number of free ROB entries here.
    DPRINTF(Commit, "Commit: ROB has %d free entries.\n",
            rob->numFreeEntries());
    toIEW->commitInfo.freeROBEntries = rob->numFreeEntries();
}

template <class Impl>
void
SimpleCommit<Impl>::commit()
{
    //////////////////////////////////////
    // Check for interrupts
    //////////////////////////////////////

    // Process interrupts if interrupts are enabled and not in PAL mode.
    // Take the PC from commit and write it to the IPR, then squash.  The
    // interrupt completing will take care of restoring the PC from that value
    // in the IPR.  Look at IPR[EXC_ADDR];
    // hwrei() is what resets the PC to the place where instruction execution
    // beings again.
#if FULL_SYSTEM
    if (//checkInterrupts &&
        cpu->check_interrupts() &&
        !cpu->inPalMode(readCommitPC())) {
        // Will need to squash all instructions currently in flight and have
        // the interrupt handler restart at the last non-committed inst.
        // Most of that can be handled through the trap() function.  The
        // processInterrupts() function really just checks for interrupts
        // and then calls trap() if there is an interrupt present.

        // CPU will handle implementation of the interrupt.
        cpu->processInterrupts();
    }
#endif // FULL_SYSTEM

    ////////////////////////////////////
    // Check for squash signal, handle that first
    ////////////////////////////////////

    // Want to mainly check if the IEW stage is telling the ROB to squash.
    // Should I also check if the commit stage is telling the ROB to squah?
    // This might be necessary to keep the same timing between the IQ and
    // the ROB...
    if (fromIEW->squash) {
        DPRINTF(Commit, "Commit: Squashing instructions in the ROB.\n");

        _status = ROBSquashing;

        InstSeqNum squashed_inst = fromIEW->squashedSeqNum;

        rob->squash(squashed_inst);

        // Send back the sequence number of the squashed instruction.
        toIEW->commitInfo.doneSeqNum = squashed_inst;

        // Send back the squash signal to tell stages that they should squash.
        toIEW->commitInfo.squash = true;

        // Send back the rob squashing signal so other stages know that the
        // ROB is in the process of squashing.
        toIEW->commitInfo.robSquashing = true;

        toIEW->commitInfo.branchMispredict = fromIEW->branchMispredict;

        toIEW->commitInfo.branchTaken = fromIEW->branchTaken;

        toIEW->commitInfo.nextPC = fromIEW->nextPC;

        toIEW->commitInfo.mispredPC = fromIEW->mispredPC;

        if (toIEW->commitInfo.branchMispredict) {
            ++branchMispredicts;
        }
    }

    if (_status != ROBSquashing) {
        // If we're not currently squashing, then get instructions.
        getInsts();

        // Try to commit any instructions.
        commitInsts();
    }

    // If the ROB is empty, we can set this stage to idle.  Use this
    // in the future when the Idle status will actually be utilized.
#if 0
    if (rob->isEmpty()) {
        DPRINTF(Commit, "Commit: ROB is empty.  Status changed to idle.\n");
        _status = Idle;
        // Schedule an event so that commit will actually wake up
        // once something gets put in the ROB.
    }
#endif
}

// Loop that goes through as many instructions in the ROB as possible and
// tries to commit them.  The actual work for committing is done by the
// commitHead() function.
template <class Impl>
void
SimpleCommit<Impl>::commitInsts()
{
    ////////////////////////////////////
    // Handle commit
    // Note that commit will be handled prior to the ROB so that the ROB
    // only tries to commit instructions it has in this current cycle, and
    // not instructions it is writing in during this cycle.
    // Can't commit and squash things at the same time...
    ////////////////////////////////////

    if (rob->isEmpty())
        return;

    DynInstPtr head_inst = rob->readHeadInst();

    unsigned num_committed = 0;

    // Commit as many instructions as possible until the commit bandwidth
    // limit is reached, or it becomes impossible to commit any more.
    while (!rob->isEmpty() &&
           head_inst->readyToCommit() &&
           num_committed < commitWidth)
    {
        DPRINTF(Commit, "Commit: Trying to commit head instruction.\n");

        // If the head instruction is squashed, it is ready to retire at any
        // time.  However, we need to avoid updating any other state
        // incorrectly if it's already been squashed.
        if (head_inst->isSquashed()) {

            DPRINTF(Commit, "Commit: Retiring squashed instruction from "
                    "ROB.\n");

            // Tell ROB to retire head instruction.  This retires the head
            // inst in the ROB without affecting any other stages.
            rob->retireHead();

            ++commitSquashedInsts;

        } else {
            // Increment the total number of non-speculative instructions
            // executed.
            // Hack for now: it really shouldn't happen until after the
            // commit is deemed to be successful, but this count is needed
            // for syscalls.
            cpu->funcExeInst++;

            // Try to commit the head instruction.
            bool commit_success = commitHead(head_inst, num_committed);

            // Update what instruction we are looking at if the commit worked.
            if (commit_success) {
                ++num_committed;

                // Send back which instruction has been committed.
                // @todo: Update this later when a wider pipeline is used.
                // Hmm, can't really give a pointer here...perhaps the
                // sequence number instead (copy).
                toIEW->commitInfo.doneSeqNum = head_inst->seqNum;

                ++commitCommittedInsts;

                if (!head_inst->isNop()) {
                    cpu->instDone();
                }
            } else {
                break;
            }
        }

        // Update the pointer to read the next instruction in the ROB.
        head_inst = rob->readHeadInst();
    }

    DPRINTF(CommitRate, "%i\n", num_committed);
    n_committed_dist.sample(num_committed);
}

template <class Impl>
bool
SimpleCommit<Impl>::commitHead(DynInstPtr &head_inst, unsigned inst_num)
{
    // Make sure instruction is valid
    assert(head_inst);

    // If the instruction is not executed yet, then it is a non-speculative
    // or store inst.  Signal backwards that it should be executed.
    if (!head_inst->isExecuted()) {
        // Keep this number correct.  We have not yet actually executed
        // and committed this instruction.
        cpu->funcExeInst--;

        if (head_inst->isNonSpeculative()) {
            DPRINTF(Commit, "Commit: Encountered a store or non-speculative "
                    "instruction at the head of the ROB, PC %#x.\n",
                    head_inst->readPC());

            toIEW->commitInfo.nonSpecSeqNum = head_inst->seqNum;

            // Change the instruction so it won't try to commit again until
            // it is executed.
            head_inst->clearCanCommit();

            ++commitNonSpecStalls;

            return false;
        } else {
            panic("Commit: Trying to commit un-executed instruction "
                  "of unknown type!\n");
        }
    }

    // Now check if it's one of the special trap or barrier or
    // serializing instructions.
    if (head_inst->isThreadSync()  ||
        head_inst->isSerializing() ||
        head_inst->isMemBarrier()  ||
        head_inst->isWriteBarrier() )
    {
        // Not handled for now.  Mem barriers and write barriers are safe
        // to simply let commit as memory accesses only happen once they
        // reach the head of commit.  Not sure about the other two.
        panic("Serializing or barrier instructions"
              " are not handled yet.\n");
    }

    // Check if the instruction caused a fault.  If so, trap.
    Fault inst_fault = head_inst->getFault();

    if (inst_fault != NoFault) {
        if (!head_inst->isNop()) {
#if FULL_SYSTEM
            cpu->trap(inst_fault);
#else // !FULL_SYSTEM
            panic("fault (%d) detected @ PC %08p", inst_fault,
                  head_inst->PC);
#endif // FULL_SYSTEM
        }
    }

    // Check if we're really ready to commit.  If not then return false.
    // I'm pretty sure all instructions should be able to commit if they've
    // reached this far.  For now leave this in as a check.
    if (!rob->isHeadReady()) {
        panic("Commit: Unable to commit head instruction!\n");
        return false;
    }

    // If it's a branch, then send back branch prediction update info
    // to the fetch stage.
    // This should be handled in the iew stage if a mispredict happens...

    if (head_inst->isControl()) {

#if 0
        toIEW->nextPC = head_inst->readPC();
        //Maybe switch over to BTB incorrect.
        toIEW->btbMissed = head_inst->btbMiss();
        toIEW->target = head_inst->nextPC;
        //Maybe also include global history information.
        //This simple version will have no branch prediction however.
#endif

        ++commitCommittedBranches;
    }

    // Now that the instruction is going to be committed, finalize its
    // trace data.
    if (head_inst->traceData) {
        head_inst->traceData->finalize();
    }

    //Finally clear the head ROB entry.
    rob->retireHead();

    // Return true to indicate that we have committed an instruction.
    return true;
}

template <class Impl>
void
SimpleCommit<Impl>::getInsts()
{
    //////////////////////////////////////
    // Handle ROB functions
    //////////////////////////////////////

    // Read any issued instructions and place them into the ROB.  Do this
    // prior to squashing to avoid having instructions in the ROB that
    // don't get squashed properly.
    int insts_to_process = min((int)renameWidth, fromRename->size);

    for (int inst_num = 0;
         inst_num < insts_to_process;
         ++inst_num)
    {
        if (!fromRename->insts[inst_num]->isSquashed()) {
            DPRINTF(Commit, "Commit: Inserting PC %#x into ROB.\n",
                    fromRename->insts[inst_num]->readPC());
            rob->insertInst(fromRename->insts[inst_num]);
        } else {
            DPRINTF(Commit, "Commit: Instruction %i PC %#x was "
                    "squashed, skipping.\n",
                    fromRename->insts[inst_num]->seqNum,
                    fromRename->insts[inst_num]->readPC());
        }
    }
}

template <class Impl>
void
SimpleCommit<Impl>::markCompletedInsts()
{
    // Grab completed insts out of the IEW instruction queue, and mark
    // instructions completed within the ROB.
    for (int inst_num = 0;
         inst_num < fromIEW->size && fromIEW->insts[inst_num];
         ++inst_num)
    {
        DPRINTF(Commit, "Commit: Marking PC %#x, SN %i ready within ROB.\n",
                fromIEW->insts[inst_num]->readPC(),
                fromIEW->insts[inst_num]->seqNum);

        // Mark the instruction as ready to commit.
        fromIEW->insts[inst_num]->setCanCommit();
    }
}

template <class Impl>
uint64_t
SimpleCommit<Impl>::readCommitPC()
{
    return rob->readHeadPC();
}