blob: bffc5d3bbde1ca36defc54100a60dc81081983c8 [file] [log] [blame]
/*
* 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
*
*/
#include "CmdScheduler.h"
#include <cassert>
#include <cmath> // For log2
#include <algorithm> // For max
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(MemorySpecification memSpec,
ifstream& trans_trace, int grouping, int interleaving, int burst, int powerdown)
{
commands.open("commands.trace", ifstream::out);
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(MemorySpecification memSpec)
{
MemTimingSpec& memTimingSpec = memSpec.memTimingSpec;
ACT.resize(2 * memSpec.memArchSpec.nbrOfBanks);
RDWR.resize(2 * memSpec.memArchSpec.nbrOfBanks);
PRE.resize(memSpec.memArchSpec.nbrOfBanks);
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 (unsigned i = 0; i < memSpec.memArchSpec.nbrOfBanks; i++) {
cmd.Type = PRECHARGE;
cmd.bank = i;
cmd.name = "PRE";
if (memSpec.id == "WIDEIO_SDR")
cmd.time = 1 - static_cast<double>(memSpec.memTimingSpec.TAW);
else
cmd.time = 1 - static_cast<double>(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[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, MemorySpecification memSpec)
{
std::string line;
transTime = 0;
unsigned newtranstime;
unsigned transAddr;
unsigned transType = 1;
trans TransItem;
if (!transTrace.empty()) {
transTrace.erase(transTrace.begin(), transTrace.end());
}
while (getline(trans_trace, line)) {
istringstream linestream(line);
string item;
unsigned itemnum = 0;
while (getline(linestream, item, ',')) {
if (itemnum == 0) {
stringstream timestamp(item);
timestamp >> newtranstime;
transTime = transTime + 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(MemorySpecification memSpec)
{
int Bs = -1;
int transType = -1;
double timer = 0;
int bankGroupPointer = 0;
int bankGroupAddr = 0;
bool collisionFound;
physicalAddr PhysicalAddress;
bool bankGroupSwitch = false;
std::vector<unsigned> bankPointer(nbrOfBankGroups, 0);
std::vector<int> bankAccessNum(nBanks, -1);
std::vector<bool> ACTSchedule(nBanks, false);
int bankAddr = -1;
double endTime = 0;
double tComing_REF = 0;
Inselfrefresh = 0;
MemTimingSpec& memTimingSpec = memSpec.memTimingSpec;
for (unsigned t = 0; t < transTrace.size(); t++) {
cmdScheduling.erase(cmdScheduling.begin(), cmdScheduling.end());
for (unsigned i = 0; i < nBanks; i++) {
ACTSchedule[i] = false;
bankAccessNum[i] = -1;
}
timingsGet = false;
timer = transTrace[t].timeStamp;
PhysicalAddress = memoryMap(transTrace[t], memSpec);
for (unsigned i = 0; i < nbrOfBankGroups; i++) {
bankPointer[i] = PhysicalAddress.bankAddr; // the bank pointer per group.
}
bankGroupPointer = PhysicalAddress.bankGroupAddr;
endTime = max(transFinish.time, PRE[transFinish.bank].time +
static_cast<int>(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 (double 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[transFinish.bank].time
+ static_cast<int>(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///////////////////
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 + j;
bankAddr = bankGroupAddr * 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<int>(memTimingSpec.RP)),
ACT[bankaccess - 4].time +
static_cast<int>(memTimingSpec.FAW));
if (memSpec.memoryType == MemoryType::WIDEIO_SDR) {
cmd.time = max(max(ACT[bankaccess - 1].time + tRRD_init,
PRE[cmd.bank].time + static_cast<int>(memTimingSpec.RP)),
ACT[bankaccess - 2].time +
static_cast<int>(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<int>(ACT.size() - 1);
ACTBank >= 0; ACTBank--) {
if (ACT[ACTBank].bank == bankAddr) {
cmd.time = max(PreRDWR.time + tSwitch_init, ACT.back().time
+ static_cast<int>(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<int>(ACT.size() - 1);
ACTBank >= 0; ACTBank--) {
if (ACT[ACTBank].bank == bankAddr) {
PRE[bankAddr].time = max(ACT.back().time +
static_cast<int>(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() >= 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(double endTime, double timer,
MemorySpecification memSpec)
{
double ZERO = 0;
MemTimingSpec& memTimingSpec = memSpec.memTimingSpec;
endTime = max(endTime, startTime);
double 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.
int cmdScheduler::getRWTP(int transType, MemorySpecification memSpec)
{
int tRWTP_init = 0;
MemTimingSpec& memTimingSpec = memSpec.memTimingSpec;
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(0, static_cast<int>(memTimingSpec.RTP - 2));
break;
case MemoryType::DDR2:
tRWTP_init = memTimingSpec.AL + memArchSpec.burstLength /
memArchSpec.dataRate +
max(static_cast<int>(memTimingSpec.RTP), 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 + memSpec.memTimingSpec.WR;
} else {
tRWTP_init = memTimingSpec.WL + memArchSpec.burstLength /
memArchSpec.dataRate + memSpec.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, MemorySpecification memSpec,
int PreType, int CurrentType)
{
MemTimingSpec& memTimingSpec = memSpec.memTimingSpec;
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,
MemorySpecification memSpec)
{
int DecLogic;
physicalAddr PhysicalAddr;
DecLogic = Trans.logicalAddress;
// row-bank-column-BI-BC-BGI-BL
if ((BGI > 1) && (memSpec.memoryType == MemoryType::DDR4)) {
unsigned colBits = static_cast<unsigned>(log2(nColumns));
unsigned bankShift = static_cast<unsigned>(colBits + ((BI > 1) ? log2(BI) : 0)
+ ((BGI > 1) ? log2(BGI) : 0));
unsigned bankMask = static_cast<unsigned>(nBanks / (BI * nbrOfBankGroups) - 1)
<< bankShift;
unsigned bankAddr = (DecLogic & bankMask) >>
static_cast<unsigned>(colBits + ((BGI > 1) ? log2(BGI) : 0));
PhysicalAddr.bankAddr = bankAddr;
unsigned bankGroupShift = static_cast<unsigned>(log2(burstLength));
unsigned bankGroupMask = (nbrOfBankGroups / BGI - 1) << bankGroupShift;
unsigned bankGroupAddr = (DecLogic & bankGroupMask) >> bankGroupShift;
PhysicalAddr.bankGroupAddr = bankGroupAddr;
unsigned colShift = static_cast<unsigned>(log2(BC * burstLength) +
((BI > 1) ? log2(BI) : 0) + ((BGI > 1) ? log2(BGI) : 0));
unsigned colMask = static_cast<unsigned>(nColumns / (BC * burstLength) - 1)
<< colShift;
unsigned colAddr = (DecLogic & colMask) >>
static_cast<unsigned>((colShift - log2(static_cast<unsigned>(BC) * burstLength)));
PhysicalAddr.colAddr = colAddr;
} else {
unsigned colBits = static_cast<unsigned>(log2(nColumns));
unsigned bankShift = static_cast<unsigned>(colBits + ((BI > 1) ? log2(BI) : 0));
unsigned bankMask = static_cast<unsigned>(nBanks / BI - 1) << bankShift;
unsigned bankAddr = (DecLogic & bankMask) >> colBits;
PhysicalAddr.bankAddr = bankAddr;
unsigned colShift = static_cast<unsigned>(log2(BC * burstLength) +
((BI > 1) ? log2(BI) : 0));
unsigned colMask = static_cast<unsigned>(nColumns / (BC * burstLength) - 1)
<< colShift;
unsigned colAddr = (DecLogic & colMask) >>
static_cast<unsigned>((colShift - log2(static_cast<unsigned>(BC) * burstLength)));
PhysicalAddr.colAddr = colAddr;
PhysicalAddr.bankGroupAddr = 0;
}
unsigned rowShift = static_cast<unsigned>(log2(nColumns * nBanks));
unsigned rowMask = static_cast<unsigned>(memSpec.memArchSpec.nbrOfRows - 1)
<< rowShift;
unsigned rowAddr = (DecLogic & rowMask) >> rowShift;
PhysicalAddr.rowAddr = rowAddr;
return PhysicalAddr;
} // cmdScheduler::memoryMap