/*
 * 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
 *    Éder F. Zulian
 */

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

using namespace std;

RefreshManagerBankwise::RefreshManagerBankwise(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;
    for (Bank bank : controller.getBanks()) {
        nextPlannedRefreshs[bank] = SC_ZERO_TIME;
        pulledin[bank] = 0;
        postponed[bank] = 0;
        currentState[bank] = ST_REFRESH;
        previousState[bank] = ST_REFRESH;
        nextState[bank] = ST_REFRESH;
        setUpDummy(refreshPayloads[bank], bank);
        planNextRefresh(bank, tREFIx, false);
    }
}

RefreshManagerBankwise::~RefreshManagerBankwise()
{
}

bool RefreshManagerBankwise::hasCollision(const ScheduledCommand &command)
{
    Bank bank = command.getBank();
    bool collisionWithPreviousRefEnd = command.getStart() <
                                       controllerCore.state->getLastCommand(Command::AutoRefresh, bank).getEnd();
    bool collisionWithNextRefStart = command.getEnd() >= nextPlannedRefreshs[bank];
    if (controllerCore.config.ControllerCoreRefEnablePostpone
            && (postponed[bank] < 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[bank] == ST_BURST) {
        // A burst due to postponed refreshes shall not be interrupted.
        collisionWithNextRefStart = true;
    }
    return collisionWithPreviousRefEnd || collisionWithNextRefStart;
}

bool RefreshManagerBankwise::doRefresh(tlm::tlm_generic_payload &payload,
                                       sc_time time)
{
    sc_assert(!isInvalidated(payload, time));
    bool pre = false;
    tlm::tlm_generic_payload &refreshPayload =
        refreshPayloads[DramExtension::getExtension(payload).getBank()];
    DramExtension &extension = DramExtension::getExtension(refreshPayload);

    if (controllerCore.state->rowBufferStates->rowBufferIsOpen(
                extension.getBank())) {
        pre = true;
        ScheduledCommand precharge(Command::Precharge, time,
                                   getExecutionTime(Command::Precharge, refreshPayload), extension);
        controllerCore.getCommandChecker(Command::Precharge).delayToSatisfyConstraints(
            precharge);
        controllerCore.state->change(precharge);
        controllerCore.controller.send(precharge, refreshPayload);
    }

    ScheduledCommand refresh(Command::AutoRefresh, time,
                             getExecutionTime(Command::AutoRefresh, refreshPayload), extension);
    controllerCore.getCommandChecker(
        Command::AutoRefresh).delayToSatisfyConstraints(refresh);
    controllerCore.state->change(refresh);
    extension.incrementRow();
    controllerCore.controller.send(refresh, refreshPayload);
    return pre;
}

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

    switch (currentState[bank]) {
    case ST_REFRESH:
        // Regular Refresh. It's possible to migrate from this to the flexible
        // refresh states
        assert(pulledin[bank] == 0 && postponed[bank] == 0);
        if (canPostpone) {
            nextRefTiming = SC_ZERO_TIME;
            nextState[bank] = ST_POSTPONE;
        } else if (canPullIn) {
            pre = doRefresh(payload, time);
            nrt = tRFCx;
            if (pre)
                nrt += controllerCore.config.memSpec.tRP;
            nextRefTiming = nrt;
            nextState[bank] = ST_PULLIN;
        } else {
            doRefresh(payload, time);
            nextRefTiming = tREFIx;
            nextState[bank] = ST_REFRESH;
        }
        break;
    case ST_PULLIN:
        // Pull-in a refresh. Try to pull-in refreshes as long it is possible.
        if (canPullIn) {
            pulledin[bank]++;
            pre = doRefresh(payload, time);
            nrt = tRFCx;
            if (pre)
                nrt += controllerCore.config.memSpec.tRP;
            nextRefTiming = nrt;
            nextState[bank] = ST_PULLIN;
        } else {
            nextRefTiming = SC_ZERO_TIME;
            nextState[bank] = ST_ALIGN;
        }
        break;
    case ST_SKIP:
        // Skip the pulled-in refreshes.
        if (pulledin[bank] == 0) {
            nextRefTiming = SC_ZERO_TIME;
            nextState[bank] = ST_REFRESH;
        } else {
            pulledin[bank]--;
            nextRefTiming = tREFIx;
            nextState[bank] = 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[bank]++;
        if ((postponed[bank] > maxpostpone) || (!pendingReq
                                                && !controllerCore.config.ControllerCoreRefForceMaxPostponeBurst)) {
            // Burst triggered by inactivity or max postpone value reached.
            nextRefTiming = SC_ZERO_TIME;
            nextState[bank] = ST_BURST;
        } else {
            nextRefTiming = tREFIx;
            nextState[bank] = ST_POSTPONE;
        }
        break;

    case ST_BURST:
        // Burst postponed refreshes.
        pre = doRefresh(payload, time);
        postponed[bank]--;
        if (postponed[bank] == 0) {
            // All refreshes issued, next state will align to tREFIx
            nextRefTiming = SC_ZERO_TIME;
            nextState[bank] = ST_ALIGN;
        } else {
            nrt = tRFCx;
            if (pre)
                nrt += controllerCore.config.memSpec.tRP;
            nextRefTiming = nrt;
            nextState[bank] = 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[bank] == ST_PULLIN) {
            nextState[bank] = ST_SKIP;
        } else {
            nextState[bank] = ST_REFRESH;
        }
        break;
    default:
        SC_REPORT_FATAL(this->name(), "Invalid state in bw flex. ref. FSM.");
        break;
    }
    planNextRefresh(bank, nextRefTiming, align);
}

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

void RefreshManagerBankwise::reInitialize(Bank bank, sc_time time)
{
    nextPlannedRefreshs[bank] = clkAlign(time, Alignment::DOWN);
    planNextRefresh(bank, tREFIx, true);
}

bool RefreshManagerBankwise::isInvalidated(tlm::tlm_generic_payload &payload,
                                           sc_time time)
{
    return nextPlannedRefreshs[DramExtension::getExtension(payload).getBank()] >
           time;
}

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

