| /* |
| * Copyright 2014 Google, Inc. |
| * Copyright (c) 2010-2014, 2017, 2020 ARM Limited |
| * All rights reserved |
| * |
| * The license below extends only to copyright in the software and shall |
| * not be construed as granting a license to any other intellectual |
| * property including but not limited to intellectual property relating |
| * to a hardware implementation of the functionality of the software |
| * licensed hereunder. You may use the software subject to the license |
| * terms below provided that you ensure that this notice is replicated |
| * unmodified and in its entirety in all distributions of the software, |
| * modified or unmodified, in source code or in binary form. |
| * |
| * 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 "cpu/o3/commit.hh" |
| |
| #include <algorithm> |
| #include <set> |
| #include <string> |
| |
| #include "base/compiler.hh" |
| #include "base/loader/symtab.hh" |
| #include "base/logging.hh" |
| #include "cpu/base.hh" |
| #include "cpu/checker/cpu.hh" |
| #include "cpu/exetrace.hh" |
| #include "cpu/o3/cpu.hh" |
| #include "cpu/o3/dyn_inst.hh" |
| #include "cpu/o3/limits.hh" |
| #include "cpu/o3/thread_state.hh" |
| #include "cpu/timebuf.hh" |
| #include "debug/Activity.hh" |
| #include "debug/Commit.hh" |
| #include "debug/CommitRate.hh" |
| #include "debug/Drain.hh" |
| #include "debug/ExecFaulting.hh" |
| #include "debug/HtmCpu.hh" |
| #include "debug/O3PipeView.hh" |
| #include "params/BaseO3CPU.hh" |
| #include "sim/faults.hh" |
| #include "sim/full_system.hh" |
| |
| namespace gem5 |
| { |
| |
| namespace o3 |
| { |
| |
| void |
| Commit::processTrapEvent(ThreadID tid) |
| { |
| // This will get reset by commit if it was switched out at the |
| // time of this event processing. |
| trapSquash[tid] = true; |
| } |
| |
| Commit::Commit(CPU *_cpu, const BaseO3CPUParams ¶ms) |
| : commitPolicy(params.smtCommitPolicy), |
| cpu(_cpu), |
| iewToCommitDelay(params.iewToCommitDelay), |
| commitToIEWDelay(params.commitToIEWDelay), |
| renameToROBDelay(params.renameToROBDelay), |
| fetchToCommitDelay(params.commitToFetchDelay), |
| renameWidth(params.renameWidth), |
| commitWidth(params.commitWidth), |
| numThreads(params.numThreads), |
| drainPending(false), |
| drainImminent(false), |
| trapLatency(params.trapLatency), |
| canHandleInterrupts(true), |
| avoidQuiesceLiveLock(false), |
| stats(_cpu, this) |
| { |
| if (commitWidth > MaxWidth) |
| fatal("commitWidth (%d) is larger than compiled limit (%d),\n" |
| "\tincrease MaxWidth in src/cpu/o3/limits.hh\n", |
| commitWidth, static_cast<int>(MaxWidth)); |
| |
| _status = Active; |
| _nextStatus = Inactive; |
| |
| if (commitPolicy == CommitPolicy::RoundRobin) { |
| //Set-Up Priority List |
| for (ThreadID tid = 0; tid < numThreads; tid++) { |
| priority_list.push_back(tid); |
| } |
| } |
| |
| for (ThreadID tid = 0; tid < MaxThreads; tid++) { |
| commitStatus[tid] = Idle; |
| changedROBNumEntries[tid] = false; |
| trapSquash[tid] = false; |
| tcSquash[tid] = false; |
| squashAfterInst[tid] = nullptr; |
| pc[tid].reset(params.isa[0]->newPCState()); |
| youngestSeqNum[tid] = 0; |
| lastCommitedSeqNum[tid] = 0; |
| trapInFlight[tid] = false; |
| committedStores[tid] = false; |
| checkEmptyROB[tid] = false; |
| renameMap[tid] = nullptr; |
| htmStarts[tid] = 0; |
| htmStops[tid] = 0; |
| } |
| interrupt = NoFault; |
| } |
| |
| std::string Commit::name() const { return cpu->name() + ".commit"; } |
| |
| void |
| Commit::regProbePoints() |
| { |
| ppCommit = new ProbePointArg<DynInstPtr>( |
| cpu->getProbeManager(), "Commit"); |
| ppCommitStall = new ProbePointArg<DynInstPtr>( |
| cpu->getProbeManager(), "CommitStall"); |
| ppSquash = new ProbePointArg<DynInstPtr>( |
| cpu->getProbeManager(), "Squash"); |
| } |
| |
| Commit::CommitStats::CommitStats(CPU *cpu, Commit *commit) |
| : statistics::Group(cpu, "commit"), |
| ADD_STAT(commitSquashedInsts, statistics::units::Count::get(), |
| "The number of squashed insts skipped by commit"), |
| ADD_STAT(commitNonSpecStalls, statistics::units::Count::get(), |
| "The number of times commit has been forced to stall to " |
| "communicate backwards"), |
| ADD_STAT(branchMispredicts, statistics::units::Count::get(), |
| "The number of times a branch was mispredicted"), |
| ADD_STAT(numCommittedDist, statistics::units::Count::get(), |
| "Number of insts commited each cycle"), |
| ADD_STAT(instsCommitted, statistics::units::Count::get(), |
| "Number of instructions committed"), |
| ADD_STAT(opsCommitted, statistics::units::Count::get(), |
| "Number of ops (including micro ops) committed"), |
| ADD_STAT(memRefs, statistics::units::Count::get(), |
| "Number of memory references committed"), |
| ADD_STAT(loads, statistics::units::Count::get(), "Number of loads committed"), |
| ADD_STAT(amos, statistics::units::Count::get(), |
| "Number of atomic instructions committed"), |
| ADD_STAT(membars, statistics::units::Count::get(), |
| "Number of memory barriers committed"), |
| ADD_STAT(branches, statistics::units::Count::get(), |
| "Number of branches committed"), |
| ADD_STAT(vectorInstructions, statistics::units::Count::get(), |
| "Number of committed Vector instructions."), |
| ADD_STAT(floating, statistics::units::Count::get(), |
| "Number of committed floating point instructions."), |
| ADD_STAT(integer, statistics::units::Count::get(), |
| "Number of committed integer instructions."), |
| ADD_STAT(functionCalls, statistics::units::Count::get(), |
| "Number of function calls committed."), |
| ADD_STAT(committedInstType, statistics::units::Count::get(), |
| "Class of committed instruction"), |
| ADD_STAT(commitEligibleSamples, statistics::units::Cycle::get(), |
| "number cycles where commit BW limit reached") |
| { |
| using namespace statistics; |
| |
| commitSquashedInsts.prereq(commitSquashedInsts); |
| commitNonSpecStalls.prereq(commitNonSpecStalls); |
| branchMispredicts.prereq(branchMispredicts); |
| |
| numCommittedDist |
| .init(0,commit->commitWidth,1) |
| .flags(statistics::pdf); |
| |
| instsCommitted |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| opsCommitted |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| memRefs |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| loads |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| amos |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| membars |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| branches |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| vectorInstructions |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| floating |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| integer |
| .init(cpu->numThreads) |
| .flags(total); |
| |
| functionCalls |
| .init(commit->numThreads) |
| .flags(total); |
| |
| committedInstType |
| .init(commit->numThreads,enums::Num_OpClass) |
| .flags(total | pdf | dist); |
| |
| committedInstType.ysubnames(enums::OpClassStrings); |
| } |
| |
| void |
| Commit::setThreads(std::vector<ThreadState *> &threads) |
| { |
| thread = threads; |
| } |
| |
| void |
| Commit::setTimeBuffer(TimeBuffer<TimeStruct> *tb_ptr) |
| { |
| 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); |
| } |
| |
| void |
| Commit::setFetchQueue(TimeBuffer<FetchStruct> *fq_ptr) |
| { |
| fetchQueue = fq_ptr; |
| |
| // Setup wire to get instructions from rename (for the ROB). |
| fromFetch = fetchQueue->getWire(-fetchToCommitDelay); |
| } |
| |
| void |
| Commit::setRenameQueue(TimeBuffer<RenameStruct> *rq_ptr) |
| { |
| renameQueue = rq_ptr; |
| |
| // Setup wire to get instructions from rename (for the ROB). |
| fromRename = renameQueue->getWire(-renameToROBDelay); |
| } |
| |
| void |
| Commit::setIEWQueue(TimeBuffer<IEWStruct> *iq_ptr) |
| { |
| iewQueue = iq_ptr; |
| |
| // Setup wire to get instructions from IEW. |
| fromIEW = iewQueue->getWire(-iewToCommitDelay); |
| } |
| |
| void |
| Commit::setIEWStage(IEW *iew_stage) |
| { |
| iewStage = iew_stage; |
| } |
| |
| void |
| Commit::setActiveThreads(std::list<ThreadID> *at_ptr) |
| { |
| activeThreads = at_ptr; |
| } |
| |
| void |
| Commit::setRenameMap(UnifiedRenameMap rm_ptr[]) |
| { |
| for (ThreadID tid = 0; tid < numThreads; tid++) |
| renameMap[tid] = &rm_ptr[tid]; |
| } |
| |
| void Commit::setROB(ROB *rob_ptr) { rob = rob_ptr; } |
| |
| void |
| Commit::startupStage() |
| { |
| rob->setActiveThreads(activeThreads); |
| rob->resetEntries(); |
| |
| // Broadcast the number of free entries. |
| for (ThreadID tid = 0; tid < numThreads; tid++) { |
| toIEW->commitInfo[tid].usedROB = true; |
| toIEW->commitInfo[tid].freeROBEntries = rob->numFreeEntries(tid); |
| toIEW->commitInfo[tid].emptyROB = true; |
| } |
| |
| // Commit must broadcast the number of free entries it has at the |
| // start of the simulation, so it starts as active. |
| cpu->activateStage(CPU::CommitIdx); |
| |
| cpu->activityThisCycle(); |
| } |
| |
| void |
| Commit::clearStates(ThreadID tid) |
| { |
| commitStatus[tid] = Idle; |
| changedROBNumEntries[tid] = false; |
| checkEmptyROB[tid] = false; |
| trapInFlight[tid] = false; |
| committedStores[tid] = false; |
| trapSquash[tid] = false; |
| tcSquash[tid] = false; |
| pc[tid].reset(cpu->tcBase(tid)->getIsaPtr()->newPCState()); |
| lastCommitedSeqNum[tid] = 0; |
| squashAfterInst[tid] = NULL; |
| } |
| |
| void Commit::drain() { drainPending = true; } |
| |
| void |
| Commit::drainResume() |
| { |
| drainPending = false; |
| drainImminent = false; |
| } |
| |
| void |
| Commit::drainSanityCheck() const |
| { |
| assert(isDrained()); |
| rob->drainSanityCheck(); |
| |
| // hardware transactional memory |
| // cannot drain partially through a transaction |
| for (ThreadID tid = 0; tid < numThreads; tid++) { |
| if (executingHtmTransaction(tid)) { |
| panic("cannot drain partially through a HTM transaction"); |
| } |
| } |
| } |
| |
| bool |
| Commit::isDrained() const |
| { |
| /* Make sure no one is executing microcode. There are two reasons |
| * for this: |
| * - Hardware virtualized CPUs can't switch into the middle of a |
| * microcode sequence. |
| * - The current fetch implementation will most likely get very |
| * confused if it tries to start fetching an instruction that |
| * is executing in the middle of a ucode sequence that changes |
| * address mappings. This can happen on for example x86. |
| */ |
| for (ThreadID tid = 0; tid < numThreads; tid++) { |
| if (pc[tid]->microPC() != 0) |
| return false; |
| } |
| |
| /* Make sure that all instructions have finished committing before |
| * declaring the system as drained. We want the pipeline to be |
| * completely empty when we declare the CPU to be drained. This |
| * makes debugging easier since CPU handover and restoring from a |
| * checkpoint with a different CPU should have the same timing. |
| */ |
| return rob->isEmpty() && |
| interrupt == NoFault; |
| } |
| |
| void |
| Commit::takeOverFrom() |
| { |
| _status = Active; |
| _nextStatus = Inactive; |
| for (ThreadID tid = 0; tid < numThreads; tid++) { |
| commitStatus[tid] = Idle; |
| changedROBNumEntries[tid] = false; |
| trapSquash[tid] = false; |
| tcSquash[tid] = false; |
| squashAfterInst[tid] = NULL; |
| } |
| rob->takeOverFrom(); |
| } |
| |
| void |
| Commit::deactivateThread(ThreadID tid) |
| { |
| std::list<ThreadID>::iterator thread_it = std::find(priority_list.begin(), |
| priority_list.end(), tid); |
| |
| if (thread_it != priority_list.end()) { |
| priority_list.erase(thread_it); |
| } |
| } |
| |
| bool |
| Commit::executingHtmTransaction(ThreadID tid) const |
| { |
| if (tid == InvalidThreadID) |
| return false; |
| else |
| return (htmStarts[tid] > htmStops[tid]); |
| } |
| |
| void |
| Commit::resetHtmStartsStops(ThreadID tid) |
| { |
| if (tid != InvalidThreadID) |
| { |
| htmStarts[tid] = 0; |
| htmStops[tid] = 0; |
| } |
| } |
| |
| |
| void |
| Commit::updateStatus() |
| { |
| // reset ROB changed variable |
| std::list<ThreadID>::iterator threads = activeThreads->begin(); |
| std::list<ThreadID>::iterator end = activeThreads->end(); |
| |
| while (threads != end) { |
| ThreadID tid = *threads++; |
| |
| changedROBNumEntries[tid] = false; |
| |
| // Also check if any of the threads has a trap pending |
| if (commitStatus[tid] == TrapPending || |
| commitStatus[tid] == FetchTrapPending) { |
| _nextStatus = Active; |
| } |
| } |
| |
| if (_nextStatus == Inactive && _status == Active) { |
| DPRINTF(Activity, "Deactivating stage.\n"); |
| cpu->deactivateStage(CPU::CommitIdx); |
| } else if (_nextStatus == Active && _status == Inactive) { |
| DPRINTF(Activity, "Activating stage.\n"); |
| cpu->activateStage(CPU::CommitIdx); |
| } |
| |
| _status = _nextStatus; |
| } |
| |
| bool |
| Commit::changedROBEntries() |
| { |
| std::list<ThreadID>::iterator threads = activeThreads->begin(); |
| std::list<ThreadID>::iterator end = activeThreads->end(); |
| |
| while (threads != end) { |
| ThreadID tid = *threads++; |
| |
| if (changedROBNumEntries[tid]) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| size_t |
| Commit::numROBFreeEntries(ThreadID tid) |
| { |
| return rob->numFreeEntries(tid); |
| } |
| |
| void |
| Commit::generateTrapEvent(ThreadID tid, Fault inst_fault) |
| { |
| DPRINTF(Commit, "Generating trap event for [tid:%i]\n", tid); |
| |
| EventFunctionWrapper *trap = new EventFunctionWrapper( |
| [this, tid]{ processTrapEvent(tid); }, |
| "Trap", true, Event::CPU_Tick_Pri); |
| |
| Cycles latency = std::dynamic_pointer_cast<SyscallRetryFault>(inst_fault) ? |
| cpu->syscallRetryLatency : trapLatency; |
| |
| // hardware transactional memory |
| if (inst_fault != nullptr && |
| std::dynamic_pointer_cast<GenericHtmFailureFault>(inst_fault)) { |
| // TODO |
| // latency = default abort/restore latency |
| // could also do some kind of exponential back off if desired |
| } |
| |
| cpu->schedule(trap, cpu->clockEdge(latency)); |
| trapInFlight[tid] = true; |
| thread[tid]->trapPending = true; |
| } |
| |
| void |
| Commit::generateTCEvent(ThreadID tid) |
| { |
| assert(!trapInFlight[tid]); |
| DPRINTF(Commit, "Generating TC squash event for [tid:%i]\n", tid); |
| |
| tcSquash[tid] = true; |
| } |
| |
| void |
| Commit::squashAll(ThreadID tid) |
| { |
| // If we want to include the squashing instruction in the squash, |
| // then use one older sequence number. |
| // Hopefully this doesn't mess things up. Basically I want to squash |
| // all instructions of this thread. |
| InstSeqNum squashed_inst = rob->isEmpty(tid) ? |
| lastCommitedSeqNum[tid] : rob->readHeadInst(tid)->seqNum - 1; |
| |
| // All younger instructions will be squashed. Set the sequence |
| // number as the youngest instruction in the ROB (0 in this case. |
| // Hopefully nothing breaks.) |
| youngestSeqNum[tid] = lastCommitedSeqNum[tid]; |
| |
| rob->squash(squashed_inst, tid); |
| changedROBNumEntries[tid] = true; |
| |
| // Send back the sequence number of the squashed instruction. |
| toIEW->commitInfo[tid].doneSeqNum = squashed_inst; |
| |
| // Send back the squash signal to tell stages that they should |
| // squash. |
| toIEW->commitInfo[tid].squash = true; |
| |
| // Send back the rob squashing signal so other stages know that |
| // the ROB is in the process of squashing. |
| toIEW->commitInfo[tid].robSquashing = true; |
| |
| toIEW->commitInfo[tid].mispredictInst = NULL; |
| toIEW->commitInfo[tid].squashInst = NULL; |
| |
| set(toIEW->commitInfo[tid].pc, pc[tid]); |
| } |
| |
| void |
| Commit::squashFromTrap(ThreadID tid) |
| { |
| squashAll(tid); |
| |
| DPRINTF(Commit, "Squashing from trap, restarting at PC %s\n", *pc[tid]); |
| |
| thread[tid]->trapPending = false; |
| thread[tid]->noSquashFromTC = false; |
| trapInFlight[tid] = false; |
| |
| trapSquash[tid] = false; |
| |
| commitStatus[tid] = ROBSquashing; |
| cpu->activityThisCycle(); |
| } |
| |
| void |
| Commit::squashFromTC(ThreadID tid) |
| { |
| squashAll(tid); |
| |
| DPRINTF(Commit, "Squashing from TC, restarting at PC %s\n", *pc[tid]); |
| |
| thread[tid]->noSquashFromTC = false; |
| assert(!thread[tid]->trapPending); |
| |
| commitStatus[tid] = ROBSquashing; |
| cpu->activityThisCycle(); |
| |
| tcSquash[tid] = false; |
| } |
| |
| void |
| Commit::squashFromSquashAfter(ThreadID tid) |
| { |
| DPRINTF(Commit, "Squashing after squash after request, " |
| "restarting at PC %s\n", *pc[tid]); |
| |
| squashAll(tid); |
| // Make sure to inform the fetch stage of which instruction caused |
| // the squash. It'll try to re-fetch an instruction executing in |
| // microcode unless this is set. |
| toIEW->commitInfo[tid].squashInst = squashAfterInst[tid]; |
| squashAfterInst[tid] = NULL; |
| |
| commitStatus[tid] = ROBSquashing; |
| cpu->activityThisCycle(); |
| } |
| |
| void |
| Commit::squashAfter(ThreadID tid, const DynInstPtr &head_inst) |
| { |
| DPRINTF(Commit, "Executing squash after for [tid:%i] inst [sn:%llu]\n", |
| tid, head_inst->seqNum); |
| |
| assert(!squashAfterInst[tid] || squashAfterInst[tid] == head_inst); |
| commitStatus[tid] = SquashAfterPending; |
| squashAfterInst[tid] = head_inst; |
| } |
| |
| void |
| Commit::tick() |
| { |
| wroteToTimeBuffer = false; |
| _nextStatus = Inactive; |
| |
| if (activeThreads->empty()) |
| return; |
| |
| std::list<ThreadID>::iterator threads = activeThreads->begin(); |
| std::list<ThreadID>::iterator end = activeThreads->end(); |
| |
| // Check if any of the threads are done squashing. Change the |
| // status if they are done. |
| while (threads != end) { |
| ThreadID tid = *threads++; |
| |
| // Clear the bit saying if the thread has committed stores |
| // this cycle. |
| committedStores[tid] = false; |
| |
| if (commitStatus[tid] == ROBSquashing) { |
| |
| if (rob->isDoneSquashing(tid)) { |
| commitStatus[tid] = Running; |
| } else { |
| DPRINTF(Commit,"[tid:%i] Still Squashing, cannot commit any" |
| " insts this cycle.\n", tid); |
| rob->doSquash(tid); |
| toIEW->commitInfo[tid].robSquashing = true; |
| wroteToTimeBuffer = true; |
| } |
| } |
| } |
| |
| commit(); |
| |
| markCompletedInsts(); |
| |
| threads = activeThreads->begin(); |
| |
| while (threads != end) { |
| ThreadID tid = *threads++; |
| |
| if (!rob->isEmpty(tid) && rob->readHeadInst(tid)->readyToCommit()) { |
| // The ROB has more instructions it can commit. Its next status |
| // will be active. |
| _nextStatus = Active; |
| |
| [[maybe_unused]] const DynInstPtr &inst = rob->readHeadInst(tid); |
| |
| DPRINTF(Commit,"[tid:%i] Instruction [sn:%llu] PC %s is head of" |
| " ROB and ready to commit\n", |
| tid, inst->seqNum, inst->pcState()); |
| |
| } else if (!rob->isEmpty(tid)) { |
| const DynInstPtr &inst = rob->readHeadInst(tid); |
| |
| ppCommitStall->notify(inst); |
| |
| DPRINTF(Commit,"[tid:%i] Can't commit, Instruction [sn:%llu] PC " |
| "%s is head of ROB and not ready\n", |
| tid, inst->seqNum, inst->pcState()); |
| } |
| |
| DPRINTF(Commit, "[tid:%i] ROB has %d insts & %d free entries.\n", |
| tid, rob->countInsts(tid), rob->numFreeEntries(tid)); |
| } |
| |
| |
| if (wroteToTimeBuffer) { |
| DPRINTF(Activity, "Activity This Cycle.\n"); |
| cpu->activityThisCycle(); |
| } |
| |
| updateStatus(); |
| } |
| |
| void |
| Commit::handleInterrupt() |
| { |
| // Verify that we still have an interrupt to handle |
| if (!cpu->checkInterrupts(0)) { |
| DPRINTF(Commit, "Pending interrupt is cleared by requestor before " |
| "it got handled. Restart fetching from the orig path.\n"); |
| toIEW->commitInfo[0].clearInterrupt = true; |
| interrupt = NoFault; |
| avoidQuiesceLiveLock = true; |
| return; |
| } |
| |
| // Wait until all in flight instructions are finished before enterring |
| // the interrupt. |
| if (canHandleInterrupts && cpu->instList.empty()) { |
| // Squash or record that I need to squash this cycle if |
| // an interrupt needed to be handled. |
| DPRINTF(Commit, "Interrupt detected.\n"); |
| |
| // Clear the interrupt now that it's going to be handled |
| toIEW->commitInfo[0].clearInterrupt = true; |
| |
| assert(!thread[0]->noSquashFromTC); |
| thread[0]->noSquashFromTC = true; |
| |
| if (cpu->checker) { |
| cpu->checker->handlePendingInt(); |
| } |
| |
| // CPU will handle interrupt. Note that we ignore the local copy of |
| // interrupt. This is because the local copy may no longer be the |
| // interrupt that the interrupt controller thinks is being handled. |
| cpu->processInterrupts(cpu->getInterrupts()); |
| |
| thread[0]->noSquashFromTC = false; |
| |
| commitStatus[0] = TrapPending; |
| |
| interrupt = NoFault; |
| |
| // Generate trap squash event. |
| generateTrapEvent(0, interrupt); |
| |
| avoidQuiesceLiveLock = false; |
| } else { |
| DPRINTF(Commit, "Interrupt pending: instruction is %sin " |
| "flight, ROB is %sempty\n", |
| canHandleInterrupts ? "not " : "", |
| cpu->instList.empty() ? "" : "not " ); |
| } |
| } |
| |
| void |
| Commit::propagateInterrupt() |
| { |
| // Don't propagate intterupts if we are currently handling a trap or |
| // in draining and the last observable instruction has been committed. |
| if (commitStatus[0] == TrapPending || interrupt || trapSquash[0] || |
| tcSquash[0] || drainImminent) |
| return; |
| |
| // Process interrupts if interrupts are enabled, not in PAL |
| // mode, and no other traps or external squashes are currently |
| // pending. |
| // @todo: Allow other threads to handle interrupts. |
| |
| // Get any interrupt that happened |
| interrupt = cpu->getInterrupts(); |
| |
| // Tell fetch that there is an interrupt pending. This |
| // will make fetch wait until it sees a non PAL-mode PC, |
| // at which point it stops fetching instructions. |
| if (interrupt != NoFault) |
| toIEW->commitInfo[0].interruptPending = true; |
| } |
| |
| void |
| Commit::commit() |
| { |
| if (FullSystem) { |
| // Check if we have a interrupt and get read to handle it |
| if (cpu->checkInterrupts(0)) |
| propagateInterrupt(); |
| } |
| |
| //////////////////////////////////// |
| // Check for any possible squashes, handle them first |
| //////////////////////////////////// |
| std::list<ThreadID>::iterator threads = activeThreads->begin(); |
| std::list<ThreadID>::iterator end = activeThreads->end(); |
| |
| int num_squashing_threads = 0; |
| |
| while (threads != end) { |
| ThreadID tid = *threads++; |
| |
| // Not sure which one takes priority. I think if we have |
| // both, that's a bad sign. |
| if (trapSquash[tid]) { |
| assert(!tcSquash[tid]); |
| squashFromTrap(tid); |
| |
| // If the thread is trying to exit (i.e., an exit syscall was |
| // executed), this trapSquash was originated by the exit |
| // syscall earlier. In this case, schedule an exit event in |
| // the next cycle to fully terminate this thread |
| if (cpu->isThreadExiting(tid)) |
| cpu->scheduleThreadExitEvent(tid); |
| } else if (tcSquash[tid]) { |
| assert(commitStatus[tid] != TrapPending); |
| squashFromTC(tid); |
| } else if (commitStatus[tid] == SquashAfterPending) { |
| // A squash from the previous cycle of the commit stage (i.e., |
| // commitInsts() called squashAfter) is pending. Squash the |
| // thread now. |
| squashFromSquashAfter(tid); |
| } |
| |
| // Squashed sequence number must be older than youngest valid |
| // instruction in the ROB. This prevents squashes from younger |
| // instructions overriding squashes from older instructions. |
| if (fromIEW->squash[tid] && |
| commitStatus[tid] != TrapPending && |
| fromIEW->squashedSeqNum[tid] <= youngestSeqNum[tid]) { |
| |
| if (fromIEW->mispredictInst[tid]) { |
| DPRINTF(Commit, |
| "[tid:%i] Squashing due to branch mispred " |
| "PC:%#x [sn:%llu]\n", |
| tid, |
| fromIEW->mispredictInst[tid]->pcState().instAddr(), |
| fromIEW->squashedSeqNum[tid]); |
| } else { |
| DPRINTF(Commit, |
| "[tid:%i] Squashing due to order violation [sn:%llu]\n", |
| tid, fromIEW->squashedSeqNum[tid]); |
| } |
| |
| DPRINTF(Commit, "[tid:%i] Redirecting to PC %#x\n", |
| tid, *fromIEW->pc[tid]); |
| |
| commitStatus[tid] = ROBSquashing; |
| |
| // If we want to include the squashing instruction in the squash, |
| // then use one older sequence number. |
| InstSeqNum squashed_inst = fromIEW->squashedSeqNum[tid]; |
| |
| if (fromIEW->includeSquashInst[tid]) { |
| squashed_inst--; |
| } |
| |
| // All younger instructions will be squashed. Set the sequence |
| // number as the youngest instruction in the ROB. |
| youngestSeqNum[tid] = squashed_inst; |
| |
| rob->squash(squashed_inst, tid); |
| changedROBNumEntries[tid] = true; |
| |
| toIEW->commitInfo[tid].doneSeqNum = squashed_inst; |
| |
| toIEW->commitInfo[tid].squash = true; |
| |
| // Send back the rob squashing signal so other stages know that |
| // the ROB is in the process of squashing. |
| toIEW->commitInfo[tid].robSquashing = true; |
| |
| toIEW->commitInfo[tid].mispredictInst = |
| fromIEW->mispredictInst[tid]; |
| toIEW->commitInfo[tid].branchTaken = |
| fromIEW->branchTaken[tid]; |
| toIEW->commitInfo[tid].squashInst = |
| rob->findInst(tid, squashed_inst); |
| if (toIEW->commitInfo[tid].mispredictInst) { |
| if (toIEW->commitInfo[tid].mispredictInst->isUncondCtrl()) { |
| toIEW->commitInfo[tid].branchTaken = true; |
| } |
| ++stats.branchMispredicts; |
| } |
| |
| set(toIEW->commitInfo[tid].pc, fromIEW->pc[tid]); |
| } |
| |
| if (commitStatus[tid] == ROBSquashing) { |
| num_squashing_threads++; |
| } |
| } |
| |
| // If commit is currently squashing, then it will have activity for the |
| // next cycle. Set its next status as active. |
| if (num_squashing_threads) { |
| _nextStatus = Active; |
| } |
| |
| if (num_squashing_threads != numThreads) { |
| // If we're not currently squashing, then get instructions. |
| getInsts(); |
| |
| // Try to commit any instructions. |
| commitInsts(); |
| } |
| |
| //Check for any activity |
| threads = activeThreads->begin(); |
| |
| while (threads != end) { |
| ThreadID tid = *threads++; |
| |
| if (changedROBNumEntries[tid]) { |
| toIEW->commitInfo[tid].usedROB = true; |
| toIEW->commitInfo[tid].freeROBEntries = rob->numFreeEntries(tid); |
| |
| wroteToTimeBuffer = true; |
| changedROBNumEntries[tid] = false; |
| if (rob->isEmpty(tid)) |
| checkEmptyROB[tid] = true; |
| } |
| |
| // ROB is only considered "empty" for previous stages if: a) |
| // ROB is empty, b) there are no outstanding stores, c) IEW |
| // stage has received any information regarding stores that |
| // committed. |
| // c) is checked by making sure to not consider the ROB empty |
| // on the same cycle as when stores have been committed. |
| // @todo: Make this handle multi-cycle communication between |
| // commit and IEW. |
| if (checkEmptyROB[tid] && rob->isEmpty(tid) && |
| !iewStage->hasStoresToWB(tid) && !committedStores[tid]) { |
| checkEmptyROB[tid] = false; |
| toIEW->commitInfo[tid].usedROB = true; |
| toIEW->commitInfo[tid].emptyROB = true; |
| toIEW->commitInfo[tid].freeROBEntries = rob->numFreeEntries(tid); |
| wroteToTimeBuffer = true; |
| } |
| |
| } |
| } |
| |
| void |
| Commit::commitInsts() |
| { |
| //////////////////////////////////// |
| // Handle commit |
| // Note that commit will be handled prior to putting new |
| // instructions in 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... |
| //////////////////////////////////// |
| |
| DPRINTF(Commit, "Trying to commit instructions in the ROB.\n"); |
| |
| unsigned num_committed = 0; |
| |
| DynInstPtr head_inst; |
| |
| // Commit as many instructions as possible until the commit bandwidth |
| // limit is reached, or it becomes impossible to commit any more. |
| while (num_committed < commitWidth) { |
| // hardware transactionally memory |
| // If executing within a transaction, |
| // need to handle interrupts specially |
| |
| ThreadID commit_thread = getCommittingThread(); |
| |
| // Check for any interrupt that we've already squashed for |
| // and start processing it. |
| if (interrupt != NoFault) { |
| // If inside a transaction, postpone interrupts |
| if (executingHtmTransaction(commit_thread)) { |
| cpu->clearInterrupts(0); |
| toIEW->commitInfo[0].clearInterrupt = true; |
| interrupt = NoFault; |
| avoidQuiesceLiveLock = true; |
| } else { |
| handleInterrupt(); |
| } |
| } |
| |
| // ThreadID commit_thread = getCommittingThread(); |
| |
| if (commit_thread == -1 || !rob->isHeadReady(commit_thread)) |
| break; |
| |
| head_inst = rob->readHeadInst(commit_thread); |
| |
| ThreadID tid = head_inst->threadNumber; |
| |
| assert(tid == commit_thread); |
| |
| DPRINTF(Commit, |
| "Trying to commit head instruction, [tid:%i] [sn:%llu]\n", |
| tid, head_inst->seqNum); |
| |
| // If the head instruction is squashed, it is ready to retire |
| // (be removed from the ROB) at any time. |
| if (head_inst->isSquashed()) { |
| |
| DPRINTF(Commit, "Retiring squashed instruction from " |
| "ROB.\n"); |
| |
| rob->retireHead(commit_thread); |
| |
| ++stats.commitSquashedInsts; |
| // Notify potential listeners that this instruction is squashed |
| ppSquash->notify(head_inst); |
| |
| // Record that the number of ROB entries has changed. |
| changedROBNumEntries[tid] = true; |
| } else { |
| set(pc[tid], head_inst->pcState()); |
| |
| // Try to commit the head instruction. |
| bool commit_success = commitHead(head_inst, num_committed); |
| |
| if (commit_success) { |
| ++num_committed; |
| stats.committedInstType[tid][head_inst->opClass()]++; |
| ppCommit->notify(head_inst); |
| |
| // hardware transactional memory |
| |
| // update nesting depth |
| if (head_inst->isHtmStart()) |
| htmStarts[tid]++; |
| |
| // sanity check |
| if (head_inst->inHtmTransactionalState()) { |
| assert(executingHtmTransaction(tid)); |
| } else { |
| assert(!executingHtmTransaction(tid)); |
| } |
| |
| // update nesting depth |
| if (head_inst->isHtmStop()) |
| htmStops[tid]++; |
| |
| changedROBNumEntries[tid] = true; |
| |
| // Set the doneSeqNum to the youngest committed instruction. |
| toIEW->commitInfo[tid].doneSeqNum = head_inst->seqNum; |
| |
| if (tid == 0) |
| canHandleInterrupts = !head_inst->isDelayedCommit(); |
| |
| // at this point store conditionals should either have |
| // been completed or predicated false |
| assert(!head_inst->isStoreConditional() || |
| head_inst->isCompleted() || |
| !head_inst->readPredicate()); |
| |
| // Updates misc. registers. |
| head_inst->updateMiscRegs(); |
| |
| // Check instruction execution if it successfully commits and |
| // is not carrying a fault. |
| if (cpu->checker) { |
| cpu->checker->verify(head_inst); |
| } |
| |
| cpu->traceFunctions(pc[tid]->instAddr()); |
| |
| head_inst->staticInst->advancePC(*pc[tid]); |
| |
| // Keep track of the last sequence number commited |
| lastCommitedSeqNum[tid] = head_inst->seqNum; |
| |
| // If this is an instruction that doesn't play nicely with |
| // others squash everything and restart fetch |
| if (head_inst->isSquashAfter()) |
| squashAfter(tid, head_inst); |
| |
| if (drainPending) { |
| if (pc[tid]->microPC() == 0 && interrupt == NoFault && |
| !thread[tid]->trapPending) { |
| // Last architectually committed instruction. |
| // Squash the pipeline, stall fetch, and use |
| // drainImminent to disable interrupts |
| DPRINTF(Drain, "Draining: %i:%s\n", tid, *pc[tid]); |
| squashAfter(tid, head_inst); |
| cpu->commitDrained(tid); |
| drainImminent = true; |
| } |
| } |
| |
| bool onInstBoundary = !head_inst->isMicroop() || |
| head_inst->isLastMicroop() || |
| !head_inst->isDelayedCommit(); |
| |
| if (onInstBoundary) { |
| int count = 0; |
| Addr oldpc; |
| // Make sure we're not currently updating state while |
| // handling PC events. |
| assert(!thread[tid]->noSquashFromTC && |
| !thread[tid]->trapPending); |
| do { |
| oldpc = pc[tid]->instAddr(); |
| thread[tid]->pcEventQueue.service( |
| oldpc, thread[tid]->getTC()); |
| count++; |
| } while (oldpc != pc[tid]->instAddr()); |
| if (count > 1) { |
| DPRINTF(Commit, |
| "PC skip function event, stopping commit\n"); |
| break; |
| } |
| } |
| |
| // Check if an instruction just enabled interrupts and we've |
| // previously had an interrupt pending that was not handled |
| // because interrupts were subsequently disabled before the |
| // pipeline reached a place to handle the interrupt. In that |
| // case squash now to make sure the interrupt is handled. |
| // |
| // If we don't do this, we might end up in a live lock |
| // situation. |
| if (!interrupt && avoidQuiesceLiveLock && |
| onInstBoundary && cpu->checkInterrupts(0)) |
| squashAfter(tid, head_inst); |
| } else { |
| DPRINTF(Commit, "Unable to commit head instruction PC:%s " |
| "[tid:%i] [sn:%llu].\n", |
| head_inst->pcState(), tid ,head_inst->seqNum); |
| break; |
| } |
| } |
| } |
| |
| DPRINTF(CommitRate, "%i\n", num_committed); |
| stats.numCommittedDist.sample(num_committed); |
| |
| if (num_committed == commitWidth) { |
| stats.commitEligibleSamples++; |
| } |
| } |
| |
| bool |
| Commit::commitHead(const DynInstPtr &head_inst, unsigned inst_num) |
| { |
| assert(head_inst); |
| |
| ThreadID tid = head_inst->threadNumber; |
| |
| // If the instruction is not executed yet, then it will need extra |
| // handling. Signal backwards that it should be executed. |
| if (!head_inst->isExecuted()) { |
| // Make sure we are only trying to commit un-executed instructions we |
| // think are possible. |
| assert(head_inst->isNonSpeculative() || head_inst->isStoreConditional() |
| || head_inst->isReadBarrier() || head_inst->isWriteBarrier() |
| || head_inst->isAtomic() |
| || (head_inst->isLoad() && head_inst->strictlyOrdered())); |
| |
| DPRINTF(Commit, |
| "Encountered a barrier or non-speculative " |
| "instruction [tid:%i] [sn:%llu] " |
| "at the head of the ROB, PC %s.\n", |
| tid, head_inst->seqNum, head_inst->pcState()); |
| |
| if (inst_num > 0 || iewStage->hasStoresToWB(tid)) { |
| DPRINTF(Commit, |
| "[tid:%i] [sn:%llu] " |
| "Waiting for all stores to writeback.\n", |
| tid, head_inst->seqNum); |
| return false; |
| } |
| |
| toIEW->commitInfo[tid].nonSpecSeqNum = head_inst->seqNum; |
| |
| // Change the instruction so it won't try to commit again until |
| // it is executed. |
| head_inst->clearCanCommit(); |
| |
| if (head_inst->isLoad() && head_inst->strictlyOrdered()) { |
| DPRINTF(Commit, "[tid:%i] [sn:%llu] " |
| "Strictly ordered load, PC %s.\n", |
| tid, head_inst->seqNum, head_inst->pcState()); |
| toIEW->commitInfo[tid].strictlyOrdered = true; |
| toIEW->commitInfo[tid].strictlyOrderedLoad = head_inst; |
| } else { |
| ++stats.commitNonSpecStalls; |
| } |
| |
| return false; |
| } |
| |
| // Check if the instruction caused a fault. If so, trap. |
| Fault inst_fault = head_inst->getFault(); |
| |
| // hardware transactional memory |
| // if a fault occurred within a HTM transaction |
| // ensure that the transaction aborts |
| if (inst_fault != NoFault && head_inst->inHtmTransactionalState()) { |
| // There exists a generic HTM fault common to all ISAs |
| if (!std::dynamic_pointer_cast<GenericHtmFailureFault>(inst_fault)) { |
| DPRINTF(HtmCpu, "%s - fault (%s) encountered within transaction" |
| " - converting to GenericHtmFailureFault\n", |
| head_inst->staticInst->getName(), inst_fault->name()); |
| inst_fault = std::make_shared<GenericHtmFailureFault>( |
| head_inst->getHtmTransactionUid(), |
| HtmFailureFaultCause::EXCEPTION); |
| } |
| // If this point is reached and the fault inherits from the HTM fault, |
| // then there is no need to raise a new fault |
| } |
| |
| // Stores mark themselves as completed. |
| if (!head_inst->isStore() && inst_fault == NoFault) { |
| head_inst->setCompleted(); |
| } |
| |
| if (inst_fault != NoFault) { |
| DPRINTF(Commit, "Inst [tid:%i] [sn:%llu] PC %s has a fault\n", |
| tid, head_inst->seqNum, head_inst->pcState()); |
| |
| if (iewStage->hasStoresToWB(tid) || inst_num > 0) { |
| DPRINTF(Commit, |
| "[tid:%i] [sn:%llu] " |
| "Stores outstanding, fault must wait.\n", |
| tid, head_inst->seqNum); |
| return false; |
| } |
| |
| head_inst->setCompleted(); |
| |
| // If instruction has faulted, let the checker execute it and |
| // check if it sees the same fault and control flow. |
| if (cpu->checker) { |
| // Need to check the instruction before its fault is processed |
| cpu->checker->verify(head_inst); |
| } |
| |
| assert(!thread[tid]->noSquashFromTC); |
| |
| // Mark that we're in state update mode so that the trap's |
| // execution doesn't generate extra squashes. |
| thread[tid]->noSquashFromTC = true; |
| |
| // Execute the trap. Although it's slightly unrealistic in |
| // terms of timing (as it doesn't wait for the full timing of |
| // the trap event to complete before updating state), it's |
| // needed to update the state as soon as possible. This |
| // prevents external agents from changing any specific state |
| // that the trap need. |
| cpu->trap(inst_fault, tid, |
| head_inst->notAnInst() ? nullStaticInstPtr : |
| head_inst->staticInst); |
| |
| // Exit state update mode to avoid accidental updating. |
| thread[tid]->noSquashFromTC = false; |
| |
| commitStatus[tid] = TrapPending; |
| |
| DPRINTF(Commit, |
| "[tid:%i] [sn:%llu] Committing instruction with fault\n", |
| tid, head_inst->seqNum); |
| if (head_inst->traceData) { |
| // We ignore ReExecution "faults" here as they are not real |
| // (architectural) faults but signal flush/replays. |
| if (debug::ExecFaulting |
| && dynamic_cast<ReExec*>(inst_fault.get()) == nullptr) { |
| |
| head_inst->traceData->setFaulting(true); |
| head_inst->traceData->setFetchSeq(head_inst->seqNum); |
| head_inst->traceData->setCPSeq(thread[tid]->numOp); |
| head_inst->traceData->dump(); |
| } |
| delete head_inst->traceData; |
| head_inst->traceData = NULL; |
| } |
| |
| // Generate trap squash event. |
| generateTrapEvent(tid, inst_fault); |
| return false; |
| } |
| |
| updateComInstStats(head_inst); |
| |
| DPRINTF(Commit, |
| "[tid:%i] [sn:%llu] Committing instruction with PC %s\n", |
| tid, head_inst->seqNum, head_inst->pcState()); |
| if (head_inst->traceData) { |
| head_inst->traceData->setFetchSeq(head_inst->seqNum); |
| head_inst->traceData->setCPSeq(thread[tid]->numOp); |
| head_inst->traceData->dump(); |
| delete head_inst->traceData; |
| head_inst->traceData = NULL; |
| } |
| if (head_inst->isReturn()) { |
| DPRINTF(Commit, |
| "[tid:%i] [sn:%llu] Return Instruction Committed PC %s \n", |
| tid, head_inst->seqNum, head_inst->pcState()); |
| } |
| |
| // Update the commit rename map |
| for (int i = 0; i < head_inst->numDestRegs(); i++) { |
| renameMap[tid]->setEntry(head_inst->flattenedDestIdx(i), |
| head_inst->renamedDestIdx(i)); |
| } |
| |
| // hardware transactional memory |
| // the HTM UID is purely for correctness and debugging purposes |
| if (head_inst->isHtmStart()) |
| iewStage->setLastRetiredHtmUid(tid, head_inst->getHtmTransactionUid()); |
| |
| // Finally clear the head ROB entry. |
| rob->retireHead(tid); |
| |
| #if TRACING_ON |
| if (debug::O3PipeView) { |
| head_inst->commitTick = curTick() - head_inst->fetchTick; |
| } |
| #endif |
| |
| // If this was a store, record it for this cycle. |
| if (head_inst->isStore() || head_inst->isAtomic()) |
| committedStores[tid] = true; |
| |
| // Return true to indicate that we have committed an instruction. |
| return true; |
| } |
| |
| void |
| Commit::getInsts() |
| { |
| DPRINTF(Commit, "Getting instructions from Rename stage.\n"); |
| |
| // Read any renamed instructions and place them into the ROB. |
| int insts_to_process = std::min((int)renameWidth, fromRename->size); |
| |
| for (int inst_num = 0; inst_num < insts_to_process; ++inst_num) { |
| const DynInstPtr &inst = fromRename->insts[inst_num]; |
| ThreadID tid = inst->threadNumber; |
| |
| if (!inst->isSquashed() && |
| commitStatus[tid] != ROBSquashing && |
| commitStatus[tid] != TrapPending) { |
| changedROBNumEntries[tid] = true; |
| |
| DPRINTF(Commit, "[tid:%i] [sn:%llu] Inserting PC %s into ROB.\n", |
| tid, inst->seqNum, inst->pcState()); |
| |
| rob->insertInst(inst); |
| |
| assert(rob->getThreadEntries(tid) <= rob->getMaxEntries(tid)); |
| |
| youngestSeqNum[tid] = inst->seqNum; |
| } else { |
| DPRINTF(Commit, "[tid:%i] [sn:%llu] " |
| "Instruction PC %s was squashed, skipping.\n", |
| tid, inst->seqNum, inst->pcState()); |
| } |
| } |
| } |
| |
| void |
| Commit::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; ++inst_num) { |
| assert(fromIEW->insts[inst_num]); |
| if (!fromIEW->insts[inst_num]->isSquashed()) { |
| DPRINTF(Commit, "[tid:%i] Marking PC %s, [sn:%llu] ready " |
| "within ROB.\n", |
| fromIEW->insts[inst_num]->threadNumber, |
| fromIEW->insts[inst_num]->pcState(), |
| fromIEW->insts[inst_num]->seqNum); |
| |
| // Mark the instruction as ready to commit. |
| fromIEW->insts[inst_num]->setCanCommit(); |
| } |
| } |
| } |
| |
| void |
| Commit::updateComInstStats(const DynInstPtr &inst) |
| { |
| ThreadID tid = inst->threadNumber; |
| |
| if (!inst->isMicroop() || inst->isLastMicroop()) |
| stats.instsCommitted[tid]++; |
| stats.opsCommitted[tid]++; |
| |
| // To match the old model, don't count nops and instruction |
| // prefetches towards the total commit count. |
| if (!inst->isNop() && !inst->isInstPrefetch()) { |
| cpu->instDone(tid, inst); |
| } |
| |
| // |
| // Control Instructions |
| // |
| if (inst->isControl()) |
| stats.branches[tid]++; |
| |
| // |
| // Memory references |
| // |
| if (inst->isMemRef()) { |
| stats.memRefs[tid]++; |
| |
| if (inst->isLoad()) { |
| stats.loads[tid]++; |
| } |
| |
| if (inst->isAtomic()) { |
| stats.amos[tid]++; |
| } |
| } |
| |
| if (inst->isFullMemBarrier()) { |
| stats.membars[tid]++; |
| } |
| |
| // Integer Instruction |
| if (inst->isInteger()) |
| stats.integer[tid]++; |
| |
| // Floating Point Instruction |
| if (inst->isFloating()) |
| stats.floating[tid]++; |
| // Vector Instruction |
| if (inst->isVector()) |
| stats.vectorInstructions[tid]++; |
| |
| // Function Calls |
| if (inst->isCall()) |
| stats.functionCalls[tid]++; |
| |
| } |
| |
| //////////////////////////////////////// |
| // // |
| // SMT COMMIT POLICY MAINTAINED HERE // |
| // // |
| //////////////////////////////////////// |
| ThreadID |
| Commit::getCommittingThread() |
| { |
| if (numThreads > 1) { |
| switch (commitPolicy) { |
| case CommitPolicy::RoundRobin: |
| return roundRobin(); |
| |
| case CommitPolicy::OldestReady: |
| return oldestReady(); |
| |
| default: |
| return InvalidThreadID; |
| } |
| } else { |
| assert(!activeThreads->empty()); |
| ThreadID tid = activeThreads->front(); |
| |
| if (commitStatus[tid] == Running || |
| commitStatus[tid] == Idle || |
| commitStatus[tid] == FetchTrapPending) { |
| return tid; |
| } else { |
| return InvalidThreadID; |
| } |
| } |
| } |
| |
| ThreadID |
| Commit::roundRobin() |
| { |
| std::list<ThreadID>::iterator pri_iter = priority_list.begin(); |
| std::list<ThreadID>::iterator end = priority_list.end(); |
| |
| while (pri_iter != end) { |
| ThreadID tid = *pri_iter; |
| |
| if (commitStatus[tid] == Running || |
| commitStatus[tid] == Idle || |
| commitStatus[tid] == FetchTrapPending) { |
| |
| if (rob->isHeadReady(tid)) { |
| priority_list.erase(pri_iter); |
| priority_list.push_back(tid); |
| |
| return tid; |
| } |
| } |
| |
| pri_iter++; |
| } |
| |
| return InvalidThreadID; |
| } |
| |
| ThreadID |
| Commit::oldestReady() |
| { |
| unsigned oldest = 0; |
| unsigned oldest_seq_num = 0; |
| bool first = true; |
| |
| std::list<ThreadID>::iterator threads = activeThreads->begin(); |
| std::list<ThreadID>::iterator end = activeThreads->end(); |
| |
| while (threads != end) { |
| ThreadID tid = *threads++; |
| |
| if (!rob->isEmpty(tid) && |
| (commitStatus[tid] == Running || |
| commitStatus[tid] == Idle || |
| commitStatus[tid] == FetchTrapPending)) { |
| |
| if (rob->isHeadReady(tid)) { |
| |
| const DynInstPtr &head_inst = rob->readHeadInst(tid); |
| |
| if (first) { |
| oldest = tid; |
| oldest_seq_num = head_inst->seqNum; |
| first = false; |
| } else if (head_inst->seqNum < oldest_seq_num) { |
| oldest = tid; |
| oldest_seq_num = head_inst->seqNum; |
| } |
| } |
| } |
| } |
| |
| if (!first) { |
| return oldest; |
| } else { |
| return InvalidThreadID; |
| } |
| } |
| |
| } // namespace o3 |
| } // namespace gem5 |