/*
 * Copyright (c) 2017, 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.
 *
 * Author: Éder F. Zulian
 */

#include <math.h>

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

#define TRUE 1
#define FALSE !(TRUE)
#define INITIAL_DISPLACEMENT FALSE

using namespace std;

RGR::RGR(sc_module_name, ControllerCore &ctrlcore) : ccore(ctrlcore),
    timing(ctrlcore.config.memSpec.refreshTimings[ccore.getBanks()[0]])
{
    fmb = ccore.config.ControllerCoreRefForceMaxPostponeBurst;
    bwl = ccore.config.BankwiseLogic;
    ri = ccore.config.getRowInc();
    auto nr = ccore.config.memSpec.NumberOfRows;
    auto nar = ccore.config.getNumAR();
    auto m = ccore.config.getRefMode();
    rpr = (nr / m) / nar;
    assert(rpr > 0);
    tREFIx = timing.tREFI / m;
    trp = ccore.config.getTrpb();
    trcd = ccore.config.memSpec.tRCD;
    postponeEnabled = ccore.config.ControllerCoreRefEnablePostpone;
    pullInEnabled = ccore.config.ControllerCoreRefEnablePullIn;
    maxpostpone = ccore.config.ControllerCoreRefMaxPostponed * m;
    maxpullin = ccore.config.ControllerCoreRefMaxPulledIn * m;
    for (Bank b : ccore.getBanks()) {
        pulledin[b] = 0;
        postponed[b] = 0;
        currentState[b] = ST_REFRESH;
        previousState[b] = ST_REFRESH;
        nextState[b] = ST_REFRESH;
        setUpDummy(rps[b], b);
    }
#if INITIAL_DISPLACEMENT == TRUE
    if (bwl) {
        auto nbs = ccore.config.memSpec.NumberOfBanks;
        for (Bank b : ccore.getBanks()) {
            nextPlannedRefreshs[b] = b.ID() * tREFIx / nbs;
        }
    }
#else
    for (Bank b : ccore.getBanks()) {
        nextPlannedRefreshs[b] = SC_ZERO_TIME;
    }
#endif
    if (bwl) {
        for (Bank b : ccore.getBanks()) {
            planNextRefresh(b, tREFIx, false);
        }
    } else {
        planNextRefresh(ccore.getBanks()[0], tREFIx, false);
    }
}

RGR::~RGR()
{
}

bool RGR::hasCollision(const ScheduledCommand &cmd)
{
    Bank b = cmd.getBank();
    if (currentState[b] == ST_BURST) {
        // A burst due to postponed refreshes shall not be interrupted.
        return true;
    }
    return false;
}

sc_time RGR::doRefresh(tlm::tlm_generic_payload &p, sc_time t)
{
    sc_assert(!isInvalidated(p, t));
    Bank b = DramExtension::getExtension(p).getBank();
    bool openBank = ccore.state->rowBufferStates->rowBufferIsOpen(b);
    bool allClosed = ccore.state->rowBufferStates->allRowBuffersAreClosed();
    bool pre = bwl ? openBank : !allClosed;
    sc_time trfcx = SC_ZERO_TIME;

    if (!bwl) {
        for (Bank b : ccore.getBanks()) {
            currentRefresh[b] = t;
        }
    } else {
        currentRefresh[b] = t;
    }

    if (pre) {
        trfcx += trp;
        if (bwl) {
            if (ccore.config.getRGRBank(b.ID())) {
                ccore.scheduleRequest(Command::PreB, rps[b]);
            }
        } else {
            for (Bank b : ccore.getBanks()) {
                auto rgrb = ccore.config.getRGRBank(b.ID());
                if (ccore.state->rowBufferStates->rowBufferIsOpen(b) && rgrb) {
                    ccore.scheduleRequest(Command::PreB, rps[Bank(b)]);
                }
            }
        }
    }

    for (unsigned r = 0; r < rpr; r += ri) {
        trfcx += trcd + trp;
        if (bwl) {
            if (ccore.config.getRGRBank(b.ID())) {
                ccore.scheduleRequest(Command::ActB, rps[b]);
                ccore.scheduleRequest(Command::PreB, rps[b]);
            }
            DramExtension::getExtension(p).incrementRow();
        } else {
            for (Bank b : ccore.getBanks()) {
                if (ccore.config.getRGRBank(b.ID())) {
                    ccore.scheduleRequest(Command::ActB, rps[b]);
                    ccore.scheduleRequest(Command::PreB, rps[b]);
                }
                DramExtension::getExtension(rps[b]).incrementRow();
            }
        }
    }

    return trfcx;
}

