| /* |
| * Copyright (c) 2011,2013,2017-2018, 2020 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) 2006 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/checker/cpu.hh" |
| |
| #include <list> |
| #include <string> |
| |
| #include "arch/generic/tlb.hh" |
| #include "cpu/base.hh" |
| #include "cpu/simple_thread.hh" |
| #include "cpu/static_inst.hh" |
| #include "cpu/thread_context.hh" |
| #include "cpu/utils.hh" |
| #include "params/CheckerCPU.hh" |
| #include "sim/full_system.hh" |
| |
| namespace gem5 |
| { |
| |
| void |
| CheckerCPU::init() |
| { |
| requestorId = systemPtr->getRequestorId(this); |
| tc->getIsaPtr()->setThreadContext(tc); |
| } |
| |
| CheckerCPU::CheckerCPU(const Params &p) |
| : BaseCPU(p, true), |
| zeroReg(params().isa[0]->regClasses().at(IntRegClass).zeroReg()), |
| systemPtr(NULL), icachePort(NULL), dcachePort(NULL), |
| tc(NULL), thread(NULL), |
| unverifiedReq(nullptr), |
| unverifiedMemData(nullptr) |
| { |
| curStaticInst = NULL; |
| curMacroStaticInst = NULL; |
| |
| numInst = 0; |
| startNumInst = 0; |
| numLoad = 0; |
| startNumLoad = 0; |
| youngestSN = 0; |
| |
| changedPC = willChangePC = false; |
| |
| exitOnError = p.exitOnError; |
| warnOnlyOnLoadError = p.warnOnlyOnLoadError; |
| mmu = p.mmu; |
| workload = p.workload; |
| |
| updateOnError = true; |
| } |
| |
| CheckerCPU::~CheckerCPU() |
| { |
| } |
| |
| void |
| CheckerCPU::setSystem(System *system) |
| { |
| const Params &p = params(); |
| |
| systemPtr = system; |
| |
| if (FullSystem) { |
| thread = new SimpleThread(this, 0, systemPtr, mmu, p.isa[0]); |
| } else { |
| thread = new SimpleThread(this, 0, systemPtr, |
| workload.size() ? workload[0] : NULL, |
| mmu, p.isa[0]); |
| } |
| |
| tc = thread->getTC(); |
| threadContexts.push_back(tc); |
| // Thread should never be null after this |
| assert(thread != NULL); |
| } |
| |
| void |
| CheckerCPU::setIcachePort(RequestPort *icache_port) |
| { |
| icachePort = icache_port; |
| } |
| |
| void |
| CheckerCPU::setDcachePort(RequestPort *dcache_port) |
| { |
| dcachePort = dcache_port; |
| } |
| |
| void |
| CheckerCPU::serialize(std::ostream &os) const |
| { |
| } |
| |
| void |
| CheckerCPU::unserialize(CheckpointIn &cp) |
| { |
| } |
| |
| RequestPtr |
| CheckerCPU::genMemFragmentRequest(Addr frag_addr, int size, |
| Request::Flags flags, |
| const std::vector<bool>& byte_enable, |
| int& frag_size, int& size_left) const |
| { |
| frag_size = std::min( |
| cacheLineSize() - addrBlockOffset(frag_addr, cacheLineSize()), |
| (Addr) size_left); |
| size_left -= frag_size; |
| |
| RequestPtr mem_req; |
| |
| // Set up byte-enable mask for the current fragment |
| auto it_start = byte_enable.cbegin() + (size - (frag_size + |
| size_left)); |
| auto it_end = byte_enable.cbegin() + (size - size_left); |
| if (isAnyActiveElement(it_start, it_end)) { |
| mem_req = std::make_shared<Request>(frag_addr, frag_size, |
| flags, requestorId, thread->pcState().instAddr(), |
| tc->contextId()); |
| mem_req->setByteEnable(std::vector<bool>(it_start, it_end)); |
| } |
| |
| return mem_req; |
| } |
| |
| Fault |
| CheckerCPU::readMem(Addr addr, uint8_t *data, unsigned size, |
| Request::Flags flags, |
| const std::vector<bool>& byte_enable) |
| { |
| assert(byte_enable.size() == size); |
| |
| Fault fault = NoFault; |
| bool checked_flags = false; |
| bool flags_match = true; |
| Addr pAddr = 0x0; |
| |
| Addr frag_addr = addr; |
| int frag_size = 0; |
| int size_left = size; |
| bool predicate; |
| |
| // Need to account for multiple accesses like the Atomic and TimingSimple |
| while (1) { |
| RequestPtr mem_req = genMemFragmentRequest(frag_addr, size, flags, |
| byte_enable, frag_size, |
| size_left); |
| |
| predicate = (mem_req != nullptr); |
| |
| // translate to physical address |
| if (predicate) { |
| fault = mmu->translateFunctional(mem_req, tc, BaseMMU::Read); |
| } |
| |
| if (predicate && !checked_flags && fault == NoFault && unverifiedReq) { |
| flags_match = checkFlags(unverifiedReq, mem_req->getVaddr(), |
| mem_req->getPaddr(), mem_req->getFlags()); |
| pAddr = mem_req->getPaddr(); |
| checked_flags = true; |
| } |
| |
| // Now do the access |
| if (predicate && fault == NoFault && |
| !mem_req->getFlags().isSet(Request::NO_ACCESS)) { |
| PacketPtr pkt = Packet::createRead(mem_req); |
| |
| pkt->dataStatic(data); |
| |
| if (!(mem_req->isUncacheable() || mem_req->isLocalAccess())) { |
| // Access memory to see if we have the same data |
| dcachePort->sendFunctional(pkt); |
| } else { |
| // Assume the data is correct if it's an uncached access |
| memcpy(data, unverifiedMemData, frag_size); |
| } |
| |
| delete pkt; |
| } |
| |
| if (fault != NoFault) { |
| if (mem_req->isPrefetch()) { |
| fault = NoFault; |
| } |
| break; |
| } |
| |
| //If we don't need to access a second cache line, stop now. |
| if (size_left == 0) |
| { |
| break; |
| } |
| |
| // Setup for accessing next cache line |
| frag_addr += frag_size; |
| data += frag_size; |
| unverifiedMemData += frag_size; |
| } |
| |
| if (!flags_match) { |
| warn("%lli: Flags do not match CPU:%#x %#x %#x Checker:%#x %#x %#x\n", |
| curTick(), unverifiedReq->getVaddr(), unverifiedReq->getPaddr(), |
| unverifiedReq->getFlags(), frag_addr, pAddr, flags); |
| handleError(); |
| } |
| |
| return fault; |
| } |
| |
| Fault |
| CheckerCPU::writeMem(uint8_t *data, unsigned size, |
| Addr addr, Request::Flags flags, uint64_t *res, |
| const std::vector<bool>& byte_enable) |
| { |
| assert(byte_enable.size() == size); |
| |
| Fault fault = NoFault; |
| bool checked_flags = false; |
| bool flags_match = true; |
| Addr pAddr = 0x0; |
| static uint8_t zero_data[64] = {}; |
| |
| Addr frag_addr = addr; |
| int frag_size = 0; |
| int size_left = size; |
| bool predicate; |
| |
| // Need to account for a multiple access like Atomic and Timing CPUs |
| while (1) { |
| RequestPtr mem_req = genMemFragmentRequest(frag_addr, size, flags, |
| byte_enable, frag_size, |
| size_left); |
| |
| predicate = (mem_req != nullptr); |
| |
| if (predicate) { |
| fault = mmu->translateFunctional(mem_req, tc, BaseMMU::Write); |
| } |
| |
| if (predicate && !checked_flags && fault == NoFault && unverifiedReq) { |
| flags_match = checkFlags(unverifiedReq, mem_req->getVaddr(), |
| mem_req->getPaddr(), mem_req->getFlags()); |
| pAddr = mem_req->getPaddr(); |
| checked_flags = true; |
| } |
| |
| /* |
| * We don't actually check memory for the store because there |
| * is no guarantee it has left the lsq yet, and therefore we |
| * can't verify the memory on stores without lsq snooping |
| * enabled. This is left as future work for the Checker: LSQ snooping |
| * and memory validation after stores have committed. |
| */ |
| bool was_prefetch = mem_req->isPrefetch(); |
| |
| //If we don't need to access a second cache line, stop now. |
| if (fault != NoFault || size_left == 0) |
| { |
| if (fault != NoFault && was_prefetch) { |
| fault = NoFault; |
| } |
| break; |
| } |
| |
| frag_addr += frag_size; |
| } |
| |
| if (!flags_match) { |
| warn("%lli: Flags do not match CPU:%#x %#x Checker:%#x %#x %#x\n", |
| curTick(), unverifiedReq->getVaddr(), unverifiedReq->getPaddr(), |
| unverifiedReq->getFlags(), frag_addr, pAddr, flags); |
| handleError(); |
| } |
| |
| // Assume the result was the same as the one passed in. This checker |
| // doesn't check if the SC should succeed or fail, it just checks the |
| // value. |
| if (unverifiedReq && res && unverifiedReq->extraDataValid()) |
| *res = unverifiedReq->getExtraData(); |
| |
| // Entire purpose here is to make sure we are getting the |
| // same data to send to the mem system as the CPU did. |
| // Cannot check this is actually what went to memory because |
| // there stores can be in ld/st queue or coherent operations |
| // overwriting values. |
| bool extraData = false; |
| if (unverifiedReq) { |
| extraData = unverifiedReq->extraDataValid() ? |
| unverifiedReq->getExtraData() : true; |
| } |
| |
| // If the request is to ZERO a cache block, there is no data to check |
| // against, but it's all zero. We need something to compare to, so use a |
| // const set of zeros. |
| if (flags & Request::STORE_NO_DATA) { |
| assert(!data); |
| assert(sizeof(zero_data) <= size); |
| data = zero_data; |
| } |
| |
| if (unverifiedReq && unverifiedMemData && |
| memcmp(data, unverifiedMemData, size) && extraData) { |
| warn("%lli: Store value does not match value sent to memory! " |
| "data: %#x inst_data: %#x", curTick(), data, |
| unverifiedMemData); |
| handleError(); |
| } |
| |
| return fault; |
| } |
| |
| /** |
| * Checks if the flags set by the Checker and Checkee match. |
| */ |
| bool |
| CheckerCPU::checkFlags(const RequestPtr &unverified_req, Addr vAddr, |
| Addr pAddr, int flags) |
| { |
| Addr unverifiedVAddr = unverified_req->getVaddr(); |
| Addr unverifiedPAddr = unverified_req->getPaddr(); |
| int unverifiedFlags = unverified_req->getFlags(); |
| |
| if (unverifiedVAddr != vAddr || |
| unverifiedPAddr != pAddr || |
| unverifiedFlags != flags) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void |
| CheckerCPU::dumpAndExit() |
| { |
| warn("%lli: Checker PC:%s", |
| curTick(), thread->pcState()); |
| panic("Checker found an error!"); |
| } |
| |
| } // namespace gem5 |