text: /*
   * 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
  