/*
 * 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.
 *
 * Authors:
 *    Lukas Steiner
 *    Derek Christ
 */

#include "MemSpecDDR5.h"

#include <DRAMSys/common/utils.h>

#include <iostream>

using namespace sc_core;
using namespace tlm;

MemSpecDDR5::MemSpecDDR5(const DRAMSys::Config::MemSpec &memSpec)
    : MemSpec(memSpec, MemoryType::DDR5,
      memSpec.memArchitectureSpec.entries.at("nbrOfChannels"),
      1,
      memSpec.memArchitectureSpec.entries.at("nbrOfRanks"),
      memSpec.memArchitectureSpec.entries.at("nbrOfBanks"),
      memSpec.memArchitectureSpec.entries.at("nbrOfBankGroups"),
      memSpec.memArchitectureSpec.entries.at("nbrOfBanks")
          / memSpec.memArchitectureSpec.entries.at("nbrOfBankGroups"),
      memSpec.memArchitectureSpec.entries.at("nbrOfBanks")
          * memSpec.memArchitectureSpec.entries.at("nbrOfRanks"),
      memSpec.memArchitectureSpec.entries.at("nbrOfBankGroups")
          * memSpec.memArchitectureSpec.entries.at("nbrOfRanks"),
      memSpec.memArchitectureSpec.entries.at("nbrOfDevices")),
      dimmRanksPerChannel(memSpec.memArchitectureSpec.entries.at("nbrOfDIMMRanks")),
      physicalRanksPerDimmRank(memSpec.memArchitectureSpec.entries.at("nbrOfPhysicalRanks")),
      physicalRanksPerChannel(physicalRanksPerDimmRank * dimmRanksPerChannel),
      logicalRanksPerPhysicalRank(memSpec.memArchitectureSpec.entries.at("nbrOfLogicalRanks")),
      logicalRanksPerChannel(logicalRanksPerPhysicalRank * physicalRanksPerChannel),
      cmdMode(memSpec.memArchitectureSpec.entries.at("cmdMode")),
      refMode(memSpec.memArchitectureSpec.entries.at("refMode")),
      RAAIMT(memSpec.memArchitectureSpec.entries.at("RAAIMT")),
      RAAMMT(memSpec.memArchitectureSpec.entries.at("RAAMMT")),
      RAACDR(memSpec.memArchitectureSpec.entries.at("RAACDR")),
      tRCD           (tCK * memSpec.memTimingSpec.entries.at("RCD")),
      tPPD           (tCK * memSpec.memTimingSpec.entries.at("PPD")),
      tRP            (tCK * memSpec.memTimingSpec.entries.at("RP")),
      tRAS           (tCK * memSpec.memTimingSpec.entries.at("RAS")),
      tRC            (tRAS + tRP),
      tRL            (tCK * memSpec.memTimingSpec.entries.at("RL")),
      tRTP           (tCK * memSpec.memTimingSpec.entries.at("RTP")),
      tRPRE          (tCK * memSpec.memTimingSpec.entries.at("RPRE")),
      tRPST          (tCK * memSpec.memTimingSpec.entries.at("RPST")),
      tRDDQS         (tCK * memSpec.memTimingSpec.entries.at("RDDQS")),
      tWL            (tCK * memSpec.memTimingSpec.entries.at("WL")),
      tWPRE          (tCK * memSpec.memTimingSpec.entries.at("WPRE")),
      tWPST          (tCK * memSpec.memTimingSpec.entries.at("WPST")),
      tWR            (tCK * memSpec.memTimingSpec.entries.at("WR")),
      tCCD_L_slr     (tCK * memSpec.memTimingSpec.entries.at("CCD_L_slr")),
      tCCD_L_WR_slr  (tCK * memSpec.memTimingSpec.entries.at("CCD_L_WR_slr")),
      tCCD_L_WR2_slr (tCK * memSpec.memTimingSpec.entries.at("CCD_L_WR2_slr")),
      tCCD_S_slr     (tCK * memSpec.memTimingSpec.entries.at("CCD_S_slr")),
      tCCD_S_WR_slr  (tCK * memSpec.memTimingSpec.entries.at("CCD_S_WR_slr")),
      tCCD_dlr       (tCK * memSpec.memTimingSpec.entries.at("CCD_dlr")),
      tCCD_WR_dlr    (tCK * memSpec.memTimingSpec.entries.at("CCD_WR_dlr")),
      tCCD_WR_dpr    (tCK * memSpec.memTimingSpec.entries.at("CCD_WR_dpr")),
      tRRD_L_slr     (tCK * memSpec.memTimingSpec.entries.at("RRD_L_slr")),
      tRRD_S_slr     (tCK * memSpec.memTimingSpec.entries.at("RRD_S_slr")),
      tRRD_dlr       (tCK * memSpec.memTimingSpec.entries.at("RRD_dlr")),
      tFAW_slr       (tCK * memSpec.memTimingSpec.entries.at("FAW_slr")),
      tFAW_dlr       (tCK * memSpec.memTimingSpec.entries.at("FAW_dlr")),
      tWTR_L         (tCK * memSpec.memTimingSpec.entries.at("WTR_L")),
      tWTR_S         (tCK * memSpec.memTimingSpec.entries.at("WTR_S")),
      tRFC_slr       ((refMode == 1) ? tCK * memSpec.memTimingSpec.entries.at("RFC1_slr")
                             : tCK * memSpec.memTimingSpec.entries.at("RFC2_slr")),
      tRFC_dlr       ((refMode == 1) ? tCK * memSpec.memTimingSpec.entries.at("RFC1_dlr")
                             : tCK * memSpec.memTimingSpec.entries.at("RFC2_dlr")),
      tRFC_dpr       ((refMode == 1) ? tCK * memSpec.memTimingSpec.entries.at("RFC1_dpr")
                             : tCK * memSpec.memTimingSpec.entries.at("RFC2_dpr")),
      tRFCsb_slr     (tCK * memSpec.memTimingSpec.entries.at("RFCsb_slr")),
      tRFCsb_dlr     (tCK * memSpec.memTimingSpec.entries.at("RFCsb_dlr")),
      tREFI          ((refMode == 1) ? tCK * memSpec.memTimingSpec.entries.at("REFI1")
                             : tCK * memSpec.memTimingSpec.entries.at("REFI2")),
      tREFIsb        (tCK * memSpec.memTimingSpec.entries.at("REFISB")),
      tREFSBRD_slr   (tCK * memSpec.memTimingSpec.entries.at("REFSBRD_slr")),
      tREFSBRD_dlr   (tCK * memSpec.memTimingSpec.entries.at("REFSBRD_dlr")),
      tRTRS          (tCK * memSpec.memTimingSpec.entries.at("RTRS")),
      tCPDED         (tCK * memSpec.memTimingSpec.entries.at("CPDED")),
      tPD            (tCK * memSpec.memTimingSpec.entries.at("PD")),
      tXP            (tCK * memSpec.memTimingSpec.entries.at("XP")),
      tACTPDEN       (tCK * memSpec.memTimingSpec.entries.at("ACTPDEN")),
      tPRPDEN        (tCK * memSpec.memTimingSpec.entries.at("PRPDEN")),
      tREFPDEN       (tCK * memSpec.memTimingSpec.entries.at("REFPDEN")),
      shortCmdOffset (cmdMode == 2 ? 1 * tCK : 0 * tCK),
      longCmdOffset  (cmdMode == 2 ? 3 * tCK : 1 * tCK),
      tBURST16       (tCK * 8),
      tBURST32       (tCK * 16)
{
    if (cmdMode == 1)
    {
        commandLengthInCycles[Command::ACT] = 2;
        commandLengthInCycles[Command::RD] = 2;
        commandLengthInCycles[Command::RDA] = 2;
        commandLengthInCycles[Command::WR] = 2;
        commandLengthInCycles[Command::WRA] = 2;
    }
    else if (cmdMode == 2)
    {
        commandLengthInCycles[Command::ACT] = 4;
        commandLengthInCycles[Command::PREPB] = 2;
        commandLengthInCycles[Command::PREAB] = 2;
        commandLengthInCycles[Command::PRESB] = 2;
        commandLengthInCycles[Command::RD] = 4;
        commandLengthInCycles[Command::RDA] = 4;
        commandLengthInCycles[Command::WR] = 4;
        commandLengthInCycles[Command::WRA] = 4;
        commandLengthInCycles[Command::REFAB] = 2;
        commandLengthInCycles[Command::RFMAB] = 2;
        commandLengthInCycles[Command::REFSB] = 2;
        commandLengthInCycles[Command::RFMSB] = 2;
        commandLengthInCycles[Command::PDEA] = 2;
        commandLengthInCycles[Command::PDXA] = 2;
        commandLengthInCycles[Command::PDEP] = 2;
        commandLengthInCycles[Command::PDXP] = 2;
        commandLengthInCycles[Command::SREFEN] = 2;
        commandLengthInCycles[Command::SREFEX] = 2;
    }
    else
        SC_REPORT_FATAL("MemSpecDDR5", "Invalid command mode!");

    if (!(refMode == 1 || refMode == 2))
        SC_REPORT_FATAL("MemSpecDDR5", "Invalid refresh mode! "
                "Set 1 for normal or 2 for fine granularity refresh mode.");

    uint64_t deviceSizeBits = static_cast<uint64_t>(banksPerRank) * rowsPerBank
                              * columnsPerRow * bitWidth * logicalRanksPerChannel;
    uint64_t deviceSizeBytes = deviceSizeBits / 8;
    memorySizeBytes = deviceSizeBytes * devicesPerRank
                      * ranksPerChannel / logicalRanksPerChannel * numberOfChannels;

    std::cout << headline << std::endl;
    std::cout << "Memory Configuration: " << std::endl << std::endl;
    std::cout << " Memory type:              " << "DDR5"                      << std::endl;
    std::cout << " Memory size in bytes:     " << memorySizeBytes             << std::endl;
    std::cout << " Channels:                 " << numberOfChannels            << std::endl;
    std::cout << " DIMMs per channel:        " << dimmRanksPerChannel << std::endl;
    std::cout << " Physical ranks per DIMM:  " << physicalRanksPerDimmRank << std::endl;
    std::cout << " Logical ranks per device: " << logicalRanksPerPhysicalRank << std::endl;
    std::cout << " Total ranks:              " << ranksPerChannel << std::endl;
    std::cout << " Bank groups per rank:     " << groupsPerRank               << std::endl;
    std::cout << " Banks per rank:           " << banksPerRank                << std::endl;
    std::cout << " Rows per bank:            " << rowsPerBank << std::endl;
    std::cout << " Columns per row:          " << columnsPerRow << std::endl;
    std::cout << " Device width in bits:     " << bitWidth                    << std::endl;
    std::cout << " Device size in bits:      " << deviceSizeBits              << std::endl;
    std::cout << " Device size in bytes:     " << deviceSizeBytes             << std::endl;
    std::cout << " Devices per rank:         " << devicesPerRank << std::endl;
    std::cout << std::endl;
}

