/*
 * 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 &params,
    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.threads[0]->getTC()->contextId(),
                                      /* 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(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)
{
    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\n",
        state,
        (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();
}

}