/*
 * Copyright (c) 2015, 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:
 *    Janik Schlemminger
 *    Matthias Jung
 *    Éder F. Zulian
 *    Felipe S. Prado
 *    Lukas Steiner
 *    Luiza Correa
 *    Derek Christ
 */

#include "Configuration.h"

#include "DRAMSys/configuration/memspec/MemSpecDDR3.h"
#include "DRAMSys/configuration/memspec/MemSpecDDR4.h"
#include "DRAMSys/configuration/memspec/MemSpecWideIO.h"
#include "DRAMSys/configuration/memspec/MemSpecLPDDR4.h"
#include "DRAMSys/configuration/memspec/MemSpecWideIO2.h"
#include "DRAMSys/configuration/memspec/MemSpecHBM2.h"
#include "DRAMSys/configuration/memspec/MemSpecGDDR5.h"
#include "DRAMSys/configuration/memspec/MemSpecGDDR5X.h"
#include "DRAMSys/configuration/memspec/MemSpecGDDR6.h"
#include "DRAMSys/configuration/memspec/MemSpecSTTMRAM.h"

#ifdef DDR5_SIM
#include "DRAMSys/configuration/memspec/MemSpecDDR5.h"
#endif
#ifdef LPDDR5_SIM
#include "DRAMSys/configuration/memspec/MemSpecLPDDR5.h"
#endif
#ifdef HBM3_SIM
#include "DRAMSys/configuration/memspec/MemSpecHBM3.h"
#endif

using namespace sc_core;

enum sc_time_unit string2TimeUnit(const std::string &s)
{
    if (s == "s")
        return SC_SEC;
    else if (s == "ms")
        return SC_MS;
    else if (s == "us")
        return SC_US;
    else if (s == "ns")
        return SC_NS;
    else if (s == "ps")
        return SC_PS;
    else if (s == "fs")
        return SC_FS;
    else {
        SC_REPORT_FATAL("Configuration",
                        ("Could not convert to enum sc_time_unit: " + s).c_str());
        throw;
    }
}

void Configuration::loadSimConfig(const DRAMSys::Config::SimConfig &simConfig)
{
    if (const auto& _addressOffset = simConfig.addressOffset)
        addressOffset = *_addressOffset;

    if (const auto& _checkTLM2Protocol = simConfig.checkTLM2Protocol)
        checkTLM2Protocol = *_checkTLM2Protocol;

    if (const auto& _databaseRecording = simConfig.databaseRecording)
        databaseRecording = *_databaseRecording;

    if (const auto& _debug = simConfig.debug)
        debug = *_debug;

    if (const auto& _enableWindowing = simConfig.enableWindowing)
        enableWindowing = *_enableWindowing;

    if (const auto& _powerAnalysis = simConfig.powerAnalysis)
    {
        powerAnalysis = *_powerAnalysis;
#ifndef DRAMPOWER
        if (powerAnalysis)
            SC_REPORT_FATAL("Configuration", "Power analysis is only supported with included DRAMPower library!");
#endif
    }

    if (const auto& _simulationName = simConfig.simulationName)
        simulationName = *_simulationName;

    if (const auto& _simulationProgressBar = simConfig.simulationProgressBar)
        simulationProgressBar = *_simulationProgressBar;

    if (const auto& _useMalloc = simConfig.useMalloc)
        useMalloc = *_useMalloc;

    if (const auto& _windowSize = simConfig.windowSize)
        windowSize = *_windowSize;

    if (windowSize == 0)
            SC_REPORT_FATAL("Configuration", "Minimum window size is 1");

    if (const auto& _storeMode = simConfig.storeMode)
        storeMode = [=] {
            if (_storeMode == DRAMSys::Config::StoreMode::NoStorage)
                return StoreMode::NoStorage;
            else // (_storeMode == DRAMSys::Config::StoreMode::Store)
                return StoreMode::Store;
        }();
}

