| /* | |
| * Copyright (c) 2017-2021 Advanced Micro Devices, Inc. | |
| * All rights reserved. | |
| * | |
| * 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. | |
| */ | |
| #include "cpu/testers/gpu_ruby_test/tester_thread.hh" | |
| #include <fstream> | |
| #include "debug/ProtocolTest.hh" | |
| namespace gem5 | |
| { | |
| TesterThread::TesterThread(const Params &p) | |
| : ClockedObject(p), | |
| threadEvent(this, "TesterThread tick"), | |
| deadlockCheckEvent(this), | |
| threadId(p.thread_id), | |
| numLanes(p.num_lanes), | |
| tester(nullptr), addrManager(nullptr), port(nullptr), | |
| scalarPort(nullptr), sqcPort(nullptr), curEpisode(nullptr), | |
| curAction(nullptr), pendingLdStCount(0), pendingFenceCount(0), | |
| pendingAtomicCount(0), lastActiveCycle(Cycles(0)), | |
| deadlockThreshold(p.deadlock_threshold) | |
| { | |
| } | |
| TesterThread::~TesterThread() | |
| { | |
| for (auto ep : episodeHistory) { | |
| assert(ep != nullptr); | |
| delete ep; | |
| } | |
| } | |
| void | |
| TesterThread::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 | |
| TesterThread::scheduleWakeup() | |
| { | |
| assert(!threadEvent.scheduled()); | |
| schedule(threadEvent, nextCycle()); | |
| } | |
| void | |
| TesterThread::scheduleDeadlockCheckEvent() | |
| { | |
| // after this first schedule, the deadlock event is scheduled by itself | |
| assert(!deadlockCheckEvent.scheduled()); | |
| schedule(deadlockCheckEvent, nextCycle()); | |
| } | |
| void | |
| TesterThread::attachTesterThreadToPorts(ProtocolTester *_tester, | |
| ProtocolTester::SeqPort *_port, | |
| ProtocolTester::GMTokenPort *_tokenPort, | |
| ProtocolTester::SeqPort *_scalarPort, | |
| ProtocolTester::SeqPort *_sqcPort) | |
| { | |
| tester = _tester; | |
| port = _port; | |
| tokenPort = _tokenPort; | |
| scalarPort = _scalarPort; | |
| sqcPort = _sqcPort; | |
| assert(tester && port); | |
| addrManager = tester->getAddressManager(); | |
| assert(addrManager); | |
| } | |
| void | |
| TesterThread::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); | |
| } | |
| int | |
| TesterThread::getTokensNeeded() | |
| { | |
| if (!tokenPort) { | |
| return 0; | |
| } | |
| int tokens_needed = 0; | |
| curAction = curEpisode->peekCurAction(); | |
| switch(curAction->getType()) { | |
| case Episode::Action::Type::ATOMIC: | |
| tokens_needed = numLanes; | |
| break; | |
| case Episode::Action::Type::LOAD: | |
| case Episode::Action::Type::STORE: | |
| for (int lane = 0; lane < numLanes; ++lane) { | |
| Location loc = curAction->getLocation(lane); | |
| if (loc != AddressManager::INVALID_LOCATION && loc >= 0) { | |
| tokens_needed++; | |
| } | |
| } | |
| break; | |
| default: | |
| tokens_needed = 0; | |
| } | |
| return tokens_needed; | |
| } | |
| bool | |
| TesterThread::isNextActionReady() | |
| { | |
| if (!curEpisode->hasMoreActions()) { | |
| return false; | |
| } else { | |
| curAction = curEpisode->peekCurAction(); | |
| // Only GPU wavefront threads have a token port. For all other types | |
| // of threads evaluate to true. | |
| bool haveTokens = true; | |
| switch(curAction->getType()) { | |
| case Episode::Action::Type::ATOMIC: | |
| haveTokens = tokenPort ? | |
| tokenPort->haveTokens(getTokensNeeded()) : true; | |
| // an atomic action must wait for all previous requests | |
| // to complete | |
| if (pendingLdStCount == 0 && | |
| pendingFenceCount == 0 && | |
| pendingAtomicCount == 0 && | |
| haveTokens) { | |
| 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 && loc >= 0) { | |
| 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); | |
| } | |
| } | |
| } | |
| haveTokens = tokenPort ? | |
| tokenPort->haveTokens(getTokensNeeded()) : true; | |
| if (!haveTokens) { | |
| return false; | |
| } | |
| return true; | |
| default: | |
| panic("The tester got an invalid action\n"); | |
| } | |
| } | |
| } | |
| void | |
| TesterThread::issueNextAction() | |
| { | |
| switch(curAction->getType()) { | |
| case Episode::Action::Type::ATOMIC: | |
| if (tokenPort) { | |
| tokenPort->acquireTokens(getTokensNeeded()); | |
| } | |
| issueAtomicOps(); | |
| break; | |
| case Episode::Action::Type::ACQUIRE: | |
| issueAcquireOp(); | |
| break; | |
| case Episode::Action::Type::RELEASE: | |
| issueReleaseOp(); | |
| break; | |
| case Episode::Action::Type::LOAD: | |
| if (tokenPort) { | |
| tokenPort->acquireTokens(getTokensNeeded()); | |
| } | |
| issueLoadOps(); | |
| break; | |
| case Episode::Action::Type::STORE: | |
| if (tokenPort) { | |
| tokenPort->acquireTokens(getTokensNeeded()); | |
| } | |
| 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 | |
| TesterThread::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); | |
| } | |
| } | |
| TesterThread::OutstandingReq | |
| TesterThread::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 | |
| TesterThread::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 " << ruby::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 | |
| TesterThread::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" | |
| << "\tTesterThread " << threadId << "\n" | |
| << "\tEpisode " << curEpisode->getEpisodeId() << "\n" | |
| << "\tLane ID " << lane << "\n" | |
| << "\tAddress " << ruby::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 | |
| TesterThread::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 | |
| TesterThread::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 | |
| TesterThread::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 " << ruby::printAddress(m.first) | |
| << ": delta (curCycle - issueCycle) = " | |
| << (cur_cycle - req.issueCycle) << std::endl; | |
| } | |
| } | |
| } | |
| void | |
| TesterThread::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; | |
| } | |
| } // namespace gem5 |