/*
 * 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
 */

#include "TimingCalculation.h"
#include "configuration/MemSpec.h"
#include "ControllerCore.h"
#include "../../common/DebugManager.h"
#include "configuration/Configuration.h"
#include "../../common/Utils.h"



sc_time getDelayToMeetConstraint(sc_time previous, sc_time start,
                                 sc_time constraint)
{
    if (previous + constraint > start)
        return previous + constraint - start;
    else
        return SC_ZERO_TIME;
}

const sc_time FrequencyToClk(double frequencyMhz)
{
    return sc_time(1 / frequencyMhz, SC_US);
}

const sc_time clkAlign(sc_time time, Alignment alignment)
{
    sc_time clk = Configuration::getInstance().memSpec.clk;
    if (alignment == UP)
        return ceil(time / clk) * clk;
    else
        return floor(time / clk) * clk;
}

// Returns the execution time for commands that have a fixed execution time
sc_time getExecutionTime(Command command, tlm::tlm_generic_payload &payload)
{
    MemSpec &config = Configuration::getInstance().memSpec;

    if (command == Command::PreB) {
        return Configuration::getInstance().getTrpb();
    } else if (command == Command::Precharge || command == Command::PrechargeAll) {
        return config.tRP;
    } else if (command == Command::ActB) {
        return config.tRCD;
    } else if (command == Command::Activate) {
        return config.tRCD;
    } else if (command == Command::Read) {
        return config.tRL + getReadAccessTime();
    } else if (command == Command::ReadA) {
        return config.tRTP + config.tRP;
    } else if (command == Command::Write) {
        return config.tWL + getWriteAccessTime();
    } else if (command == Command::WriteA) {
        return config.tWL + getWriteAccessTime() + config.tWR + config.tRP;
    } else if (command == Command::PrechargeAll) {
        return config.tRP;
    } else if (command == Command::AutoRefresh) {
        if (Configuration::getInstance().getRefMode() == 4)
            return getElementFromMap(config.refreshTimings,
                                     DramExtension::getExtension(payload).getBank()).tRFC4;
        else if (Configuration::getInstance().getRefMode() == 2)
            return getElementFromMap(config.refreshTimings,
                                     DramExtension::getExtension(payload).getBank()).tRFC2;
        else
            return getElementFromMap(config.refreshTimings,
                                     DramExtension::getExtension(payload).getBank()).tRFC;
    } else if (command == Command::PDNAX || command == Command::PDNPX
               || command == Command::SREFX) {
        return config.clk;
    } else {
        SC_REPORT_FATAL("getExecutionTime",
                        "command not known or command doesn't have a fixed execution time");
        return SC_ZERO_TIME;
    }
}

// Returns the minimum execution time for commands that have a variable execution time
sc_time getMinExecutionTimeForPowerDownCmd(Command command)
{
    MemSpec &config = Configuration::getInstance().memSpec;
    if (command == Command::PDNA || command == Command::PDNP) {
        return config.tCKE;
    } else if (command == Command::SREF) {
        return config.tCKESR;
    } else {
        SC_REPORT_FATAL("getMinimalExecutionTime",
                        "command is not know or command has a fixed execution time");
        return SC_ZERO_TIME;
    }
}

bool isClkAligned(sc_time time, sc_time clk)
{
    return !((time / clk) - ceil(time / clk));
}


sc_time getReadAccessTime()
{
    Configuration &config = Configuration::getInstance();
    return (config.memSpec.BurstLength / config.memSpec.DataRate) *
           config.memSpec.clk;
}

sc_time getWriteAccessTime()
{
    Configuration &config = Configuration::getInstance();

    if (config.memSpec.DataRate == 1) {
        return config.memSpec.clk * (config.memSpec.BurstLength);
    } else {
        return config.memSpec.clk * (config.memSpec.BurstLength /
                                     config.memSpec.DataRate);
    }
}