void Configuration::loadMCConfig(const DRAMSys::Config::McConfig &mcConfig)
{
    if (const auto& _pagePolicy = mcConfig.pagePolicy)
        pagePolicy = [=] {
            if (_pagePolicy == DRAMSys::Config::PagePolicy::Open)
                return PagePolicy::Open;
            else if (_pagePolicy == DRAMSys::Config::PagePolicy::OpenAdaptive)
                return PagePolicy::OpenAdaptive;
            else if (_pagePolicy == DRAMSys::Config::PagePolicy::Closed)
                return PagePolicy::Closed;
            else
                return PagePolicy::ClosedAdaptive;
        }();

    if (const auto& _scheduler = mcConfig.scheduler)
        scheduler = [=] {
           if (_scheduler == DRAMSys::Config::Scheduler::Fifo)
               return Scheduler::Fifo;
           else if (_scheduler == DRAMSys::Config::Scheduler::FrFcfs)
               return Scheduler::FrFcfs;
           else if (_scheduler == DRAMSys::Config::Scheduler::FrFcfsGrp)
               return Scheduler::FrFcfsGrp;
           else if (_scheduler == DRAMSys::Config::Scheduler::GrpFrFcfs)
               return Scheduler::GrpFrFcfs;
           else
               return Scheduler::GrpFrFcfsWm;
        }();

    if (const auto& _highWatermark = mcConfig.highWatermark)
        highWatermark = *mcConfig.highWatermark;

    if (const auto& _lowWatermark = mcConfig.lowWatermark)
        lowWatermark = *mcConfig.lowWatermark;

    if (const auto& _schedulerBuffer = mcConfig.schedulerBuffer)
        schedulerBuffer = [=] {
            if (_schedulerBuffer == DRAMSys::Config::SchedulerBuffer::Bankwise)
                return SchedulerBuffer::Bankwise;
            else if (_schedulerBuffer == DRAMSys::Config::SchedulerBuffer::ReadWrite)
                return SchedulerBuffer::ReadWrite;
            else
                return SchedulerBuffer::Shared;
        }();

    if (const auto& _requestBufferSize = mcConfig.requestBufferSize)
        requestBufferSize = *mcConfig.requestBufferSize;

    if (requestBufferSize == 0)
        SC_REPORT_FATAL("Configuration", "Minimum request buffer size is 1!");

    if (const auto& _cmdMux = mcConfig.cmdMux)
        cmdMux = [=] {
            if (_cmdMux == DRAMSys::Config::CmdMux::Oldest)
                return CmdMux::Oldest;
            else
                return CmdMux::Strict;
        }();

    if (const auto& _respQueue = mcConfig.respQueue)
        respQueue = [=] {
            if (_respQueue == DRAMSys::Config::RespQueue::Fifo)
                return RespQueue::Fifo;
            else
                return RespQueue::Reorder;
        }();

    if (const auto& _refreshPolicy = mcConfig.refreshPolicy)
        refreshPolicy = [=] {
            if (_refreshPolicy == DRAMSys::Config::RefreshPolicy::NoRefresh)
                return RefreshPolicy::NoRefresh;
            else if (_refreshPolicy == DRAMSys::Config::RefreshPolicy::AllBank)
                return RefreshPolicy::AllBank;
            else if (_refreshPolicy == DRAMSys::Config::RefreshPolicy::PerBank)
                return RefreshPolicy::PerBank;
            else if (_refreshPolicy == DRAMSys::Config::RefreshPolicy::Per2Bank)
                return RefreshPolicy::Per2Bank;
            else // if (policy == DRAMSys::Config::RefreshPolicy::SameBank)
                return RefreshPolicy::SameBank;
        }();

    if (const auto& _refreshMaxPostponed = mcConfig.refreshMaxPostponed)
        refreshMaxPostponed = *_refreshMaxPostponed;

    if (const auto& _refreshMaxPulledin = mcConfig.refreshMaxPulledin)
        refreshMaxPulledin = *_refreshMaxPulledin;

    if (const auto& _powerDownPolicy = mcConfig.powerDownPolicy)
        powerDownPolicy = [=] {
            if (_powerDownPolicy == DRAMSys::Config::PowerDownPolicy::NoPowerDown)
                return PowerDownPolicy::NoPowerDown;
            else
                return PowerDownPolicy::Staggered;
        }();

    if (const auto& _arbiter = mcConfig.arbiter)
        arbiter = [=] {
            if (_arbiter == DRAMSys::Config::Arbiter::Simple)
                return Arbiter::Simple;
            else if (_arbiter == DRAMSys::Config::Arbiter::Fifo)
                return Arbiter::Fifo;
            else
                return Arbiter::Reorder;
        }();

    if (const auto& _maxActiveTransactions = mcConfig.maxActiveTransactions)
         maxActiveTransactions = *_maxActiveTransactions;

    if (const auto& _refreshManagement = mcConfig.refreshManagement)
         refreshManagement = *_refreshManagement;

    if (const auto& _arbitrationDelayFw = mcConfig.arbitrationDelayFw)
    {
         arbitrationDelayFw = std::round(sc_time(*_arbitrationDelayFw, SC_NS) / memSpec->tCK) * memSpec->tCK;
    }

    if (const auto& _arbitrationDelayBw = mcConfig.arbitrationDelayBw)
    {
         arbitrationDelayBw = std::round(sc_time(*_arbitrationDelayBw, SC_NS) / memSpec->tCK) * memSpec->tCK;
    }

    if (const auto& _thinkDelayFw = mcConfig.thinkDelayFw)
    {
         thinkDelayFw = std::round(sc_time(*_thinkDelayFw, SC_NS) / memSpec->tCK) * memSpec->tCK;
    }

    if (const auto& _thinkDelayBw = mcConfig.thinkDelayBw)
    {
         thinkDelayBw = std::round(sc_time(*_thinkDelayBw, SC_NS) / memSpec->tCK) * memSpec->tCK;
    }

    if (const auto& _phyDelayFw = mcConfig.phyDelayFw)
    {
         phyDelayFw = std::round(sc_time(*_phyDelayFw, SC_NS) / memSpec->tCK) * memSpec->tCK;
    }

    if (const auto& _phyDelayBw = mcConfig.phyDelayBw)
    {
         phyDelayBw = std::round(sc_time(*_phyDelayBw, SC_NS) / memSpec->tCK) * memSpec->tCK;
    }

    {
        auto _blockingReadDelay = mcConfig.blockingReadDelay.value_or(60);
        blockingReadDelay = std::round(sc_time(_blockingReadDelay, SC_NS) / memSpec->tCK) * memSpec->tCK;
    }

    {
        auto _blockingWriteDelay = mcConfig.blockingWriteDelay.value_or(60);
        blockingWriteDelay = std::round(sc_time(_blockingWriteDelay, SC_NS) / memSpec->tCK) * memSpec->tCK;
    }
}

