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

using namespace std;
using namespace ThePipeline;

ResourcePool::ResourcePool(InOrderCPU *_cpu, ThePipeline::Params *params)
    : cpu(_cpu)
{
    //@todo: use this function to instantiate the resources in resource pool. 
    //This will help in the auto-generation of this pipeline model.
    //ThePipeline::addResources(resources, memObjects);

    int stage_width = cpu->stageWidth;

    // Declare Resource Objects
    // name - id - bandwidth - latency - CPU - Parameters
    // --------------------------------------------------
    resources.push_back(new FetchSeqUnit("fetch_seq_unit", FetchSeq,
                                         stage_width * 2, 0, _cpu, params));

    memObjects.push_back(ICache);
    resources.push_back(new FetchUnit("icache_port", ICache,
                                      stage_width * 2 + MaxThreads, 0, _cpu,
                                      params));

    resources.push_back(new DecodeUnit("decode_unit", Decode,
                                       stage_width, 0, _cpu, params));

    resources.push_back(new BranchPredictor("branch_predictor", BPred,
                                            stage_width, 0, _cpu, params));

    resources.push_back(new InstBuffer("fetch_buffer_t0", FetchBuff, 4,
                                       0, _cpu, params));

    resources.push_back(new UseDefUnit("regfile_manager", RegManager,
                                       stage_width * 3, 0, _cpu,
                                       params));

    resources.push_back(new AGENUnit("agen_unit", AGEN,
                                     stage_width, 0, _cpu, params));

    resources.push_back(new ExecutionUnit("execution_unit", ExecUnit,
                                          stage_width, 0, _cpu, params));

    resources.push_back(new MultDivUnit("mult_div_unit", MDU,
                                        stage_width * 2,
                                        0,
                                        _cpu,
                                        params));

    memObjects.push_back(DCache);
    resources.push_back(new CacheUnit("dcache_port", DCache, 
                                      stage_width * 2 + MaxThreads, 0, _cpu,
                                      params));

    gradObjects.push_back(BPred);
    resources.push_back(new GraduationUnit("graduation_unit", Grad,
                                           stage_width, 0, _cpu,
                                           params));

    resources.push_back(new InstBuffer("fetch_buffer_t1", FetchBuff2, 4,
                                       0, _cpu, params));

}

ResourcePool::~ResourcePool()
{
    cout << "Deleting resources ..." << endl;
    
    for (int i=0; i < resources.size(); i++) {
        DPRINTF(Resource, "Deleting resource: %s.\n", resources[i]->name());
        
        delete resources[i];
    }    
}


void
ResourcePool::init()
{
    for (int i=0; i < resources.size(); i++) {
        DPRINTF(Resource, "Initializing resource: %s.\n", 
                resources[i]->name());
        
        resources[i]->init();
    }
}

string
ResourcePool::name()
{
    return cpu->name() + ".ResourcePool";
}

void
ResourcePool::print()
{
    for (int i=0; i < resources.size(); i++) {
        DPRINTF(InOrderDynInst, "Res:%i %s\n",
                i, resources[i]->name());
    }

}


void
ResourcePool::regStats()
{
    DPRINTF(Resource, "Registering Stats Throughout Resource Pool.\n");

    int num_resources = resources.size();

    for (int idx = 0; idx < num_resources; idx++) {
        resources[idx]->regStats();
    }
}

Port *
ResourcePool::getPort(const std::string &if_name, int idx)
{
    DPRINTF(Resource, "Binding %s in Resource Pool.\n", if_name);

    for (int i = 0; i < memObjects.size(); i++) {
        int obj_idx = memObjects[i];
        Port *port = resources[obj_idx]->getPort(if_name, idx);
        if (port != NULL) {
            DPRINTF(Resource, "%s set to resource %s(#%i) in Resource Pool.\n", 
                    if_name, resources[obj_idx]->name(), obj_idx);
            return port;
        }
    }

    return NULL;
}

unsigned
ResourcePool::getPortIdx(const std::string &port_name)
{
    DPRINTF(Resource, "Finding Port Idx for %s.\n", port_name);

    for (int i = 0; i < memObjects.size(); i++) {
        unsigned obj_idx = memObjects[i];
        Port *port = resources[obj_idx]->getPort(port_name, obj_idx);
        if (port != NULL) {
            DPRINTF(Resource, "Returning Port Idx %i for %s.\n", obj_idx, 
                    port_name);
            return obj_idx;
        }
    }

    return 0;
}

