| /* |
| * Copyright (c) 2013 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) 2007 MIPS Technologies, 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: 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: Korey Sewell |
| * |
| */ |
| |
| #include <list> |
| #include <vector> |
| |
| #include "arch/isa_traits.hh" |
| #include "arch/locked_mem.hh" |
| #include "arch/utility.hh" |
| #include "config/the_isa.hh" |
| #include "cpu/inorder/resources/cache_unit.hh" |
| #include "cpu/inorder/cpu.hh" |
| #include "cpu/inorder/pipeline_traits.hh" |
| #include "cpu/inorder/resource_pool.hh" |
| #include "debug/Activity.hh" |
| #include "debug/AddrDep.hh" |
| #include "debug/InOrderCachePort.hh" |
| #include "debug/InOrderStall.hh" |
| #include "debug/InOrderTLB.hh" |
| #include "debug/LLSC.hh" |
| #include "debug/RefCount.hh" |
| #include "debug/ThreadModel.hh" |
| #include "mem/request.hh" |
| |
| using namespace std; |
| using namespace TheISA; |
| using namespace ThePipeline; |
| |
| #if TRACING_ON |
| static std::string |
| printMemData(uint8_t *data, unsigned size) |
| { |
| std::stringstream dataStr; |
| for (unsigned pos = 0; pos < size; pos++) { |
| ccprintf(dataStr, "%02x", data[pos]); |
| } |
| return dataStr.str(); |
| } |
| #endif |
| |
| CacheUnit::CacheUnit(string res_name, int res_id, int res_width, |
| Cycles res_latency, InOrderCPU *_cpu, |
| ThePipeline::Params *params) |
| : Resource(res_name, res_id, res_width, res_latency, _cpu), |
| cachePort(NULL), cachePortBlocked(false) |
| { |
| // Hard-Code Selection For Now |
| if (res_id == ICache) |
| _tlb = params->itb; |
| else if (res_id == DCache) |
| _tlb = params->dtb; |
| else |
| fatal("Unrecognized TLB name passed by user"); |
| |
| // Note that the CPU port is not yet instantiated (as it is done |
| // after the resource pool), we delay setting the cachePort |
| // pointer until init(). |
| |
| for (int i=0; i < MaxThreads; i++) { |
| tlbBlocked[i] = false; |
| tlbBlockSeqNum[i] = 0; |
| } |
| } |
| |
| TheISA::TLB* |
| CacheUnit::tlb() |
| { |
| return _tlb; |
| |
| } |
| |
| void |
| CacheUnit::init() |
| { |
| // Get the appropriate port from the CPU based on the resource name. |
| if (id == ICache) { |
| cachePort = &cpu->getInstPort(); |
| } else if (id == DCache) { |
| cachePort = &cpu->getDataPort(); |
| } |
| assert(cachePort != NULL); |
| |
| for (int i = 0; i < width; i++) { |
| reqs[i] = new CacheRequest(this); |
| } |
| |
| cacheBlkSize = cpu->cacheLineSize(); |
| cacheBlkMask = cacheBlkSize - 1; |
| |
| initSlots(); |
| } |
| |
| int |
| CacheUnit::getSlot(DynInstPtr inst) |
| { |
| ThreadID tid = inst->readTid(); |
| if (tlbBlocked[tid]) { |
| return -1; |
| } |
| |
| // For a Split-Load, the instruction would have processed once already |
| // causing the address to be unset. |
| if (!inst->validMemAddr() && !inst->splitInst) { |
| panic("[tid:%i][sn:%i] Mem. Addr. must be set before requesting " |
| "cache access\n", inst->readTid(), inst->seqNum); |
| } |
| |
| int new_slot = Resource::getSlot(inst); |
| inst->memTime = curTick(); |
| //@note: add back in if you want speculative loads/store capability |
| //setAddrDependency(inst); |
| return new_slot; |
| } |
| |
| void |
| CacheUnit::setAddrDependency(DynInstPtr inst) |
| { |
| Addr req_addr = inst->getMemAddr(); |
| ThreadID tid = inst->readTid(); |
| |
| addrList[tid].push_back(req_addr); |
| addrMap[tid][req_addr] = inst->seqNum; |
| |
| DPRINTF(AddrDep, |
| "[tid:%i]: [sn:%i]: Address %08p added to dependency list (size=%i)\n", |
| inst->readTid(), inst->seqNum, req_addr, addrList[tid].size()); |
| |
| //@NOTE: 10 is an arbitrarily "high" number, but to be exact |
| // we would need to know the # of outstanding accesses |
| // a priori. Information like fetch width, stage width, |
| // fetch buffer, and the branch resolution stage would be |
| // useful for the icache_port. For the dcache port, the # |
| // of outstanding cache accesses (mshrs) would be a good |
| // sanity check here. |
| //assert(addrList[tid].size() < 10); |
| } |
| |
| void |
| CacheUnit::removeAddrDependency(DynInstPtr inst) |
| { |
| ThreadID tid = inst->readTid(); |
| |
| Addr mem_addr = inst->getMemAddr(); |
| |
| inst->unsetMemAddr(); |
| |
| // Erase from Address List |
| std::list<Addr>::iterator list_it = find(addrList[tid].begin(), |
| addrList[tid].end(), |
| mem_addr); |
| assert(list_it != addrList[tid].end() || inst->splitInst); |
| |
| if (list_it != addrList[tid].end()) { |
| DPRINTF(AddrDep, |
| "[tid:%i]: [sn:%i] Address %08p removed from dependency " |
| "list\n", inst->readTid(), inst->seqNum, (*list_it)); |
| |
| addrList[tid].erase(list_it); |
| |
| // Erase From Address Map (Used for Debugging) |
| addrMap[tid].erase(addrMap[tid].find(mem_addr)); |
| } |
| |
| |
| } |
| |
| ResReqPtr |
| CacheUnit::findRequest(DynInstPtr inst) |
| { |
| for (int i = 0; i < width; i++) { |
| CacheRequest* cache_req = |
| dynamic_cast<CacheRequest*>(reqs[i]); |
| assert(cache_req); |
| |
| if (cache_req->valid && |
| cache_req->getInst() == inst && |
| cache_req->instIdx == inst->curSkedEntry->idx) { |
| return cache_req; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| ResReqPtr |
| CacheUnit::findRequest(DynInstPtr inst, int idx) |
| { |
| for (int i = 0; i < width; i++) { |
| CacheRequest* cache_req = |
| dynamic_cast<CacheRequest*>(reqs[i]); |
| assert(cache_req); |
| |
| if (cache_req->valid && |
| cache_req->getInst() == inst && |
| cache_req->instIdx == idx) { |
| return cache_req; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| |
| ResReqPtr |
| CacheUnit::getRequest(DynInstPtr inst, int stage_num, int res_idx, |
| int slot_num, unsigned cmd) |
| { |
| ScheduleEntry* sched_entry = *inst->curSkedEntry; |
| CacheRequest* cache_req = dynamic_cast<CacheRequest*>(reqs[slot_num]); |
| |
| if (!inst->validMemAddr()) { |
| panic("Mem. Addr. must be set before requesting cache access\n"); |
| } |
| |
| MemCmd::Command pkt_cmd; |
| |
| switch (sched_entry->cmd) |
| { |
| case InitSecondSplitRead: |
| pkt_cmd = MemCmd::ReadReq; |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: Read request from [sn:%i] for addr %08p\n", |
| inst->readTid(), inst->seqNum, inst->split2ndAddr); |
| break; |
| |
| case InitiateReadData: |
| pkt_cmd = MemCmd::ReadReq; |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: Read request from [sn:%i] for addr %08p\n", |
| inst->readTid(), inst->seqNum, inst->getMemAddr()); |
| break; |
| |
| case InitSecondSplitWrite: |
| pkt_cmd = MemCmd::WriteReq; |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: Write request from [sn:%i] for addr %08p\n", |
| inst->readTid(), inst->seqNum, inst->split2ndAddr); |
| break; |
| |
| case InitiateWriteData: |
| pkt_cmd = MemCmd::WriteReq; |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: Write request from [sn:%i] for addr %08p\n", |
| inst->readTid(), inst->seqNum, inst->getMemAddr()); |
| break; |
| |
| default: |
| panic("%i: Unexpected request type (%i) to %s", curTick(), |
| sched_entry->cmd, name()); |
| } |
| |
| cache_req->setRequest(inst, stage_num, id, slot_num, |
| sched_entry->cmd, pkt_cmd, |
| inst->curSkedEntry->idx); |
| return cache_req; |
| } |
| |
| void |
| CacheUnit::requestAgain(DynInstPtr inst, bool &service_request) |
| { |
| CacheReqPtr cache_req = dynamic_cast<CacheReqPtr>(findRequest(inst)); |
| assert(cache_req); |
| |
| // Check to see if this instruction is requesting the same command |
| // or a different one |
| if (cache_req->cmd != inst->curSkedEntry->cmd && |
| cache_req->instIdx == inst->curSkedEntry->idx) { |
| // If different, then update command in the request |
| cache_req->cmd = inst->curSkedEntry->cmd; |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: [sn:%i]: Updating the command for this " |
| "instruction\n", inst->readTid(), inst->seqNum); |
| |
| service_request = true; |
| } else if (inst->curSkedEntry->idx != CacheUnit::InitSecondSplitRead && |
| inst->curSkedEntry->idx != CacheUnit::InitSecondSplitWrite) { |
| // If same command, just check to see if memory access was completed |
| // but dont try to re-execute |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: [sn:%i]: requesting this resource again\n", |
| inst->readTid(), inst->seqNum); |
| |
| service_request = true; |
| } |
| } |
| |
| void |
| CacheUnit::setupMemRequest(DynInstPtr inst, CacheReqPtr cache_req, |
| int acc_size, int flags) |
| { |
| ThreadID tid = inst->readTid(); |
| Addr aligned_addr = inst->getMemAddr(); |
| |
| if (!cache_req->is2ndSplit()) { |
| if (cache_req->memReq == NULL) { |
| cache_req->memReq = |
| new Request(cpu->asid[tid], aligned_addr, acc_size, flags, |
| cpu->dataMasterId(), |
| inst->instAddr(), |
| cpu->readCpuId(), //@todo: use context id |
| tid); |
| } |
| } else { |
| assert(inst->splitInst); |
| |
| if (inst->splitMemReq == NULL) { |
| inst->splitMemReq = new Request(cpu->asid[tid], |
| inst->split2ndAddr, |
| acc_size, |
| flags, |
| cpu->dataMasterId(), |
| inst->instAddr(), |
| cpu->readCpuId(), |
| tid); |
| } |
| |
| cache_req->memReq = inst->splitMemReq; |
| } |
| } |
| |
| void |
| CacheUnit::doTLBAccess(DynInstPtr inst, CacheReqPtr cache_req, int acc_size, |
| int flags, TheISA::TLB::Mode tlb_mode) |
| { |
| ThreadID tid = inst->readTid(); |
| |
| setupMemRequest(inst, cache_req, acc_size, flags); |
| |
| //@todo: HACK: the DTB expects the correct PC in the ThreadContext |
| // but how if the memory accesses are speculative? Shouldn't |
| // we send along the requestor's PC to the translate functions? |
| ThreadContext *tc = cpu->thread[tid]->getTC(); |
| PCState old_pc = tc->pcState(); |
| tc->pcState() = inst->pcState(); |
| |
| inst->fault = |
| _tlb->translateAtomic(cache_req->memReq, tc, tlb_mode); |
| tc->pcState() = old_pc; |
| |
| if (inst->fault != NoFault) { |
| DPRINTF(InOrderTLB, "[tid:%i]: %s encountered while translating " |
| "addr:%08p for [sn:%i].\n", tid, inst->fault->name(), |
| cache_req->memReq->getVaddr(), inst->seqNum); |
| |
| tlbBlocked[tid] = true; |
| tlbBlockSeqNum[tid] = inst->seqNum; |
| |
| // Make sure nothing gets executed until after this faulting |
| // instruction gets handled. |
| inst->setSerializeAfter(); |
| |
| // Mark it as complete so it can pass through next stage. |
| // Fault Handling will happen at commit/graduation |
| cache_req->setCompleted(); |
| } else { |
| DPRINTF(InOrderTLB, "[tid:%i]: [sn:%i] virt. addr %08p translated " |
| "to phys. addr:%08p.\n", tid, inst->seqNum, |
| cache_req->memReq->getVaddr(), |
| cache_req->memReq->getPaddr()); |
| } |
| } |
| |
| void |
| CacheUnit::trap(const Fault &fault, ThreadID tid, DynInstPtr inst) |
| { |
| tlbBlocked[tid] = false; |
| } |
| |
| Fault |
| CacheUnit::read(DynInstPtr inst, Addr addr, |
| uint8_t *data, unsigned size, unsigned flags) |
| { |
| CacheReqPtr cache_req = dynamic_cast<CacheReqPtr>(findRequest(inst)); |
| assert(cache_req && "Can't Find Instruction for Read!"); |
| |
| // The block size of our peer |
| unsigned blockSize = cacheBlkSize; |
| |
| //The size of the data we're trying to read. |
| int fullSize = size; |
| inst->totalSize = size; |
| |
| if (inst->traceData) { |
| inst->traceData->setAddr(addr); |
| } |
| |
| if (inst->split2ndAccess) { |
| size = inst->split2ndSize; |
| cache_req->splitAccess = true; |
| cache_req->split2ndAccess = true; |
| |
| DPRINTF(InOrderCachePort, "[sn:%i] Split Read Access (2 of 2) for " |
| "(%#x, %#x).\n", inst->seqNum, inst->getMemAddr(), |
| inst->split2ndAddr); |
| } |
| |
| |
| //The address of the second part of this access if it needs to be split |
| //across a cache line boundary. |
| Addr secondAddr = roundDown(addr + size - 1, blockSize); |
| |
| |
| if (secondAddr > addr && !inst->split2ndAccess) { |
| |
| if (!inst->splitInst) { |
| DPRINTF(InOrderCachePort, "%i: sn[%i] Split Read Access (1 of 2) for " |
| "(%#x, %#x).\n", curTick(), inst->seqNum, addr, secondAddr); |
| |
| unsigned stage_num = cache_req->getStageNum(); |
| unsigned cmd = inst->curSkedEntry->cmd; |
| |
| // 1. Make A New Inst. Schedule w/Split Read/Complete Entered on |
| // the schedule |
| // ============================== |
| // 2. Reassign curSkedPtr to current command (InitiateRead) on new |
| // schedule |
| // ============================== |
| inst->splitInst = true; |
| inst->setBackSked(cpu->createBackEndSked(inst)); |
| inst->curSkedEntry = inst->backSked->find(stage_num, cmd); |
| } else { |
| DPRINTF(InOrderCachePort, "[tid:%i] [sn:%i] Retrying Split Read " |
| "Access (1 of 2) for (%#x, %#x).\n", inst->readTid(), |
| inst->seqNum, addr, secondAddr); |
| } |
| |
| // Save All "Total" Split Information |
| // ============================== |
| inst->splitMemData = new uint8_t[size]; |
| |
| // Split Information for First Access |
| // ============================== |
| size = secondAddr - addr; |
| cache_req->splitAccess = true; |
| |
| // Split Information for Second Access |
| // ============================== |
| inst->split2ndSize = addr + fullSize - secondAddr; |
| inst->split2ndAddr = secondAddr; |
| inst->split2ndDataPtr = inst->splitMemData + size; |
| inst->split2ndFlags = flags; |
| } |
| |
| doTLBAccess(inst, cache_req, size, flags, TheISA::TLB::Read); |
| |
| if (inst->fault == NoFault) { |
| if (!cache_req->splitAccess) { |
| cache_req->reqData = new uint8_t[size]; |
| doCacheAccess(inst, NULL); |
| } else { |
| if (!inst->split2ndAccess) { |
| cache_req->reqData = inst->splitMemData; |
| } else { |
| cache_req->reqData = inst->split2ndDataPtr; |
| } |
| |
| doCacheAccess(inst, NULL, cache_req); |
| } |
| } |
| |
| return inst->fault; |
| } |
| |
| Fault |
| CacheUnit::write(DynInstPtr inst, uint8_t *data, unsigned size, |
| Addr addr, unsigned flags, uint64_t *write_res) |
| { |
| CacheReqPtr cache_req = dynamic_cast<CacheReqPtr>(findRequest(inst)); |
| assert(cache_req && "Can't Find Instruction for Write!"); |
| |
| // The block size of our peer |
| unsigned blockSize = cacheBlkSize; |
| |
| //The size of the data we're trying to write. |
| int fullSize = size; |
| inst->totalSize = size; |
| |
| if (inst->traceData) { |
| inst->traceData->setAddr(addr); |
| } |
| |
| if (inst->split2ndAccess) { |
| size = inst->split2ndSize; |
| cache_req->splitAccess = true; |
| cache_req->split2ndAccess = true; |
| |
| DPRINTF(InOrderCachePort, "[sn:%i] Split Write Access (2 of 2) for " |
| "(%#x, %#x).\n", inst->seqNum, inst->getMemAddr(), |
| inst->split2ndAddr); |
| } |
| |
| //The address of the second part of this access if it needs to be split |
| //across a cache line boundary. |
| Addr secondAddr = roundDown(addr + size - 1, blockSize); |
| |
| if (secondAddr > addr && !inst->split2ndAccess) { |
| |
| DPRINTF(InOrderCachePort, "[sn:%i] Split Write Access (1 of 2) for " |
| "(%#x, %#x).\n", inst->seqNum, addr, secondAddr); |
| |
| // Save All "Total" Split Information |
| // ============================== |
| inst->splitInst = true; |
| |
| if (!inst->splitInstSked) { |
| assert(0 && "Split Requests Not Supported for Now..."); |
| |
| // Schedule Split Read/Complete for Instruction |
| // ============================== |
| int stage_num = cache_req->getStageNum(); |
| RSkedPtr inst_sked = (stage_num >= ThePipeline::BackEndStartStage) ? |
| inst->backSked : inst->frontSked; |
| |
| // this is just an arbitrarily high priority to ensure that this |
| // gets pushed to the back of the list |
| int stage_pri = 20; |
| |
| int isplit_cmd = CacheUnit::InitSecondSplitWrite; |
| inst_sked->push(new |
| ScheduleEntry(stage_num, |
| stage_pri, |
| cpu->resPool->getResIdx(DCache), |
| isplit_cmd, |
| 1)); |
| |
| int csplit_cmd = CacheUnit::CompleteSecondSplitWrite; |
| inst_sked->push(new |
| ScheduleEntry(stage_num + 1, |
| 1/*stage_pri*/, |
| cpu->resPool->getResIdx(DCache), |
| csplit_cmd, |
| 1)); |
| inst->splitInstSked = true; |
| } else { |
| DPRINTF(InOrderCachePort, "[tid:%i] sn:%i] Retrying Split Read " |
| "Access (1 of 2) for (%#x, %#x).\n", |
| inst->readTid(), inst->seqNum, addr, secondAddr); |
| } |
| |
| |
| |
| // Split Information for First Access |
| // ============================== |
| size = secondAddr - addr; |
| cache_req->splitAccess = true; |
| |
| // Split Information for Second Access |
| // ============================== |
| inst->split2ndSize = addr + fullSize - secondAddr; |
| inst->split2ndAddr = secondAddr; |
| inst->split2ndFlags = flags; |
| inst->splitInstSked = true; |
| } |
| |
| doTLBAccess(inst, cache_req, size, flags, TheISA::TLB::Write); |
| |
| if (inst->fault == NoFault) { |
| if (!cache_req->splitAccess) { |
| cache_req->reqData = new uint8_t[size]; |
| memcpy(cache_req->reqData, data, size); |
| |
| //inst->split2ndStoreDataPtr = cache_req->reqData; |
| //inst->split2ndStoreDataPtr += size; |
| |
| doCacheAccess(inst, write_res); |
| } else { |
| doCacheAccess(inst, write_res, cache_req); |
| } |
| |
| } |
| |
| return inst->fault; |
| } |
| |
| |
| void |
| CacheUnit::execute(int slot_num) |
| { |
| CacheReqPtr cache_req = dynamic_cast<CacheReqPtr>(reqs[slot_num]); |
| assert(cache_req); |
| |
| if (cachePortBlocked && |
| (cache_req->cmd == InitiateReadData || |
| cache_req->cmd == InitiateWriteData || |
| cache_req->cmd == InitSecondSplitRead || |
| cache_req->cmd == InitSecondSplitWrite)) { |
| DPRINTF(InOrderCachePort, "Cache Port Blocked. Cannot Access\n"); |
| cache_req->done(false); |
| return; |
| } |
| |
| DynInstPtr inst = cache_req->inst; |
| if (inst->fault != NoFault) { |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: [sn:%i]: Detected %s fault @ %x. Forwarding to " |
| "next stage.\n", inst->readTid(), inst->seqNum, inst->fault->name(), |
| inst->getMemAddr()); |
| finishCacheUnitReq(inst, cache_req); |
| return; |
| } |
| |
| if (inst->isSquashed()) { |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: [sn:%i]: Detected squashed instruction " |
| "next stage.\n", inst->readTid(), inst->seqNum); |
| finishCacheUnitReq(inst, cache_req); |
| return; |
| } |
| |
| #if TRACING_ON |
| ThreadID tid = inst->readTid(); |
| std::string acc_type = "write"; |
| #endif |
| |
| switch (cache_req->cmd) |
| { |
| |
| case InitiateReadData: |
| #if TRACING_ON |
| acc_type = "read"; |
| #endif |
| case InitiateWriteData: |
| if (cachePortBlocked) { |
| DPRINTF(InOrderCachePort, "Cache Port Blocked. Cannot Access\n"); |
| cache_req->done(false); |
| return; |
| } |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%u]: [sn:%i] Initiating data %s access to %s for " |
| "addr. %08p\n", tid, inst->seqNum, acc_type, name(), |
| cache_req->inst->getMemAddr()); |
| |
| inst->setCurResSlot(slot_num); |
| |
| if (inst->isDataPrefetch() || inst->isInstPrefetch()) { |
| inst->execute(); |
| } else { |
| inst->initiateAcc(); |
| } |
| |
| break; |
| |
| case InitSecondSplitRead: |
| DPRINTF(InOrderCachePort, |
| "[tid:%u]: [sn:%i] Initiating split data read access to %s " |
| "for addr. %08p\n", tid, inst->seqNum, name(), |
| cache_req->inst->split2ndAddr); |
| inst->split2ndAccess = true; |
| assert(inst->split2ndAddr != 0); |
| read(inst, inst->split2ndAddr, &inst->split2ndData, |
| inst->totalSize, inst->split2ndFlags); |
| break; |
| |
| case InitSecondSplitWrite: |
| DPRINTF(InOrderCachePort, |
| "[tid:%u]: [sn:%i] Initiating split data write access to %s " |
| "for addr. %08p\n", tid, inst->seqNum, name(), |
| cache_req->inst->getMemAddr()); |
| |
| inst->split2ndAccess = true; |
| assert(inst->split2ndAddr != 0); |
| write(inst, &inst->split2ndData, inst->totalSize, |
| inst->split2ndAddr, inst->split2ndFlags, NULL); |
| break; |
| |
| case CompleteReadData: |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: [sn:%i]: Trying to Complete Data Read Access\n", |
| tid, inst->seqNum); |
| |
| |
| //@todo: timing translations need to check here... |
| assert(!inst->isInstPrefetch() && "Can't Handle Inst. Prefecthes"); |
| if (cache_req->isMemAccComplete() || inst->isDataPrefetch()) { |
| finishCacheUnitReq(inst, cache_req); |
| } else { |
| DPRINTF(InOrderStall, "STALL: [tid:%i]: Data miss from %08p\n", |
| tid, cache_req->inst->getMemAddr()); |
| cache_req->setCompleted(false); |
| cache_req->setMemStall(true); |
| } |
| break; |
| |
| case CompleteWriteData: |
| { |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: [sn:%i]: Trying to Complete Data Write Access\n", |
| tid, inst->seqNum); |
| |
| |
| //@todo: check that timing translation is finished here |
| RequestPtr mem_req = cache_req->memReq; |
| if (mem_req->isCondSwap() || mem_req->isLLSC() || mem_req->isSwap()) { |
| DPRINTF(InOrderCachePort, "Detected Conditional Store Inst.\n"); |
| |
| if (!cache_req->isMemAccComplete()) { |
| DPRINTF(InOrderStall, "STALL: [tid:%i]: Data miss from %08p\n", |
| tid, cache_req->inst->getMemAddr()); |
| cache_req->setCompleted(false); |
| cache_req->setMemStall(true); |
| return; |
| } else { |
| DPRINTF(InOrderStall, "Mem Acc Completed\n"); |
| } |
| } |
| |
| if (cache_req->isMemAccPending()) { |
| DPRINTF(InOrderCachePort, "Store Instruction Pending Completion.\n"); |
| cache_req->dataPkt->reqData = cache_req->reqData; |
| cache_req->dataPkt->memReq = cache_req->memReq; |
| } else |
| DPRINTF(InOrderCachePort, "Store Instruction Finished Completion.\n"); |
| |
| //@todo: if split inst save data |
| finishCacheUnitReq(inst, cache_req); |
| } |
| break; |
| |
| case CompleteSecondSplitRead: |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: [sn:%i]: Trying to Complete Split Data Read " |
| "Access\n", tid, inst->seqNum); |
| |
| //@todo: check that timing translation is finished here |
| assert(!inst->isInstPrefetch() && "Can't Handle Inst. Prefecthes"); |
| if (cache_req->isMemAccComplete() || inst->isDataPrefetch()) { |
| finishCacheUnitReq(inst, cache_req); |
| } else { |
| DPRINTF(InOrderStall, "STALL: [tid:%i]: Data miss from %08p\n", |
| tid, cache_req->inst->split2ndAddr); |
| cache_req->setCompleted(false); |
| cache_req->setMemStall(true); |
| } |
| break; |
| |
| case CompleteSecondSplitWrite: |
| DPRINTF(InOrderCachePort, |
| "[tid:%i]: [sn:%i]: Trying to Complete Split Data Write " |
| "Access\n", tid, inst->seqNum); |
| //@todo: illegal to have a unaligned cond.swap or llsc? |
| assert(!cache_req->memReq->isSwap() && !cache_req->memReq->isCondSwap() |
| && !cache_req->memReq->isLLSC()); |
| |
| if (cache_req->isMemAccPending()) { |
| cache_req->dataPkt->reqData = cache_req->reqData; |
| cache_req->dataPkt->memReq = cache_req->memReq; |
| } |
| |
| //@todo: check that timing translation is finished here |
| finishCacheUnitReq(inst, cache_req); |
| break; |
| |
| default: |
| fatal("Unrecognized command to %s", resName); |
| } |
| } |
| |
| void |
| CacheUnit::finishCacheUnitReq(DynInstPtr inst, CacheRequest *cache_req) |
| { |
| //@note: add back in for speculative load/store capability |
| //removeAddrDependency(inst); |
| cache_req->setMemStall(false); |
| cache_req->done(); |
| } |
| |
| void |
| CacheUnit::buildDataPacket(CacheRequest *cache_req) |
| { |
| cache_req->dataPkt = new CacheReqPacket(cache_req, |
| cache_req->pktCmd, |
| cache_req->instIdx); |
| cache_req->dataPkt->refineCommand(); // handle LL/SC, etc. |
| |
| DPRINTF(InOrderCachePort, "[slot:%i]: Slot marked for %x\n", |
| cache_req->getSlot(), |
| cache_req->dataPkt->getAddr()); |
| |
| cache_req->dataPkt->hasSlot = true; |
| cache_req->dataPkt->dataStatic(cache_req->reqData); |
| } |
| |
| void |
| CacheUnit::doCacheAccess(DynInstPtr inst, uint64_t *write_res, |
| CacheReqPtr split_req) |
| { |
| Fault fault = NoFault; |
| #if TRACING_ON |
| ThreadID tid = inst->readTid(); |
| #endif |
| bool do_access = true; // flag to suppress cache access |
| |
| // Special Handling if this is a split request |
| CacheReqPtr cache_req; |
| if (split_req == NULL) |
| cache_req = dynamic_cast<CacheReqPtr>(reqs[inst->getCurResSlot()]); |
| else { |
| cache_req = split_req; |
| assert(0); |
| } |
| |
| // Make a new packet inside the CacheRequest object |
| assert(cache_req); |
| buildDataPacket(cache_req); |
| |
| // Special Handling for LL/SC or Compare/Swap |
| bool is_write = cache_req->dataPkt->isWrite(); |
| RequestPtr mem_req = cache_req->dataPkt->req; |
| if (is_write) { |
| DPRINTF(InOrderCachePort, |
| "[tid:%u]: [sn:%i]: Storing data: %s\n", |
| tid, inst->seqNum, |
| printMemData(cache_req->dataPkt->getPtr<uint8_t>(), |
| cache_req->dataPkt->getSize())); |
| |
| if (mem_req->isCondSwap()) { |
| assert(write_res); |
| cache_req->memReq->setExtraData(*write_res); |
| } |
| if (mem_req->isLLSC()) { |
| assert(cache_req->inst->isStoreConditional()); |
| DPRINTF(InOrderCachePort, "Evaluating Store Conditional access\n"); |
| do_access = TheISA::handleLockedWrite(inst.get(), mem_req, cacheBlkSize); |
| } |
| } |
| |
| // Finally, go ahead and make the access if we can... |
| DPRINTF(InOrderCachePort, |
| "[tid:%i] [sn:%i] attempting to access cache for addr %08p\n", |
| tid, inst->seqNum, cache_req->dataPkt->getAddr()); |
| |
| if (do_access) { |
| if (!cachePort->sendTimingReq(cache_req->dataPkt)) { |
| DPRINTF(InOrderCachePort, |
| "[tid:%i] [sn:%i] cannot access cache, because port " |
| "is blocked. now waiting to retry request\n", tid, |
| inst->seqNum); |
| delete cache_req->dataPkt; |
| cache_req->dataPkt = NULL; |
| |
| delete cache_req->memReq; |
| cache_req->memReq = NULL; |
| |
| cache_req->done(false); |
| cachePortBlocked = true; |
| } else { |
| DPRINTF(InOrderCachePort, |
| "[tid:%i] [sn:%i] is now waiting for cache response\n", |
| tid, inst->seqNum); |
| cache_req->setCompleted(); |
| cache_req->setMemAccPending(); |
| cachePortBlocked = false; |
| } |
| } else if (mem_req->isLLSC()){ |
| // Store-Conditional instructions complete even if they "failed" |
| assert(cache_req->inst->isStoreConditional()); |
| cache_req->setCompleted(true); |
| |
| DPRINTF(LLSC, |
| "[tid:%i]: T%i Ignoring Failed Store Conditional Access\n", |
| tid, tid); |
| |
| processCacheCompletion(cache_req->dataPkt); |
| } else { |
| delete cache_req->dataPkt; |
| cache_req->dataPkt = NULL; |
| |
| delete cache_req->memReq; |
| cache_req->memReq = NULL; |
| |
| // Make cache request again since access due to |
| // inability to access |
| DPRINTF(InOrderStall, "STALL: \n"); |
| cache_req->done(false); |
| } |
| |
| } |
| |
| bool |
| CacheUnit::processSquash(CacheReqPacket *cache_pkt) |
| { |
| // The resource may no longer be actively servicing this |
| // packet. Scenarios like a store that has been sent to the |
| // memory system or access that's been squashed. If that's |
| // the case, we can't access the request slot because it |
| // will be either invalid or servicing another request. |
| if (!cache_pkt->hasSlot) { |
| DPRINTF(InOrderCachePort, |
| "%x does not have a slot in unit, ignoring.\n", |
| cache_pkt->getAddr()); |
| |
| if (cache_pkt->reqData) { |
| delete [] cache_pkt->reqData; |
| cache_pkt->reqData = NULL; |
| } |
| |
| if (cache_pkt->memReq) { |
| delete cache_pkt->memReq; |
| cache_pkt->memReq = NULL; |
| } |
| |
| delete cache_pkt; |
| cache_pkt = NULL; |
| cpu->wakeCPU(); |
| return true; |
| } else { |
| DPRINTF(InOrderCachePort, "%x has slot %i\n", |
| cache_pkt->getAddr(), cache_pkt->cacheReq->getSlot()); |
| } |
| |
| |
| // It's possible that the request is squashed but the |
| // packet is still acknowledged by the resource. Squashes |
| // should happen at the end of the cycles and trigger the |
| // code above, but if not, this would handle any timing |
| // variations due to diff. user parameters. |
| if (cache_pkt->cacheReq->isSquashed()) { |
| DPRINTF(InOrderCachePort, |
| "Ignoring completion of squashed access, [tid:%i] [sn:%i]\n", |
| cache_pkt->cacheReq->getInst()->readTid(), |
| cache_pkt->cacheReq->getInst()->seqNum); |
| |
| cache_pkt->cacheReq->setMemAccPending(false); |
| cache_pkt->cacheReq->freeSlot(); |
| delete cache_pkt; |
| cache_pkt = NULL; |
| cpu->wakeCPU(); |
| return true; |
| } |
| |
| |
| return false; |
| } |
| |
| void |
| CacheUnit::processCacheCompletion(PacketPtr pkt) |
| { |
| //@todo: use packet sender state instead of deriving from packet class to |
| // get special state |
| CacheReqPacket* cache_pkt = dynamic_cast<CacheReqPacket*>(pkt); |
| assert(cache_pkt); |
| |
| DPRINTF(InOrderCachePort, "Finished request for %x\n", pkt->getAddr()); |
| |
| if (processSquash(cache_pkt)) |
| return; |
| |
| CacheRequest *cache_req = dynamic_cast<CacheReqPtr>( |
| findRequest(cache_pkt->cacheReq->getInst(), cache_pkt->instIdx)); |
| |
| if (!cache_req) { |
| panic("[tid:%u]: [sn:%i]: Can't find slot for cache access to " |
| "addr. %08p\n", cache_pkt->cacheReq->getInst()->readTid(), |
| cache_pkt->cacheReq->getInst()->seqNum, |
| cache_pkt->cacheReq->getInst()->getMemAddr()); |
| } |
| |
| assert(cache_req); |
| assert(cache_req == cache_pkt->cacheReq); |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%u]: [sn:%i]: [slot:%i] Waking from cache access (vaddr.%08p, paddr:%08p)\n", |
| cache_pkt->cacheReq->getInst()->readTid(), |
| cache_pkt->cacheReq->getInst()->seqNum, |
| cache_req->getSlot(), |
| cache_pkt->req->getVaddr(), |
| cache_pkt->req->getPaddr()); |
| |
| // Get resource request info |
| unsigned stage_num = cache_req->getStageNum(); |
| DynInstPtr inst = cache_req->inst; |
| ThreadID tid = cache_req->inst->readTid(); |
| |
| assert(!cache_req->isSquashed()); |
| assert(inst->staticInst && inst->isMemRef()); |
| |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%u]: [sn:%i]: Processing cache access\n", |
| tid, inst->seqNum); |
| |
| PacketPtr split_pkt = NULL; |
| if (inst->splitInst) { |
| inst->splitFinishCnt++; |
| |
| if (inst->splitFinishCnt == 2) { |
| cache_req->memReq->setVirt(0/*inst->tid*/, |
| inst->getMemAddr(), |
| inst->totalSize, |
| 0, |
| cpu->dataMasterId(), |
| 0); |
| |
| split_pkt = new Packet(cache_req->memReq, cache_req->pktCmd); |
| split_pkt->dataStatic(inst->splitMemData); |
| |
| DPRINTF(InOrderCachePort, "Completing Split Access.\n"); |
| inst->completeAcc(split_pkt); |
| } |
| } else { |
| inst->completeAcc(cache_pkt); |
| } |
| |
| inst->setExecuted(); |
| |
| if (inst->isLoad()) { |
| assert(cache_pkt->isRead()); |
| |
| if (cache_pkt->req->isLLSC()) { |
| DPRINTF(InOrderCachePort, |
| "[tid:%u]: Handling Load-Linked for [sn:%u]\n", |
| tid, inst->seqNum); |
| TheISA::handleLockedRead(inst.get(), cache_pkt->req); |
| } |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%u]: [sn:%i]: Bytes loaded were: %s\n", |
| tid, inst->seqNum, |
| (split_pkt) ? printMemData(split_pkt->getPtr<uint8_t>(), |
| split_pkt->getSize()) : |
| printMemData(cache_pkt->getPtr<uint8_t>(), |
| cache_pkt->getSize())); |
| } else if(inst->isStore()) { |
| assert(cache_pkt->isWrite()); |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%u]: [sn:%i]: Bytes stored were: %s\n", |
| tid, inst->seqNum, |
| (split_pkt) ? printMemData(split_pkt->getPtr<uint8_t>(), |
| split_pkt->getSize()) : |
| printMemData(cache_pkt->getPtr<uint8_t>(), |
| cache_pkt->getSize())); |
| } |
| |
| |
| if (split_pkt) { |
| delete split_pkt; |
| split_pkt = NULL; |
| } |
| |
| cache_req->setMemAccPending(false); |
| cache_req->setMemAccCompleted(); |
| |
| if (cache_req->isMemStall() && |
| cpu->threadModel == InOrderCPU::SwitchOnCacheMiss) { |
| DPRINTF(InOrderCachePort, "[tid:%u] Waking up from Cache Miss.\n", |
| tid); |
| |
| cpu->activateContext(tid); |
| |
| DPRINTF(ThreadModel, "Activating [tid:%i] after return from cache" |
| "miss.\n", tid); |
| } |
| |
| // Wake up the CPU (if it went to sleep and was waiting on this |
| // completion event). |
| cpu->wakeCPU(); |
| |
| DPRINTF(Activity, "[tid:%u] Activating %s due to cache completion\n", |
| tid, cpu->pipelineStage[stage_num]->name()); |
| |
| cpu->switchToActive(stage_num); |
| } |
| |
| void |
| CacheUnit::recvRetry() |
| { |
| DPRINTF(InOrderCachePort, "Unblocking Cache Port. \n"); |
| |
| assert(cachePortBlocked); |
| |
| // Clear the cache port for use again |
| cachePortBlocked = false; |
| |
| cpu->wakeCPU(); |
| } |
| |
| CacheUnitEvent::CacheUnitEvent() |
| : ResourceEvent() |
| { } |
| |
| void |
| CacheUnitEvent::process() |
| { |
| DynInstPtr inst = resource->reqs[slotIdx]->inst; |
| int stage_num = resource->reqs[slotIdx]->getStageNum(); |
| ThreadID tid = inst->threadNumber; |
| CacheReqPtr req_ptr = dynamic_cast<CacheReqPtr>(resource->reqs[slotIdx]); |
| |
| DPRINTF(InOrderTLB, "Waking up from TLB Miss caused by [sn:%i].\n", |
| inst->seqNum); |
| |
| CacheUnit* tlb_res = dynamic_cast<CacheUnit*>(resource); |
| assert(tlb_res); |
| |
| //@todo: eventually, we should do a timing translation w/ |
| // hw page table walk on tlb miss |
| DPRINTF(InOrderTLB, "Handling Fault %s : [sn:%i] %x\n", inst->fault->name(), inst->seqNum, inst->getMemAddr()); |
| inst->fault->invoke(tlb_res->cpu->tcBase(tid), inst->staticInst); |
| |
| tlb_res->tlbBlocked[tid] = false; |
| |
| tlb_res->cpu->pipelineStage[stage_num]-> |
| unsetResStall(tlb_res->reqs[slotIdx], tid); |
| |
| req_ptr->tlbStall = false; |
| |
| //@todo: timing translation needs to have some type of independent |
| // info regarding if it's squashed or not so we can |
| // free up the resource if a request gets squashed in the middle |
| // of a table walk |
| if (req_ptr->isSquashed()) { |
| req_ptr->freeSlot(); |
| } |
| |
| tlb_res->cpu->wakeCPU(); |
| } |
| |
| void |
| CacheUnit::squashDueToMemStall(DynInstPtr inst, int stage_num, |
| InstSeqNum squash_seq_num, ThreadID tid) |
| { |
| // If squashing due to memory stall, then we do NOT want to |
| // squash the instruction that caused the stall so we |
| // increment the sequence number here to prevent that. |
| // |
| // NOTE: This is only for the SwitchOnCacheMiss Model |
| // NOTE: If you have multiple outstanding misses from the same |
| // thread then you need to reevaluate this code |
| // NOTE: squash should originate from |
| // pipeline_stage.cc:processInstSchedule |
| DPRINTF(InOrderCachePort, "Squashing above [sn:%u]\n", |
| squash_seq_num + 1); |
| |
| squash(inst, stage_num, squash_seq_num + 1, tid); |
| } |
| |
| void |
| CacheUnit::squashCacheRequest(CacheReqPtr req_ptr) |
| { |
| DynInstPtr inst = req_ptr->getInst(); |
| req_ptr->setSquashed(); |
| inst->setSquashed(); |
| |
| //@note: add back in for speculative load/store capability |
| /*if (inst->validMemAddr()) { |
| DPRINTF(AddrDep, "Squash of [tid:%i] [sn:%i], attempting to " |
| "remove addr. %08p dependencies.\n", |
| inst->readTid(), |
| inst->seqNum, |
| inst->getMemAddr()); |
| |
| removeAddrDependency(inst); |
| }*/ |
| } |
| |
| |
| void |
| CacheUnit::squash(DynInstPtr inst, int stage_num, |
| InstSeqNum squash_seq_num, ThreadID tid) |
| { |
| if (tlbBlocked[tid] && |
| tlbBlockSeqNum[tid] > squash_seq_num) { |
| DPRINTF(InOrderCachePort, "Releasing TLB Block due to " |
| " squash after [sn:%i].\n", squash_seq_num); |
| tlbBlocked[tid] = false; |
| } |
| |
| for (int i = 0; i < width; i++) { |
| ResReqPtr req_ptr = reqs[i]; |
| |
| if (req_ptr->valid && |
| req_ptr->getInst()->readTid() == tid && |
| req_ptr->getInst()->seqNum > squash_seq_num) { |
| |
| DPRINTF(InOrderCachePort, |
| "[tid:%i] Squashing request from [sn:%i]\n", |
| req_ptr->getInst()->readTid(), req_ptr->getInst()->seqNum); |
| |
| if (req_ptr->isSquashed()) { |
| DPRINTF(AddrDep, "Request for [tid:%i] [sn:%i] already " |
| "squashed, ignoring squash process.\n", |
| req_ptr->getInst()->readTid(), |
| req_ptr->getInst()->seqNum); |
| continue; |
| } |
| |
| CacheReqPtr cache_req = dynamic_cast<CacheReqPtr>(req_ptr); |
| assert(cache_req); |
| |
| squashCacheRequest(cache_req); |
| |
| int req_slot_num = req_ptr->getSlot(); |
| |
| if (cache_req->tlbStall) { |
| tlbBlocked[tid] = false; |
| |
| int stall_stage = reqs[req_slot_num]->getStageNum(); |
| |
| cpu->pipelineStage[stall_stage]-> |
| unsetResStall(reqs[req_slot_num], tid); |
| } |
| |
| if (cache_req->isMemAccPending()) { |
| cache_req->dataPkt->reqData = cache_req->reqData; |
| cache_req->dataPkt->memReq = cache_req->memReq; |
| } |
| |
| if (!cache_req->tlbStall) |
| freeSlot(req_slot_num); |
| } |
| } |
| |
| } |
| |
| void |
| CacheRequest::clearRequest() |
| { |
| if (!memAccPending) { |
| if (reqData && !splitAccess) |
| delete [] reqData; |
| |
| if (memReq) |
| delete memReq; |
| |
| if (dataPkt) |
| delete dataPkt; |
| } else { |
| if (dataPkt) |
| dataPkt->hasSlot = false; |
| } |
| |
| memReq = NULL; |
| reqData = NULL; |
| dataPkt = NULL; |
| memAccComplete = false; |
| memAccPending = false; |
| tlbStall = false; |
| splitAccess = false; |
| splitAccessNum = -1; |
| split2ndAccess = false; |
| instIdx = 0; |
| fetchBufferFill = false; |
| |
| ResourceRequest::clearRequest(); |
| } |