void Configuration::loadMemSpec(const DRAMSys::Config::MemSpec &memSpecConfig)
{
    std::string memoryType = memSpecConfig.memoryType;

    if (memoryType == "DDR3")
        memSpec = std::make_unique<const MemSpecDDR3>(memSpecConfig);
    else if (memoryType == "DDR4")
        memSpec = std::make_unique<const MemSpecDDR4>(memSpecConfig);
    else if (memoryType == "LPDDR4")
        memSpec = std::make_unique<const MemSpecLPDDR4>(memSpecConfig);
    else if (memoryType == "WIDEIO_SDR")
        memSpec = std::make_unique<const MemSpecWideIO>(memSpecConfig);
    else if (memoryType == "WIDEIO2")
        memSpec = std::make_unique<const MemSpecWideIO2>(memSpecConfig);
    else if (memoryType == "HBM2")
        memSpec = std::make_unique<const MemSpecHBM2>(memSpecConfig);
    else if (memoryType == "GDDR5")
        memSpec = std::make_unique<const MemSpecGDDR5>(memSpecConfig);
    else if (memoryType == "GDDR5X")
        memSpec = std::make_unique<const MemSpecGDDR5X>(memSpecConfig);
    else if (memoryType == "GDDR6")
        memSpec = std::make_unique<const MemSpecGDDR6>(memSpecConfig);
    else if (memoryType == "STT-MRAM")
        memSpec = std::make_unique<const MemSpecSTTMRAM>(memSpecConfig);
#ifdef DDR5_SIM
    else if (memoryType == "DDR5")
        memSpec = std::make_unique<const MemSpecDDR5>(memSpecConfig);
#endif
#ifdef LPDDR5_SIM
    else if (memoryType == "LPDDR5")
        memSpec = std::make_unique<const MemSpecLPDDR5>(memSpecConfig);
#endif
#ifdef HBM3_SIM
    else if (memoryType == "HBM3")
        memSpec = std::make_unique<const MemSpecHBM3>(memSpecConfig);
#endif
    else
        SC_REPORT_FATAL("Configuration", "Unsupported DRAM type");
}
