| /* | |
| * 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 "arch/locked_mem.hh" | |
| #include "arch/registers.hh" | |
| #include "arch/utility.hh" | |
| #include "cpu/minor/cpu.hh" | |
| #include "cpu/minor/exec_context.hh" | |
| #include "cpu/minor/execute.hh" | |
| #include "cpu/minor/fetch1.hh" | |
| #include "cpu/minor/lsq.hh" | |
| #include "cpu/op_class.hh" | |
| #include "debug/Activity.hh" | |
| #include "debug/Branch.hh" | |
| #include "debug/Drain.hh" | |
| #include "debug/MinorExecute.hh" | |
| #include "debug/MinorInterrupt.hh" | |
| #include "debug/MinorMem.hh" | |
| #include "debug/MinorTrace.hh" | |
| #include "debug/PCEvent.hh" | |
| namespace Minor | |
| { | |
| Execute::Execute(const std::string &name_, | |
| MinorCPU &cpu_, | |
| MinorCPUParams ¶ms, | |
| Latch<ForwardInstData>::Output inp_, | |
| Latch<BranchData>::Input out_) : | |
| Named(name_), | |
| inp(inp_), | |
| out(out_), | |
| cpu(cpu_), | |
| issueLimit(params.executeIssueLimit), | |
| memoryIssueLimit(params.executeMemoryIssueLimit), | |
| commitLimit(params.executeCommitLimit), | |
| memoryCommitLimit(params.executeMemoryCommitLimit), | |
| processMoreThanOneInput(params.executeCycleInput), | |
| fuDescriptions(*params.executeFuncUnits), | |
| numFuncUnits(fuDescriptions.funcUnits.size()), | |
| setTraceTimeOnCommit(params.executeSetTraceTimeOnCommit), | |
| setTraceTimeOnIssue(params.executeSetTraceTimeOnIssue), | |
| allowEarlyMemIssue(params.executeAllowEarlyMemoryIssue), | |
| noCostFUIndex(fuDescriptions.funcUnits.size() + 1), | |
| lsq(name_ + ".lsq", name_ + ".dcache_port", | |
| cpu_, *this, | |
| params.executeMaxAccessesInMemory, | |
| params.executeMemoryWidth, | |
| params.executeLSQRequestsQueueSize, | |
| params.executeLSQTransfersQueueSize, | |
| params.executeLSQStoreBufferSize, | |
| params.executeLSQMaxStoreBufferStoresPerCycle), | |
| scoreboard(name_ + ".scoreboard"), | |
| inputBuffer(name_ + ".inputBuffer", "insts", | |
| params.executeInputBufferSize), | |
| inputIndex(0), | |
| lastCommitWasEndOfMacroop(true), | |
| instsBeingCommitted(params.executeCommitLimit), | |
| streamSeqNum(InstId::firstStreamSeqNum), | |
| lastPredictionSeqNum(InstId::firstPredictionSeqNum), | |
| drainState(NotDraining) | |
| { | |
| if (commitLimit < 1) { | |
| fatal("%s: executeCommitLimit must be >= 1 (%d)\n", name_, | |
| commitLimit); | |
| } | |
| if (issueLimit < 1) { | |
| fatal("%s: executeCommitLimit must be >= 1 (%d)\n", name_, | |
| issueLimit); | |
| } | |
| if (memoryIssueLimit < 1) { | |
| fatal("%s: executeMemoryIssueLimit must be >= 1 (%d)\n", name_, | |
| memoryIssueLimit); | |
| } | |
| if (memoryCommitLimit > commitLimit) { | |
| fatal("%s: executeMemoryCommitLimit (%d) must be <=" | |
| " executeCommitLimit (%d)\n", | |
| name_, memoryCommitLimit, commitLimit); | |
| } | |
| if (params.executeInputBufferSize < 1) { | |
| fatal("%s: executeInputBufferSize must be >= 1 (%d)\n", name_, | |
| params.executeInputBufferSize); | |
| } | |
| if (params.executeInputBufferSize < 1) { | |
| fatal("%s: executeInputBufferSize must be >= 1 (%d)\n", name_, | |
| params.executeInputBufferSize); | |
| } | |
| /* This should be large enough to count all the in-FU instructions | |
| * which need to be accounted for in the inFlightInsts | |
| * queue */ | |
| unsigned int total_slots = 0; | |
| /* Make FUPipelines for each MinorFU */ | |
| for (unsigned int i = 0; i < numFuncUnits; i++) { | |
| std::ostringstream fu_name; | |
| MinorFU *fu_description = fuDescriptions.funcUnits[i]; | |
| /* Note the total number of instruction slots (for sizing | |
| * the inFlightInst queue) and the maximum latency of any FU | |
| * (for sizing the activity recorder) */ | |
| total_slots += fu_description->opLat; | |
| fu_name << name_ << ".fu." << i; | |
| FUPipeline *fu = new FUPipeline(fu_name.str(), *fu_description, cpu); | |
| funcUnits.push_back(fu); | |
| } | |
| /** Check that there is a functional unit for all operation classes */ | |
| for (int op_class = No_OpClass + 1; op_class < Num_OpClass; op_class++) { | |
| bool found_fu = false; | |
| unsigned int fu_index = 0; | |
| while (fu_index < numFuncUnits && !found_fu) | |
| { | |
| if (funcUnits[fu_index]->provides( | |
| static_cast<OpClass>(op_class))) | |
| { | |
| found_fu = true; | |
| } | |
| fu_index++; | |
| } | |
| if (!found_fu) { | |
| warn("No functional unit for OpClass %s\n", | |
| Enums::OpClassStrings[op_class]); | |
| } | |
| } | |
| inFlightInsts = new Queue<QueuedInst, | |
| ReportTraitsAdaptor<QueuedInst> >( | |
| name_ + ".inFlightInsts", "insts", total_slots); | |
| inFUMemInsts = new Queue<QueuedInst, | |
| ReportTraitsAdaptor<QueuedInst> >( | |
| name_ + ".inFUMemInsts", "insts", total_slots); | |
| } | |
| const ForwardInstData * | |
| Execute::getInput() | |
| { | |
| /* Get a line from the inputBuffer to work with */ | |
| if (!inputBuffer.empty()) { | |
| const ForwardInstData &head = inputBuffer.front(); | |
| return (head.isBubble() ? NULL : &(inputBuffer.front())); | |
| } else { | |
| return NULL; | |
| } | |
| } | |
| void | |
| Execute::popInput() | |
| { | |
| if (!inputBuffer.empty()) | |
| inputBuffer.pop(); | |
| inputIndex = 0; | |
| } | |
| void | |
| Execute::tryToBranch(MinorDynInstPtr inst, Fault fault, BranchData &branch) | |
| { | |
| ThreadContext *thread = cpu.getContext(inst->id.threadId); | |
| const TheISA::PCState &pc_before = inst->pc; | |
| TheISA::PCState target = thread->pcState(); | |
| /* Force a branch for SerializeAfter instructions at the end of micro-op | |
| * sequence when we're not suspended */ | |
| bool force_branch = thread->status() != ThreadContext::Suspended && | |
| !inst->isFault() && | |
| inst->isLastOpInInst() && | |
| (inst->staticInst->isSerializeAfter() || | |
| inst->staticInst->isIprAccess()); | |
| DPRINTF(Branch, "tryToBranch before: %s after: %s%s\n", | |
| pc_before, target, (force_branch ? " (forcing)" : "")); | |
| /* Will we change the PC to something other than the next instruction? */ | |
| bool must_branch = pc_before != target || | |
| fault != NoFault || | |
| force_branch; | |
| /* The reason for the branch data we're about to generate, set below */ | |
| BranchData::Reason reason = BranchData::NoBranch; | |
| if (fault == NoFault) | |
| { | |
| TheISA::advancePC(target, inst->staticInst); | |
| thread->pcState(target); | |
| DPRINTF(Branch, "Advancing current PC from: %s to: %s\n", | |
| pc_before, target); | |
| } | |
| if (inst->predictedTaken && !force_branch) { | |
| /* Predicted to branch */ | |
| if (!must_branch) { | |
| /* No branch was taken, change stream to get us back to the | |
| * intended PC value */ | |
| DPRINTF(Branch, "Predicted a branch from 0x%x to 0x%x but" | |
| " none happened inst: %s\n", | |
| inst->pc.instAddr(), inst->predictedTarget.instAddr(), *inst); | |
| reason = BranchData::BadlyPredictedBranch; | |
| } else if (inst->predictedTarget == target) { | |
| /* Branch prediction got the right target, kill the branch and | |
| * carry on. | |
| * Note that this information to the branch predictor might get | |
| * overwritten by a "real" branch during this cycle */ | |
| DPRINTF(Branch, "Predicted a branch from 0x%x to 0x%x correctly" | |
| " inst: %s\n", | |
| inst->pc.instAddr(), inst->predictedTarget.instAddr(), *inst); | |
| reason = BranchData::CorrectlyPredictedBranch; | |
| } else { | |
| /* Branch prediction got the wrong target */ | |
| DPRINTF(Branch, "Predicted a branch from 0x%x to 0x%x" | |
| " but got the wrong target (actual: 0x%x) inst: %s\n", | |
| inst->pc.instAddr(), inst->predictedTarget.instAddr(), | |
| target.instAddr(), *inst); | |
| reason = BranchData::BadlyPredictedBranchTarget; | |
| } | |
| } else if (must_branch) { | |
| /* Unpredicted branch */ | |
| DPRINTF(Branch, "Unpredicted branch from 0x%x to 0x%x inst: %s\n", | |
| inst->pc.instAddr(), target.instAddr(), *inst); | |
| reason = BranchData::UnpredictedBranch; | |
| } else { | |
| /* No branch at all */ | |
| reason = BranchData::NoBranch; | |
| } | |
| updateBranchData(reason, inst, target, branch); | |
| } | |
| void | |
| Execute::updateBranchData( | |
| BranchData::Reason reason, | |
| MinorDynInstPtr inst, const TheISA::PCState &target, | |
| BranchData &branch) | |
| { | |
| if (reason != BranchData::NoBranch) { | |
| /* Bump up the stream sequence number on a real branch*/ | |
| if (BranchData::isStreamChange(reason)) | |
| streamSeqNum++; | |
| /* Branches (even mis-predictions) don't change the predictionSeqNum, | |
| * just the streamSeqNum */ | |
| branch = BranchData(reason, streamSeqNum, | |
| /* Maintaining predictionSeqNum if there's no inst is just a | |
| * courtesy and looks better on minorview */ | |
| (inst->isBubble() ? lastPredictionSeqNum | |
| : inst->id.predictionSeqNum), | |
| target, inst); | |
| DPRINTF(Branch, "Branch data signalled: %s\n", branch); | |
| } | |
| } | |
| void | |
| Execute::handleMemResponse(MinorDynInstPtr inst, | |
| LSQ::LSQRequestPtr response, BranchData &branch, Fault &fault) | |
| { | |
| ThreadID thread_id = inst->id.threadId; | |
| ThreadContext *thread = cpu.getContext(thread_id); | |
| ExecContext context(cpu, *cpu.threads[thread_id], *this, inst); | |
| PacketPtr packet = response->packet; | |
| bool is_load = inst->staticInst->isLoad(); | |
| bool is_store = inst->staticInst->isStore(); | |
| bool is_prefetch = inst->staticInst->isDataPrefetch(); | |
| /* If true, the trace's predicate value will be taken from the exec | |
| * context predicate, otherwise, it will be set to false */ | |
| bool use_context_predicate = true; | |
| if (response->fault != NoFault) { | |
| /* Invoke memory faults. */ | |
| DPRINTF(MinorMem, "Completing fault from DTLB access: %s\n", | |
| response->fault->name()); | |
| if (inst->staticInst->isPrefetch()) { | |
| DPRINTF(MinorMem, "Not taking fault on prefetch: %s\n", | |
| response->fault->name()); | |
| /* Don't assign to fault */ | |
| } else { | |
| /* Take the fault raised during the TLB/memory access */ | |
| fault = response->fault; | |
| fault->invoke(thread, inst->staticInst); | |
| } | |
| } else if (!packet) { | |
| DPRINTF(MinorMem, "Completing failed request inst: %s\n", | |
| *inst); | |
| use_context_predicate = false; | |
| } else if (packet->isError()) { | |
| DPRINTF(MinorMem, "Trying to commit error response: %s\n", | |
| *inst); | |
| fatal("Received error response packet for inst: %s\n", *inst); | |
| } else if (is_store || is_load || is_prefetch) { | |
| assert(packet); | |
| DPRINTF(MinorMem, "Memory response inst: %s addr: 0x%x size: %d\n", | |
| *inst, packet->getAddr(), packet->getSize()); | |
| if (is_load && packet->getSize() > 0) { | |
| DPRINTF(MinorMem, "Memory data[0]: 0x%x\n", | |
| static_cast<unsigned int>(packet->getConstPtr<uint8_t>()[0])); | |
| } | |
| /* Complete the memory access instruction */ | |
| fault = inst->staticInst->completeAcc(packet, &context, | |
| inst->traceData); | |
| if (fault != NoFault) { | |
| /* Invoke fault created by instruction completion */ | |
| DPRINTF(MinorMem, "Fault in memory completeAcc: %s\n", | |
| fault->name()); | |
| fault->invoke(thread, inst->staticInst); | |
| } else { | |
| /* Stores need to be pushed into the store buffer to finish | |
| * them off */ | |
| if (response->needsToBeSentToStoreBuffer()) | |
| lsq.sendStoreToStoreBuffer(response); | |
| } | |
| } else { | |
| fatal("There should only ever be reads, " | |
| "writes or faults at this point\n"); | |
| } | |
| lsq.popResponse(response); | |
| if (inst->traceData) { | |
| inst->traceData->setPredicate((use_context_predicate ? | |
| context.readPredicate() : false)); | |
| } | |
| doInstCommitAccounting(inst); | |
| /* Generate output to account for branches */ | |
| tryToBranch(inst, fault, branch); | |
| } | |
| bool | |
| Execute::isInterrupted(ThreadID thread_id) const | |
| { | |
| return cpu.checkInterrupts(cpu.getContext(thread_id)); | |
| } | |
| bool | |
| Execute::takeInterrupt(ThreadID thread_id, BranchData &branch) | |
| { | |
| DPRINTF(MinorInterrupt, "Considering interrupt status from PC: %s\n", | |
| cpu.getContext(thread_id)->pcState()); | |
| Fault interrupt = cpu.getInterruptController()->getInterrupt | |
| (cpu.getContext(thread_id)); | |
| if (interrupt != NoFault) { | |
| /* The interrupt *must* set pcState */ | |
| cpu.getInterruptController()->updateIntrInfo | |
| (cpu.getContext(thread_id)); | |
| interrupt->invoke(cpu.getContext(thread_id)); | |
| assert(!lsq.accessesInFlight()); | |
| DPRINTF(MinorInterrupt, "Invoking interrupt: %s to PC: %s\n", | |
| interrupt->name(), cpu.getContext(thread_id)->pcState()); | |
| /* Assume that an interrupt *must* cause a branch. Assert this? */ | |
| updateBranchData(BranchData::Interrupt, MinorDynInst::bubble(), | |
| cpu.getContext(thread_id)->pcState(), branch); | |
| } | |
| return interrupt != NoFault; | |
| } | |
| bool | |
| Execute::executeMemRefInst(MinorDynInstPtr inst, BranchData &branch, | |
| bool &passed_predicate, Fault &fault) | |
| { | |
| bool issued = false; | |
| /* Set to true if the mem op. is issued and sent to the mem system */ | |
| passed_predicate = false; | |
| if (!lsq.canRequest()) { | |
| /* Not acting on instruction yet as the memory | |
| * queues are full */ | |
| issued = false; | |
| } else { | |
| ThreadContext *thread = cpu.getContext(inst->id.threadId); | |
| TheISA::PCState old_pc = thread->pcState(); | |
| ExecContext context(cpu, *cpu.threads[inst->id.threadId], | |
| *this, inst); | |
| DPRINTF(MinorExecute, "Initiating memRef inst: %s\n", *inst); | |
| Fault init_fault = inst->staticInst->initiateAcc(&context, | |
| inst->traceData); | |
| if (init_fault != NoFault) { | |
| DPRINTF(MinorExecute, "Fault on memory inst: %s" | |
| " initiateAcc: %s\n", *inst, init_fault->name()); | |
| fault = init_fault; | |
| } else { | |
| /* Only set this if the instruction passed its | |
| * predicate */ | |
| passed_predicate = context.readPredicate(); | |
| /* Set predicate in tracing */ | |
| if (inst->traceData) | |
| inst->traceData->setPredicate(passed_predicate); | |
| /* If the instruction didn't pass its predicate (and so will not | |
| * progress from here) Try to branch to correct and branch | |
| * mis-prediction. */ | |
| if (!passed_predicate) { | |
| /* Leave it up to commit to handle the fault */ | |
| lsq.pushFailedRequest(inst); | |
| } | |
| } | |
| /* Restore thread PC */ | |
| thread->pcState(old_pc); | |
| issued = true; | |
| } | |
| return issued; | |
| } | |
| /** Increment a cyclic buffer index for indices [0, cycle_size-1] */ | |
| inline unsigned int | |
| cyclicIndexInc(unsigned int index, unsigned int cycle_size) | |
| { | |
| unsigned int ret = index + 1; | |
| if (ret == cycle_size) | |
| ret = 0; | |
| return ret; | |
| } | |
| /** Decrement a cyclic buffer index for indices [0, cycle_size-1] */ | |
| inline unsigned int | |
| cyclicIndexDec(unsigned int index, unsigned int cycle_size) | |
| { | |
| int ret = index - 1; | |
| if (ret < 0) | |
| ret = cycle_size - 1; | |
| return ret; | |
| } | |
| unsigned int | |
| Execute::issue(bool only_issue_microops) | |
| { | |
| const ForwardInstData *insts_in = getInput(); | |
| /* Early termination if we have no instructions */ | |
| if (!insts_in) | |
| return 0; | |
| /* Start from the first FU */ | |
| unsigned int fu_index = 0; | |
| /* Remains true while instructions are still being issued. If any | |
| * instruction fails to issue, this is set to false and we exit issue. | |
| * This strictly enforces in-order issue. For other issue behaviours, | |
| * a more complicated test in the outer while loop below is needed. */ | |
| bool issued = true; | |
| /* Number of insts issues this cycle to check for issueLimit */ | |
| unsigned num_insts_issued = 0; | |
| /* Number of memory ops issues this cycle to check for memoryIssueLimit */ | |
| unsigned num_mem_insts_issued = 0; | |
| /* Number of instructions discarded this cycle in order to enforce a | |
| * discardLimit. @todo, add that parameter? */ | |
| unsigned num_insts_discarded = 0; | |
| do { | |
| MinorDynInstPtr inst = insts_in->insts[inputIndex]; | |
| ThreadID thread_id = inst->id.threadId; | |
| Fault fault = inst->fault; | |
| bool discarded = false; | |
| bool issued_mem_ref = false; | |
| if (inst->isBubble()) { | |
| /* Skip */ | |
| issued = true; | |
| } else if (cpu.getContext(thread_id)->status() == | |
| ThreadContext::Suspended) | |
| { | |
| DPRINTF(MinorExecute, "Not issuing inst: %s from suspended" | |
| " thread\n", *inst); | |
| issued = false; | |
| } else if (inst->id.streamSeqNum != streamSeqNum) { | |
| DPRINTF(MinorExecute, "Discarding inst: %s as its stream" | |
| " state was unexpected, expected: %d\n", | |
| *inst, streamSeqNum); | |
| issued = true; | |
| discarded = true; | |
| } else if (fault == NoFault && only_issue_microops && | |
| /* Is this anything other than a non-first microop */ | |
| (!inst->staticInst->isMicroop() || | |
| !inst->staticInst->isFirstMicroop())) | |
| { | |
| DPRINTF(MinorExecute, "Not issuing new non-microop inst: %s\n", | |
| *inst); | |
| issued = false; | |
| } else { | |
| /* Try and issue an instruction into an FU, assume we didn't and | |
| * fix that in the loop */ | |
| issued = false; | |
| /* Try FU from 0 each instruction */ | |
| fu_index = 0; | |
| /* Try and issue a single instruction stepping through the | |
| * available FUs */ | |
| do { | |
| FUPipeline *fu = funcUnits[fu_index]; | |
| DPRINTF(MinorExecute, "Trying to issue inst: %s to FU: %d\n", | |
| *inst, fu_index); | |
| /* Does the examined fu have the OpClass-related capability | |
| * needed to execute this instruction? Faults can always | |
| * issue to any FU but probably should just 'live' in the | |
| * inFlightInsts queue rather than having an FU. */ | |
| bool fu_is_capable = (!inst->isFault() ? | |
| fu->provides(inst->staticInst->opClass()) : true); | |
| if (inst->isNoCostInst()) { | |
| /* Issue free insts. to a fake numbered FU */ | |
| fu_index = noCostFUIndex; | |
| /* And start the countdown on activity to allow | |
| * this instruction to get to the end of its FU */ | |
| cpu.activityRecorder->activity(); | |
| /* Mark the destinations for this instruction as | |
| * busy */ | |
| scoreboard.markupInstDests(inst, cpu.curCycle() + | |
| Cycles(0), cpu.getContext(thread_id), false); | |
| inst->fuIndex = noCostFUIndex; | |
| inst->extraCommitDelay = Cycles(0); | |
| inst->extraCommitDelayExpr = NULL; | |
| /* Push the instruction onto the inFlight queue so | |
| * it can be committed in order */ | |
| QueuedInst fu_inst(inst); | |
| inFlightInsts->push(fu_inst); | |
| issued = true; | |
| } else if (!fu_is_capable || fu->alreadyPushed()) { | |
| /* Skip */ | |
| if (!fu_is_capable) { | |
| DPRINTF(MinorExecute, "Can't issue as FU: %d isn't" | |
| " capable\n", fu_index); | |
| } else { | |
| DPRINTF(MinorExecute, "Can't issue as FU: %d is" | |
| " already busy\n", fu_index); | |
| } | |
| } else if (fu->stalled) { | |
| DPRINTF(MinorExecute, "Can't issue inst: %s into FU: %d," | |
| " it's stalled\n", | |
| *inst, fu_index); | |
| } else if (!fu->canInsert()) { | |
| DPRINTF(MinorExecute, "Can't issue inst: %s to busy FU" | |
| " for another: %d cycles\n", | |
| *inst, fu->cyclesBeforeInsert()); | |
| } else { | |
| MinorFUTiming *timing = (!inst->isFault() ? | |
| fu->findTiming(inst->staticInst) : NULL); | |
| const std::vector<Cycles> *src_latencies = | |
| (timing ? &(timing->srcRegsRelativeLats) | |
| : NULL); | |
| const std::vector<bool> *cant_forward_from_fu_indices = | |
| &(fu->cantForwardFromFUIndices); | |
| if (timing && timing->suppress) { | |
| DPRINTF(MinorExecute, "Can't issue inst: %s as extra" | |
| " decoding is suppressing it\n", | |
| *inst); | |
| } else if (!scoreboard.canInstIssue(inst, src_latencies, | |
| cant_forward_from_fu_indices, | |
| cpu.curCycle(), cpu.getContext(thread_id))) | |
| { | |
| DPRINTF(MinorExecute, "Can't issue inst: %s yet\n", | |
| *inst); | |
| } else { | |
| /* Can insert the instruction into this FU */ | |
| DPRINTF(MinorExecute, "Issuing inst: %s" | |
| " into FU %d\n", *inst, | |
| fu_index); | |
| Cycles extra_dest_retire_lat = Cycles(0); | |
| TimingExpr *extra_dest_retire_lat_expr = NULL; | |
| Cycles extra_assumed_lat = Cycles(0); | |
| /* Add the extraCommitDelay and extraAssumeLat to | |
| * the FU pipeline timings */ | |
| if (timing) { | |
| extra_dest_retire_lat = | |
| timing->extraCommitLat; | |
| extra_dest_retire_lat_expr = | |
| timing->extraCommitLatExpr; | |
| extra_assumed_lat = | |
| timing->extraAssumedLat; | |
| } | |
| issued_mem_ref = inst->isMemRef(); | |
| QueuedInst fu_inst(inst); | |
| /* Decorate the inst with FU details */ | |
| inst->fuIndex = fu_index; | |
| inst->extraCommitDelay = extra_dest_retire_lat; | |
| inst->extraCommitDelayExpr = | |
| extra_dest_retire_lat_expr; | |
| if (issued_mem_ref) { | |
| /* Remember which instruction this memory op | |
| * depends on so that initiateAcc can be called | |
| * early */ | |
| if (allowEarlyMemIssue) { | |
| inst->instToWaitFor = | |
| scoreboard.execSeqNumToWaitFor(inst, | |
| cpu.getContext(thread_id)); | |
| if (lsq.getLastMemBarrier() > | |
| inst->instToWaitFor) | |
| { | |
| DPRINTF(MinorExecute, "A barrier will" | |
| " cause a delay in mem ref issue of" | |
| " inst: %s until after inst" | |
| " %d(exec)\n", *inst, | |
| lsq.getLastMemBarrier()); | |
| inst->instToWaitFor = | |
| lsq.getLastMemBarrier(); | |
| } else { | |
| DPRINTF(MinorExecute, "Memory ref inst:" | |
| " %s must wait for inst %d(exec)" | |
| " before issuing\n", | |
| *inst, inst->instToWaitFor); | |
| } | |
| inst->canEarlyIssue = true; | |
| } | |
| /* Also queue this instruction in the memory ref | |
| * queue to ensure in-order issue to the LSQ */ | |
| DPRINTF(MinorExecute, "Pushing mem inst: %s\n", | |
| *inst); | |
| inFUMemInsts->push(fu_inst); | |
| } | |
| /* Issue to FU */ | |
| fu->push(fu_inst); | |
| /* And start the countdown on activity to allow | |
| * this instruction to get to the end of its FU */ | |
| cpu.activityRecorder->activity(); | |
| /* Mark the destinations for this instruction as | |
| * busy */ | |
| scoreboard.markupInstDests(inst, cpu.curCycle() + | |
| fu->description.opLat + | |
| extra_dest_retire_lat + | |
| extra_assumed_lat, | |
| cpu.getContext(thread_id), | |
| issued_mem_ref && extra_assumed_lat == Cycles(0)); | |
| /* Push the instruction onto the inFlight queue so | |
| * it can be committed in order */ | |
| inFlightInsts->push(fu_inst); | |
| issued = true; | |
| } | |
| } | |
| fu_index++; | |
| } while (fu_index != numFuncUnits && !issued); | |
| if (!issued) | |
| DPRINTF(MinorExecute, "Didn't issue inst: %s\n", *inst); | |
| } | |
| if (issued) { | |
| /* Generate MinorTrace's MinorInst lines. Do this at commit | |
| * to allow better instruction annotation? */ | |
| if (DTRACE(MinorTrace) && !inst->isBubble()) | |
| inst->minorTraceInst(*this); | |
| /* Mark up barriers in the LSQ */ | |
| if (!discarded && inst->isInst() && | |
| inst->staticInst->isMemBarrier()) | |
| { | |
| DPRINTF(MinorMem, "Issuing memory barrier inst: %s\n", *inst); | |
| lsq.issuedMemBarrierInst(inst); | |
| } | |
| if (inst->traceData && setTraceTimeOnIssue) { | |
| inst->traceData->setWhen(curTick()); | |
| } | |
| if (issued_mem_ref) | |
| num_mem_insts_issued++; | |
| if (discarded) { | |
| num_insts_discarded++; | |
| } else { | |
| num_insts_issued++; | |
| if (num_insts_issued == issueLimit) | |
| DPRINTF(MinorExecute, "Reached inst issue limit\n"); | |
| } | |
| inputIndex++; | |
| DPRINTF(MinorExecute, "Stepping to next inst inputIndex: %d\n", | |
| inputIndex); | |
| } | |
| /* Got to the end of a line */ | |
| if (inputIndex == insts_in->width()) { | |
| popInput(); | |
| /* Set insts_in to null to force us to leave the surrounding | |
| * loop */ | |
| insts_in = NULL; | |
| if (processMoreThanOneInput) { | |
| DPRINTF(MinorExecute, "Wrapping\n"); | |
| insts_in = getInput(); | |
| } | |
| } | |
| } while (insts_in && inputIndex < insts_in->width() && | |
| /* We still have instructions */ | |
| fu_index != numFuncUnits && /* Not visited all FUs */ | |
| issued && /* We've not yet failed to issue an instruction */ | |
| num_insts_issued != issueLimit && /* Still allowed to issue */ | |
| num_mem_insts_issued != memoryIssueLimit); | |
| return num_insts_issued; | |
| } | |
| bool | |
| Execute::tryPCEvents() | |
| { | |
| ThreadContext *thread = cpu.getContext(0); | |
| unsigned int num_pc_event_checks = 0; | |
| /* Handle PC events on instructions */ | |
| Addr oldPC; | |
| do { | |
| oldPC = thread->instAddr(); | |
| cpu.system->pcEventQueue.service(thread); | |
| num_pc_event_checks++; | |
| } while (oldPC != thread->instAddr()); | |
| if (num_pc_event_checks > 1) { | |
| DPRINTF(PCEvent, "Acting on PC Event to PC: %s\n", | |
| thread->pcState()); | |
| } | |
| return num_pc_event_checks > 1; | |
| } | |
| void | |
| Execute::doInstCommitAccounting(MinorDynInstPtr inst) | |
| { | |
| assert(!inst->isFault()); | |
| MinorThread *thread = cpu.threads[inst->id.threadId]; | |
| /* Increment the many and various inst and op counts in the | |
| * thread and system */ | |
| if (!inst->staticInst->isMicroop() || inst->staticInst->isLastMicroop()) | |
| { | |
| thread->numInst++; | |
| thread->numInsts++; | |
| cpu.stats.numInsts++; | |
| } | |
| thread->numOp++; | |
| thread->numOps++; | |
| cpu.stats.numOps++; | |
| cpu.system->totalNumInsts++; | |
| /* Act on events related to instruction counts */ | |
| cpu.comInstEventQueue[inst->id.threadId]->serviceEvents(thread->numInst); | |
| cpu.system->instEventQueue.serviceEvents(cpu.system->totalNumInsts); | |
| /* Set the CP SeqNum to the numOps commit number */ | |
| if (inst->traceData) | |
| inst->traceData->setCPSeq(thread->numOp); | |
| cpu.probeInstCommit(inst->staticInst); | |
| } | |
| bool | |
| Execute::commitInst(MinorDynInstPtr inst, bool early_memory_issue, | |
| BranchData &branch, Fault &fault, bool &committed, | |
| bool &completed_mem_issue) | |
| { | |
| ThreadID thread_id = inst->id.threadId; | |
| ThreadContext *thread = cpu.getContext(thread_id); | |
| bool completed_inst = true; | |
| fault = NoFault; | |
| /* Is the thread for this instruction suspended? In that case, just | |
| * stall as long as there are no pending interrupts */ | |
| if (thread->status() == ThreadContext::Suspended && | |
| !isInterrupted(thread_id)) | |
| { | |
| DPRINTF(MinorExecute, "Not committing inst from suspended thread" | |
| " inst: %s\n", *inst); | |
| completed_inst = false; | |
| } else if (inst->isFault()) { | |
| ExecContext context(cpu, *cpu.threads[thread_id], *this, inst); | |
| DPRINTF(MinorExecute, "Fault inst reached Execute: %s\n", | |
| inst->fault->name()); | |
| fault = inst->fault; | |
| inst->fault->invoke(thread, NULL); | |
| tryToBranch(inst, fault, branch); | |
| } else if (inst->staticInst->isMemRef()) { | |
| /* Memory accesses are executed in two parts: | |
| * executeMemRefInst -- calculates the EA and issues the access | |
| * to memory. This is done here. | |
| * handleMemResponse -- handles the response packet, done by | |
| * Execute::commit | |
| * | |
| * While the memory access is in its FU, the EA is being | |
| * calculated. At the end of the FU, when it is ready to | |
| * 'commit' (in this function), the access is presented to the | |
| * memory queues. When a response comes back from memory, | |
| * Execute::commit will commit it. | |
| */ | |
| bool predicate_passed = false; | |
| bool completed_mem_inst = executeMemRefInst(inst, branch, | |
| predicate_passed, fault); | |
| if (completed_mem_inst && fault != NoFault) { | |
| if (early_memory_issue) { | |
| DPRINTF(MinorExecute, "Fault in early executing inst: %s\n", | |
| fault->name()); | |
| /* Don't execute the fault, just stall the instruction | |
| * until it gets to the head of inFlightInsts */ | |
| inst->canEarlyIssue = false; | |
| /* Not completed as we'll come here again to pick up | |
| * the fault when we get to the end of the FU */ | |
| completed_inst = false; | |
| } else { | |
| DPRINTF(MinorExecute, "Fault in execute: %s\n", | |
| fault->name()); | |
| fault->invoke(thread, NULL); | |
| tryToBranch(inst, fault, branch); | |
| completed_inst = true; | |
| } | |
| } else { | |
| completed_inst = completed_mem_inst; | |
| } | |
| completed_mem_issue = completed_inst; | |
| } else if (inst->isInst() && inst->staticInst->isMemBarrier() && | |
| !lsq.canPushIntoStoreBuffer()) | |
| { | |
| DPRINTF(MinorExecute, "Can't commit data barrier inst: %s yet as" | |
| " there isn't space in the store buffer\n", *inst); | |
| completed_inst = false; | |
| } else { | |
| ExecContext context(cpu, *cpu.threads[thread_id], *this, inst); | |
| DPRINTF(MinorExecute, "Committing inst: %s\n", *inst); | |
| fault = inst->staticInst->execute(&context, | |
| inst->traceData); | |
| /* Set the predicate for tracing and dump */ | |
| if (inst->traceData) | |
| inst->traceData->setPredicate(context.readPredicate()); | |
| committed = true; | |
| if (fault != NoFault) { | |
| DPRINTF(MinorExecute, "Fault in execute of inst: %s fault: %s\n", | |
| *inst, fault->name()); | |
| fault->invoke(thread, inst->staticInst); | |
| } | |
| doInstCommitAccounting(inst); | |
| tryToBranch(inst, fault, branch); | |
| } | |
| if (completed_inst) { | |
| /* Keep a copy of this instruction's predictionSeqNum just in case | |
| * we need to issue a branch without an instruction (such as an | |
| * interrupt) */ | |
| lastPredictionSeqNum = inst->id.predictionSeqNum; | |
| /* Check to see if this instruction suspended the current thread. */ | |
| if (!inst->isFault() && | |
| thread->status() == ThreadContext::Suspended && | |
| branch.isBubble() && /* It didn't branch too */ | |
| !isInterrupted(thread_id)) /* Don't suspend if we have | |
| interrupts */ | |
| { | |
| TheISA::PCState resume_pc = cpu.getContext(0)->pcState(); | |
| assert(resume_pc.microPC() == 0); | |
| DPRINTF(MinorInterrupt, "Suspending thread: %d from Execute" | |
| " inst: %s\n", inst->id.threadId, *inst); | |
| cpu.stats.numFetchSuspends++; | |
| updateBranchData(BranchData::SuspendThread, inst, resume_pc, | |
| branch); | |
| } | |
| } | |
| return completed_inst; | |
| } | |
| void | |
| Execute::commit(bool only_commit_microops, bool discard, BranchData &branch) | |
| { | |
| Fault fault = NoFault; | |
| Cycles now = cpu.curCycle(); | |
| /** | |
| * Try and execute as many instructions from the end of FU pipelines as | |
| * possible. This *doesn't* include actually advancing the pipelines. | |
| * | |
| * We do this by looping on the front of the inFlightInsts queue for as | |
| * long as we can find the desired instruction at the end of the | |
| * functional unit it was issued to without seeing a branch or a fault. | |
| * In this function, these terms are used: | |
| * complete -- The instruction has finished its passage through | |
| * its functional unit and its fate has been decided | |
| * (committed, discarded, issued to the memory system) | |
| * commit -- The instruction is complete(d), not discarded and has | |
| * its effects applied to the CPU state | |
| * discard(ed) -- The instruction is complete but not committed | |
| * as its streamSeqNum disagrees with the current | |
| * Execute::streamSeqNum | |
| * | |
| * Commits are also possible from two other places: | |
| * | |
| * 1) Responses returning from the LSQ | |
| * 2) Mem ops issued to the LSQ ('committed' from the FUs) earlier | |
| * than their position in the inFlightInsts queue, but after all | |
| * their dependencies are resolved. | |
| */ | |
| /* Has an instruction been completed? Once this becomes false, we stop | |
| * trying to complete instructions. */ | |
| bool completed_inst = true; | |
| /* Number of insts committed this cycle to check against commitLimit */ | |
| unsigned int num_insts_committed = 0; | |
| /* Number of memory access instructions committed to check against | |
| * memCommitLimit */ | |
| unsigned int num_mem_refs_committed = 0; | |
| if (only_commit_microops && !inFlightInsts->empty()) { | |
| DPRINTF(MinorInterrupt, "Only commit microops %s %d\n", | |
| *(inFlightInsts->front().inst), | |
| lastCommitWasEndOfMacroop); | |
| } | |
| while (!inFlightInsts->empty() && /* Some more instructions to process */ | |
| !branch.isStreamChange() && /* No real branch */ | |
| fault == NoFault && /* No faults */ | |
| completed_inst && /* Still finding instructions to execute */ | |
| num_insts_committed != commitLimit /* Not reached commit limit */ | |
| ) | |
| { | |
| if (only_commit_microops) { | |
| DPRINTF(MinorInterrupt, "Committing tail of insts before" | |
| " interrupt: %s\n", | |
| *(inFlightInsts->front().inst)); | |
| } | |
| QueuedInst *head_inflight_inst = &(inFlightInsts->front()); | |
| InstSeqNum head_exec_seq_num = | |
| head_inflight_inst->inst->id.execSeqNum; | |
| /* The instruction we actually process if completed_inst | |
| * remains true to the end of the loop body. | |
| * Start by considering the the head of the in flight insts queue */ | |
| MinorDynInstPtr inst = head_inflight_inst->inst; | |
| bool committed_inst = false; | |
| bool discard_inst = false; | |
| bool completed_mem_ref = false; | |
| bool issued_mem_ref = false; | |
| bool early_memory_issue = false; | |
| /* Must set this again to go around the loop */ | |
| completed_inst = false; | |
| /* If we're just completing a macroop before an interrupt or drain, | |
| * can we stil commit another microop (rather than a memory response) | |
| * without crosing into the next full instruction? */ | |
| bool can_commit_insts = !inFlightInsts->empty() && | |
| !(only_commit_microops && lastCommitWasEndOfMacroop); | |
| /* Can we find a mem response for this inst */ | |
| LSQ::LSQRequestPtr mem_response = | |
| (inst->inLSQ ? lsq.findResponse(inst) : NULL); | |
| DPRINTF(MinorExecute, "Trying to commit canCommitInsts: %d\n", | |
| can_commit_insts); | |
| /* Test for PC events after every instruction */ | |
| if (isInbetweenInsts() && tryPCEvents()) { | |
| ThreadContext *thread = cpu.getContext(0); | |
| /* Branch as there was a change in PC */ | |
| updateBranchData(BranchData::UnpredictedBranch, | |
| MinorDynInst::bubble(), thread->pcState(), branch); | |
| } else if (mem_response && | |
| num_mem_refs_committed < memoryCommitLimit) | |
| { | |
| /* Try to commit from the memory responses next */ | |
| discard_inst = inst->id.streamSeqNum != streamSeqNum || | |
| discard; | |
| DPRINTF(MinorExecute, "Trying to commit mem response: %s\n", | |
| *inst); | |
| /* Complete or discard the response */ | |
| if (discard_inst) { | |
| DPRINTF(MinorExecute, "Discarding mem inst: %s as its" | |
| " stream state was unexpected, expected: %d\n", | |
| *inst, streamSeqNum); | |
| lsq.popResponse(mem_response); | |
| } else { | |
| handleMemResponse(inst, mem_response, branch, fault); | |
| committed_inst = true; | |
| } | |
| completed_mem_ref = true; | |
| completed_inst = true; | |
| } else if (can_commit_insts) { | |
| /* If true, this instruction will, subject to timing tweaks, | |
| * be considered for completion. try_to_commit flattens | |
| * the `if' tree a bit and allows other tests for inst | |
| * commit to be inserted here. */ | |
| bool try_to_commit = false; | |
| /* Try and issue memory ops early if they: | |
| * - Can push a request into the LSQ | |
| * - Have reached the end of their FUs | |
| * - Have had all their dependencies satisfied | |
| * - Are from the right stream | |
| * | |
| * For any other case, leave it to the normal instruction | |
| * issue below to handle them. | |
| */ | |
| if (!inFUMemInsts->empty() && lsq.canRequest()) { | |
| DPRINTF(MinorExecute, "Trying to commit from mem FUs\n"); | |
| const MinorDynInstPtr head_mem_ref_inst = | |
| inFUMemInsts->front().inst; | |
| FUPipeline *fu = funcUnits[head_mem_ref_inst->fuIndex]; | |
| const MinorDynInstPtr &fu_inst = fu->front().inst; | |
| /* Use this, possibly out of order, inst as the one | |
| * to 'commit'/send to the LSQ */ | |
| if (!fu_inst->isBubble() && | |
| !fu_inst->inLSQ && | |
| fu_inst->canEarlyIssue && | |
| streamSeqNum == fu_inst->id.streamSeqNum && | |
| head_exec_seq_num > fu_inst->instToWaitFor) | |
| { | |
| DPRINTF(MinorExecute, "Issuing mem ref early" | |
| " inst: %s instToWaitFor: %d\n", | |
| *(fu_inst), fu_inst->instToWaitFor); | |
| inst = fu_inst; | |
| try_to_commit = true; | |
| early_memory_issue = true; | |
| completed_inst = true; | |
| } | |
| } | |
| /* Try and commit FU-less insts */ | |
| if (!completed_inst && inst->isNoCostInst()) { | |
| DPRINTF(MinorExecute, "Committing no cost inst: %s", *inst); | |
| try_to_commit = true; | |
| completed_inst = true; | |
| } | |
| /* Try to issue from the ends of FUs and the inFlightInsts | |
| * queue */ | |
| if (!completed_inst && !inst->inLSQ) { | |
| DPRINTF(MinorExecute, "Trying to commit from FUs\n"); | |
| /* Try to commit from a functional unit */ | |
| /* Is the head inst of the expected inst's FU actually the | |
| * expected inst? */ | |
| QueuedInst &fu_inst = | |
| funcUnits[inst->fuIndex]->front(); | |
| InstSeqNum fu_inst_seq_num = fu_inst.inst->id.execSeqNum; | |
| if (fu_inst.inst->isBubble()) { | |
| /* No instruction ready */ | |
| completed_inst = false; | |
| } else if (fu_inst_seq_num != head_exec_seq_num) { | |
| /* Past instruction: we must have already executed it | |
| * in the same cycle and so the head inst isn't | |
| * actually at the end of its pipeline | |
| * Future instruction: handled above and only for | |
| * mem refs on their way to the LSQ */ | |
| } else /* if (fu_inst_seq_num == head_exec_seq_num) */ { | |
| /* All instructions can be committed if they have the | |
| * right execSeqNum and there are no in-flight | |
| * mem insts before us */ | |
| try_to_commit = true; | |
| completed_inst = true; | |
| } | |
| } | |
| if (try_to_commit) { | |
| discard_inst = inst->id.streamSeqNum != streamSeqNum || | |
| discard; | |
| /* Is this instruction discardable as its streamSeqNum | |
| * doesn't match? */ | |
| if (!discard_inst) { | |
| /* Try to commit or discard a non-memory instruction. | |
| * Memory ops are actually 'committed' from this FUs | |
| * and 'issued' into the memory system so we need to | |
| * account for them later (commit_was_mem_issue gets | |
| * set) */ | |
| if (inst->extraCommitDelayExpr) { | |
| DPRINTF(MinorExecute, "Evaluating expression for" | |
| " extra commit delay inst: %s\n", *inst); | |
| ThreadContext *thread = | |
| cpu.getContext(inst->id.threadId); | |
| TimingExprEvalContext context(inst->staticInst, | |
| thread, NULL); | |
| uint64_t extra_delay = inst->extraCommitDelayExpr-> | |
| eval(context); | |
| DPRINTF(MinorExecute, "Extra commit delay expr" | |
| " result: %d\n", extra_delay); | |
| if (extra_delay < 128) { | |
| inst->extraCommitDelay += Cycles(extra_delay); | |
| } else { | |
| DPRINTF(MinorExecute, "Extra commit delay was" | |
| " very long: %d\n", extra_delay); | |
| } | |
| inst->extraCommitDelayExpr = NULL; | |
| } | |
| /* Move the extraCommitDelay from the instruction | |
| * into the minimumCommitCycle */ | |
| if (inst->extraCommitDelay != Cycles(0)) { | |
| inst->minimumCommitCycle = cpu.curCycle() + | |
| inst->extraCommitDelay; | |
| inst->extraCommitDelay = Cycles(0); | |
| } | |
| /* @todo Think about making lastMemBarrier be | |
| * MAX_UINT_64 to avoid using 0 as a marker value */ | |
| if (!inst->isFault() && inst->isMemRef() && | |
| lsq.getLastMemBarrier() < | |
| inst->id.execSeqNum && | |
| lsq.getLastMemBarrier() != 0) | |
| { | |
| DPRINTF(MinorExecute, "Not committing inst: %s yet" | |
| " as there are incomplete barriers in flight\n", | |
| *inst); | |
| completed_inst = false; | |
| } else if (inst->minimumCommitCycle > now) { | |
| DPRINTF(MinorExecute, "Not committing inst: %s yet" | |
| " as it wants to be stalled for %d more cycles\n", | |
| *inst, inst->minimumCommitCycle - now); | |
| completed_inst = false; | |
| } else { | |
| completed_inst = commitInst(inst, | |
| early_memory_issue, branch, fault, | |
| committed_inst, issued_mem_ref); | |
| } | |
| } else { | |
| /* Discard instruction */ | |
| completed_inst = true; | |
| } | |
| if (completed_inst) { | |
| /* Allow the pipeline to advance. If the FU head | |
| * instruction wasn't the inFlightInsts head | |
| * but had already been committed, it would have | |
| * unstalled the pipeline before here */ | |
| if (inst->fuIndex != noCostFUIndex) | |
| funcUnits[inst->fuIndex]->stalled = false; | |
| } | |
| } | |
| } else { | |
| DPRINTF(MinorExecute, "No instructions to commit\n"); | |
| completed_inst = false; | |
| } | |
| /* All discardable instructions must also be 'completed' by now */ | |
| assert(!(discard_inst && !completed_inst)); | |
| /* Instruction committed but was discarded due to streamSeqNum | |
| * mismatch */ | |
| if (discard_inst) { | |
| DPRINTF(MinorExecute, "Discarding inst: %s as its stream" | |
| " state was unexpected, expected: %d\n", | |
| *inst, streamSeqNum); | |
| if (fault == NoFault) | |
| cpu.stats.numDiscardedOps++; | |
| } | |
| /* Mark the mem inst as being in the LSQ */ | |
| if (issued_mem_ref) { | |
| inst->fuIndex = 0; | |
| inst->inLSQ = true; | |
| } | |
| /* Pop issued (to LSQ) and discarded mem refs from the inFUMemInsts | |
| * as they've *definitely* exited the FUs */ | |
| if (completed_inst && inst->isMemRef()) { | |
| /* The MemRef could have been discarded from the FU or the memory | |
| * queue, so just check an FU instruction */ | |
| if (!inFUMemInsts->empty() && | |
| inFUMemInsts->front().inst == inst) | |
| { | |
| inFUMemInsts->pop(); | |
| } | |
| } | |
| if (completed_inst && !(issued_mem_ref && fault == NoFault)) { | |
| /* Note that this includes discarded insts */ | |
| DPRINTF(MinorExecute, "Completed inst: %s\n", *inst); | |
| /* Got to the end of a full instruction? */ | |
| lastCommitWasEndOfMacroop = inst->isFault() || | |
| inst->isLastOpInInst(); | |
| /* lastPredictionSeqNum is kept as a convenience to prevent its | |
| * value from changing too much on the minorview display */ | |
| lastPredictionSeqNum = inst->id.predictionSeqNum; | |
| /* Finished with the inst, remove it from the inst queue and | |
| * clear its dependencies */ | |
| inFlightInsts->pop(); | |
| /* Complete barriers in the LSQ/move to store buffer */ | |
| if (inst->isInst() && inst->staticInst->isMemBarrier()) { | |
| DPRINTF(MinorMem, "Completing memory barrier" | |
| " inst: %s committed: %d\n", *inst, committed_inst); | |
| lsq.completeMemBarrierInst(inst, committed_inst); | |
| } | |
| scoreboard.clearInstDests(inst, inst->isMemRef()); | |
| } | |
| /* Handle per-cycle instruction counting */ | |
| if (committed_inst) { | |
| bool is_no_cost_inst = inst->isNoCostInst(); | |
| /* Don't show no cost instructions as having taken a commit | |
| * slot */ | |
| if (DTRACE(MinorTrace) && !is_no_cost_inst) | |
| instsBeingCommitted.insts[num_insts_committed] = inst; | |
| if (!is_no_cost_inst) | |
| num_insts_committed++; | |
| if (num_insts_committed == commitLimit) | |
| DPRINTF(MinorExecute, "Reached inst commit limit\n"); | |
| /* Re-set the time of the instruction if that's required for | |
| * tracing */ | |
| if (inst->traceData) { | |
| if (setTraceTimeOnCommit) | |
| inst->traceData->setWhen(curTick()); | |
| inst->traceData->dump(); | |
| } | |
| if (completed_mem_ref) | |
| num_mem_refs_committed++; | |
| if (num_mem_refs_committed == memoryCommitLimit) | |
| DPRINTF(MinorExecute, "Reached mem ref commit limit\n"); | |
| } | |
| } | |
| } | |
| bool | |
| Execute::isInbetweenInsts() const | |
| { | |
| return lastCommitWasEndOfMacroop && | |
| !lsq.accessesInFlight(); | |
| } | |
| void | |
| Execute::evaluate() | |
| { | |
| inputBuffer.setTail(*inp.outputWire); | |
| BranchData &branch = *out.inputWire; | |
| const ForwardInstData *insts_in = getInput(); | |
| /* Do all the cycle-wise activities for dcachePort here to potentially | |
| * free up input spaces in the LSQ's requests queue */ | |
| lsq.step(); | |
| /* Has an interrupt been signalled? This may not be acted on | |
| * straighaway so this is different from took_interrupt below */ | |
| bool interrupted = false; | |
| /* If there was an interrupt signalled, was it acted on now? */ | |
| bool took_interrupt = false; | |
| if (cpu.getInterruptController()) { | |
| /* This is here because it seems that after drainResume the | |
| * interrupt controller isn't always set */ | |
| interrupted = drainState == NotDraining && isInterrupted(0); | |
| } else { | |
| DPRINTF(MinorInterrupt, "No interrupt controller\n"); | |
| } | |
| unsigned int num_issued = 0; | |
| if (DTRACE(MinorTrace)) { | |
| /* Empty the instsBeingCommitted for MinorTrace */ | |
| instsBeingCommitted.bubbleFill(); | |
| } | |
| /* THREAD threadId on isInterrupted */ | |
| /* Act on interrupts */ | |
| if (interrupted && isInbetweenInsts()) { | |
| took_interrupt = takeInterrupt(0, branch); | |
| /* Clear interrupted if no interrupt was actually waiting */ | |
| interrupted = took_interrupt; | |
| } | |
| if (took_interrupt) { | |
| /* Do no commit/issue this cycle */ | |
| } else if (!branch.isBubble()) { | |
| /* It's important that this is here to carry Fetch1 wakeups to Fetch1 | |
| * without overwriting them */ | |
| DPRINTF(MinorInterrupt, "Execute skipping a cycle to allow old" | |
| " branch to complete\n"); | |
| } else { | |
| if (interrupted) { | |
| if (inFlightInsts->empty()) { | |
| DPRINTF(MinorInterrupt, "Waiting but no insts\n"); | |
| } else { | |
| DPRINTF(MinorInterrupt, "Waiting for end of inst before" | |
| " signalling interrupt\n"); | |
| } | |
| } | |
| /* commit can set stalled flags observable to issue and so *must* be | |
| * called first */ | |
| if (drainState != NotDraining) { | |
| if (drainState == DrainCurrentInst) { | |
| /* Commit only micro-ops, don't kill anything else */ | |
| commit(true, false, branch); | |
| if (isInbetweenInsts()) | |
| setDrainState(DrainHaltFetch); | |
| /* Discard any generated branch */ | |
| branch = BranchData::bubble(); | |
| } else if (drainState == DrainAllInsts) { | |
| /* Kill all instructions */ | |
| while (getInput()) | |
| popInput(); | |
| commit(false, true, branch); | |
| } | |
| } else { | |
| /* Commit micro-ops only if interrupted. Otherwise, commit | |
| * anything you like */ | |
| commit(interrupted, false, branch); | |
| } | |
| /* This will issue merrily even when interrupted in the sure and | |
| * certain knowledge that the interrupt with change the stream */ | |
| if (insts_in) | |
| num_issued = issue(false); | |
| } | |
| /* Halt fetch, but don't do it until we have the current instruction in | |
| * the bag */ | |
| if (drainState == DrainHaltFetch) { | |
| updateBranchData(BranchData::HaltFetch, MinorDynInst::bubble(), | |
| TheISA::PCState(0), branch); | |
| cpu.wakeupOnEvent(Pipeline::ExecuteStageId); | |
| setDrainState(DrainAllInsts); | |
| } | |
| MinorDynInstPtr next_issuable_inst = NULL; | |
| bool can_issue_next = false; | |
| /* Find the next issuable instruction and see if it can be issued */ | |
| if (getInput()) { | |
| MinorDynInstPtr inst = getInput()->insts[inputIndex]; | |
| if (inst->isFault()) { | |
| can_issue_next = true; | |
| } else if (!inst->isBubble()) { | |
| if (cpu.getContext(inst->id.threadId)->status() != | |
| ThreadContext::Suspended) | |
| { | |
| next_issuable_inst = inst; | |
| } | |
| } | |
| } | |
| bool becoming_stalled = true; | |
| /* Advance the pipelines and note whether they still need to be | |
| * advanced */ | |
| for (unsigned int i = 0; i < numFuncUnits; i++) { | |
| FUPipeline *fu = funcUnits[i]; | |
| fu->advance(); | |
| /* If we need to go again, the pipeline will have been left or set | |
| * to be unstalled */ | |
| if (fu->occupancy != 0 && !fu->stalled) | |
| becoming_stalled = false; | |
| /* Could we possibly issue the next instruction? This is quite | |
| * an expensive test */ | |
| if (next_issuable_inst && !fu->stalled && | |
| scoreboard.canInstIssue(next_issuable_inst, | |
| NULL, NULL, cpu.curCycle() + Cycles(1), | |
| cpu.getContext(next_issuable_inst->id.threadId)) && | |
| fu->provides(next_issuable_inst->staticInst->opClass())) | |
| { | |
| can_issue_next = true; | |
| } | |
| } | |
| bool head_inst_might_commit = false; | |
| /* Could the head in flight insts be committed */ | |
| if (!inFlightInsts->empty()) { | |
| const QueuedInst &head_inst = inFlightInsts->front(); | |
| if (head_inst.inst->isNoCostInst()) { | |
| head_inst_might_commit = true; | |
| } else { | |
| FUPipeline *fu = funcUnits[head_inst.inst->fuIndex]; | |
| /* Head inst is commitable */ | |
| if ((fu->stalled && | |
| fu->front().inst->id == head_inst.inst->id) || | |
| lsq.findResponse(head_inst.inst)) | |
| { | |
| head_inst_might_commit = true; | |
| } | |
| } | |
| } | |
| DPRINTF(Activity, "Need to tick num issued insts: %s%s%s%s%s%s\n", | |
| (num_issued != 0 ? " (issued some insts)" : ""), | |
| (becoming_stalled ? " (becoming stalled)" : "(not becoming stalled)"), | |
| (can_issue_next ? " (can issued next inst)" : ""), | |
| (head_inst_might_commit ? "(head inst might commit)" : ""), | |
| (lsq.needsToTick() ? " (LSQ needs to tick)" : ""), | |
| (interrupted ? " (interrupted)" : "")); | |
| bool need_to_tick = | |
| num_issued != 0 || /* Issued some insts this cycle */ | |
| !becoming_stalled || /* Some FU pipelines can still move */ | |
| can_issue_next || /* Can still issue a new inst */ | |
| head_inst_might_commit || /* Could possible commit the next inst */ | |
| lsq.needsToTick() || /* Must step the dcache port */ | |
| interrupted; /* There are pending interrupts */ | |
| if (!need_to_tick) { | |
| DPRINTF(Activity, "The next cycle might be skippable as there are no" | |
| " advanceable FUs\n"); | |
| } | |
| /* Wake up if we need to tick again */ | |
| if (need_to_tick) | |
| cpu.wakeupOnEvent(Pipeline::ExecuteStageId); | |
| /* Note activity of following buffer */ | |
| if (!branch.isBubble()) | |
| cpu.activityRecorder->activity(); | |
| /* Make sure the input (if any left) is pushed */ | |
| inputBuffer.pushTail(); | |
| } | |
| void | |
| Execute::wakeupFetch(BranchData::Reason reason) | |
| { | |
| BranchData branch; | |
| assert(branch.isBubble()); | |
| /* THREAD thread id */ | |
| ThreadContext *thread = cpu.getContext(0); | |
| /* Force a branch to the current PC (which should be the next inst.) to | |
| * wake up Fetch1 */ | |
| if (!branch.isStreamChange() /* No real branch already happened */) { | |
| DPRINTF(MinorInterrupt, "Waking up Fetch (via Execute) by issuing" | |
| " a branch: %s\n", thread->pcState()); | |
| assert(thread->pcState().microPC() == 0); | |
| updateBranchData(reason, | |
| MinorDynInst::bubble(), thread->pcState(), branch); | |
| } else { | |
| DPRINTF(MinorInterrupt, "Already branching, no need for wakeup\n"); | |
| } | |
| *out.inputWire = branch; | |
| /* Make sure we get ticked */ | |
| cpu.wakeupOnEvent(Pipeline::ExecuteStageId); | |
| } | |
| void | |
| Execute::minorTrace() const | |
| { | |
| std::ostringstream insts; | |
| std::ostringstream stalled; | |
| instsBeingCommitted.reportData(insts); | |
| lsq.minorTrace(); | |
| inputBuffer.minorTrace(); | |
| scoreboard.minorTrace(); | |
| /* Report functional unit stalling in one string */ | |
| unsigned int i = 0; | |
| while (i < numFuncUnits) | |
| { | |
| stalled << (funcUnits[i]->stalled ? '1' : 'E'); | |
| i++; | |
| if (i != numFuncUnits) | |
| stalled << ','; | |
| } | |
| MINORTRACE("insts=%s inputIndex=%d streamSeqNum=%d" | |
| " stalled=%s drainState=%d isInbetweenInsts=%d\n", | |
| insts.str(), inputIndex, streamSeqNum, stalled.str(), drainState, | |
| isInbetweenInsts()); | |
| std::for_each(funcUnits.begin(), funcUnits.end(), | |
| std::mem_fun(&FUPipeline::minorTrace)); | |
| inFlightInsts->minorTrace(); | |
| inFUMemInsts->minorTrace(); | |
| } | |
| void | |
| Execute::drainResume() | |
| { | |
| DPRINTF(Drain, "MinorExecute drainResume\n"); | |
| setDrainState(NotDraining); | |
| /* Wakeup fetch and keep the pipeline running until that branch takes | |
| * effect */ | |
| wakeupFetch(BranchData::WakeupFetch); | |
| cpu.wakeupOnEvent(Pipeline::ExecuteStageId); | |
| } | |
| std::ostream &operator <<(std::ostream &os, Execute::DrainState state) | |
| { | |
| switch (state) | |
| { | |
| case Execute::NotDraining: | |
| os << "NotDraining"; | |
| break; | |
| case Execute::DrainCurrentInst: | |
| os << "DrainCurrentInst"; | |
| break; | |
| case Execute::DrainHaltFetch: | |
| os << "DrainHaltFetch"; | |
| break; | |
| case Execute::DrainAllInsts: | |
| os << "DrainAllInsts"; | |
| break; | |
| default: | |
| os << "Drain-" << static_cast<int>(state); | |
| break; | |
| } | |
| return os; | |
| } | |
| void | |
| Execute::setDrainState(DrainState state) | |
| { | |
| DPRINTF(Drain, "setDrainState: %s\n", state); | |
| drainState = state; | |
| } | |
| unsigned int | |
| Execute::drain() | |
| { | |
| DPRINTF(Drain, "MinorExecute drain\n"); | |
| if (drainState == NotDraining) { | |
| cpu.wakeupOnEvent(Pipeline::ExecuteStageId); | |
| /* Go to DrainCurrentInst if we're between microops | |
| * or waiting on an unbufferable memory operation. | |
| * Otherwise we can go straight to DrainHaltFetch | |
| */ | |
| if (isInbetweenInsts()) | |
| setDrainState(DrainHaltFetch); | |
| else | |
| setDrainState(DrainCurrentInst); | |
| } | |
| return (isDrained() ? 0 : 1); | |
| } | |
| bool | |
| Execute::isDrained() | |
| { | |
| return drainState == DrainAllInsts && | |
| inputBuffer.empty() && | |
| inFlightInsts->empty() && | |
| lsq.isDrained(); | |
| } | |
| Execute::~Execute() | |
| { | |
| for (unsigned int i = 0; i < numFuncUnits; i++) | |
| delete funcUnits[i]; | |
| delete inFlightInsts; | |
| } | |
| bool | |
| Execute::instIsRightStream(MinorDynInstPtr inst) | |
| { | |
| return inst->id.streamSeqNum == streamSeqNum; | |
| } | |
| bool | |
| Execute::instIsHeadInst(MinorDynInstPtr inst) | |
| { | |
| bool ret = false; | |
| if (!inFlightInsts->empty()) | |
| ret = inFlightInsts->front().inst->id == inst->id; | |
| return ret; | |
| } | |
| MinorCPU::MinorCPUPort & | |
| Execute::getDcachePort() | |
| { | |
| return lsq.getDcachePort(); | |
| } | |
| } |