/* * Copyright (c) 2012-2014, TU Delft * Copyright (c) 2012-2014, TU Eindhoven * Copyright (c) 2012-2014, TU 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: Karthik Chandrasekar, Yonghui Li, Sven Goossens * */ #include "CmdScheduler.h" #include #include // For log2 #include // For max #define MILLION 1000000 using namespace std; using namespace Data; // Read the traces and get the transaction. Each transaction is executed by // scheduling a number of commands to the memory. Hence, the transactions are // translated into a sequence of commands which will be used for power analysis. void cmdScheduler::transTranslation(const MemorySpecification& memSpec, ifstream& trans_trace, int grouping, int interleaving, int burst, int powerdown) { commands.open("commands.trace", ifstream::out); const MemArchitectureSpec& memArchSpec = memSpec.memArchSpec; nBanks = memArchSpec.nbrOfBanks; nColumns = memArchSpec.nbrOfColumns; burstLength = memArchSpec.burstLength; nbrOfBankGroups = memArchSpec.nbrOfBankGroups; BGI = grouping; BI = interleaving; BC = burst; power_down = powerdown; schedulingInitialization(memSpec); getTrans(trans_trace, memSpec); trans_trace.close(); commands.close(); ACT.erase(ACT.begin(), ACT.end()); PRE.erase(PRE.begin(), PRE.end()); RDWR.erase(RDWR.begin(), RDWR.end()); cmdScheduling.erase(cmdScheduling.begin(), cmdScheduling.end()); cmdList.erase(cmdList.begin(), cmdList.end()); transTrace.erase(transTrace.begin(), transTrace.end()); } // cmdScheduler::transTranslation // initialize the variables and vectors for starting command scheduling. void cmdScheduler::schedulingInitialization(const MemorySpecification& memSpec) { const MemTimingSpec& memTimingSpec = memSpec.memTimingSpec; const size_t numBanks = static_cast(memSpec.memArchSpec.nbrOfBanks); ACT.resize(2 * numBanks); RDWR.resize(2 * numBanks); PRE.resize(numBanks); bankaccess = memSpec.memArchSpec.nbrOfBanks; if (!ACT.empty()) { ACT.erase(ACT.begin(), ACT.end()); } if (!PRE.empty()) { PRE.erase(PRE.begin(), PRE.end()); } if (!RDWR.empty()) { RDWR.erase(RDWR.begin(), RDWR.end()); } ///////////////initialization////////////// for (int64_t i = 0; i < memSpec.memArchSpec.nbrOfBanks; i++) { cmd.Type = PRECHARGE; cmd.bank = static_cast(i); cmd.name = "PRE"; if (memSpec.id == "WIDEIO_SDR") { cmd.time = 1 - memSpec.memTimingSpec.TAW; } else { cmd.time = 1 - memSpec.memTimingSpec.FAW; } PRE.push_back(cmd); cmd.Type = ACTIVATE; cmd.name = "ACT"; ACT.push_back(cmd); cmd.Type = WRITE; cmd.name = "WRITE"; cmd.time = -1; RDWR[static_cast(i)].push_back(cmd); } tREF = memTimingSpec.REFI; transFinish.time = 0; transFinish.bank = 0; PreRDWR.bank = -1; PreRDWR.Type = READ; PreRDWR.name = "RD"; PreRDWR.time = -1; startTime = 0; } // cmdScheduler::schedulingInitialization // transactions are generated according to the information read from the traces. // Then the command scheduling function is triggered to generate commands and // schedule them to the memory according to the timing constraints. void cmdScheduler::getTrans(std::ifstream& trans_trace, const MemorySpecification& memSpec) { std::string line; transTime = 0; uint64_t newtranstime; uint64_t transAddr; int64_t transType = 1; trans TransItem; if (!transTrace.empty()) { transTrace.erase(transTrace.begin(), transTrace.end()); } while (getline(trans_trace, line)) { istringstream linestream(line); string item; uint64_t itemnum = 0; while (getline(linestream, item, ',')) { if (itemnum == 0) { stringstream timestamp(item); timestamp >> newtranstime; transTime = transTime + static_cast(newtranstime); } else if (itemnum == 1) { if (item == "write" || item == "WRITE") { transType = WRITE; } else { transType = READ; } } else if (itemnum == 2) { stringstream timestamp(item); timestamp >> std::hex >> transAddr; } itemnum++; } // generate a transaction TransItem.timeStamp = transTime; TransItem.logicalAddress = transAddr; TransItem.type = transType; transTrace.push_back(TransItem); if (transTrace.size() == MILLION) { // The scheduling is implemented for every MILLION transactions. // It is used to reduce the used memory during the running of this tool. analyticalScheduling(memSpec); transTrace.erase(transTrace.begin(), transTrace.end()); } } if ((transTrace.size() < MILLION) && (!transTrace.empty())) { analyticalScheduling(memSpec); transTrace.erase(transTrace.begin(), transTrace.end()); } } // cmdScheduler::getTrans // Transactions are executed individually and the command scheduling is // independent between transactions. The commands for a new transaction cannot // be scheduled until all the commands for the current one are scheduled. // After the scheduling, a sequence of commands are obtained and they are written // into commands.txt which will be used for power analysis. void cmdScheduler::analyticalScheduling(const MemorySpecification& memSpec) { int64_t transType = -1; int64_t timer = 0; uint64_t bankGroupPointer = 0; uint64_t bankGroupAddr = 0; bool collisionFound; physicalAddr PhysicalAddress; bool bankGroupSwitch = false; std::vector bankPointer(static_cast(nbrOfBankGroups), 0); std::vector bankAccessNum(static_cast(nBanks), -1); std::vector ACTSchedule(static_cast(nBanks), false); uint64_t bankAddr = 0; int64_t endTime = 0; int64_t tComing_REF = 0; Inselfrefresh = 0; const MemTimingSpec& memTimingSpec = memSpec.memTimingSpec; for (uint64_t t = 0; t < transTrace.size(); t++) { cmdScheduling.erase(cmdScheduling.begin(), cmdScheduling.end()); for (auto a : ACTSchedule) { a = false; } for (auto& b : bankAccessNum) { b = -1; } timingsGet = false; timer = transTrace[t].timeStamp; PhysicalAddress = memoryMap(transTrace[t], memSpec); for (auto& b : bankPointer) { b = PhysicalAddress.bankAddr; // the bank pointer per group. } bankGroupPointer = PhysicalAddress.bankGroupAddr; endTime = max(transFinish.time, PRE[static_cast(transFinish.bank)].time + static_cast(memTimingSpec.RP)); // Before starting the scheduling for the next transaction, it has to // check whether it is necessary for implementing power down. if (power_down == SELF_REFRESH) pdScheduling(endTime, timer, memSpec); else if (power_down == POWER_DOWN) pdScheduling(endTime, min(timer, tREF), memSpec); tComing_REF = tREF; ///////////////Scheduling Refresh//////////////////////// if (((transFinish.time >= tREF) || (timer >= tREF))) { for (int64_t i = 0; i <= ((timer - tComing_REF) > 0 ? (timer - tComing_REF) / memTimingSpec.REFI : 0); i++) { cmd.bank = 0; cmd.name = "REF"; cmd.time = max(max(max(transFinish.time, PRE[static_cast(transFinish.bank)].time + memTimingSpec.RP), tREF), startTime); if ((power_down == SELF_REFRESH && !Inselfrefresh) || power_down != SELF_REFRESH) { cmdScheduling.push_back(cmd); startTime = cmd.time + memTimingSpec.RFC; } tREF = tREF + memTimingSpec.REFI; // during the refreshing, power down should be taken into account. if (!Inselfrefresh) pdScheduling(endTime, min(timer, tREF), memSpec); } } ///////////////Execution Transactions/////////////////// uint64_t Bs = PhysicalAddress.bankAddr; transType = transTrace[t].type; tRWTP = getRWTP(transType, memSpec); for (int i = 0; i < BI; i++) { for (int k = 0; k < BC; k++) { if (memSpec.memoryType == MemoryType::DDR4) { bankGroupPointer = PhysicalAddress.bankGroupAddr; } for (int j = 0; j < BGI; j++) { bankGroupSwitch = false; if (memSpec.memoryType == MemoryType::DDR4) { if (bankGroupPointer != bankGroupAddr) { bankGroupSwitch = true; } // update to the current bank group address. bankGroupAddr = PhysicalAddress.bankGroupAddr + static_cast(j); bankAddr = bankGroupAddr * static_cast(nBanks) / nbrOfBankGroups + bankPointer[bankGroupAddr]; } else { bankAddr = Bs + i; } if (!timingsGet) { getTimingConstraints(bankGroupSwitch, memSpec, PreRDWR.Type, transType); } ////////////////ACT Scheduling/////////////////// if (!ACTSchedule[bankAddr]) { cmd.bank = bankAddr; cmd.PhysicalAddr.bankAddr = cmd.bank; cmd.PhysicalAddr.rowAddr = PhysicalAddress.rowAddr; cmd.Type = ACTIVATE; cmd.name = "ACT"; Inselfrefresh = 0; cmd.time = max(max(ACT[bankaccess - 1].time + tRRD_init, PRE[cmd.bank].time + static_cast(memTimingSpec.RP)), ACT[bankaccess - 4].time + static_cast(memTimingSpec.FAW)); if (memSpec.memoryType == MemoryType::WIDEIO_SDR) { cmd.time = max(max(ACT[bankaccess - 1].time + tRRD_init, PRE[cmd.bank].time + static_cast(memTimingSpec.RP)), ACT[bankaccess - 2].time + static_cast(memTimingSpec.TAW)); } if (i == 0 && j == 0) { cmd.time = max(cmd.time, PreRDWR.time + 1); cmd.time = max(cmd.time, timer); cmd.time = max(startTime, cmd.time); } //////////collision detection//////////////////// for (int n = 1; n <= i * BGI + j; n++) { collisionFound = false; for (unsigned m = 0; m < RDWR[bankaccess - n].size(); m++) { if (RDWR[bankaccess - n][m].time == cmd.time) { cmd.time += 1; // ACT is shifted collisionFound = true; break; } } if (collisionFound) { break; } } ACT.push_back(cmd); cmdScheduling.push_back(cmd); ACTSchedule[bankAddr] = true; bankAccessNum[bankAddr] = bankaccess; bankaccess++; } /////RDWR Scheduling////// cmd.bank = bankAddr; cmd.PhysicalAddr.bankAddr = cmd.bank; cmd.PhysicalAddr.rowAddr = PhysicalAddress.rowAddr; cmd.PhysicalAddr.colAddr = PhysicalAddress.colAddr + k * burstLength; cmd.Type = transType; switch (transType) { case READ: cmd.name = "RD"; break; case WRITE: cmd.name = "WR"; break; } for (int ACTBank = static_cast(ACT.size() - 1); ACTBank >= 0; ACTBank--) { if (ACT[ACTBank].bank == static_cast(bankAddr)) { cmd.time = max(PreRDWR.time + tSwitch_init, ACT.back().time + static_cast(memTimingSpec.RCD)); break; } } if ((i == BI - 1) && (k == BC - 1) && (j == BGI - 1)) { transFinish.time = cmd.time + 1; transFinish.bank = bankAddr; } if (k == BC - 1) { switch (transType) { case READ: cmd.name = "RDA"; break; case WRITE: cmd.name = "WRA"; break; } } PreRDWR = cmd; RDWR[bankAccessNum[bankAddr]].push_back(cmd); cmdScheduling.push_back(cmd); ////////////////PRE Scheduling//////////////////// if (k == BC - 1) { PRE[bankAddr].bank = bankAddr; PRE[bankAddr].Type = PRECHARGE; PRE[bankAddr].name = "PRE"; for (int ACTBank = static_cast(ACT.size() - 1); ACTBank >= 0; ACTBank--) { if (ACT[ACTBank].bank == static_cast(bankAddr)) { PRE[bankAddr].time = max(ACT.back().time + static_cast(memTimingSpec.RAS), PreRDWR.time + tRWTP); break; } } bankPointer[bankGroupAddr] = bankPointer[bankGroupAddr] + 1; } bankGroupPointer++; } } } // make sure the scheduled commands are stored with an ascending scheduling time sort(cmdScheduling.begin(), cmdScheduling.end(), commandItem::commandItemSorter()); // write the scheduled commands into commands.txt. for (unsigned i = 0; i < cmdScheduling.size(); i++) { cmdList.push_back(cmdScheduling[i]); } /////////////Update Vector Length///////////////// // the vector length is reduced so that less memory is used for running // this tool. if (ACT.size() >= static_cast(memSpec.memArchSpec.nbrOfBanks)) { for (int m = 0; m < BI * BGI; m++) { ACT.erase(ACT.begin()); RDWR[0].erase(RDWR[0].begin(), RDWR[0].end()); for (int h = 0; h < bankaccess - 1 - m; h++) { RDWR[h].insert(RDWR[h].begin(), RDWR[h + 1].begin(), RDWR[h + 1].end()); RDWR[h + 1].resize(0); } } bankaccess = bankaccess - (BI * BGI); } } for (unsigned j = 0; j < cmdList.size(); j++) { commands.precision(0); commands << fixed << cmdList[j].time << "," << cmdList[j].name << "," << cmdList[j].bank << endl; } cmdList.erase(cmdList.begin(), cmdList.end()); } // cmdScheduler::analyticalScheduling // to add the power down/up during the command scheduling for transactions. // It is called when the command scheduling for a transaction is finished, and it // is also called if there is a refresh. void cmdScheduler::pdScheduling(int64_t endTime, int64_t timer, const MemorySpecification& memSpec) { int64_t ZERO = 0; const MemTimingSpec& memTimingSpec = memSpec.memTimingSpec; endTime = max(endTime, startTime); int64_t pdTime = max(ZERO, timer - endTime); if ((timer > (endTime + memTimingSpec.CKE)) && (power_down == POWER_DOWN)) { cmd.bank = 0; cmd.name = "PDN_S_PRE"; cmd.time = endTime; cmdScheduling.push_back(cmd); cmd.name = "PUP_PRE"; if (pdTime > memTimingSpec.REFI) cmd.time = cmd.time + memTimingSpec.REFI; else cmd.time = cmd.time + pdTime; if (memSpec.memoryType.isLPDDRFamily()) startTime = cmd.time + memTimingSpec.XP; else startTime = cmd.time + memTimingSpec.XPDLL - memTimingSpec.RCD; cmdScheduling.push_back(cmd); } else if ((timer > (endTime + memTimingSpec.CKESR)) && (power_down == SELF_REFRESH)) { cmd.bank = 0; cmd.name = "SREN"; cmd.time = endTime; cmdScheduling.push_back(cmd); Inselfrefresh = 1; cmd.name = "SREX"; cmd.time = cmd.time + pdTime; if (memSpec.memoryType.isLPDDRFamily()) startTime = cmd.time + memTimingSpec.XS; else startTime = cmd.time + memTimingSpec.XSDLL - memTimingSpec.RCD; cmdScheduling.push_back(cmd); } } // cmdScheduler::pdScheduling // get the time when a precharge occurs after a read/write command is scheduled. // In addition, it copes with different kind of memories. int64_t cmdScheduler::getRWTP(int64_t transType, const MemorySpecification& memSpec) { int64_t tRWTP_init = 0; const MemTimingSpec& memTimingSpec = memSpec.memTimingSpec; const MemArchitectureSpec& memArchSpec = memSpec.memArchSpec; if (transType == READ) { switch (memSpec.memoryType) { case MemoryType::LPDDR: case MemoryType::WIDEIO_SDR: tRWTP_init = memArchSpec.burstLength / memArchSpec.dataRate; break; case MemoryType::LPDDR2: case MemoryType::LPDDR3: tRWTP_init = memArchSpec.burstLength / memArchSpec.dataRate + max(int64_t(0), memTimingSpec.RTP - 2); break; case MemoryType::DDR2: tRWTP_init = memTimingSpec.AL + memArchSpec.burstLength / memArchSpec.dataRate + max(memTimingSpec.RTP, int64_t(2)) - 2; break; case MemoryType::DDR3: case MemoryType::DDR4: tRWTP_init = memTimingSpec.RTP; break; default: assert("Unknown memory type" && false); } // switch } else if (transType == WRITE) { if (memSpec.memoryType == MemoryType::WIDEIO_SDR) { tRWTP_init = memTimingSpec.WL + memArchSpec.burstLength / memArchSpec.dataRate - 1 + memTimingSpec.WR; } else { tRWTP_init = memTimingSpec.WL + memArchSpec.burstLength / memArchSpec.dataRate + memTimingSpec.WR; } if ((memSpec.memoryType == MemoryType::LPDDR2) || (memSpec.memoryType == MemoryType::LPDDR3)) { tRWTP_init = tRWTP_init + 1; } } return tRWTP_init; } // cmdScheduler::getRWTP // get the timings for command scheduling according to different memories. // In particular, tSwitch_init is generally used to provide the timings for // scheduling a read/write command after a read/write command which have been // scheduled to any possible banks within any possible bank groups (DDR4). void cmdScheduler::getTimingConstraints(bool BGSwitch, const MemorySpecification& memSpec, int64_t PreType, int64_t CurrentType) { const MemTimingSpec& memTimingSpec = memSpec.memTimingSpec; const MemArchitectureSpec& memArchSpec = memSpec.memArchSpec; if (memSpec.memoryType != MemoryType::DDR4) { tRRD_init = memTimingSpec.RRD; if (PreType == CurrentType) { tSwitch_init = memTimingSpec.CCD; timingsGet = true; } if ((PreType == WRITE) && (CurrentType == READ)) { if (memSpec.memoryType == MemoryType::WIDEIO_SDR) { tSwitch_init = memTimingSpec.WL + memArchSpec.burstLength / memArchSpec.dataRate - 1 + memTimingSpec.WTR; } else { tSwitch_init = memTimingSpec.WL + memArchSpec.burstLength / memArchSpec.dataRate + memTimingSpec.WTR; } if ((memSpec.memoryType == MemoryType::LPDDR2) || (memSpec.memoryType == MemoryType::LPDDR3)) { tSwitch_init = tSwitch_init + 1; } } } if (memSpec.memoryType == MemoryType::DDR4) { if (BGSwitch) { tCCD_init = memTimingSpec.CCD_S; tRRD_init = memTimingSpec.RRD_S; tWTR_init = memTimingSpec.WTR_S; } else { tCCD_init = memTimingSpec.CCD_L; tRRD_init = memTimingSpec.RRD_L; tWTR_init = memTimingSpec.WTR_L; } if (PreType == CurrentType) { tSwitch_init = tCCD_init; timingsGet = true; } else if (PreType == WRITE && CurrentType == READ) { tSwitch_init = memTimingSpec.WL + memArchSpec.burstLength / memArchSpec.dataRate + tWTR_init; } } if ((PreType == READ) && (CurrentType == WRITE)) { tSwitch_init = memTimingSpec.RL + memArchSpec.burstLength / memArchSpec.dataRate + 2 - memTimingSpec.WL; } } // cmdScheduler::getTimingConstraints // The logical address of each transaction is translated into a physical address // which consists of bank group (for DDR4), bank, row and column addresses. cmdScheduler::physicalAddr cmdScheduler::memoryMap(trans Trans, const MemorySpecification& memSpec) { int64_t DecLogic; physicalAddr PhysicalAddr; DecLogic = Trans.logicalAddress; // row-bank-column-BI-BC-BGI-BL if (BGI > 1 && memSpec.memoryType == MemoryType::DDR4) { uint64_t colBits = uintLog2(nColumns); uint64_t bankShift = colBits + ((BI > 1) ? uintLog2(BI) : 0) + ((BGI > 1) ? uintLog2(BGI) : 0); uint64_t bankMask = (nBanks / (BI * nbrOfBankGroups) - 1) << bankShift; uint64_t bankAddr = (DecLogic & bankMask) >> (colBits + ((BGI > 1) ? uintLog2(BGI) : 0)); PhysicalAddr.bankAddr = bankAddr; uint64_t bankGroupShift = uintLog2(burstLength); uint64_t bankGroupMask = (nbrOfBankGroups / BGI - 1) << bankGroupShift; uint64_t bankGroupAddr = (DecLogic & bankGroupMask) >> bankGroupShift; PhysicalAddr.bankGroupAddr = bankGroupAddr; uint64_t colShift = uintLog2(BC * burstLength) + ((BI > 1) ? uintLog2(BI) : 0) + ((BGI > 1) ? uintLog2(BGI) : 0); uint64_t colMask = (nColumns / (BC * burstLength) - 1) << colShift; uint64_t colAddr = (DecLogic & colMask) >> (colShift - uintLog2(static_cast(BC) * burstLength)); PhysicalAddr.colAddr = colAddr; } else { uint64_t colBits = uintLog2(nColumns); uint64_t bankShift = colBits + ((BI > 1) ? uintLog2(BI) : 0); uint64_t bankMask = (nBanks / BI - 1) << bankShift; uint64_t bankAddr = (DecLogic & bankMask) >> colBits; PhysicalAddr.bankAddr = bankAddr; uint64_t colShift = (uintLog2(BC * burstLength) + ((BI > 1) ? uintLog2(BI) : 0)); uint64_t colMask = (nColumns / (BC * burstLength) - 1) << colShift; uint64_t colAddr = (DecLogic & colMask) >> (colShift - uintLog2(BC * burstLength)); PhysicalAddr.colAddr = colAddr; PhysicalAddr.bankGroupAddr = 0; } uint64_t rowShift = uintLog2(nColumns * nBanks); uint64_t rowMask = (memSpec.memArchSpec.nbrOfRows - 1) << rowShift; uint64_t rowAddr = (DecLogic & rowMask) >> rowShift; PhysicalAddr.rowAddr = rowAddr; return PhysicalAddr; } // cmdScheduler::memoryMap uint64_t cmdScheduler::uintLog2(uint64_t in) { return static_cast(log2(in)); }