blob: b499c9be8ac4681931300862b2aa808bf34a0f20 [file] [log] [blame]
/*
* Copyright (c) 2017 Advanced Micro Devices, Inc.
* All rights reserved.
*
* For use for simulation and test purposes only
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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 HOLDER 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: Tuan Ta
*/
#include "cpu/testers/gpu_ruby_test/Thread.hh"
#include <fstream>
#include "debug/ProtocolTest.hh"
Thread::Thread(const Params* p)
: ClockedObject(p),
threadEvent(this, "Thread tick"),
deadlockCheckEvent(this),
threadId(p->thread_id),
numLanes(p->num_lanes),
deadlockThreshold(p->deadlock_threshold)
{
tester = nullptr; // set by attachThreadToPorts()
addrManager = nullptr; // set by attachThreadToPorts()
port = nullptr; // set by attachThreadToPorts()
scalarPort = nullptr; // set by attachThreadToPorts()
sqcPort = nullptr; // set by attachThreadToPorts()
curEpisode = nullptr;
curAction = nullptr;
pendingLdStCount = 0;
pendingFenceCount = 0;
pendingAtomicCount = 0;
lastActiveCycle = Cycles(0);
}
Thread::~Thread()
{
for (auto ep : episodeHistory) {
assert(ep != nullptr);
delete ep;
}
}
void
Thread::wakeup()
{
// this thread is waken up by one of the following events
// - hitCallback is called
// - a new episode is created
// check if this is the first episode in this thread
if (curEpisode == nullptr) {
issueNewEpisode();
assert(curEpisode);
}
if (isNextActionReady()) {
// isNextActionReady should check if the action list is empty
assert(curAction != nullptr);
// issue the next action
issueNextAction();
} else {
// check for completion of the current episode
// completion = no outstanding requests + not having more actions
if (!curEpisode->hasMoreActions() &&
pendingLdStCount == 0 &&
pendingFenceCount == 0 &&
pendingAtomicCount == 0) {
curEpisode->completeEpisode();
// check if it's time to stop the tester
if (tester->checkExit()) {
// no more event is scheduled for this thread
return;
}
// issue the next episode
issueNewEpisode();
assert(curEpisode);
// now we get a new episode
// let's wake up the thread in the next cycle
if (!threadEvent.scheduled()) {
scheduleWakeup();
}
}
}
}
void
Thread::scheduleWakeup()
{
assert(!threadEvent.scheduled());
schedule(threadEvent, nextCycle());
}
void
Thread::scheduleDeadlockCheckEvent()
{
// after this first schedule, the deadlock event is scheduled by itself
assert(!deadlockCheckEvent.scheduled());
schedule(deadlockCheckEvent, nextCycle());
}
void
Thread::attachThreadToPorts(ProtocolTester *_tester,
ProtocolTester::SeqPort *_port,
ProtocolTester::SeqPort *_scalarPort,
ProtocolTester::SeqPort *_sqcPort)
{
tester = _tester;
port = _port;
scalarPort = _scalarPort;
sqcPort = _sqcPort;
assert(tester && port);
addrManager = tester->getAddressManager();
assert(addrManager);
}
void
Thread::issueNewEpisode()
{
int num_reg_loads = random() % tester->getEpisodeLength();
int num_reg_stores = tester->getEpisodeLength() - num_reg_loads;
// create a new episode
curEpisode = new Episode(tester, this, num_reg_loads, num_reg_stores);
episodeHistory.push_back(curEpisode);
}
bool
Thread::isNextActionReady()
{
if (!curEpisode->hasMoreActions()) {
return false;
} else {
curAction = curEpisode->peekCurAction();
switch(curAction->getType()) {
case Episode::Action::Type::ATOMIC:
// an atomic action must wait for all previous requests
// to complete
if (pendingLdStCount == 0 &&
pendingFenceCount == 0 &&
pendingAtomicCount == 0) {
return true;
}
return false;
case Episode::Action::Type::ACQUIRE:
// we should not see any outstanding ld_st or fence here
assert(pendingLdStCount == 0 &&
pendingFenceCount == 0);
// an acquire action must wait for all previous atomic
// requests to complete
if (pendingAtomicCount == 0) {
return true;
}
return false;
case Episode::Action::Type::RELEASE:
// we should not see any outstanding atomic or fence here
assert(pendingAtomicCount == 0 &&
pendingFenceCount == 0);
// a release action must wait for all previous ld/st
// requests to complete
if (pendingLdStCount == 0) {
return true;
}
return false;
case Episode::Action::Type::LOAD:
case Episode::Action::Type::STORE:
// we should not see any outstanding atomic here
assert(pendingAtomicCount == 0);
// can't issue if there is a pending fence
if (pendingFenceCount > 0) {
return false;
}
// a Load or Store is ready if it doesn't overlap
// with any outstanding request
for (int lane = 0; lane < numLanes; ++lane) {
Location loc = curAction->getLocation(lane);
if (loc != AddressManager::INVALID_LOCATION) {
Addr addr = addrManager->getAddress(loc);
if (outstandingLoads.find(addr) !=
outstandingLoads.end()) {
return false;
}
if (outstandingStores.find(addr) !=
outstandingStores.end()) {
return false;
}
if (outstandingAtomics.find(addr) !=
outstandingAtomics.end()) {
// this is not an atomic action, so the address
// should not be in outstandingAtomics list
assert(false);
}
}
}
return true;
default:
panic("The tester got an invalid action\n");
}
}
}
void
Thread::issueNextAction()
{
switch(curAction->getType()) {
case Episode::Action::Type::ATOMIC:
issueAtomicOps();
break;
case Episode::Action::Type::ACQUIRE:
issueAcquireOp();
break;
case Episode::Action::Type::RELEASE:
issueReleaseOp();
break;
case Episode::Action::Type::LOAD:
issueLoadOps();
break;
case Episode::Action::Type::STORE:
issueStoreOps();
break;
default:
panic("The tester got an invalid action\n");
}
// the current action has been issued, pop it from the action list
curEpisode->popAction();
lastActiveCycle = curCycle();
// we may be able to schedule the next action
// just wake up this thread in the next cycle
if (!threadEvent.scheduled()) {
scheduleWakeup();
}
}
void
Thread::addOutstandingReqs(OutstandingReqTable& req_table, Addr address,
int lane, Location loc, Value stored_val)
{
OutstandingReqTable::iterator it = req_table.find(address);
OutstandingReq req(lane, loc, stored_val, curCycle());
if (it == req_table.end()) {
// insert a new list of requests for this address
req_table.insert(std::pair<Addr, OutstandingReqList>(address,
OutstandingReqList(1, req)));
} else {
// add a new request
(it->second).push_back(req);
}
}
Thread::OutstandingReq
Thread::popOutstandingReq(OutstandingReqTable& req_table, Addr addr)
{
OutstandingReqTable::iterator it = req_table.find(addr);
// there must be exactly one list of requests for this address in the table
assert(it != req_table.end());
// get the request list
OutstandingReqList& req_list = it->second;
assert(!req_list.empty());
// save a request
OutstandingReq ret_req = req_list.back();
// remove the request from the list
req_list.pop_back();
// if the list is now empty, remove it from req_table
if (req_list.empty()) {
req_table.erase(it);
}
return ret_req;
}
void
Thread::validateAtomicResp(Location loc, int lane, Value ret_val)
{
if (!addrManager->validateAtomicResp(loc, ret_val)) {
std::stringstream ss;
Addr addr = addrManager->getAddress(loc);
// basic info
ss << threadName << ": Atomic Op returned unexpected value\n"
<< "\tEpisode " << curEpisode->getEpisodeId() << "\n"
<< "\tLane ID " << lane << "\n"
<< "\tAddress " << printAddress(addr) << "\n"
<< "\tAtomic Op's return value " << ret_val << "\n";
// print out basic info
warn("%s\n", ss.str());
// TODO add more detailed info
// dump all error info and exit the simulation
tester->dumpErrorLog(ss);
}
}
void
Thread::validateLoadResp(Location loc, int lane, Value ret_val)
{
if (ret_val != addrManager->getLoggedValue(loc)) {
std::stringstream ss;
Addr addr = addrManager->getAddress(loc);
// basic info
ss << threadName << ": Loaded value is not consistent with "
<< "the last stored value\n"
<< "\tThread " << threadId << "\n"
<< "\tEpisode " << curEpisode->getEpisodeId() << "\n"
<< "\tLane ID " << lane << "\n"
<< "\tAddress " << printAddress(addr) << "\n"
<< "\tLoaded value " << ret_val << "\n"
<< "\tLast writer " << addrManager->printLastWriter(loc) << "\n";
// print out basic info
warn("%s\n", ss.str());
// TODO add more detailed info
// dump all error info and exit the simulation
tester->dumpErrorLog(ss);
}
}
bool
Thread::checkDRF(Location atomic_loc, Location loc, bool isStore) const
{
if (curEpisode && curEpisode->isEpsActive()) {
// check against the current episode this thread is executing
return curEpisode->checkDRF(atomic_loc, loc, isStore, numLanes);
}
return true;
}
void
Thread::checkDeadlock()
{
if ((curCycle() - lastActiveCycle) > deadlockThreshold) {
// deadlock detected
std::stringstream ss;
ss << threadName << ": Deadlock detected\n"
<< "\tLast active cycle: " << lastActiveCycle << "\n"
<< "\tCurrent cycle: " << curCycle() << "\n"
<< "\tDeadlock threshold: " << deadlockThreshold << "\n";
// print out basic info
warn("%s\n", ss.str());
// dump all error info and exit the simulation
tester->dumpErrorLog(ss);
} else if (!tester->checkExit()) {
// schedule a future deadlock check event
assert(!deadlockCheckEvent.scheduled());
schedule(deadlockCheckEvent,
deadlockThreshold * clockPeriod() + curTick());
}
}
void
Thread::printOutstandingReqs(const OutstandingReqTable& table,
std::stringstream& ss) const
{
Cycles cur_cycle = curCycle();
for (const auto& m : table) {
for (const auto& req : m.second) {
ss << "\t\t\tAddr " << printAddress(m.first)
<< ": delta (curCycle - issueCycle) = "
<< (cur_cycle - req.issueCycle) << std::endl;
}
}
}
void
Thread::printAllOutstandingReqs(std::stringstream& ss) const
{
// dump all outstanding requests of this thread
ss << "\t\tOutstanding Loads:\n";
printOutstandingReqs(outstandingLoads, ss);
ss << "\t\tOutstanding Stores:\n";
printOutstandingReqs(outstandingStores, ss);
ss << "\t\tOutstanding Atomics:\n";
printOutstandingReqs(outstandingAtomics, ss);
ss << "\t\tNumber of outstanding acquires & releases: "
<< pendingFenceCount << std::endl;
}