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

#include "MemSpecLPDDR5.h"

#include <DRAMSys/common/utils.h>

#include <iostream>

using namespace sc_core;
using namespace tlm;

MemSpecLPDDR5::MemSpecLPDDR5(const DRAMSys::Config::MemSpec &memSpec)
    : MemSpec(memSpec, MemoryType::LPDDR5,
      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")),
      per2BankOffset(memSpec.memArchitectureSpec.entries.at("per2BankOffset")),
      tREFI   (tCK * memSpec.memTimingSpec.entries.at("REFI")),
      tREFIpb (tCK * memSpec.memTimingSpec.entries.at("REFIpb")),
      tRFCab  (tCK * memSpec.memTimingSpec.entries.at("RFCab")),
      tRFCpb  (tCK * memSpec.memTimingSpec.entries.at("RFCpb")),
      tRPab   (tCK * memSpec.memTimingSpec.entries.at("RPab")),
      tRPpb   (tCK * memSpec.memTimingSpec.entries.at("RPpb")),
      tRCab   (tCK * memSpec.memTimingSpec.entries.at("RCab")),
      tRCpb   (tCK * memSpec.memTimingSpec.entries.at("RCpb")),
      tPPD    (tCK * memSpec.memTimingSpec.entries.at("PPD")),
      tRAS    (tCK * memSpec.memTimingSpec.entries.at("RAS")),
      tRCD    (tCK * memSpec.memTimingSpec.entries.at("RCD")),
      tFAW    (tCK * memSpec.memTimingSpec.entries.at("FAW")),
      tRRD    (tCK * memSpec.memTimingSpec.entries.at("RRD")),
      //tCCD    (tCK * parseUint(memspec["memtimingspec"], "CCD")),
      tRL     (tCK * memSpec.memTimingSpec.entries.at("RL")),
      //tRPST   (tCK * parseUint(memspec["memtimingspec"], "RPST")),
      //tDQSCK  (tCK * parseUint(memspec["memtimingspec"], "DQSCK")),
      tRBTP    (tCK * memSpec.memTimingSpec.entries.at("RBTP")),
      tWL     (tCK * memSpec.memTimingSpec.entries.at("WL")),
      //tDQSS   (tCK * parseUint(memspec["memtimingspec"], "DQSS")),
      //tDQS2DQ (tCK * parseUint(memspec["memtimingspec"], "DQS2DQ")),
      tWR     (tCK * memSpec.memTimingSpec.entries.at("WR")),
      //tWPRE   (tCK * parseUint(memspec["memtimingspec"], "WPRE")),
      //tWTR    (tCK * parseUint(memspec["memtimingspec"], "WTR")),
      //tXP     (tCK * parseUint(memspec["memtimingspec"] "XP")),
      //tSR     (tCK * parseUint(memspec["memtimingspec"], "SR")),
      //tXSR    (tCK * parseUint(memspec["memtimingspec"], "XSR")),
      //tESCKE  (tCK * parseUint(memspec["memtimingspec"], "ESCKE")),
      //tCKE    (tCK * parseUint(memspec["memtimingspec"], "CKE")),
      //tCMDCKE (tCK * parseUint(memspec["memtimingspec"], "CMDCKE")),
      BL_n_min_16(tCK * memSpec.memTimingSpec.entries.at("BL_n_min_16")),
      BL_n_max_16(tCK * memSpec.memTimingSpec.entries.at("BL_n_max_16")),
      BL_n_L_16(tCK * memSpec.memTimingSpec.entries.at("BL_n_L_16")),
      BL_n_S_16(tCK * memSpec.memTimingSpec.entries.at("BL_n_S_16")),
      BL_n_min_32(tCK * memSpec.memTimingSpec.entries.at("BL_n_min_32")),
      BL_n_max_32(tCK * memSpec.memTimingSpec.entries.at("BL_n_max_32")),
      BL_n_L_32(tCK * memSpec.memTimingSpec.entries.at("BL_n_L_32")),
      BL_n_S_32(tCK * memSpec.memTimingSpec.entries.at("BL_n_S_32")),
      tWTR_L   (tCK * memSpec.memTimingSpec.entries.at("WTR_L")),
      tWTR_S   (tCK * memSpec.memTimingSpec.entries.at("WTR_S")),
      tWCK2DQO(tCK * memSpec.memTimingSpec.entries.at("WCK2DQO")),
      tpbR2act(tCK * memSpec.memTimingSpec.entries.at("pbR2act")),
      tpbR2pbR(tCK * memSpec.memTimingSpec.entries.at("pbR2pbR")),
      tRTRS   (tCK * memSpec.memTimingSpec.entries.at("RTRS")),
      tBURST16(tCK * 16 / dataRate),
      tBURST32(tCK * 32 / dataRate),
      bankMode(groupsPerRank != 1 ? BankMode::MBG : (banksPerRank == 16 ? BankMode::M16B : BankMode::M8B))
{
    commandLengthInCycles[Command::ACT] = 2;

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

    std::cout << headline << std::endl;
    std::cout << "Memory Configuration:" << std::endl << std::endl;
    std::cout << " Memory type:           " << "LPDDR5"              << std::endl;
    std::cout << " Memory size in bytes:  " << memorySizeBytes       << std::endl;
    std::cout << " Channels:              " << numberOfChannels      << std::endl;
    std::cout << " Ranks per channel:     " << ranksPerChannel << 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 MemSpecLPDDR5::getRefreshIntervalAB() const
{
    return tREFI;
}

sc_time MemSpecLPDDR5::getRefreshIntervalPB() const
{
    return tREFIpb;
}

sc_time MemSpecLPDDR5::getRefreshIntervalP2B() const
{
    return tREFIpb;
}

unsigned MemSpecLPDDR5::getPer2BankOffset() const
{
    return per2BankOffset;
}

sc_time MemSpecLPDDR5::getExecutionTime(Command command, const tlm_generic_payload& trans) const
{
    if (command == Command::PREPB)
        return tRPpb;
    else if (command == Command::PREAB)
        return tRPab;
    else if (command == Command::ACT)
        return tRCD + tCK;
    else if (command == Command::RD)
    {
        if (ControllerExtension::getBurstLength(trans) == 32)
        {
            // TODO: model BG mode BL32 interleaved burst
            if (bankMode == BankMode::MBG)
                return tRL + 2 * tCK + tBURST32;
            else
                return tRL + tBURST32;
        }
        else
            return tRL + tBURST16;
    }
    else if (command == Command::RDA)
    {
        if (ControllerExtension::getBurstLength(trans) == 32)
            return BL_n_min_32 + tRBTP + tRPpb;
        else
            return BL_n_min_16 + tRBTP + tRPpb;
    }
    else if (command == Command::WR)
    {
        if (ControllerExtension::getBurstLength(trans) == 32)
        {
            if (bankMode == BankMode::MBG)
                return tWL + 2 * tCK + tBURST32;
            else
                return tWL + tBURST32;
        }
        else
            return tWL + tBURST16;
    }
    else if (command == Command::WRA)
    {
        if (ControllerExtension::getBurstLength(trans) == 32)
            return tWL + BL_n_min_32 + tCK + tWR + tRPpb;
        else
            return tWL + BL_n_min_16 + tCK + tWR + tRPpb;
    }
    else if (command == Command::REFAB)
        return tRFCab;
    else if (command == Command::REFPB || command == Command::REFP2B)
        return tRFCpb;
    else
    {
        SC_REPORT_FATAL("getExecutionTime",
                        "command not known or command doesn't have a fixed execution time");
        return SC_ZERO_TIME;
    }
}

TimeInterval MemSpecLPDDR5::getIntervalOnDataStrobe(Command command, const tlm_generic_payload& trans) const
{
    if (command == Command::RD || command == Command::RDA)
    {
        if (ControllerExtension::getBurstLength(trans) == 32)
        {
            if (bankMode == BankMode::MBG)
                return {tRL + 2 * tCK, tRL + 2 * tCK + tBURST32};
            else
                return {tRL, tRL + tBURST32};
        }
        else
            return {tRL, tRL + tBURST16};
    }
    else if (command == Command::WR || command == Command::WRA)
    {
        if (ControllerExtension::getBurstLength(trans) == 32)
        {
            if (bankMode == BankMode::MBG)
                return {tWL + 2 * tCK, tWL + 2 * tCK + tBURST32};
            else
                return {tWL, tWL + tBURST32};
        }
        else
            return {tWL, tWL + tBURST16};
    }
    else
    {
        SC_REPORT_FATAL("MemSpecLPDDR5", "Method was called with invalid argument");
        return {};
    }
}
