Files
DRAMSys/DRAMSys/library/src/controller/Controller.cpp
2021-12-03 09:36:39 +01:00

504 lines
19 KiB
C++

/*
* Copyright (c) 2019, 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 "Controller.h"
#include "../configuration/Configuration.h"
#include "../common/dramExtensions.h"
#include "checker/CheckerDDR3.h"
#include "checker/CheckerDDR4.h"
#include "checker/CheckerDDR5.h"
#include "checker/CheckerWideIO.h"
#include "checker/CheckerLPDDR4.h"
#include "checker/CheckerWideIO2.h"
#include "checker/CheckerHBM2.h"
#include "checker/CheckerGDDR5.h"
#include "checker/CheckerGDDR5X.h"
#include "checker/CheckerGDDR6.h"
#include "checker/CheckerSTTMRAM.h"
#include "scheduler/SchedulerFifo.h"
#include "scheduler/SchedulerFrFcfs.h"
#include "scheduler/SchedulerFrFcfsGrp.h"
#include "cmdmux/CmdMuxStrict.h"
#include "cmdmux/CmdMuxOldest.h"
#include "respqueue/RespQueueFifo.h"
#include "respqueue/RespQueueReorder.h"
#include "refresh/RefreshManagerDummy.h"
#include "refresh/RefreshManagerAllBank.h"
#include "refresh/RefreshManagerPerBank.h"
#include "refresh/RefreshManagerSameBank.h"
#include "powerdown/PowerDownManagerStaggered.h"
#include "powerdown/PowerDownManagerDummy.h"
using namespace sc_core;
using namespace tlm;
Controller::Controller(const sc_module_name &name) :
ControllerIF(name)
{
SC_METHOD(controllerMethod);
sensitive << beginReqEvent << endRespEvent << controllerEvent << dataResponseEvent;
Configuration &config = Configuration::getInstance();
memSpec = config.memSpec;
ranksNumberOfPayloads = std::vector<unsigned>(memSpec->numberOfRanks);
thinkDelayFw = config.thinkDelayFw;
thinkDelayBw = config.thinkDelayBw;
phyDelayFw = config.phyDelayFw;
phyDelayBw = config.phyDelayBw;
// reserve buffer for command tuples
readyCommands.reserve(memSpec->numberOfBanks);
// instantiate timing checker
if (memSpec->memoryType == MemSpec::MemoryType::DDR3)
checker = new CheckerDDR3();
else if (memSpec->memoryType == MemSpec::MemoryType::DDR4)
checker = new CheckerDDR4();
else if (memSpec->memoryType == MemSpec::MemoryType::DDR5)
checker = new CheckerDDR5();
else if (memSpec->memoryType == MemSpec::MemoryType::WideIO)
checker = new CheckerWideIO();
else if (memSpec->memoryType == MemSpec::MemoryType::LPDDR4)
checker = new CheckerLPDDR4();
else if (memSpec->memoryType == MemSpec::MemoryType::WideIO2)
checker = new CheckerWideIO2();
else if (memSpec->memoryType == MemSpec::MemoryType::HBM2)
checker = new CheckerHBM2();
else if (memSpec->memoryType == MemSpec::MemoryType::GDDR5)
checker = new CheckerGDDR5();
else if (memSpec->memoryType == MemSpec::MemoryType::GDDR5X)
checker = new CheckerGDDR5X();
else if (memSpec->memoryType == MemSpec::MemoryType::GDDR6)
checker = new CheckerGDDR6();
else if (memSpec->memoryType == MemSpec::MemoryType::STTMRAM)
checker = new CheckerSTTMRAM();
// instantiate scheduler and command mux
if (config.scheduler == Configuration::Scheduler::Fifo)
scheduler = new SchedulerFifo();
else if (config.scheduler == Configuration::Scheduler::FrFcfs)
scheduler = new SchedulerFrFcfs();
else if (config.scheduler == Configuration::Scheduler::FrFcfsGrp)
scheduler = new SchedulerFrFcfsGrp();
if (config.cmdMux == Configuration::CmdMux::Oldest)
{
if (memSpec->hasRasAndCasBus())
cmdMux = new CmdMuxOldestRasCas();
else
cmdMux = new CmdMuxOldest();
}
else if (config.cmdMux == Configuration::CmdMux::Strict)
{
if (memSpec->hasRasAndCasBus())
cmdMux = new CmdMuxStrictRasCas();
else
cmdMux = new CmdMuxStrict();
}
if (config.respQueue == Configuration::RespQueue::Fifo)
respQueue = new RespQueueFifo();
else if (config.respQueue == Configuration::RespQueue::Reorder)
respQueue = new RespQueueReorder();
// instantiate bank machines (one per bank)
if (config.pagePolicy == Configuration::PagePolicy::Open)
{
for (unsigned bankID = 0; bankID < memSpec->numberOfBanks; bankID++)
bankMachines.push_back(new BankMachineOpen(scheduler, checker, Bank(bankID)));
}
else if (config.pagePolicy == Configuration::PagePolicy::OpenAdaptive)
{
for (unsigned bankID = 0; bankID < memSpec->numberOfBanks; bankID++)
bankMachines.push_back(new BankMachineOpenAdaptive(scheduler, checker, Bank(bankID)));
}
else if (config.pagePolicy == Configuration::PagePolicy::Closed)
{
for (unsigned bankID = 0; bankID < memSpec->numberOfBanks; bankID++)
bankMachines.push_back(new BankMachineClosed(scheduler, checker, Bank(bankID)));
}
else if (config.pagePolicy == Configuration::PagePolicy::ClosedAdaptive)
{
for (unsigned bankID = 0; bankID < memSpec->numberOfBanks; bankID++)
bankMachines.push_back(new BankMachineClosedAdaptive(scheduler, checker, Bank(bankID)));
}
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
bankMachinesOnRank.emplace_back(bankMachines.begin() + rankID * memSpec->banksPerRank,
bankMachines.begin() + (rankID + 1) * memSpec->banksPerRank);
}
// instantiate power-down managers (one per rank)
if (config.powerDownPolicy == Configuration::PowerDownPolicy::NoPowerDown)
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
PowerDownManagerIF *manager = new PowerDownManagerDummy();
powerDownManagers.push_back(manager);
}
}
else if (config.powerDownPolicy == Configuration::PowerDownPolicy::Staggered)
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
PowerDownManagerIF *manager = new PowerDownManagerStaggered(bankMachinesOnRank[rankID],
Rank(rankID), checker);
powerDownManagers.push_back(manager);
}
}
// instantiate refresh managers (one per rank)
if (config.refreshPolicy == Configuration::RefreshPolicy::NoRefresh)
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
refreshManagers.push_back(new RefreshManagerDummy());
}
else if (config.refreshPolicy == Configuration::RefreshPolicy::AllBank)
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
RefreshManagerIF *manager = new RefreshManagerAllBank
(bankMachinesOnRank[rankID], powerDownManagers[rankID], Rank(rankID), checker);
refreshManagers.push_back(manager);
}
}
else if (config.refreshPolicy == Configuration::RefreshPolicy::SameBank)
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
RefreshManagerIF *manager = new RefreshManagerSameBank
(bankMachinesOnRank[rankID], powerDownManagers[rankID], Rank(rankID), checker);
refreshManagers.push_back(manager);
}
}
else if (config.refreshPolicy == Configuration::RefreshPolicy::PerBank)
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
// TODO: remove bankMachines in constructor
RefreshManagerIF *manager = new RefreshManagerPerBank
(bankMachinesOnRank[rankID], powerDownManagers[rankID], Rank(rankID), checker);
refreshManagers.push_back(manager);
}
}
else
SC_REPORT_FATAL("Controller", "Selected refresh mode not supported!");
idleTimeCollector.start();
}
Controller::~Controller()
{
idleTimeCollector.end();
for (auto it : refreshManagers)
delete it;
for (auto it : powerDownManagers)
delete it;
for (auto it : bankMachines)
delete it;
delete respQueue;
delete cmdMux;
delete scheduler;
delete checker;
}
void Controller::controllerMethod()
{
// (1) Finish last response (END_RESP) and start new response (BEGIN_RESP)
manageResponses();
// (2) Insert new request into scheduler and send END_REQ or use backpressure
manageRequests(SC_ZERO_TIME);
// (3) Start refresh and power-down managers to issue requests for the current time
for (auto it : refreshManagers)
it->start();
for (auto it : powerDownManagers)
it->start();
// (4) Collect all ready commands from BMs, RMs and PDMs
CommandTuple::Type commandTuple;
// clear command buffer
readyCommands.clear();
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
// (4.1) Check for power-down commands (PDEA/PDEP/SREFEN or PDXA/PDXP/SREFEX)
commandTuple = powerDownManagers[rankID]->getNextCommand();
if (std::get<CommandTuple::Command>(commandTuple) != Command::NOP)
readyCommands.emplace_back(commandTuple);
else
{
// (4.2) Check for refresh commands (PREXX or REFXX)
commandTuple = refreshManagers[rankID]->getNextCommand();
if (std::get<CommandTuple::Command>(commandTuple) != Command::NOP)
readyCommands.emplace_back(commandTuple);
// (4.3) Check for bank commands (PREPB, ACT, RD/RDA or WR/WRA)
for (auto it : bankMachinesOnRank[rankID])
{
commandTuple = it->getNextCommand();
if (std::get<CommandTuple::Command>(commandTuple) != Command::NOP)
readyCommands.emplace_back(commandTuple);
}
}
}
// (5) Select one of the ready commands and issue it to the DRAM
bool readyCmdBlocked = false;
if (!readyCommands.empty())
{
commandTuple = cmdMux->selectCommand(readyCommands);
Command command = std::get<CommandTuple::Command>(commandTuple);
tlm_generic_payload *payload = std::get<CommandTuple::Payload>(commandTuple);
if (command != Command::NOP) // can happen with FIFO strict
{
Rank rank = DramExtension::getRank(payload);
Bank bank = DramExtension::getBank(payload);
if (command.isRankCommand())
{
for (auto it : bankMachinesOnRank[rank.ID()])
it->updateState(command);
}
else if (command.isGroupCommand())
{
for (unsigned bankID = (bank.ID() % memSpec->banksPerGroup);
bankID < memSpec->banksPerRank; bankID += memSpec->banksPerGroup)
bankMachinesOnRank[rank.ID()][bankID]->updateState(command);
}
else // if (isBankCommand(command))
bankMachines[bank.ID()]->updateState(command);
refreshManagers[rank.ID()]->updateState(command);
powerDownManagers[rank.ID()]->updateState(command);
checker->insert(command, payload);
if (command.isCasCommand())
{
scheduler->removeRequest(payload);
manageRequests(thinkDelayFw);
respQueue->insertPayload(payload, sc_time_stamp()
+ thinkDelayFw + phyDelayFw
+ memSpec->getIntervalOnDataStrobe(command, *payload).end
+ phyDelayBw + thinkDelayBw);
sc_time triggerTime = respQueue->getTriggerTime();
if (triggerTime != sc_max_time())
dataResponseEvent.notify(triggerTime - sc_time_stamp());
ranksNumberOfPayloads[rank.ID()]--; // TODO: move to a different place?
}
if (ranksNumberOfPayloads[rank.ID()] == 0)
powerDownManagers[rank.ID()]->triggerEntry();
sendToDram(command, payload, thinkDelayFw + phyDelayFw);
}
else
readyCmdBlocked = true;
}
// (6) Restart bank machines, refresh managers and power-down managers to issue new requests for the future
sc_time timeForNextTrigger = sc_max_time();
sc_time localTime;
for (auto it : bankMachines)
{
localTime = it->start();
if (!(localTime == sc_time_stamp() && readyCmdBlocked))
timeForNextTrigger = std::min(timeForNextTrigger, localTime);
}
for (auto it : refreshManagers)
{
localTime = it->start();
if (!(localTime == sc_time_stamp() && readyCmdBlocked))
timeForNextTrigger = std::min(timeForNextTrigger, localTime);
}
for (auto it : powerDownManagers)
{
localTime = it->start();
if (!(localTime == sc_time_stamp() && readyCmdBlocked))
timeForNextTrigger = std::min(timeForNextTrigger, localTime);
}
if (timeForNextTrigger != sc_max_time())
controllerEvent.notify(timeForNextTrigger - sc_time_stamp());
}
tlm_sync_enum Controller::nb_transport_fw(tlm_generic_payload &trans,
tlm_phase &phase, sc_time &delay)
{
if (phase == BEGIN_REQ)
{
transToAcquire.payload = &trans;
transToAcquire.time = sc_time_stamp() + delay;
beginReqEvent.notify(delay);
}
else if (phase == END_RESP)
{
transToRelease.time = sc_time_stamp() + delay;
endRespEvent.notify(delay);
}
else
SC_REPORT_FATAL("Controller", "nb_transport_fw in controller was triggered with unknown phase");
PRINTDEBUGMESSAGE(name(), "[fw] " + getPhaseName(phase) + " notification in " +
delay.to_string());
return TLM_ACCEPTED;
}
tlm_sync_enum Controller::nb_transport_bw(tlm_generic_payload &,
tlm_phase &, sc_time &)
{
SC_REPORT_FATAL("Controller", "nb_transport_bw of controller must not be called!");
return TLM_ACCEPTED;
}
unsigned int Controller::transport_dbg(tlm_generic_payload &trans)
{
return iSocket->transport_dbg(trans);
}
void Controller::manageRequests(const sc_time &delay)
{
if (transToAcquire.payload != nullptr && transToAcquire.time <= sc_time_stamp())
{
if (scheduler->hasBufferSpace())
{
NDEBUG_UNUSED(uint64_t id) = DramExtension::getChannelPayloadID(transToAcquire.payload);
PRINTDEBUGMESSAGE(name(), "Payload " + std::to_string(id) + " entered system.");
if (totalNumberOfPayloads == 0)
idleTimeCollector.end();
totalNumberOfPayloads++;
Rank rank = DramExtension::getRank(transToAcquire.payload);
if (ranksNumberOfPayloads[rank.ID()] == 0)
powerDownManagers[rank.ID()]->triggerExit();
ranksNumberOfPayloads[rank.ID()]++;
scheduler->storeRequest(transToAcquire.payload);
transToAcquire.payload->acquire();
Bank bank = DramExtension::getBank(transToAcquire.payload);
bankMachines[bank.ID()]->start();
transToAcquire.payload->set_response_status(TLM_OK_RESPONSE);
sendToFrontend(transToAcquire.payload, END_REQ, delay);
transToAcquire.payload = nullptr;
}
else
{
PRINTDEBUGMESSAGE(name(), "Total number of payloads exceeded, backpressure!");
}
}
}
void Controller::manageResponses()
{
if (transToRelease.payload != nullptr)
{
assert(transToRelease.time >= sc_time_stamp());
if (transToRelease.time == sc_time_stamp())
{
NDEBUG_UNUSED(uint64_t id) = DramExtension::getChannelPayloadID(transToRelease.payload);
PRINTDEBUGMESSAGE(name(), "Payload " + std::to_string(id) + " left system.");
numberOfBeatsServed += DramExtension::getBurstLength(transToRelease.payload);
transToRelease.payload->release();
transToRelease.payload = nullptr;
totalNumberOfPayloads--;
if (totalNumberOfPayloads == 0)
{
idleTimeCollector.start();
}
else
{
transToRelease.payload = respQueue->nextPayload();
if (transToRelease.payload != nullptr)
{
// last payload was released in this cycle
sendToFrontend(transToRelease.payload, BEGIN_RESP, memSpec->tCK);
transToRelease.time = sc_max_time();
}
else
{
sc_time triggerTime = respQueue->getTriggerTime();
if (triggerTime != sc_max_time())
dataResponseEvent.notify(triggerTime - sc_time_stamp());
}
}
}
}
else
{
transToRelease.payload = respQueue->nextPayload();
if (transToRelease.payload != nullptr)
{
if (transToRelease.time == sc_time_stamp()) // last payload was released in this cycle
sendToFrontend(transToRelease.payload, BEGIN_RESP, memSpec->tCK);
else
sendToFrontend(transToRelease.payload, BEGIN_RESP, SC_ZERO_TIME);
transToRelease.time = sc_max_time();
}
else
{
sc_time triggerTime = respQueue->getTriggerTime();
if (triggerTime != sc_max_time())
dataResponseEvent.notify(triggerTime - sc_time_stamp());
}
}
}
void Controller::sendToFrontend(tlm_generic_payload *payload, tlm_phase phase, sc_time delay)
{
tSocket->nb_transport_bw(*payload, phase, delay);
}
void Controller::sendToDram(Command command, tlm_generic_payload *payload, sc_time delay)
{
tlm_phase phase = command.toPhase();
iSocket->nb_transport_fw(*payload, phase, delay);
}