| /* | |
| * Copyright (c) 2015 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 "mem/mem_object.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) | |
| : MemObject(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)); | |
| schedule(noResponseEvent, clockEdge(progressCheck)); | |
| } | |
| BaseMasterPort & | |
| MemTest::getMasterPort(const std::string &if_name, PortID idx) | |
| { | |
| if (if_name == "port") | |
| return port; | |
| else | |
| return MemObject::getMasterPort(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 | |
| reschedule(noResponseEvent, clockEdge(progressCheck), true); | |
| } | |
| void | |
| MemTest::regStats() | |
| { | |
| MemObject::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"); | |
| } | |
| } | |
| 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)); | |
| } | |
| } | |
| MemTest * | |
| MemTestParams::create() | |
| { | |
| return new MemTest(this); | |
| } |