sc_time MemSpecDDR5::getRefreshIntervalAB() const
{
    return tREFI;
}

sc_time MemSpecDDR5::getRefreshIntervalSB() const
{
    return tREFIsb;
}

unsigned MemSpecDDR5::getRAACDR() const
{
    return RAACDR;
}

unsigned MemSpecDDR5::getRAAIMT() const
{
    return RAAIMT;
}

unsigned MemSpecDDR5::getRAAMMT() const
{
    return RAAMMT;
}

// Returns the execution time for commands that have a fixed execution time
sc_time MemSpecDDR5::getExecutionTime(Command command, const tlm_generic_payload &payload) const
{
    if (command == Command::PREPB || command == Command::PREAB || command == Command::PRESB)
        return tRP + shortCmdOffset;
    else if (command == Command::ACT)
        return tRCD + longCmdOffset;
    else if (command == Command::RD)
    {
        if (ControllerExtension::getBurstLength(payload) == 32)
            return tRL + tBURST32 + longCmdOffset;
        else
            return tRL + tBURST16 + longCmdOffset;
    }
    else if (command == Command::RDA)
        return tRTP + tRP + longCmdOffset;
    else if (command == Command::WR)
    {
        if (ControllerExtension::getBurstLength(payload) == 32)
            return tWL + tBURST32 + longCmdOffset;
        else
            return tWL + tBURST16 + longCmdOffset;
    } 
    else if (command == Command::WRA)
    {
        if (ControllerExtension::getBurstLength(payload) == 32)
            return tWL + tBURST32 + tWR + tRP + longCmdOffset;
        else
            return tWL + tBURST16 + tWR + tRP + longCmdOffset;
    }   
    else if (command == Command::REFAB || command == Command::RFMAB)
        return tRFC_slr + shortCmdOffset;
    else if (command == Command::REFSB || command == Command::RFMSB)
        return tRFCsb_slr + shortCmdOffset;
    else
    {
        SC_REPORT_FATAL("getExecutionTime",
                        "command not known or command doesn't have a fixed execution time");
        return SC_ZERO_TIME;
    }
}

TimeInterval MemSpecDDR5::getIntervalOnDataStrobe(Command command, const tlm_generic_payload &payload) const
{
    if (command == Command::RD || command == Command::RDA)
    {
        if (ControllerExtension::getBurstLength(payload) == 32)
            return {tRL + longCmdOffset, tRL + tBURST32 + longCmdOffset};
        else
            return {tRL + longCmdOffset, tRL + tBURST16 + longCmdOffset};
    }
    else if (command == Command::WR || command == Command::WRA)
    {
        if (ControllerExtension::getBurstLength(payload) == 32)
            return {tWL + longCmdOffset, tWL + tBURST32 + longCmdOffset};
        else
            return {tWL + longCmdOffset, tWL + tBURST16 + longCmdOffset};
    }
    else
    {
        SC_REPORT_FATAL("MemSpec", "Method was called with invalid argument");
        return {};
    }
}
