Files
DRAMSys/DRAMSys/library/src/controller/Controller.cpp
2020-07-01 15:41:20 +02:00

444 lines
16 KiB
C++

/*
* Copyright (c) 2019, University of 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 "Command.h"
#include "checker/CheckerDDR3.h"
#include "checker/CheckerDDR4.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 "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/RefreshManagerRankwise.h"
#include "refresh/RefreshManagerDummy.h"
#include "refresh/RefreshManagerBankwise.h"
#include "powerdown/PowerDownManagerStaggered.h"
#include "powerdown/PowerDownManagerDummy.h"
using namespace tlm;
Controller::Controller(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);
// instantiate timing checker
if (memSpec->memoryType == "DDR3")
checker = new CheckerDDR3();
else if (memSpec->memoryType == "DDR4")
checker = new CheckerDDR4();
else if (memSpec->memoryType == "WIDEIO_SDR")
checker = new CheckerWideIO();
else if (memSpec->memoryType == "LPDDR4")
checker = new CheckerLPDDR4();
else if (memSpec->memoryType == "WIDEIO2")
checker = new CheckerWideIO2();
else if (memSpec->memoryType == "HBM2")
checker = new CheckerHBM2();
else if (memSpec->memoryType == "GDDR5")
checker = new CheckerGDDR5();
else if (memSpec->memoryType == "GDDR5X")
checker = new CheckerGDDR5X();
else if (memSpec->memoryType == "GDDR6")
checker = new CheckerGDDR6();
else
SC_REPORT_FATAL("Controller", "Unsupported DRAM type!");
// instantiate scheduler and command mux
if (config.scheduler == "Fifo")
scheduler = new SchedulerFifo();
else if (config.scheduler == "FrFcfs")
scheduler = new SchedulerFrFcfs();
else if (config.scheduler == "FrFcfsGrp")
scheduler = new SchedulerFrFcfsGrp();
else
SC_REPORT_FATAL("Controller", "Selected scheduler not supported!");
if (config.cmdMux == "Oldest")
cmdMux = new CmdMuxOldest();
else if (config.cmdMux == "Strict")
cmdMux = new CmdMuxStrict();
else
SC_REPORT_FATAL("Controller", "Selected cmdmux not supported!");
if (config.respQueue == "Fifo")
respQueue = new RespQueueFifo();
else if (config.respQueue == "Reorder")
respQueue = new RespQueueReorder();
else
SC_REPORT_FATAL("Controller", "Selected respqueue not supported!");
// instantiate bank machines (one per bank)
if (config.pagePolicy == "Open")
{
for (unsigned bankID = 0; bankID < memSpec->numberOfBanks; bankID++)
bankMachines.push_back(new BankMachineOpen(scheduler, checker, Bank(bankID)));
}
else if (config.pagePolicy == "OpenAdaptive")
{
for (unsigned bankID = 0; bankID < memSpec->numberOfBanks; bankID++)
bankMachines.push_back(new BankMachineOpenAdaptive(scheduler, checker, Bank(bankID)));
}
else if (config.pagePolicy == "Closed")
{
for (unsigned bankID = 0; bankID < memSpec->numberOfBanks; bankID++)
bankMachines.push_back(new BankMachineClosed(scheduler, checker, Bank(bankID)));
}
else if (config.pagePolicy == "ClosedAdaptive")
{
for (unsigned bankID = 0; bankID < memSpec->numberOfBanks; bankID++)
bankMachines.push_back(new BankMachineClosedAdaptive(scheduler, checker, Bank(bankID)));
}
else
SC_REPORT_FATAL("Controller", "Selected page policy not supported!");
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
bankMachinesOnRank.push_back(std::vector<BankMachine *>(bankMachines.begin() + rankID * memSpec->banksPerRank,
bankMachines.begin() + (rankID + 1) * memSpec->banksPerRank));
}
// instantiate power-down managers (one per rank)
if (config.powerDownPolicy == "NoPowerDown")
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
PowerDownManagerIF *manager = new PowerDownManagerDummy();
powerDownManagers.push_back(manager);
}
}
else if (config.powerDownPolicy == "Staggered")
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
PowerDownManagerIF *manager = new PowerDownManagerStaggered(Rank(rankID), checker);
powerDownManagers.push_back(manager);
}
}
else
SC_REPORT_FATAL("Controller", "Selected power-down mode not supported!");
// instantiate refresh managers (one per rank)
if (config.refreshPolicy == "NoRefresh")
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
refreshManagers.push_back(new RefreshManagerDummy());
}
else if (config.refreshPolicy == "Rankwise")
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
RefreshManagerIF *manager = new RefreshManagerRankwise
(bankMachinesOnRank[rankID], powerDownManagers[rankID], Rank(rankID), checker);
refreshManagers.push_back(manager);
}
}
else if (config.refreshPolicy == "Bankwise")
{
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
// TODO: remove bankMachines in constructor
RefreshManagerIF *manager = new RefreshManagerBankwise
(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) Release payload if arbiter has accepted the result (finish END_RESP)
if (payloadToRelease != nullptr && timeToRelease <= sc_time_stamp())
finishEndResp();
// (2) Send next result to arbiter (start BEGIN_RESP)
if (payloadToRelease == nullptr)
startBeginResp();
// (3) Insert new request from arbiter into scheduler and restart appropriate BM (finish BEGIN_REQ)
if (payloadToAcquire != nullptr && timeToAcquire <= sc_time_stamp())
{
unsigned bankID = DramExtension::getBank(payloadToAcquire).ID();
finishBeginReq();
bankMachines[bankID]->start();
}
// (4) 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();
// (5) Choose one request and send it to DRAM
std::pair<Command, tlm_generic_payload *> commandPair;
std::vector<std::pair<Command, tlm_generic_payload *>> readyCommands;
// (5.1) Check for power-down commands (PDEA/PDEP/SREFEN or PDXA/PDXP/SREFEX)
for (unsigned rankID = 0; rankID < memSpec->numberOfRanks; rankID++)
{
commandPair = powerDownManagers[rankID]->getNextCommand();
if (commandPair.second != nullptr)
readyCommands.push_back(commandPair);
else
{
// (5.2) Check for refresh commands (PREA/PRE or REFA/REFB)
commandPair = refreshManagers[rankID]->getNextCommand();
if (commandPair.second != nullptr)
readyCommands.push_back(commandPair);
else
{
// (5.3) Check for bank commands (PRE, ACT, RD/RDA or WR/WRA)
for (auto it : bankMachinesOnRank[rankID])
{
commandPair = it->getNextCommand();
if (commandPair.second != nullptr)
readyCommands.push_back(commandPair);
}
}
}
}
bool readyCmdBlocked = false;
if (!readyCommands.empty())
{
commandPair = cmdMux->selectCommand(readyCommands);
if (commandPair.second != nullptr) // can happen with FIFO strict
{
Rank rank = DramExtension::getRank(commandPair.second);
BankGroup bankgroup = DramExtension::getBankGroup(commandPair.second);
Bank bank = DramExtension::getBank(commandPair.second);
if (isRankCommand(commandPair.first))
{
for (auto it : bankMachinesOnRank[rank.ID()])
it->updateState(commandPair.first);
}
else
bankMachines[bank.ID()]->updateState(commandPair.first);
refreshManagers[rank.ID()]->updateState(commandPair.first, commandPair.second);
powerDownManagers[rank.ID()]->updateState(commandPair.first);
checker->insert(commandPair.first, rank, bankgroup, bank);
if (isCasCommand(commandPair.first))
{
scheduler->removeRequest(commandPair.second);
respQueue->insertPayload(commandPair.second, memSpec->getIntervalOnDataStrobe(commandPair.first).end);
sc_time triggerTime = respQueue->getTriggerTime();
if (triggerTime != sc_max_time())
dataResponseEvent.notify(triggerTime - sc_time_stamp());
ranksNumberOfPayloads[rank.ID()]--;
}
if (ranksNumberOfPayloads[rank.ID()] == 0)
powerDownManagers[rank.ID()]->triggerEntry();
sendToDram(commandPair.first, commandPair.second);
}
else
readyCmdBlocked = true;
}
// (6) Accept request from arbiter if scheduler is not full, otherwise backpressure (start END_REQ)
if (payloadToAcquire != nullptr && timeToAcquire == sc_max_time())
startEndReq();
// (7) Restart bank machines, refresh managers and power-down managers to issue new requests for the future
// TODO: check if all calls are necessary
sc_time timeForNextTrigger = sc_max_time();
for (auto it : bankMachines)
{
sc_time localTime = it->start();
if (!(localTime == sc_time_stamp() && readyCmdBlocked))
timeForNextTrigger = std::min(timeForNextTrigger, localTime);
}
for (auto it : refreshManagers)
timeForNextTrigger = std::min(timeForNextTrigger, it->start());
for (auto it : powerDownManagers)
timeForNextTrigger = std::min(timeForNextTrigger, it->start());
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)
{
sc_time notificationDelay = delay + Configuration::getInstance().memSpec->tCK;
if (phase == BEGIN_REQ)
{
payloadToAcquire = &trans;
timeToAcquire = sc_time_stamp() + notificationDelay;
beginReqEvent.notify(notificationDelay);
}
else if (phase == END_RESP)
{
timeToRelease = sc_time_stamp() + notificationDelay;
endRespEvent.notify(notificationDelay);
}
else
SC_REPORT_FATAL("Controller", "nb_transport_fw in controller was triggered with unknown phase");
PRINTDEBUGMESSAGE(name(), "[fw] " + getPhaseName(phase) + " notification in " +
notificationDelay.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::finishBeginReq()
{
uint64_t id __attribute__((unused)) = DramExtension::getPayloadID(payloadToAcquire);
PRINTDEBUGMESSAGE(name(), "Payload " + std::to_string(id) + " entered system.");
if (totalNumberOfPayloads == 0)
idleTimeCollector.end();
totalNumberOfPayloads++;
Rank rank = DramExtension::getRank(payloadToAcquire);
if (ranksNumberOfPayloads[rank.ID()] == 0)
powerDownManagers[rank.ID()]->triggerExit();
ranksNumberOfPayloads[rank.ID()]++;
scheduler->storeRequest(payloadToAcquire);
payloadToAcquire->acquire();
timeToAcquire = sc_max_time();
}
void Controller::startEndReq()
{
if (scheduler->hasBufferSpace())
{
payloadToAcquire->set_response_status(TLM_OK_RESPONSE);
sendToFrontend(payloadToAcquire, END_REQ);
payloadToAcquire = nullptr;
}
else
PRINTDEBUGMESSAGE(name(), "Total number of payloads exceeded, backpressure!");
}
void Controller::startBeginResp()
{
payloadToRelease = respQueue->nextPayload();
if (payloadToRelease != nullptr)
sendToFrontend(payloadToRelease, BEGIN_RESP);
else
{
sc_time triggerTime = respQueue->getTriggerTime();
if (triggerTime != sc_max_time())
dataResponseEvent.notify(triggerTime - sc_time_stamp());
}
}
void Controller::finishEndResp()
{
uint64_t id __attribute__((unused)) = DramExtension::getPayloadID(payloadToRelease);
PRINTDEBUGMESSAGE(name(), "Payload " + std::to_string(id) + " left system.");
payloadToRelease->release();
payloadToRelease = nullptr;
timeToRelease = sc_max_time();
numberOfTransactionsServed++;
totalNumberOfPayloads--;
if (totalNumberOfPayloads == 0)
idleTimeCollector.start();
}
void Controller::sendToFrontend(tlm_generic_payload *payload, tlm_phase phase)
{
sc_time delay = SC_ZERO_TIME;
tSocket->nb_transport_bw(*payload, phase, delay);
}
void Controller::sendToDram(Command command, tlm_generic_payload *payload)
{
sc_time delay = SC_ZERO_TIME;
tlm_phase phase = commandToPhase(command);
iSocket->nb_transport_fw(*payload, phase, delay);
}