/*
 * 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:
 *    Robert Gernhardt
 *    Matthias Jung
 *    Felipe S. Prado
 *    Ana Mativi
 *    Éder F. Zulian
 */

#include "RefreshManager.h"
#include "../ControllerCore.h"
#include "../TimingCalculation.h"
#include "../../../common/Utils.h"

using namespace tlm;

RefreshManager::RefreshManager(sc_module_name,
                               ControllerCore &controller) : controllerCore(controller),
    timing(controller.config.memSpec.refreshTimings[Bank(0)])
{
    auto m = controllerCore.config.getRefMode();
    tREFIx = timing.tREFI / m;
    tRFCx = m == 4 ? timing.tRFC4 : m == 2 ? timing.tRFC2 : timing.tRFC;
    postponeEnabled = controllerCore.config.ControllerCoreRefEnablePostpone;
    pullInEnabled = controllerCore.config.ControllerCoreRefEnablePullIn;
    maxpostpone = controllerCore.config.ControllerCoreRefMaxPostponed * m;
    maxpullin = controllerCore.config.ControllerCoreRefMaxPulledIn * m;
    pulledin = 0;
    postponed = 0;
    currentState = ST_REFRESH;
    previousState = ST_REFRESH;
    nextState = ST_REFRESH;
    nextPlannedRefresh = SC_ZERO_TIME;
    for (Bank bank : controller.getBanks()) {
        setUpDummy(refreshPayloads[bank], bank);
    }
    planNextRefresh(tREFIx, false);
}

RefreshManager::~RefreshManager()
{
}

//Check if a command will be scheduled during the next refresh period
bool RefreshManager::hasCollision(const ScheduledCommand &command)
{
    bool collisionWithPreviousRefEnd = command.getStart() <
                                       controllerCore.state->getLastCommand(Command::AutoRefresh).getEnd();
    bool collisionWithNextRefStart = command.getEnd() >= nextPlannedRefresh;
    if (controllerCore.config.ControllerCoreRefEnablePostpone
            && (postponed < maxpostpone)) {
        // Flexible refresh is on and have "credits" to postpone.
        // Then there will not be a collision with next refresh because
        // nextPlannedRefresh will be updated.
        collisionWithNextRefStart = false;
    }
    if (currentState == ST_BURST) {
        // A burst due to postponed refreshes shall not be interrupted.
        collisionWithNextRefStart = true;
    }
    return collisionWithPreviousRefEnd || collisionWithNextRefStart;
}

bool RefreshManager::doRefresh(tlm::tlm_generic_payload &payload, sc_time time)
{
    sc_assert(!isInvalidated(payload, time));
    bool pre = false;
    // If any row is open, precharge all.
    if (!controllerCore.state->rowBufferStates->allRowBuffersAreClosed()) {
        pre = true;
        ScheduledCommand prechargeAllMaster(Command::PrechargeAll, time,
                                            getExecutionTime(Command::PrechargeAll, refreshPayloads[Bank(0)]),
                                            refreshPayloads[Bank(0)]);
        controllerCore.getCommandChecker(
            Command::PrechargeAll).delayToSatisfyConstraints(prechargeAllMaster);

        for (size_t i = 1; i < controllerCore.getBanks().size(); i++) {
            ScheduledCommand prechargeAll(Command::PrechargeAll,
                                          prechargeAllMaster.getStart(), prechargeAllMaster.getExecutionTime(),
                                          refreshPayloads[Bank(i)]);
            controllerCore.state->change(prechargeAll);
        }
        controllerCore.state->change(prechargeAllMaster);
        controllerCore.controller.send(prechargeAllMaster, refreshPayloads[Bank(0)]);
    }

    //Otherwise just the AutoRefresh command is scheduled.
    ScheduledCommand refreshAllMaster(Command::AutoRefresh, time,
                                      getExecutionTime(Command::AutoRefresh, refreshPayloads[Bank(0)]),
                                      DramExtension::getExtension(refreshPayloads[Bank(0)]));
    controllerCore.getCommandChecker(
        Command::AutoRefresh).delayToSatisfyConstraints(refreshAllMaster);

    for (size_t i = 1; i < controllerCore.getBanks().size(); i++) {
        ScheduledCommand refresh(Command::AutoRefresh, refreshAllMaster.getStart(),
                                 refreshAllMaster.getExecutionTime(), refreshPayloads[Bank(i)]);
        controllerCore.state->change(refresh);
        DramExtension::getExtension(refreshPayloads[Bank(i)]).incrementRow();
    }
    controllerCore.state->change(refreshAllMaster);
    DramExtension::getExtension(refreshPayloads[Bank(0)]).incrementRow();
    controllerCore.controller.send(refreshAllMaster, refreshPayloads[Bank(0)]);
    return pre;
}


