/*
 * Copyright (c) 2021, Technische Universität 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: Lukas Steiner
 */

#include "CheckerLPDDR5.h"

#include "DRAMSys/common/DebugManager.h"

#include <algorithm>

using namespace sc_core;
using namespace tlm;

CheckerLPDDR5::CheckerLPDDR5(const Configuration& config)
{
    memSpec = dynamic_cast<const MemSpecLPDDR5 *>(config.memSpec.get());
    if (memSpec == nullptr)
        SC_REPORT_FATAL("CheckerLPDDR5", "Wrong MemSpec chosen");
    else
    {
        lastScheduledByCommandAndRank = std::vector<std::vector<sc_time>>
                (Command::numberOfCommands(), std::vector<sc_time>(memSpec->ranksPerChannel, sc_max_time()));
        lastScheduledByCommandAndBankGroup = std::vector<std::vector<sc_time>>
                (Command::numberOfCommands(), std::vector<sc_time>(memSpec->bankGroupsPerChannel, sc_max_time()));
        lastScheduledByCommandAndBank = std::vector<std::vector<sc_time>>
                (Command::numberOfCommands(), std::vector<sc_time>(memSpec->banksPerChannel, sc_max_time()));
        lastScheduledByCommand = std::vector<sc_time>(Command::numberOfCommands(), sc_max_time());
        lastCommandOnBus = sc_max_time();

        last4Activates = std::vector<std::queue<sc_time>>(memSpec->ranksPerChannel);

        lastBurstLengthByCommandAndRank = std::vector<std::vector<uint8_t>>
                (Command::WRA + 1, std::vector<uint8_t>(memSpec->ranksPerChannel));
        lastBurstLengthByCommandAndBankGroup = std::vector<std::vector<uint8_t>>
                (Command::WRA + 1, std::vector<uint8_t>(memSpec->bankGroupsPerChannel));
        lastBurstLengthByCommandAndBank = std::vector<std::vector<uint8_t>>
                (Command::WRA + 1, std::vector<uint8_t>(memSpec->banksPerChannel));
        lastBurstLengthByCommand = std::vector<uint8_t>(Command::WRA + 1);

        tBURST16 = 16 / memSpec->dataRate * memSpec->tCK;
        tBURST32 = 32 / memSpec->dataRate * memSpec->tCK;
    }
}