void RGR::scheduleRefresh(tlm::tlm_generic_payload &p, sc_time t)
{
    sc_time nrt;
    sc_time trfcx;
    Bank b = DramExtension::getExtension(p).getBank();
    bool preq = bwl ? ccore.hasPendingRequests(b) : ccore.hasPendingRequests();
    bool postpone = postponeEnabled && preq && (postponed[b] < maxpostpone);
    bool pullIn = pullInEnabled && !preq && (pulledin[b] < maxpullin);
    previousState[b] = currentState[b];
    currentState[b] = nextState[b];
    bool align = false;

    switch (currentState[b]) {
    case ST_REFRESH:
        assert(pulledin[b] == 0 && postponed[b] == 0);
        if (postpone) {
            nrt = SC_ZERO_TIME;
            nextState[b] = ST_POSTPONE;
        } else if (pullIn) {
            trfcx = doRefresh(p, t);
            nrt = trfcx;
            nextState[b] = ST_PULLIN;
        } else {
            doRefresh(p, t);
            nrt = tREFIx;
            nextState[b] = ST_REFRESH;
        }
        break;
    case ST_PULLIN:
        if (pullIn) {
            trfcx = doRefresh(p, t);
            pulledin[b]++;
            nrt = trfcx;
            nextState[b] = ST_PULLIN;
        } else {
            nrt = SC_ZERO_TIME;
            nextState[b] = ST_ALIGN;
        }
        break;
    case ST_SKIP:
        if (pulledin[b] == 0) {
            nrt = SC_ZERO_TIME;
            nextState[b] = ST_REFRESH;
        } else {
            pulledin[b]--;
            nrt = tREFIx;
            nextState[b] = ST_SKIP;
        }
        break;
    case ST_POSTPONE:
        postponed[b]++;
        if ((postponed[b] > maxpostpone) || (!preq && !fmb)) {
            // Burst triggered by inactivity or max postpone value reached.
            nrt = SC_ZERO_TIME;
            nextState[b] = ST_BURST;
        } else {
            nrt = tREFIx;
            nextState[b] = ST_POSTPONE;
        }
        break;
    case ST_BURST:
        doRefresh(p, t);
        postponed[b]--;
        if (postponed[b] == 0) {
            nrt = SC_ZERO_TIME;
            nextState[b] = ST_ALIGN;
        } else {
            nrt = SC_ZERO_TIME;
            nextState[b] = ST_BURST;
        }
        break;
    case ST_ALIGN:
        nrt = tREFIx;
        align = true;
        if (previousState[b] == ST_PULLIN) {
            nextState[b] = ST_SKIP;
        } else {
            nextState[b] = ST_REFRESH;
        }
        break;
    default:
        SC_REPORT_FATAL(this->name(), "RGR flex FSM invalid state.");
        break;
    }
    planNextRefresh(bwl ? b : ccore.getBanks()[0], nrt, align);
}

void RGR::planNextRefresh(Bank b, sc_time t, bool align)
{
    if (align) {
        nextPlannedRefreshs[b] = trunc(nextPlannedRefreshs[b].to_double() /
                                       tREFIx.to_double()) * tREFIx;
    }
    nextPlannedRefreshs[b] += t;
    ccore.controller.send(REFTrigger, nextPlannedRefreshs[b], rps[b]);
}

void RGR::reInitialize(Bank b, sc_time t)
{
    nextPlannedRefreshs[b] = clkAlign(t, Alignment::DOWN);
    planNextRefresh(b, tREFIx, true);
}

bool RGR::isInvalidated(tlm::tlm_generic_payload &p, sc_time t)
{
    return nextPlannedRefreshs[DramExtension::getExtension(p).getBank()] > t;
}

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