/*
 * 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 "cpu/inorder/resources/mult_div_unit.hh"
#include "cpu/inorder/cpu.hh"
#include "cpu/inorder/resource_pool.hh"
#include "cpu/op_class.hh"
#include "debug/InOrderMDU.hh"
#include "debug/Resource.hh"

using namespace std;
using namespace ThePipeline;

MultDivUnit::MultDivUnit(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),
      multRepeatRate(params->multRepeatRate),
      multLatency(params->multLatency),
      div8RepeatRate(params->div8RepeatRate),
      div8Latency(params->div8Latency),
      div16RepeatRate(params->div16RepeatRate),
      div16Latency(params->div16Latency),
      div24RepeatRate(params->div24RepeatRate),
      div24Latency(params->div24Latency),
      div32RepeatRate(params->div32RepeatRate),
      div32Latency(params->div32Latency),
      lastMDUCycle(0), lastOpType(No_OpClass)
{ }

void
MultDivUnit::regStats()
{
    multiplies
        .name(name() + ".multiplies")
        .desc("Number of Multipy Operations Executed");

    divides
        .name(name() + ".divides")
        .desc("Number of Divide Operations Executed");

    Resource::regStats();
}

void
MultDivUnit::init()
{
    // Set Up Resource Events to Appropriate Resource BandWidth
    resourceEvent = new MDUEvent[width];

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

    initSlots();
}

//@TODO: Should we push this behavior into base-class to generically
//       accomodate all multicyle resources?
void
MultDivUnit::requestAgain(DynInstPtr inst, bool &service_request)
{
    ResReqPtr mult_div_req = findRequest(inst);
    assert(mult_div_req);

    service_request = true;

    // Check to see if this instruction is requesting the same command
    // or a different one
    if (mult_div_req->cmd != inst->curSkedEntry->cmd) {
        // If different, then update command in the request
        mult_div_req->cmd = inst->curSkedEntry->cmd;
        DPRINTF(InOrderMDU,
                "[tid:%i]: [sn:%i]: Updating the command for this "
                "instruction\n", inst->readTid(), inst->seqNum);
    } else {
        // If same command, just check to see if access was completed
        // but dont try to re-execute
        DPRINTF(InOrderMDU,
                "[tid:%i]: [sn:%i]: requesting this resource again\n",
                inst->readTid(), inst->seqNum);
    }
}
int
MultDivUnit::getSlot(DynInstPtr inst)
{
    // If MDU already has instruction, return current slot.
    int slot_num = findSlot(inst);

    // If we have this instruction's request already then return
    if (slot_num != -1 &&         
        inst->curSkedEntry->cmd == reqs[slot_num]->cmd)
        return slot_num;
    
    unsigned repeat_rate = 0;

    /** Enforce MDU dependencies after a multiply is seen last */
    if (lastOpType == IntMultOp) {
        repeat_rate = multRepeatRate;
    }

    /** Enforce dependencies after a divide is seen last */
    if (lastOpType == IntDivOp) {
        switch (lastDivSize) {
          case 8:
            repeat_rate = div8RepeatRate;
            break;

          case 16:
            repeat_rate = div16RepeatRate;
            break;

          case 24:
            repeat_rate = div24RepeatRate;
            break;

          case 32:
            repeat_rate = div32RepeatRate;
            break;
        }
    }

    if (lastMDUCycle + repeat_rate > curTick()) {
        DPRINTF(InOrderMDU, "MDU not ready to process another inst. until %i, "
                "denying request.\n", lastMDUCycle + repeat_rate);
        return -1;
    } else {
        int rval = Resource::getSlot(inst);
        DPRINTF(InOrderMDU, "MDU request should pass: %i.\n",
                rval);

        if (rval != -1) {            
            lastMDUCycle = curTick();
            lastOpType = inst->opClass();
            lastInstName = inst->staticInst->getName();
        }
      
        return rval;
    }
}

int
MultDivUnit::getDivOpSize(DynInstPtr inst)
{
    // Get RT Register from instruction (index #1)
    uint32_t div_op = inst->readIntSrc(1);

    if (div_op <= 0xFF) {
        return 8;
    } else if (div_op <= 0xFFFF) {
        return 16;
    } else if (div_op <= 0xFFFFFF) {
        return 24;
    } else {
        return 32;
    }
}