sc_time CheckerLPDDR5::timeToSatisfyConstraints(Command command, const tlm_generic_payload& payload) const
{
    Rank rank = ControllerExtension::getRank(payload);
    BankGroup bankGroup = ControllerExtension::getBankGroup(payload);
    Bank bank = ControllerExtension::getBank(payload);

    sc_time lastCommandStart;
    sc_time earliestTimeToStart = sc_time_stamp();

    // TODO: model BG mode BL32 interleaved burst
    if (command == Command::RD || command == Command::RDA)
    {
        unsigned burstLength = ControllerExtension::getBurstLength(payload);
        assert(!(memSpec->bankMode == MemSpecLPDDR5::BankMode::M8B) || (burstLength == 32)); // 8B mode -> BL32
        assert(!(memSpec->bankMode == MemSpecLPDDR5::BankMode::MBG) || (memSpec->dataRate == 8)); // BG mode -> 4:1 ratio
        assert(burstLength <= memSpec->maxBurstLength);

        lastCommandStart = lastScheduledByCommandAndBank[Command::ACT][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRCD + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::RD][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::RD][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_L_32);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_L_16);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::RD][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::RD][rank.ID()] == 32)
            {
                // TODO: model BG mode BL32 interleaved burst, remove this fix
                if (memSpec->bankMode == MemSpecLPDDR5::BankMode::MBG)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_32 + 2 * memSpec->tCK);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_32);
            }
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_16);
        }

        lastCommandStart = lastScheduledByCommand[Command::RD] != lastScheduledByCommandAndRank[Command::RD][rank.ID()] ?
                lastScheduledByCommand[Command::RD] : sc_max_time();
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommand[Command::RD] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST32 + memSpec->tRTRS);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST16 + memSpec->tRTRS);
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::RDA][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::RDA][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_L_32);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_L_16);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::RDA][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::RDA][rank.ID()] == 32)
            {
                // TODO: model BG mode BL32 interleaved burst, remove this fix
                if (memSpec->bankMode == MemSpecLPDDR5::BankMode::MBG)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_32 + 2 * memSpec->tCK);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_32);
            }
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_16);
        }

        lastCommandStart = lastScheduledByCommand[Command::RDA] != lastScheduledByCommandAndRank[Command::RDA][rank.ID()] ?
                lastScheduledByCommand[Command::RDA] : sc_max_time();
        if (lastCommandStart != sc_max_time())
        {
            // TODO: BG mode with BL32
            if (lastBurstLengthByCommand[Command::RDA] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST32 + memSpec->tRTRS);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST16 + memSpec->tRTRS);
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::WR][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::WR][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_max_32 + memSpec->tWTR_L);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_max_16 + memSpec->tWTR_L);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::WR][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::WR][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tWTR_S);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tWTR_S);
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::WRA][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::WRA][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_max_32 + memSpec->tWTR_L);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_max_16 + memSpec->tWTR_L);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::WRA][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::WRA][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tWTR_S);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tWTR_S);
        }
    }
    else if (command == Command::WR || command == Command::WRA)
    {
        unsigned burstLength = ControllerExtension::getBurstLength(payload);
        assert(!(memSpec->bankMode == MemSpecLPDDR5::BankMode::M8B) || (burstLength == 32)); // 8B mode -> BL32
        assert(!(memSpec->bankMode == MemSpecLPDDR5::BankMode::MBG) || (memSpec->dataRate == 8)); // BG mode -> 4:1 ratio
        assert(burstLength <= memSpec->maxBurstLength);

        lastCommandStart = lastScheduledByCommandAndBank[Command::ACT][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRCD + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::RD][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::RD][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRL
                        + memSpec->BL_n_max_32 + memSpec->tWCK2DQO - memSpec->tWL);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRL
                        + memSpec->BL_n_max_16 + memSpec->tWCK2DQO - memSpec->tWL);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::RD][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::RD][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRL
                        + memSpec->BL_n_min_32 + memSpec->tWCK2DQO - memSpec->tWL);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRL
                        + memSpec->BL_n_min_16 + memSpec->tWCK2DQO - memSpec->tWL);
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::RDA][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::RDA][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRL
                        + memSpec->BL_n_max_32 + memSpec->tWCK2DQO - memSpec->tWL);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRL
                        + memSpec->BL_n_max_16 + memSpec->tWCK2DQO - memSpec->tWL);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::RDA][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::RDA][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRL
                        + memSpec->BL_n_min_32 + memSpec->tWCK2DQO - memSpec->tWL);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRL
                        + memSpec->BL_n_min_16 + memSpec->tWCK2DQO - memSpec->tWL);
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::WR][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::WR][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_L_32);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_L_16);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::WR][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::WR][rank.ID()] == 32)
            {
                // TODO: model BG mode BL32 interleaved burst, remove this fix
                if (memSpec->bankMode == MemSpecLPDDR5::BankMode::MBG)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_32 + 2 * memSpec->tCK);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_32);
            }
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_16);
        }

        lastCommandStart = lastScheduledByCommand[Command::WR] != lastScheduledByCommandAndRank[Command::WR][rank.ID()] ?
                lastScheduledByCommand[Command::WR] : sc_max_time();
        if (lastCommandStart != sc_max_time())
        {
            // TODO: BG mode with BL32
            if (lastBurstLengthByCommand[Command::WR] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST32 + memSpec->tRTRS);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST16 + memSpec->tRTRS);
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::WRA][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::WRA][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_L_32);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_L_16);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::WRA][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::WRA][rank.ID()] == 32)
            {
                // TODO: model BG mode BL32 interleaved burst, remove this fix
                if (memSpec->bankMode == MemSpecLPDDR5::BankMode::MBG)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_32 + 2 * memSpec->tCK);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_32);
            }
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_S_16);
        }

        lastCommandStart = lastScheduledByCommand[Command::WRA] != lastScheduledByCommandAndRank[Command::WRA][rank.ID()] ?
                lastScheduledByCommand[Command::WRA] : sc_max_time();
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommand[Command::WRA] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST32 + memSpec->tRTRS);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST16 + memSpec->tRTRS);
        }
    }
    else if (command == Command::ACT)
    {
        lastCommandStart = lastScheduledByCommandAndBank[Command::ACT][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRCpb);

        lastCommandStart = lastScheduledByCommandAndRank[Command::ACT][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRRD);

        lastCommandStart = lastScheduledByCommandAndBank[Command::RDA][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::RDA][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_32
                        + memSpec->tRBTP + memSpec->tRPpb - memSpec->tCK);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_16
                        + memSpec->tRBTP + memSpec->tRPpb - memSpec->tCK);
        }

        lastCommandStart = lastScheduledByCommandAndBank[Command::WRA][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::RDA][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb - memSpec->tCK);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb - memSpec->tCK);
        }

        lastCommandStart = lastScheduledByCommandAndBank[Command::PREPB][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRPpb - memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndRank[Command::PREAB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRPab - memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndRank[Command::REFAB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCab - memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndRank[Command::REFPB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tpbR2act - memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndRank[Command::REFP2B][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tpbR2act - memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndBank[Command::REFPB][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCpb - memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndBank[Command::REFP2B][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCpb - memSpec->tCK);

        if (last4Activates[rank.ID()].size() >= 4)
            earliestTimeToStart = std::max(earliestTimeToStart, last4Activates[rank.ID()].front() + memSpec->tFAW - memSpec->tCK);
    }
    else if (command == Command::PREPB)
    {
        lastCommandStart = lastScheduledByCommandAndBank[Command::ACT][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRAS + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndBank[Command::RD][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::RD][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_32
                        + memSpec->tRBTP);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_16
                        + memSpec->tRBTP);
        }

        lastCommandStart = lastScheduledByCommandAndBank[Command::WR][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::WR][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tCK + memSpec->tWR);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tCK + memSpec->tWR);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::PREPB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tPPD);
    }
    else if (command == Command::PREAB)
    {
        lastCommandStart = lastScheduledByCommandAndRank[Command::ACT][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRAS + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndRank[Command::RD][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::RD][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_32
                        + memSpec->tRBTP);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_16
                        + memSpec->tRBTP);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::RDA][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::RDA][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_32
                        + memSpec->tRBTP);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_16
                        + memSpec->tRBTP);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::WR][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::WR][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tCK + memSpec->tWR);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tCK + memSpec->tWR);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::WRA][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::WRA][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tCK + memSpec->tWR);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tCK + memSpec->tWR);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::PREPB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tPPD);
    }
    else if (command == Command::REFAB)
    {
        lastCommandStart = lastScheduledByCommandAndRank[Command::ACT][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRCpb + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndRank[Command::RDA][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::RDA][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_32
                        + memSpec->tRBTP + memSpec->tRPpb);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_16
                        + memSpec->tRBTP + memSpec->tRPpb);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::WRA][rank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndRank[Command::WRA][rank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb);
        }

        lastCommandStart = lastScheduledByCommandAndRank[Command::PREPB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRPpb);

        lastCommandStart = lastScheduledByCommandAndRank[Command::PREAB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRPab);

        lastCommandStart = lastScheduledByCommandAndRank[Command::REFAB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCab);
    }
    else if (command == Command::REFPB)
    {
        lastCommandStart = lastScheduledByCommandAndBank[Command::ACT][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRCpb + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndRank[Command::ACT][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRRD + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndBank[Command::RDA][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::RDA][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_32
                        + memSpec->tRBTP + memSpec->tRPpb);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_16
                        + memSpec->tRBTP + memSpec->tRPpb);
        }

        lastCommandStart = lastScheduledByCommandAndBank[Command::WRA][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::WRA][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb);
        }

        lastCommandStart = lastScheduledByCommandAndBank[Command::PREPB][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRPpb);

        lastCommandStart = lastScheduledByCommandAndRank[Command::PREAB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRPab);

        lastCommandStart = lastScheduledByCommandAndBank[Command::REFPB][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCpb);

        lastCommandStart = lastScheduledByCommandAndRank[Command::REFPB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tpbR2pbR);

        if (last4Activates[rank.ID()].size() >= 4)
            earliestTimeToStart = std::max(earliestTimeToStart, last4Activates[rank.ID()].front() + memSpec->tFAW);
    }
    else if (command == Command::REFP2B)
    {
        Bank secondBank = Bank(bank.ID() + memSpec->getPer2BankOffset());

        lastCommandStart = lastScheduledByCommandAndBank[Command::ACT][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRCpb + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndBank[Command::ACT][secondBank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRCpb + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndRank[Command::ACT][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRRD + memSpec->tCK);

        lastCommandStart = lastScheduledByCommandAndBank[Command::RDA][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::RDA][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_32
                        + memSpec->tRBTP + memSpec->tRPpb);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_16
                        + memSpec->tRBTP + memSpec->tRPpb);
        }

        lastCommandStart = lastScheduledByCommandAndBank[Command::RDA][secondBank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::RDA][secondBank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_32
                        + memSpec->tRBTP + memSpec->tRPpb);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->BL_n_min_16
                        + memSpec->tRBTP + memSpec->tRPpb);
        }

        lastCommandStart = lastScheduledByCommandAndBank[Command::WRA][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::WRA][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb);
        }

        lastCommandStart = lastScheduledByCommandAndBank[Command::WRA][secondBank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::WRA][secondBank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_32 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tWL
                        + memSpec->BL_n_min_16 + memSpec->tCK + memSpec->tWR + memSpec->tRPpb);
        }

        lastCommandStart = lastScheduledByCommandAndBank[Command::PREPB][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRPpb);

        lastCommandStart = lastScheduledByCommandAndBank[Command::PREPB][secondBank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRPpb);

        lastCommandStart = lastScheduledByCommandAndRank[Command::PREAB][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRPab);

        lastCommandStart = lastScheduledByCommandAndBank[Command::REFP2B][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCpb);

        lastCommandStart = lastScheduledByCommandAndRank[Command::REFP2B][rank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tpbR2pbR);

        if (last4Activates[rank.ID()].size() >= 4)
            earliestTimeToStart = std::max(earliestTimeToStart, last4Activates[rank.ID()].front() + memSpec->tFAW);
    }
    else
        SC_REPORT_FATAL("CheckerLPDDR5", "Unknown command!");

    // Check if command bus is free
    if (lastCommandOnBus != sc_max_time())
        earliestTimeToStart = std::max(earliestTimeToStart, lastCommandOnBus + memSpec->tCK);

    return earliestTimeToStart;
}

