gem5/src/cpu/minor/fetch1.cc

782 lines
24 KiB
C++

/*
* 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 <cstring>
#include <iomanip>
#include <sstream>
#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 &params,
Latch<BranchData>::Output inp_,
Latch<ForwardLineData>::Input out_,
Latch<BranchData>::Output prediction_,
std::vector<InputBuffer<ForwardLineData>> &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<ThreadID> 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<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(const 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::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<int>(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();
}
}