diff options
Diffstat (limited to 'src/cpu/minor/fetch1.cc')
-rw-r--r-- | src/cpu/minor/fetch1.cc | 676 |
1 files changed, 676 insertions, 0 deletions
diff --git a/src/cpu/minor/fetch1.cc b/src/cpu/minor/fetch1.cc new file mode 100644 index 000000000..45dc5eddc --- /dev/null +++ b/src/cpu/minor/fetch1.cc @@ -0,0 +1,676 @@ +/* + * 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 <cstring> +#include <iomanip> +#include <sstream> + +#include "base/cast.hh" +#include "cpu/minor/fetch1.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<BranchData>::Output inp_, + Latch<ForwardLineData>::Input out_, + Latch<BranchData>::Output prediction_, + Reservable &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), + state(FetchWaitingForPC), + pc(0), + streamSeqNum(InstId::firstStreamSeqNum), + predictionSeqNum(InstId::firstPredictionSeqNum), + blocked(false), + 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); + } +} + +void +Fetch1::fetchLine() +{ + /* 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 = 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(0 /* thread */, + streamSeqNum, predictionSeqNum, + lineSeqNum); + + FetchRequestPtr request = new FetchRequest(*this, request_id, 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, pc, line_offset, request_size); + + request->request.setThreadContext(cpu.cpuId(), /* thread id */ 0); + 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 */ + 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 = pc.instAddr() & + ((Addr) (1 << sizeof(TheISA::MachInst)) - 1); + + pc.set(aligned_pc + request_size + pc_low_bits); +#else + 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<int>(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( + Fault fault_, 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 +{ + Request &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<FetchRequestPtr> + (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::recvRetry() +{ + 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<int>(state); + break; + } + return os; +} + +void +Fetch1::changeStream(const BranchData &branch) +{ + updateExpectedSeqNums(branch); + + /* Start fetching again if we were stopped */ + switch (branch.reason) { + case BranchData::SuspendThread: + DPRINTF(Fetch, "Suspending fetch: %s\n", branch); + state = FetchWaitingForPC; + break; + case BranchData::HaltFetch: + DPRINTF(Fetch, "Halting fetch\n"); + state = FetchHalted; + break; + default: + DPRINTF(Fetch, "Changing stream on branch: %s\n", branch); + state = FetchRunning; + break; + } + pc = branch.target; +} + +void +Fetch1::updateExpectedSeqNums(const BranchData &branch) +{ + DPRINTF(Fetch, "Updating streamSeqNum from: %d to %d," + " predictionSeqNum from: %d to %d\n", + streamSeqNum, branch.newStreamSeqNum, + predictionSeqNum, branch.newPredictionSeqNum); + + /* Change the stream */ + 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 */ + predictionSeqNum = branch.newPredictionSeqNum; +} + +void +Fetch1::processResponse(Fetch1::FetchRequestPtr response, + ForwardLineData &line) +{ + 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()); + 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()); + + blocked = !nextStageReserve.canReserve(); + + /* Are we changing stream? Look to the Execute branches first, then + * to predicted changes of stream from Fetch2 */ + /* @todo, find better way to express ignoring branch predictions */ + if (execute_branch.isStreamChange() && + execute_branch.reason != BranchData::BranchPrediction) + { + if (state == FetchHalted) { + if (execute_branch.reason == BranchData::WakeupFetch) { + DPRINTF(Fetch, "Waking up fetch: %s\n", execute_branch); + changeStream(execute_branch); + } else { + 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 (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 != streamSeqNum) { + DPRINTF(Fetch, "Not changing stream on prediction: %s," + " streamSeqNum mismatch\n", + fetch2_branch); + } else { + changeStream(fetch2_branch); + } + } + + /* Can we fetch? */ + /* The bare minimum requirements for initiating a fetch */ + /* THREAD need to handle multiple threads */ + if (state == FetchRunning && /* We are actually fetching */ + !blocked && /* Space in the Fetch2 inputBuffer */ + /* The thread we're going to fetch for (thread 0), is active */ + cpu.getContext(0)->status() == ThreadContext::Active && + numInFlightFetches() < fetchLimit) + { + fetchLine(); + /* Take up a slot in the fetch queue */ + nextStageReserve.reserve(); + } + + /* 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.freeReservation(); + + DPRINTF(Fetch, "Discarding translated fetch at 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 */ +} + +bool +Fetch1::isDrained() +{ + DPRINTF(Drain, "isDrained %s %s%s%s\n", + state == FetchHalted, + (numInFlightFetches() == 0 ? "" : "inFlightFetches "), + ((*out.inputWire).isBubble() ? "" : "outputtingLine")); + + return state == FetchHalted && + numInFlightFetches() == 0 && + (*out.inputWire).isBubble(); +} + +void +Fetch1::FetchRequest::reportData(std::ostream &os) const +{ + os << id; +} + +bool Fetch1::FetchRequest::isDiscardable() const +{ + /* Can't discard lines in TLB/memory */ + return state != InTranslation && state != RequestIssuing && + (id.streamSeqNum != fetch.streamSeqNum || + id.predictionSeqNum != fetch.predictionSeqNum); +} + +void +Fetch1::minorTrace() const +{ + std::ostringstream data; + + if (blocked) + data << 'B'; + else + (*out.inputWire).reportData(data); + + MINORTRACE("state=%s icacheState=%s in_tlb_mem=%s/%s" + " streamSeqNum=%d lines=%s\n", state, icacheState, + numFetchesInITLB, numFetchesInMemorySystem, + streamSeqNum, data.str()); + requests.minorTrace(); + transfers.minorTrace(); +} + +} |