From e164d0828794440c0b2f2779de8d738c8a4ce846 Mon Sep 17 00:00:00 2001 From: Gabe Black Date: Wed, 3 Mar 2021 04:20:58 -0800 Subject: [PATCH] cpu: De-templatize the O3 DefaultCommit. Change-Id: I054cb344a5e3829caf6cbd26e931514b877c1577 Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/42114 Tested-by: kokoro Reviewed-by: Gabe Black Maintainer: Gabe Black --- src/cpu/o3/commit.cc | 1535 ++++++++++++++++++++++++++++++++++- src/cpu/o3/commit.hh | 22 +- src/cpu/o3/commit_impl.hh | 1611 ------------------------------------- src/cpu/o3/cpu.hh | 2 +- src/cpu/o3/rename.hh | 4 +- 5 files changed, 1544 insertions(+), 1630 deletions(-) delete mode 100644 src/cpu/o3/commit_impl.hh diff --git a/src/cpu/o3/commit.cc b/src/cpu/o3/commit.cc index 0be621a688..fd010a9ba3 100644 --- a/src/cpu/o3/commit.cc +++ b/src/cpu/o3/commit.cc @@ -1,4 +1,17 @@ /* + * 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. * @@ -26,7 +39,1523 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "cpu/o3/commit_impl.hh" -#include "cpu/o3/isa_specific.hh" +#include "cpu/o3/commit.hh" -template class DefaultCommit; +#include +#include +#include + +#include "base/compiler.hh" +#include "base/loader/symtab.hh" +#include "base/logging.hh" +#include "config/the_isa.hh" +#include "cpu/base.hh" +#include "cpu/checker/cpu.hh" +#include "cpu/exetrace.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/DerivO3CPU.hh" +#include "sim/faults.hh" +#include "sim/full_system.hh" + +void +DefaultCommit::processTrapEvent(ThreadID tid) +{ + // This will get reset by commit if it was switched out at the + // time of this event processing. + trapSquash[tid] = true; +} + +DefaultCommit::DefaultCommit(FullO3CPU *_cpu, + const DerivO3CPUParams ¶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 > O3MaxWidth) + fatal("commitWidth (%d) is larger than compiled limit (%d),\n" + "\tincrease O3MaxWidth in src/cpu/o3/limits.hh\n", + commitWidth, static_cast(O3MaxWidth)); + + _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 < O3MaxThreads; tid++) { + commitStatus[tid] = Idle; + changedROBNumEntries[tid] = false; + trapSquash[tid] = false; + tcSquash[tid] = false; + squashAfterInst[tid] = nullptr; + pc[tid].set(0); + 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 +DefaultCommit::name() const +{ + return cpu->name() + ".commit"; +} + +void +DefaultCommit::regProbePoints() +{ + ppCommit = new ProbePointArg( + cpu->getProbeManager(), "Commit"); + ppCommitStall = new ProbePointArg( + cpu->getProbeManager(), "CommitStall"); + ppSquash = new ProbePointArg( + cpu->getProbeManager(), "Squash"); +} + +DefaultCommit::CommitStats::CommitStats(FullO3CPU *cpu, + DefaultCommit *commit) + : Stats::Group(cpu, "commit"), + ADD_STAT(commitSquashedInsts, Stats::Units::Count::get(), + "The number of squashed insts skipped by commit"), + ADD_STAT(commitNonSpecStalls, Stats::Units::Count::get(), + "The number of times commit has been forced to stall to " + "communicate backwards"), + ADD_STAT(branchMispredicts, Stats::Units::Count::get(), + "The number of times a branch was mispredicted"), + ADD_STAT(numCommittedDist, Stats::Units::Count::get(), + "Number of insts commited each cycle"), + ADD_STAT(instsCommitted, Stats::Units::Count::get(), + "Number of instructions committed"), + ADD_STAT(opsCommitted, Stats::Units::Count::get(), + "Number of ops (including micro ops) committed"), + ADD_STAT(memRefs, Stats::Units::Count::get(), + "Number of memory references committed"), + ADD_STAT(loads, Stats::Units::Count::get(), "Number of loads committed"), + ADD_STAT(amos, Stats::Units::Count::get(), + "Number of atomic instructions committed"), + ADD_STAT(membars, Stats::Units::Count::get(), + "Number of memory barriers committed"), + ADD_STAT(branches, Stats::Units::Count::get(), + "Number of branches committed"), + ADD_STAT(vectorInstructions, Stats::Units::Count::get(), + "Number of committed Vector instructions."), + ADD_STAT(floating, Stats::Units::Count::get(), + "Number of committed floating point instructions."), + ADD_STAT(integer, Stats::Units::Count::get(), + "Number of committed integer instructions."), + ADD_STAT(functionCalls, Stats::Units::Count::get(), + "Number of function calls committed."), + ADD_STAT(committedInstType, Stats::Units::Count::get(), + "Class of committed instruction"), + ADD_STAT(commitEligibleSamples, Stats::Units::Cycle::get(), + "number cycles where commit BW limit reached") +{ + using namespace Stats; + + commitSquashedInsts.prereq(commitSquashedInsts); + commitNonSpecStalls.prereq(commitNonSpecStalls); + branchMispredicts.prereq(branchMispredicts); + + numCommittedDist + .init(0,commit->commitWidth,1) + .flags(Stats::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 +DefaultCommit::setThreads(std::vector &threads) +{ + thread = threads; +} + +void +DefaultCommit::setTimeBuffer(TimeBuffer *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 +DefaultCommit::setFetchQueue(TimeBuffer *fq_ptr) +{ + fetchQueue = fq_ptr; + + // Setup wire to get instructions from rename (for the ROB). + fromFetch = fetchQueue->getWire(-fetchToCommitDelay); +} + +void +DefaultCommit::setRenameQueue(TimeBuffer *rq_ptr) +{ + renameQueue = rq_ptr; + + // Setup wire to get instructions from rename (for the ROB). + fromRename = renameQueue->getWire(-renameToROBDelay); +} + +void +DefaultCommit::setIEWQueue(TimeBuffer *iq_ptr) +{ + iewQueue = iq_ptr; + + // Setup wire to get instructions from IEW. + fromIEW = iewQueue->getWire(-iewToCommitDelay); +} + +void +DefaultCommit::setIEWStage(DefaultIEW *iew_stage) +{ + iewStage = iew_stage; +} + +void +DefaultCommit::setActiveThreads(std::list *at_ptr) +{ + activeThreads = at_ptr; +} + +void +DefaultCommit::setRenameMap(UnifiedRenameMap rm_ptr[]) +{ + for (ThreadID tid = 0; tid < numThreads; tid++) + renameMap[tid] = &rm_ptr[tid]; +} + +void +DefaultCommit::setROB(ROB *rob_ptr) +{ + rob = rob_ptr; +} + +void +DefaultCommit::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(FullO3CPU::CommitIdx); + + cpu->activityThisCycle(); +} + +void +DefaultCommit::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].set(0); + lastCommitedSeqNum[tid] = 0; + squashAfterInst[tid] = NULL; +} + +void +DefaultCommit::drain() +{ + drainPending = true; +} + +void +DefaultCommit::drainResume() +{ + drainPending = false; + drainImminent = false; +} + +void +DefaultCommit::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 +DefaultCommit::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 +DefaultCommit::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 +DefaultCommit::deactivateThread(ThreadID tid) +{ + std::list::iterator thread_it = std::find(priority_list.begin(), + priority_list.end(), tid); + + if (thread_it != priority_list.end()) { + priority_list.erase(thread_it); + } +} + +bool +DefaultCommit::executingHtmTransaction(ThreadID tid) const +{ + if (tid == InvalidThreadID) + return false; + else + return (htmStarts[tid] > htmStops[tid]); +} + +void +DefaultCommit::resetHtmStartsStops(ThreadID tid) +{ + if (tid != InvalidThreadID) + { + htmStarts[tid] = 0; + htmStops[tid] = 0; + } +} + + +void +DefaultCommit::updateStatus() +{ + // reset ROB changed variable + std::list::iterator threads = activeThreads->begin(); + std::list::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(FullO3CPU::CommitIdx); + } else if (_nextStatus == Active && _status == Inactive) { + DPRINTF(Activity, "Activating stage.\n"); + cpu->activateStage(FullO3CPU::CommitIdx); + } + + _status = _nextStatus; +} + +bool +DefaultCommit::changedROBEntries() +{ + std::list::iterator threads = activeThreads->begin(); + std::list::iterator end = activeThreads->end(); + + while (threads != end) { + ThreadID tid = *threads++; + + if (changedROBNumEntries[tid]) { + return true; + } + } + + return false; +} + +size_t +DefaultCommit::numROBFreeEntries(ThreadID tid) +{ + return rob->numFreeEntries(tid); +} + +void +DefaultCommit::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(inst_fault) ? + cpu->syscallRetryLatency : trapLatency; + + // hardware transactional memory + if (inst_fault != nullptr && + std::dynamic_pointer_cast(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 +DefaultCommit::generateTCEvent(ThreadID tid) +{ + assert(!trapInFlight[tid]); + DPRINTF(Commit, "Generating TC squash event for [tid:%i]\n", tid); + + tcSquash[tid] = true; +} + +void +DefaultCommit::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; + + toIEW->commitInfo[tid].pc = pc[tid]; +} + +void +DefaultCommit::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 +DefaultCommit::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 +DefaultCommit::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 +DefaultCommit::squashAfter(ThreadID tid, const O3DynInstPtr &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 +DefaultCommit::tick() +{ + wroteToTimeBuffer = false; + _nextStatus = Inactive; + + if (activeThreads->empty()) + return; + + std::list::iterator threads = activeThreads->begin(); + std::list::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; + + GEM5_VAR_USED const O3DynInstPtr &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 O3DynInstPtr &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 +DefaultCommit::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 +DefaultCommit::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 +DefaultCommit::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::iterator threads = activeThreads->begin(); + std::list::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]->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].nextInstAddr()); + + 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; + } + + 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 +DefaultCommit::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; + + O3DynInstPtr 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 { + pc[tid] = head_inst->pcState(); + + // 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. + thread[tid]->funcExeInst++; + + // 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 +DefaultCommit::commitHead(const O3DynInstPtr &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()) { + // Keep this number correct. We have not yet actually executed + // and committed this instruction. + thread[tid]->funcExeInst--; + + // 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(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( + 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(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->regs.flattenedDestIdx(i), + head_inst->regs.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 +DefaultCommit::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 O3DynInstPtr &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", + inst->seqNum, tid, 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", + inst->seqNum, tid, inst->pcState()); + } + } +} + +void +DefaultCommit::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 +DefaultCommit::updateComInstStats(const O3DynInstPtr &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 +DefaultCommit::getCommittingThread() +{ + if (numThreads > 1) { + switch (commitPolicy) { + + case CommitPolicy::Aggressive: + //If Policy is Aggressive, commit will call + //this function multiple times per + //cycle + return oldestReady(); + + 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 +DefaultCommit::roundRobin() +{ + std::list::iterator pri_iter = priority_list.begin(); + std::list::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 +DefaultCommit::oldestReady() +{ + unsigned oldest = 0; + bool first = true; + + std::list::iterator threads = activeThreads->begin(); + std::list::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 O3DynInstPtr &head_inst = rob->readHeadInst(tid); + + if (first) { + oldest = tid; + first = false; + } else if (head_inst->seqNum < oldest) { + oldest = tid; + } + } + } + } + + if (!first) { + return oldest; + } else { + return InvalidThreadID; + } +} diff --git a/src/cpu/o3/commit.hh b/src/cpu/o3/commit.hh index f9cba54e18..cae30e6710 100644 --- a/src/cpu/o3/commit.hh +++ b/src/cpu/o3/commit.hh @@ -83,11 +83,10 @@ struct O3ThreadState; * supports multiple cycle squashing, to model a ROB that can only * remove a certain number of instructions per cycle. */ -template class DefaultCommit { public: - typedef O3ThreadState Thread; + typedef O3ThreadState Thread; /** Overall commit status. Used to determine if the CPU can deschedule * itself due to a lack of activity. @@ -130,7 +129,7 @@ class DefaultCommit public: /** Construct a DefaultCommit with the given parameters. */ - DefaultCommit(FullO3CPU *_cpu, const DerivO3CPUParams ¶ms); + DefaultCommit(FullO3CPU *_cpu, const DerivO3CPUParams ¶ms); /** Returns the name of the DefaultCommit. */ std::string name() const; @@ -324,26 +323,26 @@ class DefaultCommit TimeBuffer *timeBuffer; /** Wire to write information heading to previous stages. */ - typename TimeBuffer::wire toIEW; + TimeBuffer::wire toIEW; /** Wire to read information from IEW (for ROB). */ - typename TimeBuffer::wire robInfoFromIEW; + TimeBuffer::wire robInfoFromIEW; TimeBuffer *fetchQueue; - typename TimeBuffer::wire fromFetch; + TimeBuffer::wire fromFetch; /** IEW instruction queue interface. */ TimeBuffer *iewQueue; /** Wire to read information from IEW queue. */ - typename TimeBuffer::wire fromIEW; + TimeBuffer::wire fromIEW; /** Rename instruction queue interface, for ROB. */ TimeBuffer *renameQueue; /** Wire to read information from rename queue. */ - typename TimeBuffer::wire fromRename; + TimeBuffer::wire fromRename; public: /** ROB interface. */ @@ -351,7 +350,7 @@ class DefaultCommit private: /** Pointer to O3CPU. */ - FullO3CPU *cpu; + FullO3CPU *cpu; /** Vector of all of the threads. */ std::vector thread; @@ -403,9 +402,6 @@ class DefaultCommit /** Commit width, in instructions. */ const unsigned commitWidth; - /** Number of Reorder Buffers */ - unsigned numRobs; - /** Number of Active Threads */ const ThreadID numThreads; @@ -474,7 +470,7 @@ class DefaultCommit struct CommitStats : public Stats::Group { - CommitStats(FullO3CPU *cpu, DefaultCommit *commit); + CommitStats(FullO3CPU *cpu, DefaultCommit *commit); /** Stat for the total number of squashed instructions discarded by * commit. */ diff --git a/src/cpu/o3/commit_impl.hh b/src/cpu/o3/commit_impl.hh deleted file mode 100644 index 409290e663..0000000000 --- a/src/cpu/o3/commit_impl.hh +++ /dev/null @@ -1,1611 +0,0 @@ -/* - * 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-2006 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. - */ -#ifndef __CPU_O3_COMMIT_IMPL_HH__ -#define __CPU_O3_COMMIT_IMPL_HH__ - -#include -#include -#include - -#include "base/compiler.hh" -#include "base/loader/symtab.hh" -#include "base/logging.hh" -#include "config/the_isa.hh" -#include "cpu/base.hh" -#include "cpu/checker/cpu.hh" -#include "cpu/exetrace.hh" -#include "cpu/null_static_inst.hh" -#include "cpu/o3/commit.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/DerivO3CPU.hh" -#include "sim/faults.hh" -#include "sim/full_system.hh" - -template -void -DefaultCommit::processTrapEvent(ThreadID tid) -{ - // This will get reset by commit if it was switched out at the - // time of this event processing. - trapSquash[tid] = true; -} - -template -DefaultCommit::DefaultCommit(FullO3CPU *_cpu, - const DerivO3CPUParams ¶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 > O3MaxWidth) - fatal("commitWidth (%d) is larger than compiled limit (%d),\n" - "\tincrease O3MaxWidth in src/cpu/o3/limits.hh\n", - commitWidth, static_cast(O3MaxWidth)); - - _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 < O3MaxThreads; tid++) { - commitStatus[tid] = Idle; - changedROBNumEntries[tid] = false; - trapSquash[tid] = false; - tcSquash[tid] = false; - squashAfterInst[tid] = nullptr; - pc[tid].set(0); - 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; -} - -template -std::string -DefaultCommit::name() const -{ - return cpu->name() + ".commit"; -} - -template -void -DefaultCommit::regProbePoints() -{ - ppCommit = new ProbePointArg( - cpu->getProbeManager(), "Commit"); - ppCommitStall = new ProbePointArg( - cpu->getProbeManager(), "CommitStall"); - ppSquash = new ProbePointArg( - cpu->getProbeManager(), "Squash"); -} - -template -DefaultCommit::CommitStats::CommitStats(FullO3CPU *cpu, - DefaultCommit *commit) - : Stats::Group(cpu, "commit"), - ADD_STAT(commitSquashedInsts, Stats::Units::Count::get(), - "The number of squashed insts skipped by commit"), - ADD_STAT(commitNonSpecStalls, Stats::Units::Count::get(), - "The number of times commit has been forced to stall to " - "communicate backwards"), - ADD_STAT(branchMispredicts, Stats::Units::Count::get(), - "The number of times a branch was mispredicted"), - ADD_STAT(numCommittedDist, Stats::Units::Count::get(), - "Number of insts commited each cycle"), - ADD_STAT(instsCommitted, Stats::Units::Count::get(), - "Number of instructions committed"), - ADD_STAT(opsCommitted, Stats::Units::Count::get(), - "Number of ops (including micro ops) committed"), - ADD_STAT(memRefs, Stats::Units::Count::get(), - "Number of memory references committed"), - ADD_STAT(loads, Stats::Units::Count::get(), "Number of loads committed"), - ADD_STAT(amos, Stats::Units::Count::get(), - "Number of atomic instructions committed"), - ADD_STAT(membars, Stats::Units::Count::get(), - "Number of memory barriers committed"), - ADD_STAT(branches, Stats::Units::Count::get(), - "Number of branches committed"), - ADD_STAT(vectorInstructions, Stats::Units::Count::get(), - "Number of committed Vector instructions."), - ADD_STAT(floating, Stats::Units::Count::get(), - "Number of committed floating point instructions."), - ADD_STAT(integer, Stats::Units::Count::get(), - "Number of committed integer instructions."), - ADD_STAT(functionCalls, Stats::Units::Count::get(), - "Number of function calls committed."), - ADD_STAT(committedInstType, Stats::Units::Count::get(), - "Class of committed instruction"), - ADD_STAT(commitEligibleSamples, Stats::Units::Cycle::get(), - "number cycles where commit BW limit reached") -{ - using namespace Stats; - - commitSquashedInsts.prereq(commitSquashedInsts); - commitNonSpecStalls.prereq(commitNonSpecStalls); - branchMispredicts.prereq(branchMispredicts); - - numCommittedDist - .init(0,commit->commitWidth,1) - .flags(Stats::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); -} - -template -void -DefaultCommit::setThreads(std::vector &threads) -{ - thread = threads; -} - -template -void -DefaultCommit::setTimeBuffer(TimeBuffer *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); -} - -template -void -DefaultCommit::setFetchQueue(TimeBuffer *fq_ptr) -{ - fetchQueue = fq_ptr; - - // Setup wire to get instructions from rename (for the ROB). - fromFetch = fetchQueue->getWire(-fetchToCommitDelay); -} - -template -void -DefaultCommit::setRenameQueue(TimeBuffer *rq_ptr) -{ - renameQueue = rq_ptr; - - // Setup wire to get instructions from rename (for the ROB). - fromRename = renameQueue->getWire(-renameToROBDelay); -} - -template -void -DefaultCommit::setIEWQueue(TimeBuffer *iq_ptr) -{ - iewQueue = iq_ptr; - - // Setup wire to get instructions from IEW. - fromIEW = iewQueue->getWire(-iewToCommitDelay); -} - -template -void -DefaultCommit::setIEWStage(DefaultIEW *iew_stage) -{ - iewStage = iew_stage; -} - -template -void -DefaultCommit::setActiveThreads(std::list *at_ptr) -{ - activeThreads = at_ptr; -} - -template -void -DefaultCommit::setRenameMap(UnifiedRenameMap rm_ptr[]) -{ - for (ThreadID tid = 0; tid < numThreads; tid++) - renameMap[tid] = &rm_ptr[tid]; -} - -template -void -DefaultCommit::setROB(ROB *rob_ptr) -{ - rob = rob_ptr; -} - -template -void -DefaultCommit::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(FullO3CPU::CommitIdx); - - cpu->activityThisCycle(); -} - -template -void -DefaultCommit::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].set(0); - lastCommitedSeqNum[tid] = 0; - squashAfterInst[tid] = NULL; -} - -template -void -DefaultCommit::drain() -{ - drainPending = true; -} - -template -void -DefaultCommit::drainResume() -{ - drainPending = false; - drainImminent = false; -} - -template -void -DefaultCommit::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"); - } - } -} - -template -bool -DefaultCommit::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; -} - -template -void -DefaultCommit::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(); -} - -template -void -DefaultCommit::deactivateThread(ThreadID tid) -{ - std::list::iterator thread_it = std::find(priority_list.begin(), - priority_list.end(), tid); - - if (thread_it != priority_list.end()) { - priority_list.erase(thread_it); - } -} - -template -bool -DefaultCommit::executingHtmTransaction(ThreadID tid) const -{ - if (tid == InvalidThreadID) - return false; - else - return (htmStarts[tid] > htmStops[tid]); -} - -template -void -DefaultCommit::resetHtmStartsStops(ThreadID tid) -{ - if (tid != InvalidThreadID) - { - htmStarts[tid] = 0; - htmStops[tid] = 0; - } -} - - -template -void -DefaultCommit::updateStatus() -{ - // reset ROB changed variable - std::list::iterator threads = activeThreads->begin(); - std::list::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(FullO3CPU::CommitIdx); - } else if (_nextStatus == Active && _status == Inactive) { - DPRINTF(Activity, "Activating stage.\n"); - cpu->activateStage(FullO3CPU::CommitIdx); - } - - _status = _nextStatus; -} - -template -bool -DefaultCommit::changedROBEntries() -{ - std::list::iterator threads = activeThreads->begin(); - std::list::iterator end = activeThreads->end(); - - while (threads != end) { - ThreadID tid = *threads++; - - if (changedROBNumEntries[tid]) { - return true; - } - } - - return false; -} - -template -size_t -DefaultCommit::numROBFreeEntries(ThreadID tid) -{ - return rob->numFreeEntries(tid); -} - -template -void -DefaultCommit::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(inst_fault) ? - cpu->syscallRetryLatency : trapLatency; - - // hardware transactional memory - if (inst_fault != nullptr && - std::dynamic_pointer_cast(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; -} - -template -void -DefaultCommit::generateTCEvent(ThreadID tid) -{ - assert(!trapInFlight[tid]); - DPRINTF(Commit, "Generating TC squash event for [tid:%i]\n", tid); - - tcSquash[tid] = true; -} - -template -void -DefaultCommit::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; - - toIEW->commitInfo[tid].pc = pc[tid]; -} - -template -void -DefaultCommit::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(); -} - -template -void -DefaultCommit::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; -} - -template -void -DefaultCommit::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(); -} - -template -void -DefaultCommit::squashAfter(ThreadID tid, const O3DynInstPtr &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; -} - -template -void -DefaultCommit::tick() -{ - wroteToTimeBuffer = false; - _nextStatus = Inactive; - - if (activeThreads->empty()) - return; - - std::list::iterator threads = activeThreads->begin(); - std::list::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; - - GEM5_VAR_USED const O3DynInstPtr &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 O3DynInstPtr &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(); -} - -template -void -DefaultCommit::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 " ); - } -} - -template -void -DefaultCommit::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; -} - -template -void -DefaultCommit::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::iterator threads = activeThreads->begin(); - std::list::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]->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].nextInstAddr()); - - 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; - } - - 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; - } - - } -} - -template -void -DefaultCommit::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; - - O3DynInstPtr 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 { - pc[tid] = head_inst->pcState(); - - // 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. - thread[tid]->funcExeInst++; - - // 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++; - } -} - -template -bool -DefaultCommit::commitHead( - const O3DynInstPtr &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()) { - // Keep this number correct. We have not yet actually executed - // and committed this instruction. - thread[tid]->funcExeInst--; - - // 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(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( - 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(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->regs.flattenedDestIdx(i), - head_inst->regs.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; -} - -template -void -DefaultCommit::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 O3DynInstPtr &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", - inst->seqNum, tid, 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", - inst->seqNum, tid, inst->pcState()); - } - } -} - -template -void -DefaultCommit::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(); - } - } -} - -template -void -DefaultCommit::updateComInstStats(const O3DynInstPtr &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 // -// // -//////////////////////////////////////// -template -ThreadID -DefaultCommit::getCommittingThread() -{ - if (numThreads > 1) { - switch (commitPolicy) { - - case CommitPolicy::Aggressive: - //If Policy is Aggressive, commit will call - //this function multiple times per - //cycle - return oldestReady(); - - 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; - } - } -} - -template -ThreadID -DefaultCommit::roundRobin() -{ - std::list::iterator pri_iter = priority_list.begin(); - std::list::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; -} - -template -ThreadID -DefaultCommit::oldestReady() -{ - unsigned oldest = 0; - bool first = true; - - std::list::iterator threads = activeThreads->begin(); - std::list::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 O3DynInstPtr &head_inst = rob->readHeadInst(tid); - - if (first) { - oldest = tid; - first = false; - } else if (head_inst->seqNum < oldest) { - oldest = tid; - } - } - } - } - - if (!first) { - return oldest; - } else { - return InvalidThreadID; - } -} - -#endif//__CPU_O3_COMMIT_IMPL_HH__ diff --git a/src/cpu/o3/cpu.hh b/src/cpu/o3/cpu.hh index b328843428..ca5d7bea48 100644 --- a/src/cpu/o3/cpu.hh +++ b/src/cpu/o3/cpu.hh @@ -503,7 +503,7 @@ class FullO3CPU : public BaseO3CPU DefaultIEW iew; /** The commit stage. */ - DefaultCommit commit; + DefaultCommit commit; /** The rename mode of the vector registers */ Enums::VecRegRenameMode vecMode; diff --git a/src/cpu/o3/rename.hh b/src/cpu/o3/rename.hh index df4a60cf49..b00f36d12c 100644 --- a/src/cpu/o3/rename.hh +++ b/src/cpu/o3/rename.hh @@ -143,7 +143,7 @@ class DefaultRename /** Sets pointer to commit stage. Used only for initialization. */ void - setCommitStage(DefaultCommit *commit_stage) + setCommitStage(DefaultCommit *commit_stage) { commit_ptr = commit_stage; } @@ -153,7 +153,7 @@ class DefaultRename DefaultIEW *iew_ptr; /** Pointer to commit stage. Used only for initialization. */ - DefaultCommit *commit_ptr; + DefaultCommit *commit_ptr; public: /** Initializes variables for the stage. */