cpu: Make sure that a drained timing CPU isn't executing ucode
Currently, the timing CPU can be in the middle of a microcode sequence or multicycle (stayAtPC is true) instruction when it is drained. This leads to two problems: * When switching to a hardware virtualized CPU, we obviously can't execute gem5 microcode. * If stayAtPC is true we might execute half of an instruction twice when restoring a checkpoint or switching CPUs, which leads to an incorrect execution. After applying this patch, the CPU will be on a proper instruction boundary, which means that it is safe to switch to any CPU model (including hardware virtualized ones). This changeset also fixes a bug where the timing CPU sometimes switches out with while stayAtPC is true, which corrupts the target state after a CPU switch or checkpoint. Note: This changeset removes the so_state variable from checkpoints since the drain state isn't used anymore.
This commit is contained in:
parent
52ff37caa3
commit
f9bcf46371
|
@ -94,11 +94,10 @@ TimingSimpleCPU::TimingCPUPort::TickEvent::schedule(PacketPtr _pkt, Tick t)
|
||||||
TimingSimpleCPU::TimingSimpleCPU(TimingSimpleCPUParams *p)
|
TimingSimpleCPU::TimingSimpleCPU(TimingSimpleCPUParams *p)
|
||||||
: BaseSimpleCPU(p), fetchTranslation(this), icachePort(this),
|
: BaseSimpleCPU(p), fetchTranslation(this), icachePort(this),
|
||||||
dcachePort(this), ifetch_pkt(NULL), dcache_pkt(NULL), previousCycle(0),
|
dcachePort(this), ifetch_pkt(NULL), dcache_pkt(NULL), previousCycle(0),
|
||||||
fetchEvent(this)
|
fetchEvent(this), drainManager(NULL)
|
||||||
{
|
{
|
||||||
_status = Idle;
|
_status = Idle;
|
||||||
|
|
||||||
setDrainState(Drainable::Running);
|
|
||||||
system->totalNumInsts = 0;
|
system->totalNumInsts = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,36 +106,27 @@ TimingSimpleCPU::~TimingSimpleCPU()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
TimingSimpleCPU::serialize(ostream &os)
|
|
||||||
{
|
|
||||||
Drainable::State so_state(getDrainState());
|
|
||||||
SERIALIZE_ENUM(so_state);
|
|
||||||
BaseSimpleCPU::serialize(os);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
TimingSimpleCPU::unserialize(Checkpoint *cp, const string §ion)
|
|
||||||
{
|
|
||||||
Drainable::State so_state;
|
|
||||||
UNSERIALIZE_ENUM(so_state);
|
|
||||||
BaseSimpleCPU::unserialize(cp, section);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int
|
unsigned int
|
||||||
TimingSimpleCPU::drain(DrainManager *drain_manager)
|
TimingSimpleCPU::drain(DrainManager *drain_manager)
|
||||||
{
|
{
|
||||||
// TimingSimpleCPU is ready to drain if it's not waiting for
|
|
||||||
// an access to complete.
|
|
||||||
if (_status == Idle ||
|
if (_status == Idle ||
|
||||||
_status == BaseSimpleCPU::Running ||
|
(_status == BaseSimpleCPU::Running && isDrained()) ||
|
||||||
_status == SwitchedOut) {
|
_status == SwitchedOut) {
|
||||||
setDrainState(Drainable::Drained);
|
assert(!fetchEvent.scheduled());
|
||||||
|
DPRINTF(Drain, "No need to drain.\n");
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
setDrainState(Drainable::Draining);
|
|
||||||
drainManager = drain_manager;
|
drainManager = drain_manager;
|
||||||
DPRINTF(Drain, "CPU not drained\n");
|
DPRINTF(Drain, "Requesting drain: %s\n", pcState());
|
||||||
|
|
||||||
|
// The fetch event can become descheduled if a drain didn't
|
||||||
|
// succeed on the first attempt. We need to reschedule it if
|
||||||
|
// the CPU is waiting for a microcode routine to complete.
|
||||||
|
if (_status == BaseSimpleCPU::Running && !isDrained() &&
|
||||||
|
!fetchEvent.scheduled()) {
|
||||||
|
schedule(fetchEvent, nextCycle());
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,6 +134,8 @@ TimingSimpleCPU::drain(DrainManager *drain_manager)
|
||||||
void
|
void
|
||||||
TimingSimpleCPU::drainResume()
|
TimingSimpleCPU::drainResume()
|
||||||
{
|
{
|
||||||
|
assert(!fetchEvent.scheduled());
|
||||||
|
|
||||||
DPRINTF(SimpleCPU, "Resume\n");
|
DPRINTF(SimpleCPU, "Resume\n");
|
||||||
if (_status != SwitchedOut && _status != Idle) {
|
if (_status != SwitchedOut && _status != Idle) {
|
||||||
if (system->getMemoryMode() != Enums::timing) {
|
if (system->getMemoryMode() != Enums::timing) {
|
||||||
|
@ -151,13 +143,25 @@ TimingSimpleCPU::drainResume()
|
||||||
"'timing' mode.\n");
|
"'timing' mode.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fetchEvent.scheduled())
|
|
||||||
deschedule(fetchEvent);
|
|
||||||
|
|
||||||
schedule(fetchEvent, nextCycle());
|
schedule(fetchEvent, nextCycle());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setDrainState(Drainable::Running);
|
bool
|
||||||
|
TimingSimpleCPU::tryCompleteDrain()
|
||||||
|
{
|
||||||
|
if (!drainManager)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DPRINTF(Drain, "tryCompleteDrain: %s\n", pcState());
|
||||||
|
if (!isDrained())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DPRINTF(Drain, "CPU done draining, processing drain event\n");
|
||||||
|
drainManager->signalDrainDone();
|
||||||
|
drainManager = NULL;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -165,14 +169,13 @@ TimingSimpleCPU::switchOut()
|
||||||
{
|
{
|
||||||
BaseSimpleCPU::switchOut();
|
BaseSimpleCPU::switchOut();
|
||||||
|
|
||||||
|
assert(!fetchEvent.scheduled());
|
||||||
assert(_status == BaseSimpleCPU::Running || _status == Idle);
|
assert(_status == BaseSimpleCPU::Running || _status == Idle);
|
||||||
|
assert(!stayAtPC);
|
||||||
|
assert(microPC() == 0);
|
||||||
|
|
||||||
_status = SwitchedOut;
|
_status = SwitchedOut;
|
||||||
numCycles += curCycle() - previousCycle;
|
numCycles += curCycle() - previousCycle;
|
||||||
|
|
||||||
// If we've been scheduled to resume but are then told to switch out,
|
|
||||||
// we'll need to cancel it.
|
|
||||||
if (fetchEvent.scheduled())
|
|
||||||
deschedule(fetchEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,12 +347,7 @@ TimingSimpleCPU::translationFault(Fault fault)
|
||||||
|
|
||||||
postExecute();
|
postExecute();
|
||||||
|
|
||||||
if (getDrainState() == Drainable::Draining) {
|
advanceInst(fault);
|
||||||
advancePC(fault);
|
|
||||||
completeDrain();
|
|
||||||
} else {
|
|
||||||
advanceInst(fault);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -618,7 +616,6 @@ TimingSimpleCPU::sendFetch(Fault fault, RequestPtr req, ThreadContext *tc)
|
||||||
void
|
void
|
||||||
TimingSimpleCPU::advanceInst(Fault fault)
|
TimingSimpleCPU::advanceInst(Fault fault)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (_status == Faulting)
|
if (_status == Faulting)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -634,6 +631,9 @@ TimingSimpleCPU::advanceInst(Fault fault)
|
||||||
if (!stayAtPC)
|
if (!stayAtPC)
|
||||||
advancePC(fault);
|
advancePC(fault);
|
||||||
|
|
||||||
|
if (tryCompleteDrain())
|
||||||
|
return;
|
||||||
|
|
||||||
if (_status == BaseSimpleCPU::Running) {
|
if (_status == BaseSimpleCPU::Running) {
|
||||||
// kick off fetch of next instruction... callback from icache
|
// kick off fetch of next instruction... callback from icache
|
||||||
// response will cause that instruction to be executed,
|
// response will cause that instruction to be executed,
|
||||||
|
@ -660,16 +660,6 @@ TimingSimpleCPU::completeIfetch(PacketPtr pkt)
|
||||||
numCycles += curCycle() - previousCycle;
|
numCycles += curCycle() - previousCycle;
|
||||||
previousCycle = curCycle();
|
previousCycle = curCycle();
|
||||||
|
|
||||||
if (getDrainState() == Drainable::Draining) {
|
|
||||||
if (pkt) {
|
|
||||||
delete pkt->req;
|
|
||||||
delete pkt;
|
|
||||||
}
|
|
||||||
|
|
||||||
completeDrain();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
preExecute();
|
preExecute();
|
||||||
if (curStaticInst && curStaticInst->isMemRef()) {
|
if (curStaticInst && curStaticInst->isMemRef()) {
|
||||||
// load or store: just send to dcache
|
// load or store: just send to dcache
|
||||||
|
@ -816,25 +806,9 @@ TimingSimpleCPU::completeDataAccess(PacketPtr pkt)
|
||||||
|
|
||||||
postExecute();
|
postExecute();
|
||||||
|
|
||||||
if (getDrainState() == Drainable::Draining) {
|
|
||||||
advancePC(fault);
|
|
||||||
completeDrain();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
advanceInst(fault);
|
advanceInst(fault);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
TimingSimpleCPU::completeDrain()
|
|
||||||
{
|
|
||||||
DPRINTF(Drain, "CPU done draining, processing drain event\n");
|
|
||||||
setDrainState(Drainable::Drained);
|
|
||||||
drainManager->signalDrainDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TimingSimpleCPU::DcachePort::recvTimingResp(PacketPtr pkt)
|
TimingSimpleCPU::DcachePort::recvTimingResp(PacketPtr pkt)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2012 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) 2002-2005 The Regents of The University of Michigan
|
* Copyright (c) 2002-2005 The Regents of The University of Michigan
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -44,9 +56,6 @@ class TimingSimpleCPU : public BaseSimpleCPU
|
||||||
|
|
||||||
virtual void init();
|
virtual void init();
|
||||||
|
|
||||||
public:
|
|
||||||
DrainManager *drainManager;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -246,9 +255,6 @@ class TimingSimpleCPU : public BaseSimpleCPU
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
virtual void serialize(std::ostream &os);
|
|
||||||
virtual void unserialize(Checkpoint *cp, const std::string §ion);
|
|
||||||
|
|
||||||
unsigned int drain(DrainManager *drain_manager);
|
unsigned int drain(DrainManager *drain_manager);
|
||||||
void drainResume();
|
void drainResume();
|
||||||
|
|
||||||
|
@ -302,7 +308,36 @@ class TimingSimpleCPU : public BaseSimpleCPU
|
||||||
virtual const char *description() const;
|
virtual const char *description() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
void completeDrain();
|
/**
|
||||||
|
* Check if a system is in a drained state.
|
||||||
|
*
|
||||||
|
* We need to drain if:
|
||||||
|
* <ul>
|
||||||
|
* <li>We are in the middle of a microcode sequence as some CPUs
|
||||||
|
* (e.g., HW accelerated CPUs) can't be started in the middle
|
||||||
|
* of a gem5 microcode sequence.
|
||||||
|
*
|
||||||
|
* <li>Stay at PC is true.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
bool isDrained() {
|
||||||
|
return microPC() == 0 &&
|
||||||
|
!stayAtPC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to complete a drain request.
|
||||||
|
*
|
||||||
|
* @returns true if the CPU is drained, false otherwise.
|
||||||
|
*/
|
||||||
|
bool tryCompleteDrain();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drain manager to use when signaling drain completion
|
||||||
|
*
|
||||||
|
* This pointer is non-NULL when draining and NULL otherwise.
|
||||||
|
*/
|
||||||
|
DrainManager *drainManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // __CPU_SIMPLE_TIMING_HH__
|
#endif // __CPU_SIMPLE_TIMING_HH__
|
||||||
|
|
Loading…
Reference in a new issue