/*
 * Copyright (c) 2015, University of Kaiserslautern
 * All rights reserved.
 *
 * 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.
 *
 * Authors:
 *    Janik Schlemminger
 *    Matthias Jung
 *    Felipe S. Prado
 */

#include "PowerDownChecker.h"
#include "../../TimingCalculation.h"

sc_time PowerDownChecker::getTimeConstraintToEnterPowerDown(Command lastCmd,
                                                            Command pdnCmd) const
{
    sc_assert(pdnCmd == Command::SREF || pdnCmd == Command::PDNA
              || pdnCmd == Command::PDNP);

    sc_time constraint;

    if (lastCmd == Command::Read || lastCmd == Command::ReadA) {
        constraint = config.memSpec.tRL + getReadAccessTime() + config.memSpec.clk;
    } else if (lastCmd == Command::Write) {
        constraint = config.memSpec.tWL + getWriteAccessTime() + config.memSpec.tWR;
    } else if (lastCmd == Command::WriteA) {
        constraint = config.memSpec.tWL + getWriteAccessTime() + config.memSpec.tWR +
                     config.memSpec.clk;
    } else if (lastCmd == Command::AutoRefresh) {
        auto m = Configuration::getInstance().getRefMode();
        if (m == 4)
            constraint = config.memSpec.tRFC4;
        else if (m == 2)
            constraint = config.memSpec.tRFC2;
        else
            constraint = config.memSpec.tRFC;
    } else if (lastCmd == Command::PDNPX || lastCmd == Command::PDNAX) {
        constraint = config.memSpec.tXP;
    } else if (lastCmd == Command::SREFX) {
        constraint = config.memSpec.tXSR;
    } else if (lastCmd == Command::Precharge || lastCmd == Command::PrechargeAll) {
        constraint = config.memSpec.tRP;
    } else {
        reportFatal("Powerdown checker",
                    commandToString(pdnCmd) + " can not follow " + commandToString(lastCmd));
    }

    return constraint;
}

void PowerDownChecker::delayToSatisfyConstraints(ScheduledCommand &command)
const
{
    sc_assert(command.commandIsIn({Command::PDNA, Command::PDNP, Command::SREF, Command::PDNAX, Command::PDNPX, Command::SREFX}));

    // Power Down commmand (one of the listed above)
    Command pdnCmd = command.getCommand();
    Bank bank = command.getBank();

    sc_time timeConstraint;

    if (pdnCmd == Command::PDNA || pdnCmd == Command::PDNP
            || pdnCmd == Command::SREF) {
        // Entering in one of the Power Down modes:
        // PDNA - Active Power Down
        // PDNP - Precharge Power Down
        // SREF - Self Refresh

        // Get the last scheduled command
        ScheduledCommand lastSchedCmd;
        if(Configuration::getInstance().BankwiseLogic)
        {
            lastSchedCmd = state.getLastScheduledCommand(bank);
        }
        else
        {
            lastSchedCmd = state.getLastScheduledCommand();
        }

        if (lastSchedCmd.isValidCommand()) {
            // Get the start time for the last scheduled command
            sc_time lastSchedCmdStart = lastSchedCmd.getStart();
            // Get the last command
            Command lastCmd = lastSchedCmd.getCommand();

            timeConstraint = getTimeConstraintToEnterPowerDown(lastCmd, pdnCmd);

            command.establishMinDistanceFromStart(lastSchedCmdStart, timeConstraint);
        }

    } else if (pdnCmd == Command::PDNAX) {
        // Leaving Active Power Down
        timeConstraint = config.memSpec.tCKE;
        command.establishMinDistanceFromStart(state.getLastCommand(Command::PDNA,
                                                                   bank).getStart(), timeConstraint);
    } else if (pdnCmd == Command::PDNPX) {
        // Leaving Precharge Power Down
        timeConstraint = config.memSpec.tCKE;
        command.establishMinDistanceFromStart(state.getLastCommand(Command::PDNP,
                                                                   bank).getStart(), timeConstraint);
    } else if (pdnCmd == Command::SREFX) {
        // Leaving Self Refresh
        timeConstraint = config.memSpec.tCKESR;
        command.establishMinDistanceFromStart(state.getLastCommand(Command::SREF,
                                                                   bank).getStart(), timeConstraint);
    }

    state.bus.moveCommandToNextFreeSlot(command);
}

