/*
 * Copyright (c) 2014-2015 Advanced Micro Devices, Inc.
 * All rights reserved.
 *
 * For use for simulation and test purposes only
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. Neither the name of the copyright holder 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 HOLDER 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.
 *
 * Author: John Kalamatianos, Sooraj Puthoor
 */

#include "gpu-compute/exec_stage.hh"

#include "gpu-compute/compute_unit.hh"
#include "gpu-compute/wavefront.hh"

ExecStage::ExecStage(const ComputeUnitParams *p) : numSIMDs(p->num_SIMDs),
    numMemUnits(p->num_global_mem_pipes + p->num_shared_mem_pipes),
    vectorAluInstAvail(nullptr), glbMemInstAvail(nullptr),
    shrMemInstAvail(nullptr), lastTimeInstExecuted(false),
    thisTimeInstExecuted(false), instrExecuted (false),
    executionResourcesUsed(0)
{
    numTransActiveIdle = 0;
    idle_dur = 0;
}

void
ExecStage::init(ComputeUnit *cu)
{
    computeUnit = cu;
    _name = computeUnit->name() + ".ExecStage";
    dispatchList = &computeUnit->dispatchList;
    vectorAluInstAvail = &(computeUnit->vectorAluInstAvail);
    glbMemInstAvail= &(computeUnit->glbMemInstAvail);
    shrMemInstAvail= &(computeUnit->shrMemInstAvail);
    idle_dur = 0;
}

void
ExecStage::collectStatistics(enum STAT_STATUS stage, int unitId) {
    if (stage == IdleExec) {
        // count cycles of no vector ALU instruction executed
        // even if one was the oldest in a WV of that vector SIMD unit
        if (computeUnit->isVecAlu(unitId) && vectorAluInstAvail->at(unitId)) {
            numCyclesWithNoInstrTypeIssued[unitId]++;
        }

        // count cycles of no global memory (vector) instruction executed
        // even if one was the oldest in a WV of that vector SIMD unit
        if (computeUnit->isGlbMem(unitId) && *glbMemInstAvail > 0) {
            numCyclesWithNoInstrTypeIssued[unitId]++;
            (*glbMemInstAvail)--;
        }

        // count cycles of no shared memory (vector) instruction executed
        // even if one was the oldest in a WV of that vector SIMD unit
        if (computeUnit->isShrMem(unitId) && *shrMemInstAvail > 0) {
            numCyclesWithNoInstrTypeIssued[unitId]++;
            (*shrMemInstAvail)--;
        }
    } else if (stage == BusyExec) {
        // count the number of cycles an instruction to a specific unit
        // was issued
        numCyclesWithInstrTypeIssued[unitId]++;
        thisTimeInstExecuted = true;
        instrExecuted = true;
        ++executionResourcesUsed;
    } else if (stage == PostExec) {
        // count the number of transitions from active to idle
        if (lastTimeInstExecuted && !thisTimeInstExecuted) {
            ++numTransActiveIdle;
        }

        if (!lastTimeInstExecuted && thisTimeInstExecuted) {
            idleDur.sample(idle_dur);
            idle_dur = 0;
        } else if (!thisTimeInstExecuted) {
            idle_dur++;
        }

        lastTimeInstExecuted = thisTimeInstExecuted;
        // track the number of cycles we either issued one vector instruction
        // or issued no instructions at all
        if (instrExecuted) {
            numCyclesWithInstrIssued++;
        } else {
            numCyclesWithNoIssue++;
        }

        spc.sample(executionResourcesUsed);
    }
}

void
ExecStage::initStatistics()
{
    instrExecuted = false;
    executionResourcesUsed = 0;
    thisTimeInstExecuted = false;
}

void
ExecStage::exec()
{
    initStatistics();

    for (int unitId = 0; unitId < (numSIMDs + numMemUnits); ++unitId) {
         // if dispatch list for this execution resource is empty,
         // skip this execution resource this cycle
         if (dispatchList->at(unitId).second == EMPTY) {
             collectStatistics(IdleExec, unitId);
             continue;
         }

         collectStatistics(BusyExec, unitId);
         // execute an instruction for the WF
         dispatchList->at(unitId).first->exec();
         // clear the dispatch list entry
         dispatchList->at(unitId).second = EMPTY;
         dispatchList->at(unitId).first = (Wavefront*)nullptr;
    }

    collectStatistics(PostExec, 0);
}

void
ExecStage::regStats()
{
    numTransActiveIdle
       .name(name() + ".num_transitions_active_to_idle")
       .desc("number of CU transitions from active to idle")
        ;

    numCyclesWithNoIssue
        .name(name() + ".num_cycles_with_no_issue")
        .desc("number of cycles the CU issues nothing")
        ;

    numCyclesWithInstrIssued
        .name(name() + ".num_cycles_with_instr_issued")
        .desc("number of cycles the CU issued at least one instruction")
        ;

    spc
        .init(0, numSIMDs + numMemUnits, 1)
        .name(name() + ".spc")
        .desc("Execution units active per cycle (Exec unit=SIMD,MemPipe)")
        ;

    idleDur
        .init(0,75,5)
        .name(name() + ".idle_duration_in_cycles")
        .desc("duration of idle periods in cycles")
        ;

    numCyclesWithInstrTypeIssued
        .init(numSIMDs + numMemUnits)
        .name(name() + ".num_cycles_with_instrtype_issue")
        .desc("Number of cycles at least one instruction of specific type "
              "issued")
        ;

    numCyclesWithNoInstrTypeIssued
        .init(numSIMDs + numMemUnits)
       .name(name() + ".num_cycles_with_instr_type_no_issue")
       .desc("Number of cycles no instruction of specific type issued")
       ;

    for (int i = 0; i < numSIMDs; ++i) {
        numCyclesWithInstrTypeIssued.subname(i, csprintf("ALU%d",i));
        numCyclesWithNoInstrTypeIssued.subname(i, csprintf("ALU%d",i));
    }

    numCyclesWithInstrTypeIssued.subname(numSIMDs, csprintf("GM"));
    numCyclesWithNoInstrTypeIssued.subname(numSIMDs, csprintf("GM"));
    numCyclesWithInstrTypeIssued.subname(numSIMDs + 1, csprintf("LM"));
    numCyclesWithNoInstrTypeIssued.subname(numSIMDs + 1, csprintf("LM"));
}
