/* * Copyright (c) 2013-2014 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. * * 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: Andrew Bardsley */ #include "cpu/minor/fetch1.hh" #include #include #include #include "base/cast.hh" #include "cpu/minor/pipeline.hh" #include "debug/Drain.hh" #include "debug/Fetch.hh" #include "debug/MinorTrace.hh" namespace Minor { Fetch1::Fetch1(const std::string &name_, MinorCPU &cpu_, MinorCPUParams ¶ms, Latch::Output inp_, Latch::Input out_, Latch::Output prediction_, std::vector> &next_stage_input_buffer) : Named(name_), cpu(cpu_), inp(inp_), out(out_), prediction(prediction_), nextStageReserve(next_stage_input_buffer), icachePort(name_ + ".icache_port", *this, cpu_), lineSnap(params.fetch1LineSnapWidth), maxLineWidth(params.fetch1LineWidth), fetchLimit(params.fetch1FetchLimit), fetchInfo(params.numThreads), threadPriority(0), requests(name_ + ".requests", "lines", params.fetch1FetchLimit), transfers(name_ + ".transfers", "lines", params.fetch1FetchLimit), icacheState(IcacheRunning), lineSeqNum(InstId::firstLineSeqNum), numFetchesInMemorySystem(0), numFetchesInITLB(0) { if (lineSnap == 0) { lineSnap = cpu.cacheLineSize(); DPRINTF(Fetch, "lineSnap set to cache line size of: %d\n", lineSnap); } if (maxLineWidth == 0) { maxLineWidth = cpu.cacheLineSize(); DPRINTF(Fetch, "maxLineWidth set to cache line size of: %d\n", maxLineWidth); } /* These assertions should be copied to the Python config. as well */ if ((lineSnap % sizeof(TheISA::MachInst)) != 0) { fatal("%s: fetch1LineSnapWidth must be a multiple " "of sizeof(TheISA::MachInst) (%d)\n", name_, sizeof(TheISA::MachInst)); } if (!(maxLineWidth >= lineSnap && (maxLineWidth % sizeof(TheISA::MachInst)) == 0)) { fatal("%s: fetch1LineWidth must be a multiple of" " sizeof(TheISA::MachInst)" " (%d), and >= fetch1LineSnapWidth (%d)\n", name_, sizeof(TheISA::MachInst), lineSnap); } if (fetchLimit < 1) { fatal("%s: fetch1FetchLimit must be >= 1 (%d)\n", name_, fetchLimit); } } inline ThreadID Fetch1::getScheduledThread() { /* Select thread via policy. */ std::vector priority_list; switch (cpu.threadPolicy) { case Enums::SingleThreaded: priority_list.push_back(0); break; case Enums::RoundRobin: priority_list = cpu.roundRobinPriority(threadPriority); break; case Enums::Random: priority_list = cpu.randomPriority(); break; default: panic("Unknown fetch policy"); } for (auto tid : priority_list) { if (cpu.getContext(tid)->status() == ThreadContext::Active && !fetchInfo[tid].blocked && fetchInfo[tid].state == FetchRunning) { threadPriority = tid; return tid; } } return InvalidThreadID; } void Fetch1::fetchLine(ThreadID tid) { /* Reference the currently used thread state. */ Fetch1ThreadInfo &thread = fetchInfo[tid]; /* If line_offset != 0, a request is pushed for the remainder of the * line. */ /* Use a lower, sizeof(MachInst) aligned address for the fetch */ Addr aligned_pc = thread.pc.instAddr() & ~((Addr) lineSnap - 1); unsigned int line_offset = aligned_pc % lineSnap; unsigned int request_size = maxLineWidth - line_offset; /* Fill in the line's id */ InstId request_id(tid, thread.streamSeqNum, thread.predictionSeqNum, lineSeqNum); FetchRequestPtr request = new FetchRequest(*this, request_id, thread.pc); DPRINTF(Fetch, "Inserting fetch into the fetch queue " "%s addr: 0x%x pc: %s line_offset: %d request_size: %d\n", request_id, aligned_pc, thread.pc, line_offset, request_size); request->request->setContext(cpu.threads[tid]->getTC()->contextId()); request->request->setVirt(0 /* asid */, aligned_pc, request_size, Request::INST_FETCH, cpu.instMasterId(), /* I've no idea why we need the PC, but give it */ thread.pc.instAddr()); DPRINTF(Fetch, "Submitting ITLB request\n"); numFetchesInITLB++; request->state = FetchRequest::InTranslation; /* Reserve space in the queues upstream of requests for results */ transfers.reserve(); requests.push(request); /* Submit the translation request. The response will come * through finish/markDelayed on this request as it bears * the Translation interface */ cpu.threads[request->id.threadId]->itb->translateTiming( request->request, cpu.getContext(request->id.threadId), request, BaseTLB::Execute); lineSeqNum++; /* Step the PC for the next line onto the line aligned next address. * Note that as instructions can span lines, this PC is only a * reliable 'new' PC if the next line has a new stream sequence number. */ #if THE_ISA == ALPHA_ISA /* Restore the low bits of the PC used as address space flags */ Addr pc_low_bits = thread.pc.instAddr() & ((Addr) (1 << sizeof(TheISA::MachInst)) - 1); thread.pc.set(aligned_pc + request_size + pc_low_bits); #else thread.pc.set(aligned_pc + request_size); #endif } std::ostream & operator <<(std::ostream &os, Fetch1::IcacheState state) { switch (state) { case Fetch1::IcacheRunning: os << "IcacheRunning"; break; case Fetch1::IcacheNeedsRetry: os << "IcacheNeedsRetry"; break; default: os << "IcacheState-" << static_cast(state); break; } return os; } void Fetch1::FetchRequest::makePacket() { /* Make the necessary packet for a memory transaction */ packet = new Packet(request, MemCmd::ReadReq); packet->allocate(); /* This FetchRequest becomes SenderState to allow the response to be * identified */ packet->pushSenderState(this); } void Fetch1::FetchRequest::finish(const Fault &fault_, const RequestPtr &request_, ThreadContext *tc, BaseTLB::Mode mode) { fault = fault_; state = Translated; fetch.handleTLBResponse(this); /* Let's try and wake up the processor for the next cycle */ fetch.cpu.wakeupOnEvent(Pipeline::Fetch1StageId); } void Fetch1::handleTLBResponse(FetchRequestPtr response) { numFetchesInITLB--; if (response->fault != NoFault) { DPRINTF(Fetch, "Fault in address ITLB translation: %s, " "paddr: 0x%x, vaddr: 0x%x\n", response->fault->name(), (response->request->hasPaddr() ? response->request->getPaddr() : 0), response->request->getVaddr()); if (DTRACE(MinorTrace)) minorTraceResponseLine(name(), response); } else { DPRINTF(Fetch, "Got ITLB response\n"); } response->state = FetchRequest::Translated; tryToSendToTransfers(response); } Fetch1::FetchRequest::~FetchRequest() { if (packet) delete packet; } void Fetch1::tryToSendToTransfers(FetchRequestPtr request) { if (!requests.empty() && requests.front() != request) { DPRINTF(Fetch, "Fetch not at front of requests queue, can't" " issue to memory\n"); return; } if (request->state == FetchRequest::InTranslation) { DPRINTF(Fetch, "Fetch still in translation, not issuing to" " memory\n"); return; } if (request->isDiscardable() || request->fault != NoFault) { /* Discarded and faulting requests carry on through transfers * as Complete/packet == NULL */ request->state = FetchRequest::Complete; moveFromRequestsToTransfers(request); /* Wake up the pipeline next cycle as there will be no event * for this queue->queue transfer */ cpu.wakeupOnEvent(Pipeline::Fetch1StageId); } else if (request->state == FetchRequest::Translated) { if (!request->packet) request->makePacket(); /* Ensure that the packet won't delete the request */ assert(request->packet->needsResponse()); if (tryToSend(request)) moveFromRequestsToTransfers(request); } else { DPRINTF(Fetch, "Not advancing line fetch\n"); } } void Fetch1::moveFromRequestsToTransfers(FetchRequestPtr request) { assert(!requests.empty() && requests.front() == request); requests.pop(); transfers.push(request); } bool Fetch1::tryToSend(FetchRequestPtr request) { bool ret = false; if (icachePort.sendTimingReq(request->packet)) { /* Invalidate the fetch_requests packet so we don't * accidentally fail to deallocate it (or use it!) * later by overwriting it */ request->packet = NULL; request->state = FetchRequest::RequestIssuing; numFetchesInMemorySystem++; ret = true; DPRINTF(Fetch, "Issued fetch request to memory: %s\n", request->id); } else { /* Needs to be resent, wait for that */ icacheState = IcacheNeedsRetry; DPRINTF(Fetch, "Line fetch needs to retry: %s\n", request->id); } return ret; } void Fetch1::stepQueues() { IcacheState old_icache_state = icacheState; switch (icacheState) { case IcacheRunning: /* Move ITLB results on to the memory system */ if (!requests.empty()) { tryToSendToTransfers(requests.front()); } break; case IcacheNeedsRetry: break; } if (icacheState != old_icache_state) { DPRINTF(Fetch, "Step in state %s moving to state %s\n", old_icache_state, icacheState); } } void Fetch1::popAndDiscard(FetchQueue &queue) { if (!queue.empty()) { delete queue.front(); queue.pop(); } } unsigned int Fetch1::numInFlightFetches() { return requests.occupiedSpace() + transfers.occupiedSpace(); } /** Print the appropriate MinorLine line for a fetch response */ void Fetch1::minorTraceResponseLine(const std::string &name, Fetch1::FetchRequestPtr response) const { const RequestPtr &request M5_VAR_USED = response->request; if (response->packet && response->packet->isError()) { MINORLINE(this, "id=F;%s vaddr=0x%x fault=\"error packet\"\n", response->id, request->getVaddr()); } else if (response->fault != NoFault) { MINORLINE(this, "id=F;%s vaddr=0x%x fault=\"%s\"\n", response->id, request->getVaddr(), response->fault->name()); } else { MINORLINE(this, "id=%s size=%d vaddr=0x%x paddr=0x%x\n", response->id, request->getSize(), request->getVaddr(), request->getPaddr()); } } bool Fetch1::recvTimingResp(PacketPtr response) { DPRINTF(Fetch, "recvTimingResp %d\n", numFetchesInMemorySystem); /* Only push the response if we didn't change stream? No, all responses * should hit the responses queue. It's the job of 'step' to throw them * away. */ FetchRequestPtr fetch_request = safe_cast (response->popSenderState()); /* Fixup packet in fetch_request as this may have changed */ assert(!fetch_request->packet); fetch_request->packet = response; numFetchesInMemorySystem--; fetch_request->state = FetchRequest::Complete; if (DTRACE(MinorTrace)) minorTraceResponseLine(name(), fetch_request); if (response->isError()) { DPRINTF(Fetch, "Received error response packet: %s\n", fetch_request->id); } /* We go to idle even if there are more things to do on the queues as * it's the job of step to actually step us on to the next transaction */ /* Let's try and wake up the processor for the next cycle to move on * queues */ cpu.wakeupOnEvent(Pipeline::Fetch1StageId); /* Never busy */ return true; } void Fetch1::recvReqRetry() { DPRINTF(Fetch, "recvRetry\n"); assert(icacheState == IcacheNeedsRetry); assert(!requests.empty()); FetchRequestPtr retryRequest = requests.front(); icacheState = IcacheRunning; if (tryToSend(retryRequest)) moveFromRequestsToTransfers(retryRequest); } std::ostream & operator <<(std::ostream &os, Fetch1::FetchState state) { switch (state) { case Fetch1::FetchHalted: os << "FetchHalted"; break; case Fetch1::FetchWaitingForPC: os << "FetchWaitingForPC"; break; case Fetch1::FetchRunning: os << "FetchRunning"; break; default: os << "FetchState-" << static_cast(state); break; } return os; } void Fetch1::changeStream(const BranchData &branch) { Fetch1ThreadInfo &thread = fetchInfo[branch.threadId]; updateExpectedSeqNums(branch); /* Start fetching again if we were stopped */ switch (branch.reason) { case BranchData::SuspendThread: { if (thread.wakeupGuard) { DPRINTF(Fetch, "Not suspending fetch due to guard: %s\n", branch); } else { DPRINTF(Fetch, "Suspending fetch: %s\n", branch); thread.state = FetchWaitingForPC; } } break; case BranchData::HaltFetch: DPRINTF(Fetch, "Halting fetch\n"); thread.state = FetchHalted; break; default: DPRINTF(Fetch, "Changing stream on branch: %s\n", branch); thread.state = FetchRunning; break; } thread.pc = branch.target; } void Fetch1::updateExpectedSeqNums(const BranchData &branch) { Fetch1ThreadInfo &thread = fetchInfo[branch.threadId]; DPRINTF(Fetch, "Updating streamSeqNum from: %d to %d," " predictionSeqNum from: %d to %d\n", thread.streamSeqNum, branch.newStreamSeqNum, thread.predictionSeqNum, branch.newPredictionSeqNum); /* Change the stream */ thread.streamSeqNum = branch.newStreamSeqNum; /* Update the prediction. Note that it's possible for this to * actually set the prediction to an *older* value if new * predictions have been discarded by execute */ thread.predictionSeqNum = branch.newPredictionSeqNum; } void Fetch1::processResponse(Fetch1::FetchRequestPtr response, ForwardLineData &line) { Fetch1ThreadInfo &thread = fetchInfo[response->id.threadId]; PacketPtr packet = response->packet; /* Pass the prefetch abort (if any) on to Fetch2 in a ForwardLineData * structure */ line.setFault(response->fault); /* Make sequence numbers valid in return */ line.id = response->id; /* Set PC to virtual address */ line.pc = response->pc; /* Set the lineBase, which is a sizeof(MachInst) aligned address <= * pc.instAddr() */ line.lineBaseAddr = response->request->getVaddr(); if (response->fault != NoFault) { /* Stop fetching if there was a fault */ /* Should probably try to flush the queues as well, but we * can't be sure that this fault will actually reach Execute, and we * can't (currently) selectively remove this stream from the queues */ DPRINTF(Fetch, "Stopping line fetch because of fault: %s\n", response->fault->name()); thread.state = Fetch1::FetchWaitingForPC; } else { line.adoptPacketData(packet); /* Null the response's packet to prevent the response from trying to * deallocate the packet */ response->packet = NULL; } } void Fetch1::evaluate() { const BranchData &execute_branch = *inp.outputWire; const BranchData &fetch2_branch = *prediction.outputWire; ForwardLineData &line_out = *out.inputWire; assert(line_out.isBubble()); for (ThreadID tid = 0; tid < cpu.numThreads; tid++) fetchInfo[tid].blocked = !nextStageReserve[tid].canReserve(); /** Are both branches from later stages valid and for the same thread? */ if (execute_branch.threadId != InvalidThreadID && execute_branch.threadId == fetch2_branch.threadId) { Fetch1ThreadInfo &thread = fetchInfo[execute_branch.threadId]; /* Are we changing stream? Look to the Execute branches first, then * to predicted changes of stream from Fetch2 */ if (execute_branch.isStreamChange()) { if (thread.state == FetchHalted) { DPRINTF(Fetch, "Halted, ignoring branch: %s\n", execute_branch); } else { changeStream(execute_branch); } if (!fetch2_branch.isBubble()) { DPRINTF(Fetch, "Ignoring simultaneous prediction: %s\n", fetch2_branch); } /* The streamSeqNum tagging in request/response ->req should handle * discarding those requests when we get to them. */ } else if (thread.state != FetchHalted && fetch2_branch.isStreamChange()) { /* Handle branch predictions by changing the instruction source * if we're still processing the same stream (as set by streamSeqNum) * as the one of the prediction. */ if (fetch2_branch.newStreamSeqNum != thread.streamSeqNum) { DPRINTF(Fetch, "Not changing stream on prediction: %s," " streamSeqNum mismatch\n", fetch2_branch); } else { changeStream(fetch2_branch); } } } else { /* Fetch2 and Execute branches are for different threads */ if (execute_branch.threadId != InvalidThreadID && execute_branch.isStreamChange()) { if (fetchInfo[execute_branch.threadId].state == FetchHalted) { DPRINTF(Fetch, "Halted, ignoring branch: %s\n", execute_branch); } else { changeStream(execute_branch); } } if (fetch2_branch.threadId != InvalidThreadID && fetch2_branch.isStreamChange()) { if (fetchInfo[fetch2_branch.threadId].state == FetchHalted) { DPRINTF(Fetch, "Halted, ignoring branch: %s\n", fetch2_branch); } else if (fetch2_branch.newStreamSeqNum != fetchInfo[fetch2_branch.threadId].streamSeqNum) { DPRINTF(Fetch, "Not changing stream on prediction: %s," " streamSeqNum mismatch\n", fetch2_branch); } else { changeStream(fetch2_branch); } } } if (numInFlightFetches() < fetchLimit) { ThreadID fetch_tid = getScheduledThread(); if (fetch_tid != InvalidThreadID) { DPRINTF(Fetch, "Fetching from thread %d\n", fetch_tid); /* Generate fetch to selected thread */ fetchLine(fetch_tid); /* Take up a slot in the fetch queue */ nextStageReserve[fetch_tid].reserve(); } else { DPRINTF(Fetch, "No active threads available to fetch from\n"); } } /* Halting shouldn't prevent fetches in flight from being processed */ /* Step fetches through the icachePort queues and memory system */ stepQueues(); /* As we've thrown away early lines, if there is a line, it must * be from the right stream */ if (!transfers.empty() && transfers.front()->isComplete()) { Fetch1::FetchRequestPtr response = transfers.front(); if (response->isDiscardable()) { nextStageReserve[response->id.threadId].freeReservation(); DPRINTF(Fetch, "Discarding translated fetch as it's for" " an old stream\n"); /* Wake up next cycle just in case there was some other * action to do */ cpu.wakeupOnEvent(Pipeline::Fetch1StageId); } else { DPRINTF(Fetch, "Processing fetched line: %s\n", response->id); processResponse(response, line_out); } popAndDiscard(transfers); } /* If we generated output, and mark the stage as being active * to encourage that output on to the next stage */ if (!line_out.isBubble()) cpu.activityRecorder->activity(); /* Fetch1 has no inputBuffer so the only activity we can have is to * generate a line output (tested just above) or to initiate a memory * fetch which will signal activity when it returns/needs stepping * between queues */ /* This looks hackish. And it is, but there doesn't seem to be a better * way to do this. The signal from commit to suspend fetch takes 1 * clock cycle to propagate to fetch. However, a legitimate wakeup * may occur between cycles from the memory system. Thus wakeup guard * prevents us from suspending in that case. */ for (auto& thread : fetchInfo) { thread.wakeupGuard = false; } } void Fetch1::wakeupFetch(ThreadID tid) { ThreadContext *thread_ctx = cpu.getContext(tid); Fetch1ThreadInfo &thread = fetchInfo[tid]; thread.pc = thread_ctx->pcState(); thread.state = FetchRunning; thread.wakeupGuard = true; DPRINTF(Fetch, "[tid:%d]: Changing stream wakeup %s\n", tid, thread_ctx->pcState()); cpu.wakeupOnEvent(Pipeline::Fetch1StageId); } bool Fetch1::isDrained() { bool drained = numInFlightFetches() == 0 && (*out.inputWire).isBubble(); for (ThreadID tid = 0; tid < cpu.numThreads; tid++) { Fetch1ThreadInfo &thread = fetchInfo[tid]; DPRINTF(Drain, "isDrained[tid:%d]: %s %s%s\n", tid, thread.state == FetchHalted, (numInFlightFetches() == 0 ? "" : "inFlightFetches "), ((*out.inputWire).isBubble() ? "" : "outputtingLine")); drained = drained && (thread.state != FetchRunning); } return drained; } void Fetch1::FetchRequest::reportData(std::ostream &os) const { os << id; } bool Fetch1::FetchRequest::isDiscardable() const { Fetch1ThreadInfo &thread = fetch.fetchInfo[id.threadId]; /* Can't discard lines in TLB/memory */ return state != InTranslation && state != RequestIssuing && (id.streamSeqNum != thread.streamSeqNum || id.predictionSeqNum != thread.predictionSeqNum); } void Fetch1::minorTrace() const { // TODO: Un-bork minorTrace for THREADS // bork bork bork const Fetch1ThreadInfo &thread = fetchInfo[0]; std::ostringstream data; if (thread.blocked) data << 'B'; else (*out.inputWire).reportData(data); MINORTRACE("state=%s icacheState=%s in_tlb_mem=%s/%s" " streamSeqNum=%d lines=%s\n", thread.state, icacheState, numFetchesInITLB, numFetchesInMemorySystem, thread.streamSeqNum, data.str()); requests.minorTrace(); transfers.minorTrace(); } }