/*
 * 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.
 *
 * Authors: Erik Hallnor
 *          Steve Reinhardt
 *          Andreas Hansson
 */

#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),
      masterId(p->system->getMasterId(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()),
      suppressFuncWarnings(p->suppress_func_warnings)
{
    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 || !suppressFuncWarnings) {
            warn("%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++;
            numReadsStat++;

            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++;
            numWritesStat++;
        }
    }

    // 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);
}

void
MemTest::regStats()
{
    ClockedObject::regStats();

    using namespace Stats;

    numReadsStat
        .name(name() + ".num_reads")
        .desc("number of read accesses completed")
        ;

    numWritesStat
        .name(name() + ".num_writes")
        .desc("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, masterId);
    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
        uint8_t M5_VAR_USED 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);
    }
}

MemTest *
MemTestParams::create()
{
    return new MemTest(this);
}