/*
 * Copyright (c) 2020, 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 "CheckerDDR5.h"

#include "DRAMSys/common/DebugManager.h"

#include <algorithm>

using namespace sc_core;
using namespace tlm;

CheckerDDR5::CheckerDDR5(const Configuration& config)
{
    memSpec = dynamic_cast<const MemSpecDDR5 *>(config.memSpec.get());
    if (memSpec == nullptr)
        SC_REPORT_FATAL("CheckerDDR5", "Wrong MemSpec chosen");

    lastScheduledByCommandAndDimmRank = std::vector<std::vector<sc_time>>
            (Command::numberOfCommands(), std::vector<sc_time>(memSpec->dimmRanksPerChannel, sc_max_time()));
    lastScheduledByCommandAndPhysicalRank = std::vector<std::vector<sc_time>>
            (Command::numberOfCommands(), std::vector<sc_time>(memSpec->physicalRanksPerChannel, sc_max_time()));
    lastScheduledByCommandAndLogicalRank = std::vector<std::vector<sc_time>>
            (Command::numberOfCommands(), std::vector<sc_time>(memSpec->logicalRanksPerChannel, 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());
    // Required for Same Bank Refresh
    lastScheduledByCommandAndBankInGroup = std::vector<std::vector<sc_time>>(Command::numberOfCommands(),
            std::vector<sc_time>(memSpec->ranksPerChannel * memSpec->banksPerGroup, sc_max_time()));
    lastCommandOnBus = sc_max_time();
    dummyCommandOnBus.start = sc_max_time();
    dummyCommandOnBus.end = sc_max_time();

    last4ActivatesLogical = std::vector<std::queue<sc_time>>(memSpec->logicalRanksPerChannel);
    last4ActivatesPhysical = std::vector<std::queue<sc_time>>(memSpec->physicalRanksPerChannel);

    lastBurstLengthByCommandAndDimmRank = std::vector<std::vector<uint8_t>>
            (Command::WRA + 1, std::vector<uint8_t>(memSpec->dimmRanksPerChannel));
    lastBurstLengthByCommandAndPhysicalRank = std::vector<std::vector<uint8_t>>
            (Command::WRA + 1, std::vector<uint8_t>(memSpec->physicalRanksPerChannel));
    lastBurstLengthByCommandAndLogicalRank = std::vector<std::vector<uint8_t>>
            (Command::WRA + 1, std::vector<uint8_t>(memSpec->logicalRanksPerChannel));
    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);
    lastBurstLengthByCommandAndBankInGroup = std::vector<std::vector<uint8_t>>
            (Command::WRA + 1, std::vector<uint8_t>(memSpec->ranksPerChannel * memSpec->banksPerGroup));

    cmdLengthDiff = memSpec->cmdMode * memSpec->tCK;

    tBURST16 = 8 * memSpec->tCK;
    tBURST32 = 16 * memSpec->tCK;
    tWTRA = memSpec->tWR - memSpec->tRTP;
    tWRRDA = memSpec->tWL + tBURST16 + tWTRA; // tWTRA = tWR - tRTP
    tWRPRE = memSpec->tWL + tBURST16 + memSpec->tWR;
    tRDAACT = memSpec->tRTP + memSpec->tRP;
    tWRAACT = tWRPRE + memSpec->tRP;
    tCCD_L_RTW_slr = memSpec->tRL - memSpec->tWL + tBURST16 + 2 * memSpec->tCK
                     - memSpec->tRDDQS + memSpec->tRPST + memSpec->tWPRE;
    tCCD_S_RTW_slr = memSpec->tRL - memSpec->tWL + tBURST16 + 2 * memSpec->tCK
                     - memSpec->tRDDQS + memSpec->tRPST + memSpec->tWPRE;
    tCCD_RTW_dlr = memSpec->tRL - memSpec->tWL + tBURST16 + 2 * memSpec->tCK
                   - memSpec->tRDDQS + memSpec->tRPST + memSpec->tWPRE;
    tRDRD_dpr = tBURST16 + memSpec->tRTRS;
    tRDRD_ddr = tBURST16 + memSpec->tRTRS;
    tRDWR_dpr = memSpec->tRL - memSpec->tWL + tBURST16 + memSpec->tRTRS
                - memSpec->tRDDQS + memSpec->tRPST + memSpec->tWPRE;
    tRDWR_ddr = memSpec->tRL - memSpec->tWL + tBURST16 + memSpec->tRTRS
                - memSpec->tRDDQS + memSpec->tRPST + memSpec->tWPRE;
    tCCD_L_WTR_slr = memSpec->tWL + tBURST16 + memSpec->tWTR_L;
    tCCD_S_WTR_slr = memSpec->tWL + tBURST16 + memSpec->tWTR_S;
    tCCD_WTR_dlr = memSpec->tWL + tBURST16 + memSpec->tWTR_S;
    tWRWR_dpr = std::max(memSpec->tCCD_WR_dpr, tBURST16 + memSpec->tRTRS);
    tWRWR_ddr = tBURST16 + memSpec->tRTRS;
    tWRRD_dpr = memSpec->tWL - memSpec->tRL + tBURST16 + memSpec->tRTRS
                + memSpec->tRDDQS + memSpec->tWPST + memSpec->tRPRE;
    tWRRD_ddr = memSpec->tWL - memSpec->tRL + tBURST16 + memSpec->tRTRS
                + memSpec->tRDDQS + memSpec->tWPST + memSpec->tRPRE;
    tRDPDEN = memSpec->tRL + tBURST16 + cmdLengthDiff;
    tWRPDEN = memSpec->tWL + tBURST16 + memSpec->tWR + cmdLengthDiff;
    tWRAPDEN = memSpec->tWL + tBURST16 + memSpec->tWR + cmdLengthDiff;
}

sc_time CheckerDDR5::timeToSatisfyConstraints(Command command, const tlm_generic_payload& payload) const
{
    Rank logicalRank = ControllerExtension::getRank(payload);
    Rank physicalRank = Rank(logicalRank.ID() / memSpec->logicalRanksPerPhysicalRank);
    Rank dimmRank = Rank(physicalRank.ID() / memSpec->physicalRanksPerDimmRank);
    BankGroup bankGroup = ControllerExtension::getBankGroup(payload);
    Bank bank = ControllerExtension::getBank(payload);
    Bank bankInGroup = Bank(logicalRank.ID() * memSpec->banksPerGroup + bank.ID() % memSpec->banksPerGroup);
    
    sc_time lastCommandStart;
    sc_time earliestTimeToStart = sc_time_stamp();

    if (command == Command::RD || command == Command::RDA)
    {
        unsigned burstLength = ControllerExtension::getBurstLength(payload);
        assert((burstLength == 16) || (burstLength == 32));
        assert(!(burstLength == 32) || (memSpec->bitWidth == 4));
        assert(burstLength <= memSpec->maxBurstLength);

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

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::RD][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_L_slr);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RD][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_S_slr);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::RD][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndPhysicalRank[Command::RD][physicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST32);

            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_dlr);
        }

        lastCommandStart = lastScheduledByCommand[Command::RD];
        if (lastCommandStart != lastScheduledByCommandAndPhysicalRank[Command::RD][physicalRank.ID()]) // different physical rank
        {
            if (lastCommandStart == lastScheduledByCommandAndDimmRank[Command::RD][dimmRank.ID()]) // same DIMM
            {
                if (lastBurstLengthByCommandAndDimmRank[Command::RD][dimmRank.ID()] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDRD_dpr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDRD_dpr);
            }
            else // different DIMM
            {
                if (lastBurstLengthByCommand[Command::RD] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDRD_ddr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDRD_ddr);
            }
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::RDA][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_L_slr);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RDA][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_S_slr);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::RDA][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndPhysicalRank[Command::RDA][physicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST32);

            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_dlr);
        }

        lastCommandStart = lastScheduledByCommand[Command::RDA];
        if (lastCommandStart != lastScheduledByCommandAndPhysicalRank[Command::RDA][physicalRank.ID()]) // different physical rank
        {
            if (lastCommandStart == lastScheduledByCommandAndDimmRank[Command::RDA][dimmRank.ID()]) // same DIMM
            {
                if (lastBurstLengthByCommandAndDimmRank[Command::RDA][dimmRank.ID()] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDRD_dpr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDRD_dpr);
            }
            else // different DIMM
            {
                if (lastBurstLengthByCommand[Command::RDA] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDRD_ddr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDRD_ddr);
            }
        }

        if (command == Command::RDA)
        {
            lastCommandStart = lastScheduledByCommandAndBank[Command::WR][bank.ID()];
            if (lastCommandStart != sc_max_time())
            {
                if (lastBurstLengthByCommandAndBank[Command::WR][bank.ID()] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRDA + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRDA);
            }
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::WR][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::WR][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_L_WTR_slr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_L_WTR_slr);
        }

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::WR][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndLogicalRank[Command::WR][logicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_S_WTR_slr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_S_WTR_slr);
        }

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::WR][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndPhysicalRank[Command::WR][physicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_WTR_dlr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_WTR_dlr);
        }

        lastCommandStart = lastScheduledByCommand[Command::WR];
        if (lastCommandStart != lastScheduledByCommandAndPhysicalRank[Command::WR][physicalRank.ID()]) // different physical rank
        {
            if (lastCommandStart == lastScheduledByCommandAndDimmRank[Command::WR][dimmRank.ID()]) // same DIMM
            {
                if (lastBurstLengthByCommandAndDimmRank[Command::WR][dimmRank.ID()] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRD_dpr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRD_dpr);
            }
            else // different DIMM
            {
                if (lastBurstLengthByCommand[Command::WR] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRD_ddr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRD_ddr);
            }
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::WRA][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::WRA][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_L_WTR_slr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_L_WTR_slr);
        }

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::WRA][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndLogicalRank[Command::WRA][logicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_S_WTR_slr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_S_WTR_slr);
        }

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::WRA][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndPhysicalRank[Command::WRA][physicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_WTR_dlr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_WTR_dlr);
        }

        lastCommandStart = lastScheduledByCommand[Command::WRA];
        if (lastCommandStart != lastScheduledByCommandAndPhysicalRank[Command::WRA][physicalRank.ID()]) // different physical rank
        {
            if (lastCommandStart == lastScheduledByCommandAndDimmRank[Command::WRA][dimmRank.ID()]) // same DIMM
            {
                if (lastBurstLengthByCommandAndDimmRank[Command::WRA][dimmRank.ID()] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRD_dpr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRD_dpr);
            }
            else // different DIMM
            {
                if (lastBurstLengthByCommand[Command::WRA] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRD_ddr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRRD_ddr);
            }
        }
    }
    else if (command == Command::WR || command == Command::WRA)
    {
        unsigned burstLength = ControllerExtension::getBurstLength(payload);
        assert((burstLength == 16) || (burstLength == 32));
        assert(!(burstLength == 32) || (memSpec->bitWidth == 4));
        assert(burstLength <= memSpec->maxBurstLength);

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

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::RD][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::RD][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_L_RTW_slr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_L_RTW_slr);
        }

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RD][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndLogicalRank[Command::RD][logicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_S_RTW_slr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_S_RTW_slr);
        }

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::RD][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndPhysicalRank[Command::RD][physicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_RTW_dlr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_RTW_dlr);
        }

        lastCommandStart = lastScheduledByCommand[Command::RD];
        if (lastCommandStart != lastScheduledByCommandAndPhysicalRank[Command::RD][physicalRank.ID()]) // different physical rank
        {
            if (lastCommandStart == lastScheduledByCommandAndDimmRank[Command::RD][dimmRank.ID()]) // same DIMM
            {
                if (lastBurstLengthByCommandAndDimmRank[Command::RD][dimmRank.ID()] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDWR_dpr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDWR_dpr);
            }
            else // different DIMM
            {
                if (lastBurstLengthByCommand[Command::RD] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDWR_ddr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDWR_ddr);
            }
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::RDA][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::RDA][bankGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_L_RTW_slr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_L_RTW_slr);
        }

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RDA][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndLogicalRank[Command::RDA][logicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_S_RTW_slr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_S_RTW_slr);
        }

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::RDA][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndPhysicalRank[Command::RDA][physicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_RTW_dlr + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tCCD_RTW_dlr);
        }

        lastCommandStart = lastScheduledByCommand[Command::RDA];
        if (lastCommandStart != lastScheduledByCommandAndPhysicalRank[Command::RDA][physicalRank.ID()]) // different physical rank
        {
            if (lastCommandStart == lastScheduledByCommandAndDimmRank[Command::RDA][dimmRank.ID()]) // same DIMM
            {
                if (lastBurstLengthByCommandAndDimmRank[Command::RDA][dimmRank.ID()] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDWR_dpr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDWR_dpr);
            }
            else // different DIMM
            {
                if (lastBurstLengthByCommand[Command::RDA] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDWR_ddr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDWR_ddr);
            }
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::WR][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::WR][bankGroup.ID()] == 32)
            {
                if (burstLength == 16 && memSpec->bitWidth == 4) // second WR requires RMW
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST16 + memSpec->tCCD_L_WR_slr);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST16 + memSpec->tCCD_L_WR2_slr);
            }
            else
            {
                if (burstLength == 16 && memSpec->bitWidth == 4) // second WR requires RMW
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_L_WR_slr);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_L_WR2_slr);
            }
        }

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::WR][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_S_WR_slr);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::WR][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndPhysicalRank[Command::WR][physicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST32);

            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_WR_dlr);
        }

        lastCommandStart = lastScheduledByCommand[Command::WR];
        if (lastCommandStart != lastScheduledByCommandAndPhysicalRank[Command::WR][physicalRank.ID()]) // different physical rank
        {
            if (lastCommandStart == lastScheduledByCommandAndDimmRank[Command::WR][dimmRank.ID()]) // same DIMM
            {
                if (lastBurstLengthByCommandAndDimmRank[Command::WR][dimmRank.ID()] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRWR_dpr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRWR_dpr);
            }
            else // different DIMM
            {
                if (lastBurstLengthByCommand[Command::WR] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRWR_ddr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRWR_ddr);
            }
        }

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::WRA][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankGroup[Command::WRA][bankGroup.ID()] == 32)
            {
                if (burstLength == 16 && memSpec->bitWidth == 4) // second WR requires RMW
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST16 + memSpec->tCCD_L_WR_slr);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST16 + memSpec->tCCD_L_WR2_slr);
            }
            else
            {
                if (burstLength == 16 && memSpec->bitWidth == 4) // second WR requires RMW
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_L_WR_slr);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_L_WR2_slr);
            }
        }

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::WRA][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_S_WR_slr);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::WRA][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndPhysicalRank[Command::WRA][physicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tBURST32);

            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tCCD_WR_dlr);
        }

        lastCommandStart = lastScheduledByCommand[Command::WRA];
        if (lastCommandStart != lastScheduledByCommandAndPhysicalRank[Command::WRA][physicalRank.ID()]) // different physical rank
        {
            if (lastCommandStart == lastScheduledByCommandAndDimmRank[Command::WRA][dimmRank.ID()]) // same DIMM
            {
                if (lastBurstLengthByCommandAndDimmRank[Command::WRA][dimmRank.ID()] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRWR_dpr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRWR_dpr);
            }
            else // different DIMM
            {
                if (lastBurstLengthByCommand[Command::WRA] == 32)
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRWR_ddr + tBURST16);
                else
                    earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRWR_ddr);
            }
        }
    }
    else if (command == Command::ACT)
    {
        lastCommandStart = lastScheduledByCommandAndBank[Command::ACT][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRC);

        lastCommandStart = lastScheduledByCommandAndBankGroup[Command::ACT][bankGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRRD_L_slr);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::ACT][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRRD_S_slr);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::ACT][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRRD_dlr);

        lastCommandStart = lastScheduledByCommandAndBank[Command::RDA][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDAACT);

        lastCommandStart = lastScheduledByCommandAndBank[Command::WRA][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::WRA][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRAACT + tBURST16);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRAACT);
        }

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

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::PREAB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRP - cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::PRESB][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRP - cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::REFAB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_slr - cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RFMAB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_slr - cmdLengthDiff);

        // TODO: No tRFC_dlr and tRFC_dpr between REFAB and ACT?

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::REFSB][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCsb_slr - cmdLengthDiff);

        // TODO: No tRFCsb_dlr between REFSB and ACT?

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::REFSB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tREFSBRD_slr - cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::REFSB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tREFSBRD_dlr - cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::RFMSB][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCsb_slr - cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RFMSB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tREFSBRD_slr - cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::RFMSB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tREFSBRD_dlr - cmdLengthDiff);

        if (last4ActivatesLogical[logicalRank.ID()].size() >= 4)
            earliestTimeToStart = std::max(earliestTimeToStart, last4ActivatesLogical[logicalRank.ID()].front()
                    + memSpec->tFAW_slr - memSpec->longCmdOffset);

        if (last4ActivatesPhysical[physicalRank.ID()].size() >= 4)
            earliestTimeToStart = std::max(earliestTimeToStart, last4ActivatesPhysical[physicalRank.ID()].front()
                    + memSpec->tFAW_dlr - memSpec->longCmdOffset);
    }
    else if (command == Command::PREPB)
    {
        lastCommandStart = lastScheduledByCommandAndBank[Command::ACT][bank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRAS + cmdLengthDiff);

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

        lastCommandStart = lastScheduledByCommandAndBank[Command::WR][bank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBank[Command::WR][bank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + tBURST16 + cmdLengthDiff);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + cmdLengthDiff);
        }

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::PREPB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tPPD);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::PREAB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tPPD);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::PRESB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tPPD);
    }
    else if (command == Command::PREAB)
    {
        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::ACT][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRAS + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RD][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRTP + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RDA][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRTP + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::WR][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndLogicalRank[Command::WR][logicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + tBURST16 + cmdLengthDiff);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + cmdLengthDiff);
        }

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::WRA][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndLogicalRank[Command::WRA][logicalRank.ID()])
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + tBURST16 + cmdLengthDiff);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + cmdLengthDiff);
        }

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::PREPB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tPPD);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::PREAB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tPPD);

        // PRESB tPPD
    }
    else if (command == Command::PRESB)
    {
        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::ACT][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRAS + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::RD][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRTP + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::RDA][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRTP + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::WR][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankInGroup[Command::WR][bankInGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + tBURST16 + cmdLengthDiff);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + cmdLengthDiff);
        }

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::WRA][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankInGroup[Command::WRA][bankInGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + tBURST16 + cmdLengthDiff);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + cmdLengthDiff);
        }

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::PREPB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tPPD);

        // PREA tRP

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::PRESB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tPPD);
    }
    else if (command == Command::REFAB || command == Command::RFMAB)
    {
        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::ACT][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRC + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::RDA][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDAACT + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::WRA][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndLogicalRank[Command::WRA][logicalRank.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + tBURST16 + memSpec->tRP + cmdLengthDiff);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRPRE + memSpec->tRP + cmdLengthDiff);
        }

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::PREPB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRP);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::PREAB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRP);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::REFAB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_slr);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::REFAB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_dlr);

        lastCommandStart = lastScheduledByCommandAndDimmRank[Command::REFAB][dimmRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_dpr);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RFMAB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_slr);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::RFMAB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_dlr);

        lastCommandStart = lastScheduledByCommandAndDimmRank[Command::RFMAB][dimmRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_dpr);

        // REFSB tRFCsb_slr/dlr
        // PRESB tRP
    }
    else if (command == Command::REFSB || command == Command::RFMSB)
    {
        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::ACT][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRC + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::ACT][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRRD_L_slr + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::RDA][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tRDAACT + cmdLengthDiff);

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::WRA][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
        {
            if (lastBurstLengthByCommandAndBankInGroup[Command::WRA][bankInGroup.ID()] == 32)
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRAACT + tBURST16 + cmdLengthDiff);
            else
                earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + tWRAACT + cmdLengthDiff);
        }

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::PREPB][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRP);

        // PREA tRP

        lastCommandStart = lastScheduledByCommandAndBankInGroup[Command::PRESB][bankInGroup.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRP);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::REFAB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_slr);

        // TODO: check this
        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::REFAB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_dlr);

        // TODO: check this
        lastCommandStart = lastScheduledByCommandAndDimmRank[Command::REFAB][dimmRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_dpr);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RFMAB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_slr);

        // TODO: check this
        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::RFMAB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_dlr);

        // TODO: check this
        lastCommandStart = lastScheduledByCommandAndDimmRank[Command::RFMAB][dimmRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFC_dpr);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::REFSB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCsb_slr);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::REFSB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCsb_dlr);

        lastCommandStart = lastScheduledByCommandAndLogicalRank[Command::RFMSB][logicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCsb_slr);

        lastCommandStart = lastScheduledByCommandAndPhysicalRank[Command::RFMSB][physicalRank.ID()];
        if (lastCommandStart != sc_max_time())
            earliestTimeToStart = std::max(earliestTimeToStart, lastCommandStart + memSpec->tRFCsb_dlr);

        if (last4ActivatesLogical[logicalRank.ID()].size() >= 4)
            earliestTimeToStart = std::max(earliestTimeToStart, last4ActivatesLogical[logicalRank.ID()].front()
                    + memSpec->tFAW_slr - memSpec->shortCmdOffset);

        if (last4ActivatesPhysical[physicalRank.ID()].size() >= 4)
            earliestTimeToStart = std::max(earliestTimeToStart, last4ActivatesPhysical[physicalRank.ID()].front()
                    + memSpec->tFAW_dlr - memSpec->shortCmdOffset);
    }
    else
        SC_REPORT_FATAL("CheckerDDR5", "Unknown command!");

    if (lastCommandOnBus != sc_max_time())
        earliestTimeToStart = std::max(earliestTimeToStart, lastCommandOnBus + memSpec->tCK);

    if (dummyCommandOnBus.start != sc_max_time())
    {
        TimeInterval currentCommandOnBus(earliestTimeToStart,
                                         earliestTimeToStart + memSpec->getCommandLength(command));
        if (currentCommandOnBus.intersects(dummyCommandOnBus))
            earliestTimeToStart = dummyCommandOnBus.end;
    }

    return earliestTimeToStart;
}

void CheckerDDR5::insert(Command command, const tlm_generic_payload& payload)
{
    Rank logicalRank = ControllerExtension::getRank(payload);
    Rank physicalRank = Rank(logicalRank.ID() / memSpec->logicalRanksPerPhysicalRank);
    Rank dimmRank = Rank(physicalRank.ID() / memSpec->physicalRanksPerDimmRank);
    BankGroup bankGroup = ControllerExtension::getBankGroup(payload);
    Bank bank = ControllerExtension::getBank(payload);
    Bank bankInGroup = Bank(logicalRank.ID() * memSpec->banksPerGroup + bank.ID() % memSpec->banksPerGroup);
    unsigned burstLength = ControllerExtension::getBurstLength(payload);

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

    lastScheduledByCommandAndDimmRank[command][dimmRank.ID()] = sc_time_stamp();
    lastScheduledByCommandAndPhysicalRank[command][physicalRank.ID()] = sc_time_stamp();
    lastScheduledByCommandAndLogicalRank[command][logicalRank.ID()] = sc_time_stamp();
    lastScheduledByCommandAndBankGroup[command][bankGroup.ID()] = sc_time_stamp();
    lastScheduledByCommandAndBank[command][bank.ID()] = sc_time_stamp();
    lastScheduledByCommand[command] = sc_time_stamp();
    lastScheduledByCommandAndBankInGroup[command][bankInGroup.ID()] = sc_time_stamp();

    if (command.isCasCommand())
    {
        lastBurstLengthByCommandAndDimmRank[command][dimmRank.ID()] = burstLength;
        lastBurstLengthByCommandAndPhysicalRank[command ][physicalRank.ID()] = burstLength;
        lastBurstLengthByCommandAndLogicalRank[command][logicalRank.ID()] = burstLength;
        lastBurstLengthByCommandAndBankGroup[command][bankGroup.ID()] = burstLength;
        lastBurstLengthByCommandAndBank[command][bank.ID()] = burstLength;
        lastBurstLengthByCommand[command] = burstLength;
        lastBurstLengthByCommandAndBankInGroup[command][bankInGroup.ID()] = burstLength;

        if (burstLength == 32)
        {
            dummyCommandOnBus.start = sc_time_stamp() + tBURST16;
            dummyCommandOnBus.end = sc_time_stamp() + tBURST16 + memSpec->getCommandLength(command);
        }
    }

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

    if (command == Command::ACT || command == Command::REFSB || command == Command::RFMSB)
    {
        if (last4ActivatesLogical[logicalRank.ID()].size() == 4)
            last4ActivatesLogical[logicalRank.ID()].pop();
        last4ActivatesLogical[logicalRank.ID()].push(lastCommandOnBus);

        if (last4ActivatesPhysical[physicalRank.ID()].size() == 4)
            last4ActivatesPhysical[physicalRank.ID()].pop();
        last4ActivatesPhysical[physicalRank.ID()].push(lastCommandOnBus);
    }
}