void RefreshManager::scheduleRefresh(tlm::tlm_generic_payload &payload,
                                     sc_time time)
{
    sc_time nextRefTiming;
    bool pendingReq = controllerCore.hasPendingRequests();
    bool canPostpone = postponeEnabled && pendingReq && (postponed < maxpostpone);
    bool canPullIn = pullInEnabled && !pendingReq && (pulledin < maxpullin);
    previousState = currentState;
    currentState = nextState;
    bool align = false;
    sc_time nrt;
    bool pre;

    switch (currentState) {
    case ST_REFRESH:
        // Regular Refresh. It's possible to migrate from this to the flexible
        // refresh states
        assert(pulledin == 0 && postponed == 0);
        if (canPostpone) {
            nextRefTiming = SC_ZERO_TIME;
            nextState = ST_POSTPONE;
        } else if (canPullIn) {
            pre = doRefresh(payload, time);
            nrt = tRFCx;
            if (pre)
                nrt += controllerCore.config.memSpec.tRP;
            nextRefTiming = nrt;
            nextState = ST_PULLIN;
        } else {
            doRefresh(payload, time);
            nextRefTiming = tREFIx;
            nextState = ST_REFRESH;
        }
        break;
    case ST_PULLIN:
        // Pull-in a refresh. Try to pull-in refreshes as long it is possible.
        if (canPullIn) {
            pulledin++;
            pre = doRefresh(payload, time);
            nrt = tRFCx;
            if (pre)
                nrt += controllerCore.config.memSpec.tRP;
            nextRefTiming = nrt;
            nextState = ST_PULLIN;
        } else {
            nextRefTiming = SC_ZERO_TIME;
            nextState = ST_ALIGN;
        }
        break;
    case ST_SKIP:
        // Skip the pulled-in refreshes.
        if (pulledin == 0) {
            nextRefTiming = SC_ZERO_TIME;
            nextState = ST_REFRESH;
        } else {
            pulledin--;
            nextRefTiming = tREFIx;
            nextState = ST_SKIP;
        }
        break;
    case ST_POSTPONE:
        // Postpone Refresh. Delaying refreshes as long as there are pending
        // requests and credits to postpone. Should be followed by a burst
        // refresh.
        postponed++;
        if ((postponed > maxpostpone) || (!pendingReq
                                          && !controllerCore.config.ControllerCoreRefForceMaxPostponeBurst)) {
            // Burst triggered by inactivity or max postpone value reached.
            nextRefTiming = SC_ZERO_TIME;
            nextState = ST_BURST;
        } else {
            nextRefTiming = tREFIx;
            nextState = ST_POSTPONE;
        }
        break;
    case ST_BURST:
        // Burst postponed refreshes.
        pre = doRefresh(payload, time);
        postponed--;
        if (postponed == 0) {
            // All refreshes issued, next state will align to tREFIx
            nextRefTiming = SC_ZERO_TIME;
            nextState = ST_ALIGN;
        } else {
            nrt = tRFCx;
            if (pre)
                nrt += controllerCore.config.memSpec.tRP;
            nextRefTiming = nrt;
            nextState = ST_BURST;
        }
        break;
    case ST_ALIGN:
        // Align Refresh. Adjusting the timing so the next REF timing will be
        // a in a time multiple of tREFIx
        nextRefTiming = tREFIx;
        align = true;
        if (previousState == ST_PULLIN) {
            nextState = ST_SKIP;
        } else {
            nextState = ST_REFRESH;
        }
        break;
    default:
        SC_REPORT_FATAL(this->name(), "Invalid state in flexible refresh FSM.");
        break;
    }
    planNextRefresh(nextRefTiming, align);
}

void RefreshManager::planNextRefresh(sc_time nextRefTiming, bool align)
{
    if (align) {
        nextPlannedRefresh = trunc(nextPlannedRefresh.to_double() / tREFIx.to_double())
                             * tREFIx;
    }
    nextPlannedRefresh += nextRefTiming;
    controllerCore.controller.send(REFTrigger, nextPlannedRefresh,
                                   refreshPayloads[Bank(0)]);
}

void RefreshManager::reInitialize(Bank, sc_time time)
{
    nextPlannedRefresh = clkAlign(time, Alignment::DOWN);
    planNextRefresh(tREFIx, true);
}

bool RefreshManager::isInvalidated(tlm::tlm_generic_payload &payload
                                   __attribute__((unused)), sc_time time)
{
    return nextPlannedRefresh > time;
}

void RefreshManager::printDebugMessage(std::string message)
{
    DebugManager::getInstance().printDebugMessage(this->name(), message);
}