unsigned
ResourcePool::getResIdx(const std::string &res_name)
{
    DPRINTF(Resource, "Finding Resource Idx for %s.\n", res_name);

    int num_resources = resources.size();

    for (int idx = 0; idx < num_resources; idx++) {
        if (resources[idx]->name() == res_name)
            return idx;
    }

    panic("Can't find resource idx for: %s\n", res_name);
    return 0;
}

unsigned
ResourcePool::getResIdx(const ThePipeline::ResourceId &res_id)
{
    int num_resources = resources.size();

    for (int idx = 0; idx < num_resources; idx++) {
        if (resources[idx]->getId() == res_id)
            return idx;
    }

    // todo: change return value to int and return a -1 here
    //       maybe even have enumerated type
    //       panic for now...
    panic("Can't find resource idx for: %i\n", res_id);

    return 0;
}

ResReqPtr
ResourcePool::request(int res_idx, DynInstPtr inst)
{
    //Make Sure This is a valid resource ID
    assert(res_idx >= 0 && res_idx < resources.size());

    return resources[res_idx]->request(inst);
}

void
ResourcePool::squash(DynInstPtr inst, int res_idx, InstSeqNum done_seq_num,
                     ThreadID tid)
{
    resources[res_idx]->squash(inst, ThePipeline::NumStages-1, done_seq_num, 
                               tid);
}

void
ResourcePool::trap(Fault fault, ThreadID tid, DynInstPtr inst)
{
    DPRINTF(Resource, "[tid:%i] Broadcasting Trap to all "
            "resources.\n", tid);

    int num_resources = resources.size();

    for (int idx = 0; idx < num_resources; idx++)
        resources[idx]->trap(fault, tid, inst);
}

int
ResourcePool::slotsAvail(int res_idx)
{
    return resources[res_idx]->slotsAvail();
}

int
ResourcePool::slotsInUse(int res_idx)
{
    return resources[res_idx]->slotsInUse();
}

//@todo: split this function and call this version schedulePoolEvent
//       and use this scheduleEvent for scheduling a specific event on 
//       a resource
//@todo: For arguments that arent being used in a ResPoolEvent, a dummyParam
//       or some typedef can be used to signify what's important info
//       to the event construction
void
ResourcePool::scheduleEvent(InOrderCPU::CPUEventType e_type, DynInstPtr inst,
                            int delay,  int res_idx, ThreadID tid)
{
    assert(delay >= 0);

    Tick when = cpu->nextCycle(curTick() + cpu->ticks(delay));

    switch ((int)e_type)
    {
      case ResourcePool::InstGraduated:
        {
            DPRINTF(Resource, "Scheduling Inst-Graduated Resource Pool "
                    "Event for tick %i.\n", curTick() + delay);
            ResPoolEventPri grad_pri = ResGrad_Pri;
            ResPoolEvent *res_pool_event = 
                new ResPoolEvent(this,
                                 e_type,
                                 inst,
                                 inst->squashingStage,
                                 inst->seqNum,
                                 inst->readTid(),
                                 grad_pri);
            cpu->schedule(res_pool_event, when);
        }
        break;

      case ResourcePool::SquashAll:
        {
            DPRINTF(Resource, "Scheduling Squash Resource Pool Event for "
                    "tick %i.\n", curTick() + delay);
            ResPoolEventPri squash_pri = ResSquash_Pri;
            ResPoolEvent *res_pool_event = 
                new ResPoolEvent(this,
                                 e_type,
                                 inst,
                                 inst->squashingStage,
                                 inst->squashSeqNum,
                                 inst->readTid(),
                                 squash_pri);
            cpu->schedule(res_pool_event, when);
        }
        break;

      case ResourcePool::UpdateAfterContextSwitch:
        {
            DPRINTF(Resource, "Scheduling UpdatePC Resource Pool Event "
                    "for tick %i.\n",
                    curTick() + delay);
            ResPoolEvent *res_pool_event = new ResPoolEvent(this,
                                                            e_type,
                                                            inst,
                                                            inst->squashingStage,
                                                            inst->seqNum,
                                                            inst->readTid());
            cpu->schedule(res_pool_event, when);
        }
        break;

      default:
        DPRINTF(Resource, "Ignoring Unrecognized CPU Event (%s).\n", 
                InOrderCPU::eventNames[e_type]);
    }
}

void
ResourcePool::unscheduleEvent(int res_idx, DynInstPtr inst)
{
    resources[res_idx]->unscheduleEvent(inst);
}

void
ResourcePool::squashAll(DynInstPtr inst, int stage_num,
                        InstSeqNum done_seq_num, ThreadID tid)
{
    DPRINTF(Resource, "[tid:%i] Broadcasting Squash All Event "
            " starting w/stage %i for all instructions above [sn:%i].\n",
             tid, stage_num, done_seq_num);

    int num_resources = resources.size();

    for (int idx = 0; idx < num_resources; idx++) {
        resources[idx]->squash(inst, stage_num, done_seq_num, tid);
    }
}

