text: /*
   * Copyright (c) 2015, 2019 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) 2002-2005 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.
   */
  
  #include "cpu/testers/memtest/memtest.hh"
  
  #include "base/random.hh"
  #include "base/statistics.hh"
  #include "base/trace.hh"
  #include "debug/MemTest.hh"
  #include "sim/sim_exit.hh"
  #include "sim/stats.hh"
  #include "sim/system.hh"
  
  using namespace std;
  
  unsigned int TESTER_ALLOCATOR = 0;
  
  bool
  MemTest::CpuPort::recvTimingResp(PacketPtr pkt)
  {
      memtest.completeRequest(pkt);
      return true;
  }
  
  void
  MemTest::CpuPort::recvReqRetry()
  {
      memtest.recvRetry();
  }
  
  bool
  MemTest::sendPkt(PacketPtr pkt) {
      if (atomic) {
          port.sendAtomic(pkt);
          completeRequest(pkt);
      } else {
          if (!port.sendTimingReq(pkt)) {
              retryPkt = pkt;
              return false;
          }
      }
      return true;
  }
  
  MemTest::MemTest(const Params &p)
      : ClockedObject(p),
        tickEvent([this]{ tick(); }, name()),
        noRequestEvent([this]{ noRequest(); }, name()),
        noResponseEvent([this]{ noResponse(); }, name()),
        port("port", *this),
        retryPkt(nullptr),
        size(p.size),
        interval(p.interval),
        percentReads(p.percent_reads),
        percentFunctional(p.percent_functional),
        percentUncacheable(p.percent_uncacheable),
        requestorId(p.system->getRequestorId(this)),
        blockSize(p.system->cacheLineSize()),
        blockAddrMask(blockSize - 1),
        progressInterval(p.progress_interval),
        progressCheck(p.progress_check),
        nextProgressMessage(p.progress_interval),
        maxLoads(p.max_loads),
        atomic(p.system->isAtomicMode()),
        suppressFuncErrors(p.suppress_func_errors), stats(this)
  {
      id = TESTER_ALLOCATOR++;
      fatal_if(id >= blockSize, "Too many testers, only %d allowed\n",
               blockSize - 1);
  
      baseAddr1 = 0x100000;
      baseAddr2 = 0x400000;
      uncacheAddr = 0x800000;
  
      // set up counters
      numReads = 0;
      numWrites = 0;
  
      // kick things into action
      schedule(tickEvent, curTick());
      schedule(noRequestEvent, clockEdge(progressCheck));
  }
  
  Port &
  MemTest::getPort(const std::string &if_name, PortID idx)
  {
      if (if_name == "port")
          return port;
      else
          return ClockedObject::getPort(if_name, idx);
  }
  
  void
  MemTest::completeRequest(PacketPtr pkt, bool functional)
  {
      const RequestPtr &req = pkt->req;
      assert(req->getSize() == 1);
  
      // this address is no longer outstanding
      auto remove_addr = outstandingAddrs.find(req->getPaddr());
      assert(remove_addr != outstandingAddrs.end());
      outstandingAddrs.erase(remove_addr);
  
      DPRINTF(MemTest, "Completing %s at address %x (blk %x) %s\n",
              pkt->isWrite() ? "write" : "read",
              req->getPaddr(), blockAlign(req->getPaddr()),
              pkt->isError() ? "error" : "success");
  
      const uint8_t *pkt_data = pkt->getConstPtr<uint8_t>();
  
      if (pkt->isError()) {
          if (!functional || !suppressFuncErrors)
              panic( "%s access failed at %#x\n",
                  pkt->isWrite() ? "Write" : "Read", req->getPaddr());
      } else {
          if (pkt->isRead()) {
              uint8_t ref_data = referenceData[req->getPaddr()];
              if (pkt_data[0] != ref_data) {
                  panic("%s: read of %x (blk %x) @ cycle %d "
                        "returns %x, expected %x\n", name(),
                        req->getPaddr(), blockAlign(req->getPaddr()), curTick(),
                        pkt_data[0], ref_data);
              }
  
              numReads++;
              stats.numReads++;
  
              if (numReads == (uint64_t)nextProgressMessage) {
                  ccprintf(cerr, "%s: completed %d read, %d write accesses @%d\n",
                           name(), numReads, numWrites, curTick());
                  nextProgressMessage += progressInterval;
              }
  
              if (maxLoads != 0 && numReads >= maxLoads)
                  exitSimLoop("maximum number of loads reached");
          } else {
              assert(pkt->isWrite());
  
              // update the reference data
              referenceData[req->getPaddr()] = pkt_data[0];
              numWrites++;
              stats.numWrites++;
          }
      }
  
      // the packet will delete the data
      delete pkt;
  
      // finally shift the response timeout forward if we are still
      // expecting responses; deschedule it otherwise
      if (outstandingAddrs.size() != 0)
          reschedule(noResponseEvent, clockEdge(progressCheck));
      else if (noResponseEvent.scheduled())
          deschedule(noResponseEvent);
  }
  MemTest::MemTestStats::MemTestStats(Stats::Group *parent)
        : Stats::Group(parent),
        ADD_STAT(numReads, "number of read accesses completed"),
        ADD_STAT(numWrites, "number of write accesses completed")
  {
  
  }
  
  void
  MemTest::tick()
  {
      // we should never tick if we are waiting for a retry
      assert(!retryPkt);
  
      // create a new request
      unsigned cmd = random_mt.random(0, 100);
      uint8_t data = random_mt.random<uint8_t>();
      bool uncacheable = random_mt.random(0, 100) < percentUncacheable;
      unsigned base = random_mt.random(0, 1);
      Request::Flags flags;
      Addr paddr;
  
      // generate a unique address
      do {
          unsigned offset = random_mt.random<unsigned>(0, size - 1);
  
          // use the tester id as offset within the block for false sharing
          offset = blockAlign(offset);
          offset += id;
  
          if (uncacheable) {
              flags.set(Request::UNCACHEABLE);
              paddr = uncacheAddr + offset;
          } else  {
              paddr = ((base) ? baseAddr1 : baseAddr2) + offset;
          }
      } while (outstandingAddrs.find(paddr) != outstandingAddrs.end());
  
      bool do_functional = (random_mt.random(0, 100) < percentFunctional) &&
          !uncacheable;
      RequestPtr req = std::make_shared<Request>(paddr, 1, flags, requestorId);
      req->setContext(id);
  
      outstandingAddrs.insert(paddr);
  
      // sanity check
      panic_if(outstandingAddrs.size() > 100,
               "Tester %s has more than 100 outstanding requests\n", name());
  
      PacketPtr pkt = nullptr;
      uint8_t *pkt_data = new uint8_t[1];
  
      if (cmd < percentReads) {
          // start by ensuring there is a reference value if we have not
          // seen this address before
          M5_VAR_USED uint8_t ref_data = 0;
          auto ref = referenceData.find(req->getPaddr());
          if (ref == referenceData.end()) {
              referenceData[req->getPaddr()] = 0;
          } else {
              ref_data = ref->second;
          }
  
          DPRINTF(MemTest,
                  "Initiating %sread at addr %x (blk %x) expecting %x\n",
                  do_functional ? "functional " : "", req->getPaddr(),
                  blockAlign(req->getPaddr()), ref_data);
  
          pkt = new Packet(req, MemCmd::ReadReq);
          pkt->dataDynamic(pkt_data);
      } else {
          DPRINTF(MemTest, "Initiating %swrite at addr %x (blk %x) value %x\n",
                  do_functional ? "functional " : "", req->getPaddr(),
                  blockAlign(req->getPaddr()), data);
  
          pkt = new Packet(req, MemCmd::WriteReq);
          pkt->dataDynamic(pkt_data);
          pkt_data[0] = data;
      }
  
      // there is no point in ticking if we are waiting for a retry
      bool keep_ticking = true;
      if (do_functional) {
          pkt->setSuppressFuncError();
          port.sendFunctional(pkt);
          completeRequest(pkt, true);
      } else {
          keep_ticking = sendPkt(pkt);
      }
  
      if (keep_ticking) {
          // schedule the next tick
          schedule(tickEvent, clockEdge(interval));
  
          // finally shift the timeout for sending of requests forwards
          // as we have successfully sent a packet
          reschedule(noRequestEvent, clockEdge(progressCheck), true);
      } else {
          DPRINTF(MemTest, "Waiting for retry\n");
      }
  
      // Schedule noResponseEvent now if we are expecting a response
      if (!noResponseEvent.scheduled() && (outstandingAddrs.size() != 0))
          schedule(noResponseEvent, clockEdge(progressCheck));
  }
  
  void
  MemTest::noRequest()
  {
      panic("%s did not send a request for %d cycles", name(), progressCheck);
  }
  
  void
  MemTest::noResponse()
  {
      panic("%s did not see a response for %d cycles", name(), progressCheck);
  }
  
  void
  MemTest::recvRetry()
  {
      assert(retryPkt);
      if (port.sendTimingReq(retryPkt)) {
          DPRINTF(MemTest, "Proceeding after successful retry\n");
  
          retryPkt = nullptr;
          // kick things into action again
          schedule(tickEvent, clockEdge(interval));
          reschedule(noRequestEvent, clockEdge(progressCheck), true);
      }
  }
  