/*
 * Copyright (c) 2007 MIPS Technologies, Inc.
 * All rights reserved.
 *
 * 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: Korey Sewell
 *
 */

#include <list>
#include <vector>

#include "arch/isa_traits.hh"
#include "config/the_isa.hh"
#include "cpu/inorder/resources/use_def.hh"
#include "cpu/inorder/cpu.hh"
#include "cpu/inorder/pipeline_traits.hh"
#include "debug/InOrderStall.hh"
#include "debug/InOrderUseDef.hh"

using namespace std;
using namespace TheISA;
using namespace ThePipeline;

UseDefUnit::UseDefUnit(string res_name, int res_id, int res_width,
                       Cycles res_latency, InOrderCPU *_cpu,
                       ThePipeline::Params *params)
    : Resource(res_name, res_id, res_width, res_latency, _cpu)
{
    for (ThreadID tid = 0; tid < ThePipeline::MaxThreads; tid++) {
        nonSpecInstActive[tid] = &cpu->nonSpecInstActive[tid];
        nonSpecSeqNum[tid] = &cpu->nonSpecSeqNum[tid];
        serializeOnNextInst[tid] =  false;
        serializeAfterSeqNum[tid] = 0;
        regDepMap[tid] = &cpu->archRegDepMap[tid];
    }

}

void
UseDefUnit::regStats()
{
    uniqueRegsPerSwitch
        .name(name() + ".uniqueRegsPerSwitch")
        .desc("Number of Unique Registers Needed Per Context Switch")
        .prereq(uniqueRegsPerSwitch);

    intRegFileReads
        .name(name() + ".intRegFileReads")
        .desc("Number of Reads from Int. Register File");

    intRegFileWrites
        .name(name() + ".intRegFileWrites")
        .desc("Number of Writes to Int. Register File");

    intRegFileAccs
        .name(name() + ".intRegFileAccesses")
        .desc("Total Accesses (Read+Write) to the Int. Register File");
    intRegFileAccs = intRegFileReads + intRegFileWrites;

    floatRegFileReads
        .name(name() + ".floatRegFileReads")
        .desc("Number of Reads from FP Register File");

    floatRegFileWrites
        .name(name() + ".floatRegFileWrites")
        .desc("Number of Writes to FP Register File");

    floatRegFileAccs
        .name(name() + ".floatRegFileAccesses")
        .desc("Total Accesses (Read+Write) to the FP Register File");
    floatRegFileAccs = floatRegFileReads + floatRegFileWrites;

    //@todo: add miscreg reads/writes
    //       add forwarding by type???

    regForwards
        .name(name() + ".regForwards")
        .desc("Number of Registers Read Through Forwarding Logic");
    
    Resource::regStats();
}

void
UseDefUnit::init()
{
    // Set Up Resource Events to Appropriate Resource BandWidth
    if (latency > Cycles(0)) {
        resourceEvent = new ResourceEvent[width];
    } else {
        resourceEvent = NULL;
    }

    for (int i = 0; i < width; i++) {
        reqs[i] = new UseDefRequest(this);
    }

    initSlots();
}

ResReqPtr
UseDefUnit::getRequest(DynInstPtr inst, int stage_num, int res_idx,
                     int slot_num, unsigned cmd)
{
    UseDefRequest *ud_req = dynamic_cast<UseDefRequest*>(reqs[slot_num]);
    ud_req->setRequest(inst, stage_num, id, slot_num, cmd,
                       inst->curSkedEntry->idx);
    return ud_req;
}


ResReqPtr
UseDefUnit::findRequest(DynInstPtr inst)
{
    for (int i = 0; i < width; i++) {
        UseDefRequest* ud_req =
            dynamic_cast<UseDefRequest*>(reqs[i]);
        assert(ud_req);

        if (ud_req->valid &&
            ud_req->getInst() == inst &&
            ud_req->cmd == inst->curSkedEntry->cmd &&
            ud_req->useDefIdx == inst->curSkedEntry->idx) {
            return ud_req;
        }
    }

    return NULL;
}