void 
MultDivUnit::execute(int slot_num)
{
    ResourceRequest* mult_div_req = reqs[slot_num];
    DynInstPtr inst = reqs[slot_num]->inst;
    if (inst->fault != NoFault) {
        DPRINTF(InOrderMDU,
                "[tid:%i]: [sn:%i]: Detected %s fault @ %x. Forwarding to "
                "next stage.\n", inst->readTid(), inst->seqNum, inst->fault->name(),
                inst->pcState());
        mult_div_req->done();
        return;
    }

    DPRINTF(InOrderMDU, "Executing [sn:%i] ...\n", slot_num);

    switch (mult_div_req->cmd)
    {
      case StartMultDiv:
        {
            DPRINTF(InOrderMDU, "Start MDU called ...\n");

            OpClass op_class = inst->opClass();
            if (op_class == IntMultOp) {
                scheduleEvent(slot_num, multLatency);
            } else if (op_class == IntDivOp) {
                int op_size = getDivOpSize(inst);

                switch (op_size)
                {
                  case 8:
                    scheduleEvent(slot_num, div8Latency);
                    break;

                  case 16:
                    scheduleEvent(slot_num, div16Latency);
                    break;

                  case 24:
                    scheduleEvent(slot_num, div24Latency);
                    break;

                  case 32:
                    scheduleEvent(slot_num, div32Latency);
                    break;
                }

                lastDivSize = op_size;
            }

            // Allow to pass through to next stage while
            // event processes
            mult_div_req->setProcessing();
            mult_div_req->setCompleted();
        }
        break;
        
      case MultDiv:
        DPRINTF(InOrderMDU, "Execute MDU called ...\n");        
        exeMulDiv(slot_num);        
        mult_div_req->done();
        break;


      case EndMultDiv:
        //@TODO: Why not allow high-latency requests to sleep
        //      within stage until event wakes up????
        //      Seems wasteful to continually check to see if
        //      this is done when we have a event in parallel
        //      counting down the time
        {
            DPRINTF(InOrderMDU, "End MDU called ...\n");    
            if (!mult_div_req->isProcessing()) {
                DPRINTF(InOrderMDU, "Mult/Div finished.\n");                    
                mult_div_req->done();            
            } else {                
                mult_div_req->setCompleted(false);
            }
            
        }
        break;

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

void 
MultDivUnit::exeMulDiv(int slot_num)
{
    ResourceRequest* mult_div_req = reqs[slot_num];
    DynInstPtr inst = reqs[slot_num]->inst;

    inst->fault = inst->execute();

    if (inst->opClass() == IntMultOp) {
        multiplies++;
    } else if (inst->opClass() == IntDivOp) {
        divides++;
    }

    if (inst->fault == NoFault) {
        inst->setExecuted();

        DPRINTF(InOrderMDU, "[tid:%i]: The result of execution is 0x%x.\n",
                inst->readTid(), inst->readIntResult(0));
    } else {
        DPRINTF(InOrderMDU, "[tid:%i]: [sn:%i]: had a %s "
                "fault.\n", inst->readTid(), inst->seqNum, inst->fault->name());
    }    

    mult_div_req->setProcessing(false);
    cpu->wakeCPU();
}

void
MultDivUnit::squash(DynInstPtr inst, int stage_num, InstSeqNum squash_seq_num,
                 ThreadID tid)
{
    for (int i = 0; i < width; i++) {
        ResReqPtr req_ptr = reqs[i];
        DynInstPtr inst = req_ptr->getInst();

        if (req_ptr->valid &&
            inst->readTid() == tid &&
            inst->seqNum > squash_seq_num) {

            DPRINTF(InOrderMDU, "[tid:%i]: Squashing [sn:%i].\n",
                    req_ptr->getInst()->readTid(),
                    req_ptr->getInst()->seqNum);

            req_ptr->setSquashed();

            int req_slot_num = req_ptr->getSlot();

            if (req_ptr->isProcessing())
                DPRINTF(InOrderMDU, "[tid:%i]: Squashed [sn:%i], but "
                        "waiting for MDU operation to complete.\n",
                        req_ptr->getInst()->readTid(),
                        req_ptr->getInst()->seqNum);
            else
                freeSlot(req_slot_num);
        }
    }
}

MDUEvent::MDUEvent()
    : ResourceEvent()
{ }

void
MDUEvent::process()
{
    MultDivUnit* mdu_res = reinterpret_cast<MultDivUnit*>(resource);

    mdu_res->exeMulDiv(slotIdx);

    ResourceRequest* mult_div_req = resource->reqs[slotIdx];

    if (mult_div_req->isSquashed())
        mdu_res->freeSlot(slotIdx);
}


