cpu: Rewrite O3 draining to avoid stopping in microcode
Previously, the O3 CPU could stop in the middle of a microcode sequence. This patch makes sure that the pipeline stops when it has committed a normal instruction or exited from a microcode sequence. Additionally, it makes sure that the pipeline has no instructions in flight when it is drained, which should make draining more robust. Draining is controlled in the commit stage, which checks if the next PC after a committed instruction is in microcode. If this isn't the case, it requests a squash of all instructions after that the instruction that just committed and immediately signals a drain stall to the fetch stage. The CPU then continues to execute until the pipeline and all associated buffers are empty.
This commit is contained in:
@@ -57,6 +57,7 @@
|
||||
#include "debug/Activity.hh"
|
||||
#include "debug/Commit.hh"
|
||||
#include "debug/CommitRate.hh"
|
||||
#include "debug/Drain.hh"
|
||||
#include "debug/ExecFaulting.hh"
|
||||
#include "params/DerivO3CPU.hh"
|
||||
#include "sim/faults.hh"
|
||||
@@ -99,7 +100,6 @@ DefaultCommit<Impl>::DefaultCommit(O3CPU *_cpu, DerivO3CPUParams *params)
|
||||
commitWidth(params->commitWidth),
|
||||
numThreads(params->numThreads),
|
||||
drainPending(false),
|
||||
switchedOut(false),
|
||||
trapLatency(params->trapLatency),
|
||||
canHandleInterrupts(true)
|
||||
{
|
||||
@@ -369,35 +369,59 @@ DefaultCommit<Impl>::startupStage()
|
||||
}
|
||||
|
||||
template <class Impl>
|
||||
bool
|
||||
void
|
||||
DefaultCommit<Impl>::drain()
|
||||
{
|
||||
drainPending = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class Impl>
|
||||
void
|
||||
DefaultCommit<Impl>::switchOut()
|
||||
DefaultCommit<Impl>::drainResume()
|
||||
{
|
||||
switchedOut = true;
|
||||
drainPending = false;
|
||||
rob->switchOut();
|
||||
}
|
||||
|
||||
template <class Impl>
|
||||
void
|
||||
DefaultCommit<Impl>::resume()
|
||||
DefaultCommit<Impl>::drainSanityCheck() const
|
||||
{
|
||||
drainPending = false;
|
||||
assert(isDrained());
|
||||
rob->drainSanityCheck();
|
||||
}
|
||||
|
||||
template <class Impl>
|
||||
bool
|
||||
DefaultCommit<Impl>::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 <class Impl>
|
||||
void
|
||||
DefaultCommit<Impl>::takeOverFrom()
|
||||
{
|
||||
switchedOut = false;
|
||||
_status = Active;
|
||||
_nextStatus = Inactive;
|
||||
for (ThreadID tid = 0; tid < numThreads; tid++) {
|
||||
@@ -624,13 +648,6 @@ DefaultCommit<Impl>::tick()
|
||||
wroteToTimeBuffer = false;
|
||||
_nextStatus = Inactive;
|
||||
|
||||
if (drainPending && cpu->instList.empty() && !iewStage->hasStoresToWB() &&
|
||||
interrupt == NoFault) {
|
||||
cpu->signalDrained();
|
||||
drainPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeThreads->empty())
|
||||
return;
|
||||
|
||||
@@ -1018,6 +1035,14 @@ DefaultCommit<Impl>::commitInsts()
|
||||
if (head_inst->isSquashAfter())
|
||||
squashAfter(tid, head_inst);
|
||||
|
||||
if (drainPending) {
|
||||
DPRINTF(Drain, "Draining: %i:%s\n", tid, pc[tid]);
|
||||
if (pc[tid].microPC() == 0 && interrupt == NoFault) {
|
||||
squashAfter(tid, head_inst);
|
||||
cpu->commitDrained(tid);
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
Addr oldpc;
|
||||
// Debug statement. Checks to make sure we're not
|
||||
|
||||
Reference in New Issue
Block a user