void
UseDefUnit::execute(int slot_idx)
{
    UseDefRequest* ud_req = dynamic_cast<UseDefRequest*>(reqs[slot_idx]);
    DynInstPtr inst = ud_req->inst;
    ThreadID tid = inst->readTid();
    InstSeqNum seq_num = inst->seqNum;
    int ud_idx = ud_req->useDefIdx;

    if (serializeOnNextInst[tid] &&
        seq_num > serializeAfterSeqNum[tid]) {
        inst->setSerializeBefore();
        serializeOnNextInst[tid] = false;
    }

    if ((inst->isIprAccess() || inst->isSerializeBefore()) &&
        cpu->instList[tid].front() != inst) {
        DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i] Serialize before instruction encountered."
                " Blocking until pipeline is clear.\n", tid, seq_num);
        ud_req->done(false);
        return;
    } else if (inst->isStoreConditional() || inst->isSerializeAfter()) {
        DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i] Serialize after instruction encountered."
                " Blocking until pipeline is clear.\n", tid, seq_num);
        serializeOnNextInst[tid] = true;
        serializeAfterSeqNum[tid] = seq_num;
    }

    if (inst->fault != NoFault) {
        DPRINTF(InOrderUseDef,
                "[tid:%i]: [sn:%i]: Detected %s fault @ %x. Forwarding to "
                "next stage.\n", inst->readTid(), inst->seqNum, inst->fault->name(),
                inst->pcState());
        ud_req->done();
        return;
    }

    // If there is a non-speculative instruction
    // in the pipeline then stall instructions here
    // ---
    if (*nonSpecInstActive[tid] == true && seq_num > *nonSpecSeqNum[tid]) {
        DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i] cannot execute because"
                "there is non-speculative instruction [sn:%i] has not "
                "graduated.\n", tid, seq_num, *nonSpecSeqNum[tid]);
        ud_req->done(false);
        return;
    } else if (inst->isNonSpeculative()) {
        *nonSpecInstActive[tid] = true;
        *nonSpecSeqNum[tid] = seq_num;
    }

    switch (ud_req->cmd)
    {
      case ReadSrcReg:
        {
            InOrderCPU::RegType reg_type;
            RegIndex reg_idx = inst->_srcRegIdx[ud_idx];
            RegIndex flat_idx = cpu->flattenRegIdx(reg_idx, reg_type, tid);
            inst->flattenSrcReg(ud_idx, flat_idx);
            
            if (flat_idx == TheISA::ZeroReg && reg_type == InOrderCPU::IntType) {
                DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Ignoring Reading of ISA-ZeroReg "
                        "(Int. Reg %i).\n", tid, inst->seqNum, flat_idx);
                ud_req->done();
                return;
            } else {
                DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Attempting to read source "
                        "register idx %i (reg #%i, flat#%i).\n",
                        tid, seq_num, ud_idx, reg_idx, flat_idx);
            }

            if (regDepMap[tid]->canRead(reg_type, flat_idx, inst)) {
                switch (reg_type)
                {
                  case InOrderCPU::IntType:
                    {
                        uniqueIntRegMap[flat_idx] = true;

                        DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Reading Int Reg %i"
                                " (%i) from Register File:0x%x.\n",
                                tid, seq_num,
                                reg_idx, flat_idx,
                                cpu->readIntReg(flat_idx,inst->readTid()));
                        inst->setIntSrc(ud_idx,
                                        cpu->readIntReg(flat_idx,
                                                        inst->readTid()));
                        intRegFileReads++;
                    }
                    break;

                  case InOrderCPU::FloatType:
                    {
                        uniqueFloatRegMap[flat_idx] = true;
                        DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Reading Float Reg %i"
                                " (%i) from Register File:%x (%08f).\n",
                                tid, seq_num,
                                reg_idx - FP_Base_DepTag, flat_idx,
                                cpu->readFloatRegBits(flat_idx,
                                                      inst->readTid()),
                                cpu->readFloatReg(flat_idx,
                                                  inst->readTid()));

                        inst->setFloatSrc(ud_idx,
                                          cpu->readFloatReg(flat_idx,
                                                            inst->readTid()));
                        inst->setFloatRegBitsSrc(ud_idx,
                                                 cpu->readFloatRegBits(flat_idx,
                                                                       inst->readTid()));
                        floatRegFileReads++;
                    }
                    break;

                  case InOrderCPU::MiscType:
                    {
                        uniqueMiscRegMap[flat_idx] = true;
                        DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Reading Misc Reg %i "
                                " (%i) from Register File:0x%x.\n",
                                tid, seq_num,
                                reg_idx - Ctrl_Base_DepTag, flat_idx,
                                cpu->readMiscReg(flat_idx,
                                inst->readTid()));
                        inst->setIntSrc(ud_idx,
                                        cpu->readMiscReg(flat_idx,
                                                         inst->readTid()));
                    }
                    break;

                  default:
                    panic("Invalid Register Type: %i", reg_type);
                }

                ud_req->done();
            } else {
                // Look for forwarding opportunities
                DynInstPtr forward_inst = regDepMap[tid]->canForward(reg_type,
                                                                     flat_idx,
                                                                     inst);

                if (forward_inst) {
                    int dest_reg_idx =
                        forward_inst->getDestIdxNum(flat_idx);

                    switch (reg_type)
                    {
                      case InOrderCPU::IntType:
                        {
                            DPRINTF(InOrderUseDef, "[tid:%i]: Forwarding dest."
                                    " reg %i (%i), value 0x%x from "
                                    "[sn:%i] to [sn:%i] source #%x.\n",
                                    tid, reg_idx, flat_idx,
                                    forward_inst->readIntResult(dest_reg_idx),
                                    forward_inst->seqNum, 
                                    inst->seqNum, ud_idx);
                            inst->setIntSrc(ud_idx, 
                                            forward_inst->
                                            readIntResult(dest_reg_idx));
                        }
                        break;

                      case InOrderCPU::FloatType:
                        {
                            DPRINTF(InOrderUseDef, "[tid:%i]: Forwarding dest."
                                    " reg %i (%i) value 0x%x from "
                                    "[sn:%i] to [sn:%i] source #%i.\n",
                                    tid, reg_idx - FP_Base_DepTag, flat_idx,
                                    forward_inst->readFloatResult(dest_reg_idx),
                                    forward_inst->seqNum, inst->seqNum, ud_idx);
                            inst->setFloatSrc(ud_idx,
                                              forward_inst->
                                              readFloatResult(dest_reg_idx));
                        }
                        break;

                      case InOrderCPU::MiscType:
                        {
                            DPRINTF(InOrderUseDef, "[tid:%i]: Forwarding dest."
                                    " reg %i (%i) value 0x%x from "
                                    "[sn:%i] to [sn:%i] source #%i.\n",
                                    tid, reg_idx - Ctrl_Base_DepTag, flat_idx,
                                    forward_inst->readIntResult(dest_reg_idx),
                                    forward_inst->seqNum, 
                                    inst->seqNum, ud_idx);
                            inst->setIntSrc(ud_idx, 
                                            forward_inst->
                                            readIntResult(dest_reg_idx));
                        }
                        break;

                      default:
                        panic("Invalid Register Type: %i", reg_type);
                    }

                    regForwards++;
                    ud_req->done();
                } else {
                    DPRINTF(InOrderUseDef, "[tid:%i]: Source register idx: %i "
                            "is not ready to read.\n",
                            tid, reg_idx);
                    DPRINTF(InOrderStall, "STALL: [tid:%i]: waiting to read "
                            "register (idx=%i)\n",
                            tid, reg_idx);
                    ud_req->done(false);
                }
            }
        }
        break;

      case WriteDestReg:
        {
            InOrderCPU::RegType reg_type;
            RegIndex reg_idx = inst->_destRegIdx[ud_idx];
            RegIndex flat_idx = cpu->flattenRegIdx(reg_idx, reg_type, tid);

            if (flat_idx == TheISA::ZeroReg && reg_type == InOrderCPU::IntType) {
                DPRINTF(IntRegs, "[tid:%i]: Ignoring Writing of ISA-ZeroReg "
                        "(Int. Reg %i)\n", tid, flat_idx);
                ud_req->done();
                return;
            }

            if (regDepMap[tid]->canWrite(reg_type, flat_idx, inst)) {
                DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Flattening register idx %i "
                        "(%i) and Attempting to write to Register File.\n",
                        tid, seq_num, reg_idx, flat_idx);

                switch (reg_type)
                {
                  case InOrderCPU::IntType:
                    {
                        uniqueIntRegMap[flat_idx] = true;

                        DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Writing Int. Result "
                                "0x%x to register idx %i (%i).\n",
                                tid, seq_num, inst->readIntResult(ud_idx),
                                reg_idx, flat_idx);

                        // Remove Dependencies
                        regDepMap[tid]->removeFront(reg_type, flat_idx, inst);

                        cpu->setIntReg(flat_idx,
                                       inst->readIntResult(ud_idx),
                                       inst->readTid());
                        intRegFileWrites++;
                    }
                    break;

                  case InOrderCPU::FloatType:
                    {
                        uniqueFloatRegMap[flat_idx] = true;

                        // Remove Reg. Dependecny Block on this Register
                        regDepMap[tid]->removeFront(reg_type, flat_idx, inst);

                        if (inst->resultType(ud_idx) == 
                            InOrderDynInst::FloatBits) {
                            DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Writing FP-Bits "
                                    "Result %08f (bits:0x%x) to register "
                                    "idx %i (%i).\n",
                                    tid, seq_num,
                                    inst->readFloatResult(ud_idx), 
                                    inst->readFloatBitsResult(ud_idx),
                                    reg_idx - FP_Base_DepTag, flat_idx);

                            // Check for FloatRegBits Here
                            cpu->setFloatRegBits(flat_idx,
                                                 inst->readFloatBitsResult(ud_idx),
                                                 inst->readTid());
                        } else if (inst->resultType(ud_idx) == 
                                   InOrderDynInst::Float) {
                            DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Writing Float "
                                    "Result %08f (bits:0x%x) to register "
                                    "idx %i (%i).\n",
                                    tid, seq_num, inst->readFloatResult(ud_idx),
                                    inst->readIntResult(ud_idx), 
                                    reg_idx - FP_Base_DepTag, flat_idx);

                            cpu->setFloatReg(flat_idx,
                                             inst->readFloatResult(ud_idx),
                                             inst->readTid());
                        } else if (inst->resultType(ud_idx) == 
                                   InOrderDynInst::Double) {
                            DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Writing Double "
                                    "Result %08f (bits:0x%x) to register "
                                    "idx %i (%i).\n",
                                    tid, seq_num,
                                    inst->readFloatResult(ud_idx), 
                                    inst->readIntResult(ud_idx), 
                                    reg_idx - FP_Base_DepTag, flat_idx);

                            cpu->setFloatReg(flat_idx,
                                             inst->readFloatResult(ud_idx),
                                             inst->readTid());
                        } else {
                            panic("Result Type Not Set For [sn:%i] %s.\n", 
                                  inst->seqNum, inst->instName());
                        }

                        floatRegFileWrites++;
                    }
                    break;

                  case InOrderCPU::MiscType:
                    {
                        uniqueMiscRegMap[flat_idx] = true;

                        DPRINTF(InOrderUseDef, "[tid:%i]: Writing Misc. 0x%x "
                                "to register idx %i.\n",
                                tid, inst->readIntResult(ud_idx), reg_idx - Ctrl_Base_DepTag);

                        // Remove Dependencies
                        regDepMap[tid]->removeFront(reg_type, flat_idx, inst);

                        cpu->setMiscReg(flat_idx,
                                    inst->readIntResult(ud_idx),
                                        inst->readTid());
                    }
                    break;

                  default:
                    panic("Invalid Register Type: %i", reg_type);
                }

                ud_req->done();
            } else {
                DPRINTF(InOrderUseDef, "[tid:%i]: [sn:%i]: Dest. register idx: %i is "
                        "not ready to write.\n",
                        tid, seq_num, reg_idx);
                DPRINTF(InOrderStall, "STALL: [tid:%i]: waiting to write "
                        "register (idx=%i)\n",
                        tid, reg_idx);
                ud_req->done(false);
            }
        }
        break;

      case MarkDestRegs:
        {
            regDepMap[tid]->insert(inst);
            ud_req->done();
        }
        break;

      default:
        fatal("Unrecognized command to %s", resName);
    }

}

void
UseDefUnit::updateAfterContextSwitch(DynInstPtr inst, ThreadID tid)
{
    uniqueRegsPerSwitch = uniqueIntRegMap.size() + uniqueFloatRegMap.size()
        + uniqueMiscRegMap.size();
    uniqueIntRegMap.clear();
    uniqueFloatRegMap.clear();
    uniqueMiscRegMap.clear();
}