/* * 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 SimpleCommit::SimpleCommit(Params ¶ms) : dcacheInterface(params.dcacheInterface), iewToCommitDelay(params.iewToCommitDelay), renameToROBDelay(params.renameToROBDelay), renameWidth(params.renameWidth), iewWidth(params.executeWidth), commitWidth(params.commitWidth) { _status = Idle; } template void SimpleCommit::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 void SimpleCommit::setCPU(FullCPU *cpu_ptr) { DPRINTF(Commit, "Commit: Setting CPU pointer.\n"); cpu = cpu_ptr; } template void SimpleCommit::setTimeBuffer(TimeBuffer *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 void SimpleCommit::setRenameQueue(TimeBuffer *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 void SimpleCommit::setIEWQueue(TimeBuffer *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 void SimpleCommit::setROB(ROB *rob_ptr) { DPRINTF(Commit, "Commit: Setting ROB pointer.\n"); rob = rob_ptr; } template void SimpleCommit::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 void SimpleCommit::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 void SimpleCommit::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 bool SimpleCommit::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 void SimpleCommit::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 void SimpleCommit::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 uint64_t SimpleCommit::readCommitPC() { return rob->readHeadPC(); }