void CheckerLPDDR5::insert(Command command, const tlm_generic_payload& payload)
{
    Rank rank = ControllerExtension::getRank(payload);
    BankGroup bankGroup = ControllerExtension::getBankGroup(payload);
    Bank bank = ControllerExtension::getBank(payload);
    unsigned burstLength = ControllerExtension::getBurstLength(payload);

    PRINTDEBUGMESSAGE("CheckerLPDDR5", "Changing state on bank " + std::to_string(bank.ID())
                                       + " command is " + command.toString());

    lastScheduledByCommandAndRank[command][rank.ID()] = sc_time_stamp();
    lastScheduledByCommandAndBankGroup[command][bankGroup.ID()] = sc_time_stamp();
    lastScheduledByCommandAndBank[command][bank.ID()] = sc_time_stamp();
    lastScheduledByCommand[command] = sc_time_stamp();

    if (command.isCasCommand())
    {
        lastBurstLengthByCommandAndRank[command][rank.ID()] = burstLength;
        lastBurstLengthByCommandAndBankGroup[command][bankGroup.ID()] = burstLength;
        lastBurstLengthByCommandAndBank[command][bank.ID()] = burstLength;
        lastBurstLengthByCommand[command] = burstLength;
    }

    if (command == Command::REFP2B)
        lastScheduledByCommandAndBank[command][bank.ID() + memSpec->getPer2BankOffset()] = sc_time_stamp();

    lastCommandOnBus = sc_time_stamp() + memSpec->getCommandLength(command) - memSpec->tCK;

    if (command == Command::ACT || command == Command::REFPB || command == Command::REFP2B
            || command == Command::RFMPB || command == Command::RFMP2B)
    {
        if (last4Activates[rank.ID()].size() == 4)
            last4Activates[rank.ID()].pop();
        last4Activates[rank.ID()].push(lastCommandOnBus);
    }
}