void
ResourcePool::squashDueToMemStall(DynInstPtr inst, int stage_num,
                             InstSeqNum done_seq_num, ThreadID tid)
{
    DPRINTF(Resource, "[tid:%i] Broadcasting SquashDueToMemStall Event"
            " starting w/stage %i for all instructions above [sn:%i].\n",
            tid, stage_num, done_seq_num);

    int num_resources = resources.size();

    for (int idx = 0; idx < num_resources; idx++) {
        resources[idx]->squashDueToMemStall(inst, stage_num, done_seq_num, 
                                            tid);
    }
}

void
ResourcePool::activateThread(ThreadID tid)
{
    bool do_activate = cpu->threadModel != InOrderCPU::SwitchOnCacheMiss ||
        cpu->numActiveThreads() < 1 ||
        cpu->activeThreadId() == tid;
    
        
    if (do_activate) {
        DPRINTF(Resource, "[tid:%i] Broadcasting Thread Activation to all "
                    "resources.\n", tid);
 
        int num_resources = resources.size();
 
        for (int idx = 0; idx < num_resources; idx++) {
            resources[idx]->activateThread(tid);
        }
    } else {
        DPRINTF(Resource, "[tid:%i] Ignoring Thread Activation to all "
                    "resources.\n", tid);
     }
}

void
ResourcePool::deactivateThread(ThreadID tid)
{
    DPRINTF(Resource, "[tid:%i] Broadcasting Thread Deactivation to all "
            "resources.\n", tid);

    int num_resources = resources.size();

    for (int idx = 0; idx < num_resources; idx++) {
        resources[idx]->deactivateThread(tid);
    }
}

void
ResourcePool::suspendThread(ThreadID tid)
{
    DPRINTF(Resource, "[tid:%i] Broadcasting Thread Suspension to all "
            "resources.\n", tid);

    int num_resources = resources.size();

    for (int idx = 0; idx < num_resources; idx++) {
        resources[idx]->suspendThread(tid);
    }
}

void
ResourcePool::instGraduated(InstSeqNum seq_num, ThreadID tid)
{
    DPRINTF(Resource, "[tid:%i] Broadcasting [sn:%i] graduation to "
            "appropriate resources.\n", tid, seq_num);

    int num_resources = gradObjects.size();

    for (int idx = 0; idx < num_resources; idx++) {
        resources[gradObjects[idx]]->instGraduated(seq_num, tid);
    }
}

void
ResourcePool::updateAfterContextSwitch(DynInstPtr inst, ThreadID tid)
{
    DPRINTF(Resource, "[tid:%i] Broadcasting Update PC to all resources.\n",
            tid);

    int num_resources = resources.size();

    for (int idx = 0; idx < num_resources; idx++) {
        resources[idx]->updateAfterContextSwitch(inst, tid);
    }
}

ResourcePool::ResPoolEvent::ResPoolEvent(ResourcePool *_resPool,
                                         InOrderCPU::CPUEventType e_type,
                                         DynInstPtr _inst,
                                         int stage_num,
                                         InstSeqNum seq_num,
                                         ThreadID _tid,
                                         ResPoolEventPri res_pri)
    : Event(res_pri), resPool(_resPool),
      eventType(e_type), inst(_inst), seqNum(seq_num),
      stageNum(stage_num), tid(_tid)
{ }


void
ResourcePool::ResPoolEvent::process()
{
    switch ((int)eventType)
    {

      case ResourcePool::InstGraduated:
        resPool->instGraduated(seqNum, tid);
        break;

      case ResourcePool::SquashAll:
        resPool->squashAll(inst, stageNum, seqNum, tid);
        break;

      case ResourcePool::UpdateAfterContextSwitch:
        resPool->updateAfterContextSwitch(inst, tid);
        break;

      default:
        fatal("Unrecognized Event Type");
    }

    resPool->cpu->cpuEventRemoveList.push(this);
}


const char *
ResourcePool::ResPoolEvent::description()
{
    return "Resource Pool event";
}

/** Schedule resource event, regardless of its current state. */
void
ResourcePool::ResPoolEvent::scheduleEvent(int delay)
{
    InOrderCPU *cpu = resPool->cpu;
    assert(!scheduled() || squashed());
    cpu->reschedule(this, cpu->nextCycle(curTick() + cpu->ticks(delay)), true);
}

/** Unschedule resource event, regardless of its current state. */
void
ResourcePool::ResPoolEvent::unscheduleEvent()
{
    if (scheduled())
        squash();
}