gem5/src/cpu/o3/mem_dep_unit_impl.hh
Andreas Sandberg 1814a85a05 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.
2013-01-07 13:05:46 -05:00

597 lines
18 KiB
C++

/*
* 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) 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.
*
* Authors: Kevin Lim
*/
#include <map>
#include "cpu/o3/inst_queue.hh"
#include "cpu/o3/mem_dep_unit.hh"
#include "debug/MemDepUnit.hh"
#include "params/DerivO3CPU.hh"
template <class MemDepPred, class Impl>
MemDepUnit<MemDepPred, Impl>::MemDepUnit()
: loadBarrier(false), loadBarrierSN(0), storeBarrier(false),
storeBarrierSN(0), iqPtr(NULL)
{
}
template <class MemDepPred, class Impl>
MemDepUnit<MemDepPred, Impl>::MemDepUnit(DerivO3CPUParams *params)
: _name(params->name + ".memdepunit"),
depPred(params->store_set_clear_period, params->SSITSize,
params->LFSTSize),
loadBarrier(false), loadBarrierSN(0), storeBarrier(false),
storeBarrierSN(0), iqPtr(NULL)
{
DPRINTF(MemDepUnit, "Creating MemDepUnit object.\n");
}
template <class MemDepPred, class Impl>
MemDepUnit<MemDepPred, Impl>::~MemDepUnit()
{
for (ThreadID tid = 0; tid < Impl::MaxThreads; tid++) {
ListIt inst_list_it = instList[tid].begin();
MemDepHashIt hash_it;
while (!instList[tid].empty()) {
hash_it = memDepHash.find((*inst_list_it)->seqNum);
assert(hash_it != memDepHash.end());
memDepHash.erase(hash_it);
instList[tid].erase(inst_list_it++);
}
}
#ifdef DEBUG
assert(MemDepEntry::memdep_count == 0);
#endif
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::init(DerivO3CPUParams *params, ThreadID tid)
{
DPRINTF(MemDepUnit, "Creating MemDepUnit %i object.\n",tid);
_name = csprintf("%s.memDep%d", params->name, tid);
id = tid;
depPred.init(params->store_set_clear_period, params->SSITSize,
params->LFSTSize);
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::regStats()
{
insertedLoads
.name(name() + ".insertedLoads")
.desc("Number of loads inserted to the mem dependence unit.");
insertedStores
.name(name() + ".insertedStores")
.desc("Number of stores inserted to the mem dependence unit.");
conflictingLoads
.name(name() + ".conflictingLoads")
.desc("Number of conflicting loads.");
conflictingStores
.name(name() + ".conflictingStores")
.desc("Number of conflicting stores.");
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::drainSanityCheck() const
{
assert(instsToReplay.empty());
assert(memDepHash.empty());
for (int i = 0; i < Impl::MaxThreads; ++i)
assert(instList[i].empty());
assert(instsToReplay.empty());
assert(memDepHash.empty());
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::takeOverFrom()
{
// Be sure to reset all state.
loadBarrier = storeBarrier = false;
loadBarrierSN = storeBarrierSN = 0;
depPred.clear();
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::setIQ(InstructionQueue<Impl> *iq_ptr)
{
iqPtr = iq_ptr;
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::insert(DynInstPtr &inst)
{
ThreadID tid = inst->threadNumber;
MemDepEntryPtr inst_entry = new MemDepEntry(inst);
// Add the MemDepEntry to the hash.
memDepHash.insert(
std::pair<InstSeqNum, MemDepEntryPtr>(inst->seqNum, inst_entry));
#ifdef DEBUG
MemDepEntry::memdep_insert++;
#endif
instList[tid].push_back(inst);
inst_entry->listIt = --(instList[tid].end());
// Check any barriers and the dependence predictor for any
// producing memrefs/stores.
InstSeqNum producing_store;
if (inst->isLoad() && loadBarrier) {
DPRINTF(MemDepUnit, "Load barrier [sn:%lli] in flight\n",
loadBarrierSN);
producing_store = loadBarrierSN;
} else if (inst->isStore() && storeBarrier) {
DPRINTF(MemDepUnit, "Store barrier [sn:%lli] in flight\n",
storeBarrierSN);
producing_store = storeBarrierSN;
} else {
producing_store = depPred.checkInst(inst->instAddr());
}
MemDepEntryPtr store_entry = NULL;
// If there is a producing store, try to find the entry.
if (producing_store != 0) {
DPRINTF(MemDepUnit, "Searching for producer\n");
MemDepHashIt hash_it = memDepHash.find(producing_store);
if (hash_it != memDepHash.end()) {
store_entry = (*hash_it).second;
DPRINTF(MemDepUnit, "Proucer found\n");
}
}
// If no store entry, then instruction can issue as soon as the registers
// are ready.
if (!store_entry) {
DPRINTF(MemDepUnit, "No dependency for inst PC "
"%s [sn:%lli].\n", inst->pcState(), inst->seqNum);
inst_entry->memDepReady = true;
if (inst->readyToIssue()) {
inst_entry->regsReady = true;
moveToReady(inst_entry);
}
} else {
// Otherwise make the instruction dependent on the store/barrier.
DPRINTF(MemDepUnit, "Adding to dependency list; "
"inst PC %s is dependent on [sn:%lli].\n",
inst->pcState(), producing_store);
if (inst->readyToIssue()) {
inst_entry->regsReady = true;
}
// Clear the bit saying this instruction can issue.
inst->clearCanIssue();
// Add this instruction to the list of dependents.
store_entry->dependInsts.push_back(inst_entry);
if (inst->isLoad()) {
++conflictingLoads;
} else {
++conflictingStores;
}
}
if (inst->isStore()) {
DPRINTF(MemDepUnit, "Inserting store PC %s [sn:%lli].\n",
inst->pcState(), inst->seqNum);
depPred.insertStore(inst->instAddr(), inst->seqNum, inst->threadNumber);
++insertedStores;
} else if (inst->isLoad()) {
++insertedLoads;
} else {
panic("Unknown type! (most likely a barrier).");
}
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::insertNonSpec(DynInstPtr &inst)
{
ThreadID tid = inst->threadNumber;
MemDepEntryPtr inst_entry = new MemDepEntry(inst);
// Insert the MemDepEntry into the hash.
memDepHash.insert(
std::pair<InstSeqNum, MemDepEntryPtr>(inst->seqNum, inst_entry));
#ifdef DEBUG
MemDepEntry::memdep_insert++;
#endif
// Add the instruction to the list.
instList[tid].push_back(inst);
inst_entry->listIt = --(instList[tid].end());
// Might want to turn this part into an inline function or something.
// It's shared between both insert functions.
if (inst->isStore()) {
DPRINTF(MemDepUnit, "Inserting store PC %s [sn:%lli].\n",
inst->pcState(), inst->seqNum);
depPred.insertStore(inst->instAddr(), inst->seqNum, inst->threadNumber);
++insertedStores;
} else if (inst->isLoad()) {
++insertedLoads;
} else {
panic("Unknown type! (most likely a barrier).");
}
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::insertBarrier(DynInstPtr &barr_inst)
{
InstSeqNum barr_sn = barr_inst->seqNum;
// Memory barriers block loads and stores, write barriers only stores.
if (barr_inst->isMemBarrier()) {
loadBarrier = true;
loadBarrierSN = barr_sn;
storeBarrier = true;
storeBarrierSN = barr_sn;
DPRINTF(MemDepUnit, "Inserted a memory barrier %s SN:%lli\n",
barr_inst->pcState(),barr_sn);
} else if (barr_inst->isWriteBarrier()) {
storeBarrier = true;
storeBarrierSN = barr_sn;
DPRINTF(MemDepUnit, "Inserted a write barrier\n");
}
ThreadID tid = barr_inst->threadNumber;
MemDepEntryPtr inst_entry = new MemDepEntry(barr_inst);
// Add the MemDepEntry to the hash.
memDepHash.insert(
std::pair<InstSeqNum, MemDepEntryPtr>(barr_sn, inst_entry));
#ifdef DEBUG
MemDepEntry::memdep_insert++;
#endif
// Add the instruction to the instruction list.
instList[tid].push_back(barr_inst);
inst_entry->listIt = --(instList[tid].end());
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::regsReady(DynInstPtr &inst)
{
DPRINTF(MemDepUnit, "Marking registers as ready for "
"instruction PC %s [sn:%lli].\n",
inst->pcState(), inst->seqNum);
MemDepEntryPtr inst_entry = findInHash(inst);
inst_entry->regsReady = true;
if (inst_entry->memDepReady) {
DPRINTF(MemDepUnit, "Instruction has its memory "
"dependencies resolved, adding it to the ready list.\n");
moveToReady(inst_entry);
} else {
DPRINTF(MemDepUnit, "Instruction still waiting on "
"memory dependency.\n");
}
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::nonSpecInstReady(DynInstPtr &inst)
{
DPRINTF(MemDepUnit, "Marking non speculative "
"instruction PC %s as ready [sn:%lli].\n",
inst->pcState(), inst->seqNum);
MemDepEntryPtr inst_entry = findInHash(inst);
moveToReady(inst_entry);
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::reschedule(DynInstPtr &inst)
{
instsToReplay.push_back(inst);
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::replay(DynInstPtr &inst)
{
DynInstPtr temp_inst;
// For now this replay function replays all waiting memory ops.
while (!instsToReplay.empty()) {
temp_inst = instsToReplay.front();
MemDepEntryPtr inst_entry = findInHash(temp_inst);
DPRINTF(MemDepUnit, "Replaying mem instruction PC %s [sn:%lli].\n",
temp_inst->pcState(), temp_inst->seqNum);
moveToReady(inst_entry);
instsToReplay.pop_front();
}
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::completed(DynInstPtr &inst)
{
DPRINTF(MemDepUnit, "Completed mem instruction PC %s [sn:%lli].\n",
inst->pcState(), inst->seqNum);
ThreadID tid = inst->threadNumber;
// Remove the instruction from the hash and the list.
MemDepHashIt hash_it = memDepHash.find(inst->seqNum);
assert(hash_it != memDepHash.end());
instList[tid].erase((*hash_it).second->listIt);
(*hash_it).second = NULL;
memDepHash.erase(hash_it);
#ifdef DEBUG
MemDepEntry::memdep_erase++;
#endif
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::completeBarrier(DynInstPtr &inst)
{
wakeDependents(inst);
completed(inst);
InstSeqNum barr_sn = inst->seqNum;
DPRINTF(MemDepUnit, "barrier completed: %s SN:%lli\n", inst->pcState(),
inst->seqNum);
if (inst->isMemBarrier()) {
if (loadBarrierSN == barr_sn)
loadBarrier = false;
if (storeBarrierSN == barr_sn)
storeBarrier = false;
} else if (inst->isWriteBarrier()) {
if (storeBarrierSN == barr_sn)
storeBarrier = false;
}
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::wakeDependents(DynInstPtr &inst)
{
// Only stores and barriers have dependents.
if (!inst->isStore() && !inst->isMemBarrier() && !inst->isWriteBarrier()) {
return;
}
MemDepEntryPtr inst_entry = findInHash(inst);
for (int i = 0; i < inst_entry->dependInsts.size(); ++i ) {
MemDepEntryPtr woken_inst = inst_entry->dependInsts[i];
if (!woken_inst->inst) {
// Potentially removed mem dep entries could be on this list
continue;
}
DPRINTF(MemDepUnit, "Waking up a dependent inst, "
"[sn:%lli].\n",
woken_inst->inst->seqNum);
if (woken_inst->regsReady && !woken_inst->squashed) {
moveToReady(woken_inst);
} else {
woken_inst->memDepReady = true;
}
}
inst_entry->dependInsts.clear();
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::squash(const InstSeqNum &squashed_num,
ThreadID tid)
{
if (!instsToReplay.empty()) {
ListIt replay_it = instsToReplay.begin();
while (replay_it != instsToReplay.end()) {
if ((*replay_it)->threadNumber == tid &&
(*replay_it)->seqNum > squashed_num) {
instsToReplay.erase(replay_it++);
} else {
++replay_it;
}
}
}
ListIt squash_it = instList[tid].end();
--squash_it;
MemDepHashIt hash_it;
while (!instList[tid].empty() &&
(*squash_it)->seqNum > squashed_num) {
DPRINTF(MemDepUnit, "Squashing inst [sn:%lli]\n",
(*squash_it)->seqNum);
if ((*squash_it)->seqNum == loadBarrierSN)
loadBarrier = false;
if ((*squash_it)->seqNum == storeBarrierSN)
storeBarrier = false;
hash_it = memDepHash.find((*squash_it)->seqNum);
assert(hash_it != memDepHash.end());
(*hash_it).second->squashed = true;
(*hash_it).second = NULL;
memDepHash.erase(hash_it);
#ifdef DEBUG
MemDepEntry::memdep_erase++;
#endif
instList[tid].erase(squash_it--);
}
// Tell the dependency predictor to squash as well.
depPred.squash(squashed_num, tid);
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::violation(DynInstPtr &store_inst,
DynInstPtr &violating_load)
{
DPRINTF(MemDepUnit, "Passing violating PCs to store sets,"
" load: %#x, store: %#x\n", violating_load->instAddr(),
store_inst->instAddr());
// Tell the memory dependence unit of the violation.
depPred.violation(store_inst->instAddr(), violating_load->instAddr());
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::issue(DynInstPtr &inst)
{
DPRINTF(MemDepUnit, "Issuing instruction PC %#x [sn:%lli].\n",
inst->instAddr(), inst->seqNum);
depPred.issued(inst->instAddr(), inst->seqNum, inst->isStore());
}
template <class MemDepPred, class Impl>
inline typename MemDepUnit<MemDepPred,Impl>::MemDepEntryPtr &
MemDepUnit<MemDepPred, Impl>::findInHash(const DynInstPtr &inst)
{
MemDepHashIt hash_it = memDepHash.find(inst->seqNum);
assert(hash_it != memDepHash.end());
return (*hash_it).second;
}
template <class MemDepPred, class Impl>
inline void
MemDepUnit<MemDepPred, Impl>::moveToReady(MemDepEntryPtr &woken_inst_entry)
{
DPRINTF(MemDepUnit, "Adding instruction [sn:%lli] "
"to the ready list.\n", woken_inst_entry->inst->seqNum);
assert(!woken_inst_entry->squashed);
iqPtr->addReadyMemInst(woken_inst_entry->inst);
}
template <class MemDepPred, class Impl>
void
MemDepUnit<MemDepPred, Impl>::dumpLists()
{
for (ThreadID tid = 0; tid < Impl::MaxThreads; tid++) {
cprintf("Instruction list %i size: %i\n",
tid, instList[tid].size());
ListIt inst_list_it = instList[tid].begin();
int num = 0;
while (inst_list_it != instList[tid].end()) {
cprintf("Instruction:%i\nPC: %s\n[sn:%i]\n[tid:%i]\nIssued:%i\n"
"Squashed:%i\n\n",
num, (*inst_list_it)->pcState(),
(*inst_list_it)->seqNum,
(*inst_list_it)->threadNumber,
(*inst_list_it)->isIssued(),
(*inst_list_it)->isSquashed());
inst_list_it++;
++num;
}
}
cprintf("Memory dependence hash size: %i\n", memDepHash.size());
#ifdef DEBUG
cprintf("Memory dependence entries: %i\n", MemDepEntry::memdep_count);
#endif
}