| /* |
| * 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); |
| } |
| |
| 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 = tokenPort ? tokenPort->haveTokens(numLanes) : true; |
| |
| 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 && |
| 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 || !haveTokens) { |
| 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 |
| TesterThread::issueNextAction() |
| { |
| switch(curAction->getType()) { |
| case Episode::Action::Type::ATOMIC: |
| if (tokenPort) { |
| tokenPort->acquireTokens(numLanes); |
| } |
| 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(numLanes); |
| } |
| issueLoadOps(); |
| break; |
| case Episode::Action::Type::STORE: |
| if (tokenPort) { |
| tokenPort->acquireTokens(numLanes); |
| } |
| 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 |