| /* |
| * Copyright (c) 2010-2012 ARM Limited |
| * All rights reserved |
| * |
| * The license below extends only to copyright in the software and shall |
| * not be construed as granting a license to any other intellectual |
| * property including but not limited to intellectual property relating |
| * to a hardware implementation of the functionality of the software |
| * licensed hereunder. You may use the software subject to the license |
| * terms below provided that you ensure that this notice is replicated |
| * unmodified and in its entirety in all distributions of the software, |
| * modified or unmodified, in source code or in binary form. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer; |
| * 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; |
| * neither the name of the copyright holders 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 |
| * OWNER 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: Andreas Hansson |
| * Ani Udipi |
| */ |
| |
| #include "base/trace.hh" |
| #include "debug/Drain.hh" |
| #include "debug/DRAM.hh" |
| #include "debug/DRAMWR.hh" |
| #include "mem/simple_dram.hh" |
| |
| using namespace std; |
| |
| SimpleDRAM::SimpleDRAM(const SimpleDRAMParams* p) : |
| AbstractMemory(p), |
| port(name() + ".port", *this), |
| retryRdReq(false), retryWrReq(false), |
| rowHitFlag(false), stopReads(false), actTicks(p->activation_limit, 0), |
| writeEvent(this), respondEvent(this), |
| refreshEvent(this), nextReqEvent(this), drainManager(NULL), |
| bytesPerCacheLine(0), |
| linesPerRowBuffer(p->lines_per_rowbuffer), |
| ranksPerChannel(p->ranks_per_channel), |
| banksPerRank(p->banks_per_rank), channels(p->channels), rowsPerBank(0), |
| readBufferSize(p->read_buffer_size), |
| writeBufferSize(p->write_buffer_size), |
| writeThresholdPerc(p->write_thresh_perc), |
| tWTR(p->tWTR), tBURST(p->tBURST), |
| tRCD(p->tRCD), tCL(p->tCL), tRP(p->tRP), |
| tRFC(p->tRFC), tREFI(p->tREFI), |
| tXAW(p->tXAW), activationLimit(p->activation_limit), |
| memSchedPolicy(p->mem_sched_policy), addrMapping(p->addr_mapping), |
| pageMgmt(p->page_policy), |
| busBusyUntil(0), writeStartTime(0), |
| prevArrival(0), numReqs(0) |
| { |
| // create the bank states based on the dimensions of the ranks and |
| // banks |
| banks.resize(ranksPerChannel); |
| for (size_t c = 0; c < ranksPerChannel; ++c) { |
| banks[c].resize(banksPerRank); |
| } |
| |
| // round the write threshold percent to a whole number of entries |
| // in the buffer |
| writeThreshold = writeBufferSize * writeThresholdPerc / 100.0; |
| } |
| |
| void |
| SimpleDRAM::init() |
| { |
| if (!port.isConnected()) { |
| fatal("SimpleDRAM %s is unconnected!\n", name()); |
| } else { |
| port.sendRangeChange(); |
| } |
| |
| // get the burst size from the connected port as it is currently |
| // assumed to be equal to the cache line size |
| bytesPerCacheLine = port.peerBlockSize(); |
| |
| // we could deal with plenty options here, but for now do a quick |
| // sanity check |
| if (bytesPerCacheLine != 64 && bytesPerCacheLine != 32) |
| panic("Unexpected burst size %d", bytesPerCacheLine); |
| |
| // determine the rows per bank by looking at the total capacity |
| uint64_t capacity = ULL(1) << ceilLog2(AbstractMemory::size()); |
| |
| DPRINTF(DRAM, "Memory capacity %lld (%lld) bytes\n", capacity, |
| AbstractMemory::size()); |
| rowsPerBank = capacity / (bytesPerCacheLine * linesPerRowBuffer * |
| banksPerRank * ranksPerChannel); |
| |
| if (range.interleaved()) { |
| if (channels != range.stripes()) |
| panic("%s has %d interleaved address stripes but %d channel(s)\n", |
| name(), range.stripes(), channels); |
| |
| if (addrMapping == Enums::openmap) { |
| if (bytesPerCacheLine * linesPerRowBuffer != |
| range.granularity()) { |
| panic("Interleaving of %s doesn't match open address map\n", |
| name()); |
| } |
| } else if (addrMapping == Enums::closemap) { |
| if (bytesPerCacheLine != range.granularity()) |
| panic("Interleaving of %s doesn't match closed address map\n", |
| name()); |
| } |
| } |
| } |
| |
| void |
| SimpleDRAM::startup() |
| { |
| // print the configuration of the controller |
| printParams(); |
| |
| // kick off the refresh |
| schedule(refreshEvent, curTick() + tREFI); |
| } |
| |
| Tick |
| SimpleDRAM::recvAtomic(PacketPtr pkt) |
| { |
| DPRINTF(DRAM, "recvAtomic: %s 0x%x\n", pkt->cmdString(), pkt->getAddr()); |
| |
| // do the actual memory access and turn the packet into a response |
| access(pkt); |
| |
| Tick latency = 0; |
| if (!pkt->memInhibitAsserted() && pkt->hasData()) { |
| // this value is not supposed to be accurate, just enough to |
| // keep things going, mimic a closed page |
| latency = tRP + tRCD + tCL; |
| } |
| return latency; |
| } |
| |
| bool |
| SimpleDRAM::readQueueFull() const |
| { |
| DPRINTF(DRAM, "Read queue limit %d current size %d\n", |
| readBufferSize, readQueue.size() + respQueue.size()); |
| |
| return (readQueue.size() + respQueue.size()) == readBufferSize; |
| } |
| |
| bool |
| SimpleDRAM::writeQueueFull() const |
| { |
| DPRINTF(DRAM, "Write queue limit %d current size %d\n", |
| writeBufferSize, writeQueue.size()); |
| return writeQueue.size() == writeBufferSize; |
| } |
| |
| SimpleDRAM::DRAMPacket* |
| SimpleDRAM::decodeAddr(PacketPtr pkt) |
| { |
| // decode the address based on the address mapping scheme |
| // |
| // with R, C, B and K denoting rank, column, bank and rank, |
| // respectively, and going from MSB to LSB, the two schemes are |
| // RKBC (openmap) and RCKB (closedmap) |
| uint8_t rank; |
| uint16_t bank; |
| uint16_t row; |
| |
| Addr addr = pkt->getAddr(); |
| |
| // truncate the address to the access granularity |
| addr = addr / bytesPerCacheLine; |
| |
| // we have removed the lowest order address bits that denote the |
| // position within the cache line, proceed and select the |
| // appropriate bits for bank, rank and row (no column address is |
| // needed) |
| if (addrMapping == Enums::openmap) { |
| // the lowest order bits denote the column to ensure that |
| // sequential cache lines occupy the same row |
| addr = addr / linesPerRowBuffer; |
| |
| // take out the channel part of the address, note that this has |
| // to match with how accesses are interleaved between the |
| // controllers in the address mapping |
| addr = addr / channels; |
| |
| // after the column bits, we get the bank bits to interleave |
| // over the banks |
| bank = addr % banksPerRank; |
| addr = addr / banksPerRank; |
| |
| // after the bank, we get the rank bits which thus interleaves |
| // over the ranks |
| rank = addr % ranksPerChannel; |
| addr = addr / ranksPerChannel; |
| |
| // lastly, get the row bits |
| row = addr % rowsPerBank; |
| addr = addr / rowsPerBank; |
| } else if (addrMapping == Enums::closemap) { |
| // optimise for closed page mode and utilise maximum |
| // parallelism of the DRAM (at the cost of power) |
| |
| // take out the channel part of the address, not that this has |
| // to match with how accesses are interleaved between the |
| // controllers in the address mapping |
| addr = addr / channels; |
| |
| // start with the bank bits, as this provides the maximum |
| // opportunity for parallelism between requests |
| bank = addr % banksPerRank; |
| addr = addr / banksPerRank; |
| |
| // next get the rank bits |
| rank = addr % ranksPerChannel; |
| addr = addr / ranksPerChannel; |
| |
| // next the column bits which we do not need to keep track of |
| // and simply skip past |
| addr = addr / linesPerRowBuffer; |
| |
| // lastly, get the row bits |
| row = addr % rowsPerBank; |
| addr = addr / rowsPerBank; |
| } else |
| panic("Unknown address mapping policy chosen!"); |
| |
| assert(rank < ranksPerChannel); |
| assert(bank < banksPerRank); |
| assert(row < rowsPerBank); |
| |
| DPRINTF(DRAM, "Address: %lld Rank %d Bank %d Row %d\n", |
| pkt->getAddr(), rank, bank, row); |
| |
| // create the corresponding DRAM packet with the entry time and |
| // ready time set to the current tick, the latter will be updated |
| // later |
| return new DRAMPacket(pkt, rank, bank, row, pkt->getAddr(), |
| banks[rank][bank]); |
| } |
| |
| void |
| SimpleDRAM::addToReadQueue(PacketPtr pkt) |
| { |
| // only add to the read queue here. whenever the request is |
| // eventually done, set the readyTime, and call schedule() |
| assert(!pkt->isWrite()); |
| |
| // First check write buffer to see if the data is already at |
| // the controller |
| list<DRAMPacket*>::const_iterator i; |
| Addr addr = pkt->getAddr(); |
| |
| // @todo: add size check |
| for (i = writeQueue.begin(); i != writeQueue.end(); ++i) { |
| if ((*i)->addr == addr){ |
| servicedByWrQ++; |
| DPRINTF(DRAM, "Read to %lld serviced by write queue\n", addr); |
| bytesRead += bytesPerCacheLine; |
| bytesConsumedRd += pkt->getSize(); |
| accessAndRespond(pkt); |
| return; |
| } |
| } |
| |
| DRAMPacket* dram_pkt = decodeAddr(pkt); |
| |
| assert(readQueue.size() + respQueue.size() < readBufferSize); |
| rdQLenPdf[readQueue.size() + respQueue.size()]++; |
| |
| DPRINTF(DRAM, "Adding to read queue\n"); |
| |
| readQueue.push_back(dram_pkt); |
| |
| // Update stats |
| uint32_t bank_id = banksPerRank * dram_pkt->rank + dram_pkt->bank; |
| assert(bank_id < ranksPerChannel * banksPerRank); |
| perBankRdReqs[bank_id]++; |
| |
| avgRdQLen = readQueue.size() + respQueue.size(); |
| |
| // If we are not already scheduled to get the read request out of |
| // the queue, do so now |
| if (!nextReqEvent.scheduled() && !stopReads) { |
| DPRINTF(DRAM, "Request scheduled immediately\n"); |
| schedule(nextReqEvent, curTick()); |
| } |
| } |
| |
| void |
| SimpleDRAM::processWriteEvent() |
| { |
| assert(!writeQueue.empty()); |
| uint32_t numWritesThisTime = 0; |
| |
| DPRINTF(DRAMWR, "Beginning DRAM Writes\n"); |
| Tick temp1 M5_VAR_USED = std::max(curTick(), busBusyUntil); |
| Tick temp2 M5_VAR_USED = std::max(curTick(), maxBankFreeAt()); |
| |
| // @todo: are there any dangers with the untimed while loop? |
| while (!writeQueue.empty()) { |
| if (numWritesThisTime > writeThreshold) { |
| DPRINTF(DRAMWR, "Hit write threshold %d\n", writeThreshold); |
| break; |
| } |
| |
| chooseNextWrite(); |
| DRAMPacket* dram_pkt = writeQueue.front(); |
| // What's the earliest the request can be put on the bus |
| Tick schedTime = std::max(curTick(), busBusyUntil); |
| |
| DPRINTF(DRAMWR, "Asking for latency estimate at %lld\n", |
| schedTime + tBURST); |
| |
| pair<Tick, Tick> lat = estimateLatency(dram_pkt, schedTime + tBURST); |
| Tick accessLat = lat.second; |
| |
| // look at the rowHitFlag set by estimateLatency |
| if (rowHitFlag) |
| writeRowHits++; |
| |
| Bank& bank = dram_pkt->bank_ref; |
| |
| if (pageMgmt == Enums::open) { |
| bank.openRow = dram_pkt->row; |
| bank.freeAt = schedTime + tBURST + std::max(accessLat, tCL); |
| busBusyUntil = bank.freeAt - tCL; |
| |
| if (!rowHitFlag) { |
| bank.tRASDoneAt = bank.freeAt + tRP; |
| recordActivate(bank.freeAt - tCL - tRCD); |
| busBusyUntil = bank.freeAt - tCL - tRCD; |
| } |
| } else if (pageMgmt == Enums::close) { |
| bank.freeAt = schedTime + tBURST + accessLat + tRP + tRP; |
| // Work backwards from bank.freeAt to determine activate time |
| recordActivate(bank.freeAt - tRP - tRP - tCL - tRCD); |
| busBusyUntil = bank.freeAt - tRP - tRP - tCL - tRCD; |
| DPRINTF(DRAMWR, "processWriteEvent::bank.freeAt for " |
| "banks_id %d is %lld\n", |
| dram_pkt->rank * banksPerRank + dram_pkt->bank, |
| bank.freeAt); |
| } else |
| panic("Unknown page management policy chosen\n"); |
| |
| DPRINTF(DRAMWR, "Done writing to address %lld\n", dram_pkt->addr); |
| |
| DPRINTF(DRAMWR, "schedtime is %lld, tBURST is %lld, " |
| "busbusyuntil is %lld\n", |
| schedTime, tBURST, busBusyUntil); |
| |
| writeQueue.pop_front(); |
| delete dram_pkt; |
| |
| numWritesThisTime++; |
| } |
| |
| DPRINTF(DRAMWR, "Completed %d writes, bus busy for %lld ticks,"\ |
| "banks busy for %lld ticks\n", numWritesThisTime, |
| busBusyUntil - temp1, maxBankFreeAt() - temp2); |
| |
| // Update stats |
| avgWrQLen = writeQueue.size(); |
| |
| // turn the bus back around for reads again |
| busBusyUntil += tWTR; |
| stopReads = false; |
| |
| if (retryWrReq) { |
| retryWrReq = false; |
| port.sendRetry(); |
| } |
| |
| // if there is nothing left in any queue, signal a drain |
| if (writeQueue.empty() && readQueue.empty() && |
| respQueue.empty () && drainManager) { |
| drainManager->signalDrainDone(); |
| drainManager = NULL; |
| } |
| |
| // Once you're done emptying the write queue, check if there's |
| // anything in the read queue, and call schedule if required. The |
| // retry above could already have caused it to be scheduled, so |
| // first check |
| if (!nextReqEvent.scheduled()) |
| schedule(nextReqEvent, busBusyUntil); |
| } |
| |
| void |
| SimpleDRAM::triggerWrites() |
| { |
| DPRINTF(DRAM, "Writes triggered at %lld\n", curTick()); |
| // Flag variable to stop any more read scheduling |
| stopReads = true; |
| |
| writeStartTime = std::max(busBusyUntil, curTick()) + tWTR; |
| |
| DPRINTF(DRAM, "Writes scheduled at %lld\n", writeStartTime); |
| |
| assert(writeStartTime >= curTick()); |
| assert(!writeEvent.scheduled()); |
| schedule(writeEvent, writeStartTime); |
| } |
| |
| void |
| SimpleDRAM::addToWriteQueue(PacketPtr pkt) |
| { |
| // only add to the write queue here. whenever the request is |
| // eventually done, set the readyTime, and call schedule() |
| assert(pkt->isWrite()); |
| |
| DRAMPacket* dram_pkt = decodeAddr(pkt); |
| |
| assert(writeQueue.size() < writeBufferSize); |
| wrQLenPdf[writeQueue.size()]++; |
| |
| DPRINTF(DRAM, "Adding to write queue\n"); |
| |
| writeQueue.push_back(dram_pkt); |
| |
| // Update stats |
| uint32_t bank_id = banksPerRank * dram_pkt->rank + dram_pkt->bank; |
| assert(bank_id < ranksPerChannel * banksPerRank); |
| perBankWrReqs[bank_id]++; |
| |
| avgWrQLen = writeQueue.size(); |
| |
| // we do not wait for the writes to be send to the actual memory, |
| // but instead take responsibility for the consistency here and |
| // snoop the write queue for any upcoming reads |
| |
| bytesConsumedWr += pkt->getSize(); |
| bytesWritten += bytesPerCacheLine; |
| accessAndRespond(pkt); |
| |
| // If your write buffer is starting to fill up, drain it! |
| if (writeQueue.size() > writeThreshold && !stopReads){ |
| triggerWrites(); |
| } |
| } |
| |
| void |
| SimpleDRAM::printParams() const |
| { |
| // Sanity check print of important parameters |
| DPRINTF(DRAM, |
| "Memory controller %s physical organization\n" \ |
| "Bytes per cacheline %d\n" \ |
| "Lines per row buffer %d\n" \ |
| "Rows per bank %d\n" \ |
| "Banks per rank %d\n" \ |
| "Ranks per channel %d\n" \ |
| "Total mem capacity %u\n", |
| name(), bytesPerCacheLine, linesPerRowBuffer, rowsPerBank, |
| banksPerRank, ranksPerChannel, bytesPerCacheLine * |
| linesPerRowBuffer * rowsPerBank * banksPerRank * ranksPerChannel); |
| |
| string scheduler = memSchedPolicy == Enums::fcfs ? "FCFS" : "FR-FCFS"; |
| string address_mapping = addrMapping == Enums::openmap ? "OPENMAP" : |
| "CLOSEMAP"; |
| string page_policy = pageMgmt == Enums::open ? "OPEN" : "CLOSE"; |
| |
| DPRINTF(DRAM, |
| "Memory controller %s characteristics\n" \ |
| "Read buffer size %d\n" \ |
| "Write buffer size %d\n" \ |
| "Write buffer thresh %d\n" \ |
| "Scheduler %s\n" \ |
| "Address mapping %s\n" \ |
| "Page policy %s\n", |
| name(), readBufferSize, writeBufferSize, writeThreshold, |
| scheduler, address_mapping, page_policy); |
| |
| DPRINTF(DRAM, "Memory controller %s timing specs\n" \ |
| "tRCD %d ticks\n" \ |
| "tCL %d ticks\n" \ |
| "tRP %d ticks\n" \ |
| "tBURST %d ticks\n" \ |
| "tRFC %d ticks\n" \ |
| "tREFI %d ticks\n" \ |
| "tWTR %d ticks\n" \ |
| "tXAW (%d) %d ticks\n", |
| name(), tRCD, tCL, tRP, tBURST, tRFC, tREFI, tWTR, |
| activationLimit, tXAW); |
| } |
| |
| void |
| SimpleDRAM::printQs() const { |
| |
| list<DRAMPacket*>::const_iterator i; |
| |
| DPRINTF(DRAM, "===READ QUEUE===\n\n"); |
| for (i = readQueue.begin() ; i != readQueue.end() ; ++i) { |
| DPRINTF(DRAM, "Read %lu\n", (*i)->addr); |
| } |
| DPRINTF(DRAM, "\n===RESP QUEUE===\n\n"); |
| for (i = respQueue.begin() ; i != respQueue.end() ; ++i) { |
| DPRINTF(DRAM, "Response %lu\n", (*i)->addr); |
| } |
| DPRINTF(DRAM, "\n===WRITE QUEUE===\n\n"); |
| for (i = writeQueue.begin() ; i != writeQueue.end() ; ++i) { |
| DPRINTF(DRAM, "Write %lu\n", (*i)->addr); |
| } |
| } |
| |
| bool |
| SimpleDRAM::recvTimingReq(PacketPtr pkt) |
| { |
| /// @todo temporary hack to deal with memory corruption issues until |
| /// 4-phase transactions are complete |
| for (int x = 0; x < pendingDelete.size(); x++) |
| delete pendingDelete[x]; |
| pendingDelete.clear(); |
| |
| // This is where we enter from the outside world |
| DPRINTF(DRAM, "recvTimingReq: request %s addr %lld size %d\n", |
| pkt->cmdString(),pkt->getAddr(), pkt->getSize()); |
| |
| // simply drop inhibited packets for now |
| if (pkt->memInhibitAsserted()) { |
| DPRINTF(DRAM,"Inhibited packet -- Dropping it now\n"); |
| pendingDelete.push_back(pkt); |
| return true; |
| } |
| |
| if (pkt->getSize() == bytesPerCacheLine) |
| cpuReqs++; |
| |
| // Every million accesses, print the state of the queues |
| if (numReqs % 1000000 == 0) |
| printQs(); |
| |
| // Calc avg gap between requests |
| if (prevArrival != 0) { |
| totGap += curTick() - prevArrival; |
| } |
| prevArrival = curTick(); |
| |
| unsigned size = pkt->getSize(); |
| if (size > bytesPerCacheLine) |
| panic("Request size %d is greater than burst size %d", |
| size, bytesPerCacheLine); |
| |
| // check local buffers and do not accept if full |
| if (pkt->isRead()) { |
| assert(size != 0); |
| if (readQueueFull()) { |
| DPRINTF(DRAM, "Read queue full, not accepting\n"); |
| // remember that we have to retry this port |
| retryRdReq = true; |
| numRdRetry++; |
| return false; |
| } else { |
| readPktSize[ceilLog2(size)]++; |
| addToReadQueue(pkt); |
| readReqs++; |
| numReqs++; |
| } |
| } else if (pkt->isWrite()) { |
| assert(size != 0); |
| if (writeQueueFull()) { |
| DPRINTF(DRAM, "Write queue full, not accepting\n"); |
| // remember that we have to retry this port |
| retryWrReq = true; |
| numWrRetry++; |
| return false; |
| } else { |
| writePktSize[ceilLog2(size)]++; |
| addToWriteQueue(pkt); |
| writeReqs++; |
| numReqs++; |
| } |
| } else { |
| DPRINTF(DRAM,"Neither read nor write, ignore timing\n"); |
| neitherReadNorWrite++; |
| accessAndRespond(pkt); |
| } |
| |
| retryRdReq = false; |
| retryWrReq = false; |
| return true; |
| } |
| |
| void |
| SimpleDRAM::processRespondEvent() |
| { |
| DPRINTF(DRAM, |
| "processRespondEvent(): Some req has reached its readyTime\n"); |
| |
| PacketPtr pkt = respQueue.front()->pkt; |
| |
| // Actually responds to the requestor |
| bytesConsumedRd += pkt->getSize(); |
| bytesRead += bytesPerCacheLine; |
| accessAndRespond(pkt); |
| |
| delete respQueue.front(); |
| respQueue.pop_front(); |
| |
| // Update stats |
| avgRdQLen = readQueue.size() + respQueue.size(); |
| |
| if (!respQueue.empty()) { |
| assert(respQueue.front()->readyTime >= curTick()); |
| assert(!respondEvent.scheduled()); |
| schedule(respondEvent, respQueue.front()->readyTime); |
| } else { |
| // if there is nothing left in any queue, signal a drain |
| if (writeQueue.empty() && readQueue.empty() && |
| drainManager) { |
| drainManager->signalDrainDone(); |
| drainManager = NULL; |
| } |
| } |
| |
| // We have made a location in the queue available at this point, |
| // so if there is a read that was forced to wait, retry now |
| if (retryRdReq) { |
| retryRdReq = false; |
| port.sendRetry(); |
| } |
| } |
| |
| void |
| SimpleDRAM::chooseNextWrite() |
| { |
| // This method does the arbitration between write requests. The |
| // chosen packet is simply moved to the head of the write |
| // queue. The other methods know that this is the place to |
| // look. For example, with FCFS, this method does nothing |
| assert(!writeQueue.empty()); |
| |
| if (writeQueue.size() == 1) { |
| DPRINTF(DRAMWR, "Single write request, nothing to do\n"); |
| return; |
| } |
| |
| if (memSchedPolicy == Enums::fcfs) { |
| // Do nothing, since the correct request is already head |
| } else if (memSchedPolicy == Enums::frfcfs) { |
| list<DRAMPacket*>::iterator i = writeQueue.begin(); |
| bool foundRowHit = false; |
| while (!foundRowHit && i != writeQueue.end()) { |
| DRAMPacket* dram_pkt = *i; |
| const Bank& bank = dram_pkt->bank_ref; |
| if (bank.openRow == dram_pkt->row) { //FR part |
| DPRINTF(DRAMWR, "Write row buffer hit\n"); |
| writeQueue.erase(i); |
| writeQueue.push_front(dram_pkt); |
| foundRowHit = true; |
| } else { //FCFS part |
| ; |
| } |
| ++i; |
| } |
| } else |
| panic("No scheduling policy chosen\n"); |
| |
| DPRINTF(DRAMWR, "Selected next write request\n"); |
| } |
| |
| bool |
| SimpleDRAM::chooseNextRead() |
| { |
| // This method does the arbitration between read requests. The |
| // chosen packet is simply moved to the head of the queue. The |
| // other methods know that this is the place to look. For example, |
| // with FCFS, this method does nothing |
| if (readQueue.empty()) { |
| DPRINTF(DRAM, "No read request to select\n"); |
| return false; |
| } |
| |
| // If there is only one request then there is nothing left to do |
| if (readQueue.size() == 1) |
| return true; |
| |
| if (memSchedPolicy == Enums::fcfs) { |
| // Do nothing, since the request to serve is already the first |
| // one in the read queue |
| } else if (memSchedPolicy == Enums::frfcfs) { |
| for (list<DRAMPacket*>::iterator i = readQueue.begin(); |
| i != readQueue.end() ; ++i) { |
| DRAMPacket* dram_pkt = *i; |
| const Bank& bank = dram_pkt->bank_ref; |
| // Check if it is a row hit |
| if (bank.openRow == dram_pkt->row) { //FR part |
| DPRINTF(DRAM, "Row buffer hit\n"); |
| readQueue.erase(i); |
| readQueue.push_front(dram_pkt); |
| break; |
| } else { //FCFS part |
| ; |
| } |
| } |
| } else |
| panic("No scheduling policy chosen!\n"); |
| |
| DPRINTF(DRAM, "Selected next read request\n"); |
| return true; |
| } |
| |
| void |
| SimpleDRAM::accessAndRespond(PacketPtr pkt) |
| { |
| DPRINTF(DRAM, "Responding to Address %lld.. ",pkt->getAddr()); |
| |
| bool needsResponse = pkt->needsResponse(); |
| // do the actual memory access which also turns the packet into a |
| // response |
| access(pkt); |
| |
| // turn packet around to go back to requester if response expected |
| if (needsResponse) { |
| // access already turned the packet into a response |
| assert(pkt->isResponse()); |
| |
| // @todo someone should pay for this |
| pkt->busFirstWordDelay = pkt->busLastWordDelay = 0; |
| |
| // queue the packet in the response queue to be sent out the |
| // next tick |
| port.schedTimingResp(pkt, curTick() + 1); |
| } else { |
| // @todo the packet is going to be deleted, and the DRAMPacket |
| // is still having a pointer to it |
| pendingDelete.push_back(pkt); |
| } |
| |
| DPRINTF(DRAM, "Done\n"); |
| |
| return; |
| } |
| |
| pair<Tick, Tick> |
| SimpleDRAM::estimateLatency(DRAMPacket* dram_pkt, Tick inTime) |
| { |
| // If a request reaches a bank at tick 'inTime', how much time |
| // *after* that does it take to finish the request, depending |
| // on bank status and page open policy. Note that this method |
| // considers only the time taken for the actual read or write |
| // to complete, NOT any additional time thereafter for tRAS or |
| // tRP. |
| Tick accLat = 0; |
| Tick bankLat = 0; |
| rowHitFlag = false; |
| |
| const Bank& bank = dram_pkt->bank_ref; |
| if (pageMgmt == Enums::open) { // open-page policy |
| if (bank.openRow == dram_pkt->row) { |
| // When we have a row-buffer hit, |
| // we don't care about tRAS having expired or not, |
| // but do care about bank being free for access |
| rowHitFlag = true; |
| |
| if (bank.freeAt < inTime) { |
| // CAS latency only |
| accLat += tCL; |
| bankLat += tCL; |
| } else { |
| accLat += 0; |
| bankLat += 0; |
| } |
| |
| } else { |
| // Row-buffer miss, need to close existing row |
| // once tRAS has expired, then open the new one, |
| // then add cas latency. |
| Tick freeTime = std::max(bank.tRASDoneAt, bank.freeAt); |
| |
| if (freeTime > inTime) |
| accLat += freeTime - inTime; |
| |
| accLat += tRP + tRCD + tCL; |
| bankLat += tRP + tRCD + tCL; |
| } |
| } else if (pageMgmt == Enums::close) { |
| // With a close page policy, no notion of |
| // bank.tRASDoneAt |
| if (bank.freeAt > inTime) |
| accLat += bank.freeAt - inTime; |
| |
| // page already closed, simply open the row, and |
| // add cas latency |
| accLat += tRCD + tCL; |
| bankLat += tRCD + tCL; |
| } else |
| panic("No page management policy chosen\n"); |
| |
| DPRINTF(DRAM, "Returning < %lld, %lld > from estimateLatency()\n", |
| bankLat, accLat); |
| |
| return make_pair(bankLat, accLat); |
| } |
| |
| void |
| SimpleDRAM::processNextReqEvent() |
| { |
| scheduleNextReq(); |
| } |
| |
| void |
| SimpleDRAM::recordActivate(Tick act_tick) |
| { |
| assert(actTicks.size() == activationLimit); |
| |
| DPRINTF(DRAM, "Activate at tick %d\n", act_tick); |
| |
| // sanity check |
| if (actTicks.back() && (act_tick - actTicks.back()) < tXAW) { |
| panic("Got %d activates in window %d (%d - %d) which is smaller " |
| "than %d\n", activationLimit, act_tick - actTicks.back(), |
| act_tick, actTicks.back(), tXAW); |
| } |
| |
| // shift the times used for the book keeping, the last element |
| // (highest index) is the oldest one and hence the lowest value |
| actTicks.pop_back(); |
| |
| // record an new activation (in the future) |
| actTicks.push_front(act_tick); |
| |
| // cannot activate more than X times in time window tXAW, push the |
| // next one (the X + 1'st activate) to be tXAW away from the |
| // oldest in our window of X |
| if (actTicks.back() && (act_tick - actTicks.back()) < tXAW) { |
| DPRINTF(DRAM, "Enforcing tXAW with X = %d, next activate no earlier " |
| "than %d\n", activationLimit, actTicks.back() + tXAW); |
| for(int i = 0; i < ranksPerChannel; i++) |
| for(int j = 0; j < banksPerRank; j++) |
| // next activate must not happen before end of window |
| banks[i][j].freeAt = std::max(banks[i][j].freeAt, |
| actTicks.back() + tXAW); |
| } |
| } |
| |
| void |
| SimpleDRAM::doDRAMAccess(DRAMPacket* dram_pkt) |
| { |
| |
| DPRINTF(DRAM, "Timing access to addr %lld, rank/bank/row %d %d %d\n", |
| dram_pkt->addr, dram_pkt->rank, dram_pkt->bank, dram_pkt->row); |
| |
| // estimate the bank and access latency |
| pair<Tick, Tick> lat = estimateLatency(dram_pkt, curTick()); |
| Tick bankLat = lat.first; |
| Tick accessLat = lat.second; |
| |
| // This request was woken up at this time based on a prior call |
| // to estimateLatency(). However, between then and now, both the |
| // accessLatency and/or busBusyUntil may have changed. We need |
| // to correct for that. |
| |
| Tick addDelay = (curTick() + accessLat < busBusyUntil) ? |
| busBusyUntil - (curTick() + accessLat) : 0; |
| |
| Bank& bank = dram_pkt->bank_ref; |
| |
| // Update bank state |
| if (pageMgmt == Enums::open) { |
| bank.openRow = dram_pkt->row; |
| bank.freeAt = curTick() + addDelay + accessLat; |
| // If you activated a new row do to this access, the next access |
| // will have to respect tRAS for this bank. Assume tRAS ~= 3 * tRP. |
| // Also need to account for t_XAW |
| if (!rowHitFlag) { |
| bank.tRASDoneAt = bank.freeAt + tRP; |
| recordActivate(bank.freeAt - tCL - tRCD); //since this is open page, |
| //no tRP by default |
| } |
| } else if (pageMgmt == Enums::close) { // accounting for tRAS also |
| // assuming that tRAS ~= 3 * tRP, and tRC ~= 4 * tRP, as is common |
| // (refer Jacob/Ng/Wang and Micron datasheets) |
| bank.freeAt = curTick() + addDelay + accessLat + tRP + tRP; |
| recordActivate(bank.freeAt - tRP - tRP - tCL - tRCD); //essentially (freeAt - tRC) |
| DPRINTF(DRAM,"doDRAMAccess::bank.freeAt is %lld\n",bank.freeAt); |
| } else |
| panic("No page management policy chosen\n"); |
| |
| // Update request parameters |
| dram_pkt->readyTime = curTick() + addDelay + accessLat + tBURST; |
| |
| |
| DPRINTF(DRAM, "Req %lld: curtick is %lld accessLat is %d " \ |
| "readytime is %lld busbusyuntil is %lld. " \ |
| "Scheduling at readyTime\n", dram_pkt->addr, |
| curTick(), accessLat, dram_pkt->readyTime, busBusyUntil); |
| |
| // Make sure requests are not overlapping on the databus |
| assert (dram_pkt->readyTime - busBusyUntil >= tBURST); |
| |
| // Update bus state |
| busBusyUntil = dram_pkt->readyTime; |
| |
| DPRINTF(DRAM,"Access time is %lld\n", |
| dram_pkt->readyTime - dram_pkt->entryTime); |
| |
| // Update stats |
| totMemAccLat += dram_pkt->readyTime - dram_pkt->entryTime; |
| totBankLat += bankLat; |
| totBusLat += tBURST; |
| totQLat += dram_pkt->readyTime - dram_pkt->entryTime - bankLat - tBURST; |
| |
| if (rowHitFlag) |
| readRowHits++; |
| |
| // At this point we're done dealing with the request |
| // It will be moved to a separate response queue with a |
| // correct readyTime, and eventually be sent back at that |
| //time |
| moveToRespQ(); |
| |
| // The absolute soonest you have to start thinking about the |
| // next request is the longest access time that can occur before |
| // busBusyUntil. Assuming you need to meet tRAS, then precharge, |
| // open a new row, and access, it is ~4*tRCD. |
| |
| |
| Tick newTime = (busBusyUntil > 4 * tRCD) ? |
| std::max(busBusyUntil - 4 * tRCD, curTick()) : |
| curTick(); |
| |
| if (!nextReqEvent.scheduled() && !stopReads){ |
| schedule(nextReqEvent, newTime); |
| } else { |
| if (newTime < nextReqEvent.when()) |
| reschedule(nextReqEvent, newTime); |
| } |
| |
| |
| } |
| |
| void |
| SimpleDRAM::moveToRespQ() |
| { |
| // Remove from read queue |
| DRAMPacket* dram_pkt = readQueue.front(); |
| readQueue.pop_front(); |
| |
| // Insert into response queue sorted by readyTime |
| // It will be sent back to the requestor at its |
| // readyTime |
| if (respQueue.empty()) { |
| respQueue.push_front(dram_pkt); |
| assert(!respondEvent.scheduled()); |
| assert(dram_pkt->readyTime >= curTick()); |
| schedule(respondEvent, dram_pkt->readyTime); |
| } else { |
| bool done = false; |
| list<DRAMPacket*>::iterator i = respQueue.begin(); |
| while (!done && i != respQueue.end()) { |
| if ((*i)->readyTime > dram_pkt->readyTime) { |
| respQueue.insert(i, dram_pkt); |
| done = true; |
| } |
| ++i; |
| } |
| |
| if (!done) |
| respQueue.push_back(dram_pkt); |
| |
| assert(respondEvent.scheduled()); |
| |
| if (respQueue.front()->readyTime < respondEvent.when()) { |
| assert(respQueue.front()->readyTime >= curTick()); |
| reschedule(respondEvent, respQueue.front()->readyTime); |
| } |
| } |
| } |
| |
| void |
| SimpleDRAM::scheduleNextReq() |
| { |
| DPRINTF(DRAM, "Reached scheduleNextReq()\n"); |
| |
| // Figure out which read request goes next, and move it to the |
| // front of the read queue |
| if (!chooseNextRead()) { |
| // In the case there is no read request to go next, see if we |
| // are asked to drain, and if so trigger writes, this also |
| // ensures that if we hit the write limit we will do this |
| // multiple times until we are completely drained |
| if (drainManager && !writeQueue.empty() && !writeEvent.scheduled()) |
| triggerWrites(); |
| } else { |
| doDRAMAccess(readQueue.front()); |
| } |
| } |
| |
| Tick |
| SimpleDRAM::maxBankFreeAt() const |
| { |
| Tick banksFree = 0; |
| |
| for(int i = 0; i < ranksPerChannel; i++) |
| for(int j = 0; j < banksPerRank; j++) |
| banksFree = std::max(banks[i][j].freeAt, banksFree); |
| |
| return banksFree; |
| } |
| |
| void |
| SimpleDRAM::processRefreshEvent() |
| { |
| DPRINTF(DRAM, "Refreshing at tick %ld\n", curTick()); |
| |
| Tick banksFree = std::max(curTick(), maxBankFreeAt()) + tRFC; |
| |
| for(int i = 0; i < ranksPerChannel; i++) |
| for(int j = 0; j < banksPerRank; j++) |
| banks[i][j].freeAt = banksFree; |
| |
| schedule(refreshEvent, curTick() + tREFI); |
| } |
| |
| void |
| SimpleDRAM::regStats() |
| { |
| using namespace Stats; |
| |
| AbstractMemory::regStats(); |
| |
| readReqs |
| .name(name() + ".readReqs") |
| .desc("Total number of read requests seen"); |
| |
| writeReqs |
| .name(name() + ".writeReqs") |
| .desc("Total number of write requests seen"); |
| |
| servicedByWrQ |
| .name(name() + ".servicedByWrQ") |
| .desc("Number of read reqs serviced by write Q"); |
| |
| cpuReqs |
| .name(name() + ".cpureqs") |
| .desc("Reqs generatd by CPU via cache - shady"); |
| |
| neitherReadNorWrite |
| .name(name() + ".neitherReadNorWrite") |
| .desc("Reqs where no action is needed"); |
| |
| perBankRdReqs |
| .init(banksPerRank * ranksPerChannel) |
| .name(name() + ".perBankRdReqs") |
| .desc("Track reads on a per bank basis"); |
| |
| perBankWrReqs |
| .init(banksPerRank * ranksPerChannel) |
| .name(name() + ".perBankWrReqs") |
| .desc("Track writes on a per bank basis"); |
| |
| avgRdQLen |
| .name(name() + ".avgRdQLen") |
| .desc("Average read queue length over time") |
| .precision(2); |
| |
| avgWrQLen |
| .name(name() + ".avgWrQLen") |
| .desc("Average write queue length over time") |
| .precision(2); |
| |
| totQLat |
| .name(name() + ".totQLat") |
| .desc("Total cycles spent in queuing delays"); |
| |
| totBankLat |
| .name(name() + ".totBankLat") |
| .desc("Total cycles spent in bank access"); |
| |
| totBusLat |
| .name(name() + ".totBusLat") |
| .desc("Total cycles spent in databus access"); |
| |
| totMemAccLat |
| .name(name() + ".totMemAccLat") |
| .desc("Sum of mem lat for all requests"); |
| |
| avgQLat |
| .name(name() + ".avgQLat") |
| .desc("Average queueing delay per request") |
| .precision(2); |
| |
| avgQLat = totQLat / (readReqs - servicedByWrQ); |
| |
| avgBankLat |
| .name(name() + ".avgBankLat") |
| .desc("Average bank access latency per request") |
| .precision(2); |
| |
| avgBankLat = totBankLat / (readReqs - servicedByWrQ); |
| |
| avgBusLat |
| .name(name() + ".avgBusLat") |
| .desc("Average bus latency per request") |
| .precision(2); |
| |
| avgBusLat = totBusLat / (readReqs - servicedByWrQ); |
| |
| avgMemAccLat |
| .name(name() + ".avgMemAccLat") |
| .desc("Average memory access latency") |
| .precision(2); |
| |
| avgMemAccLat = totMemAccLat / (readReqs - servicedByWrQ); |
| |
| numRdRetry |
| .name(name() + ".numRdRetry") |
| .desc("Number of times rd buffer was full causing retry"); |
| |
| numWrRetry |
| .name(name() + ".numWrRetry") |
| .desc("Number of times wr buffer was full causing retry"); |
| |
| readRowHits |
| .name(name() + ".readRowHits") |
| .desc("Number of row buffer hits during reads"); |
| |
| writeRowHits |
| .name(name() + ".writeRowHits") |
| .desc("Number of row buffer hits during writes"); |
| |
| readRowHitRate |
| .name(name() + ".readRowHitRate") |
| .desc("Row buffer hit rate for reads") |
| .precision(2); |
| |
| readRowHitRate = (readRowHits / (readReqs - servicedByWrQ)) * 100; |
| |
| writeRowHitRate |
| .name(name() + ".writeRowHitRate") |
| .desc("Row buffer hit rate for writes") |
| .precision(2); |
| |
| writeRowHitRate = (writeRowHits / writeReqs) * 100; |
| |
| readPktSize |
| .init(ceilLog2(bytesPerCacheLine) + 1) |
| .name(name() + ".readPktSize") |
| .desc("Categorize read packet sizes"); |
| |
| writePktSize |
| .init(ceilLog2(bytesPerCacheLine) + 1) |
| .name(name() + ".writePktSize") |
| .desc("Categorize write packet sizes"); |
| |
| rdQLenPdf |
| .init(readBufferSize) |
| .name(name() + ".rdQLenPdf") |
| .desc("What read queue length does an incoming req see"); |
| |
| wrQLenPdf |
| .init(writeBufferSize) |
| .name(name() + ".wrQLenPdf") |
| .desc("What write queue length does an incoming req see"); |
| |
| |
| bytesRead |
| .name(name() + ".bytesRead") |
| .desc("Total number of bytes read from memory"); |
| |
| bytesWritten |
| .name(name() + ".bytesWritten") |
| .desc("Total number of bytes written to memory"); |
| |
| bytesConsumedRd |
| .name(name() + ".bytesConsumedRd") |
| .desc("bytesRead derated as per pkt->getSize()"); |
| |
| bytesConsumedWr |
| .name(name() + ".bytesConsumedWr") |
| .desc("bytesWritten derated as per pkt->getSize()"); |
| |
| avgRdBW |
| .name(name() + ".avgRdBW") |
| .desc("Average achieved read bandwidth in MB/s") |
| .precision(2); |
| |
| avgRdBW = (bytesRead / 1000000) / simSeconds; |
| |
| avgWrBW |
| .name(name() + ".avgWrBW") |
| .desc("Average achieved write bandwidth in MB/s") |
| .precision(2); |
| |
| avgWrBW = (bytesWritten / 1000000) / simSeconds; |
| |
| avgConsumedRdBW |
| .name(name() + ".avgConsumedRdBW") |
| .desc("Average consumed read bandwidth in MB/s") |
| .precision(2); |
| |
| avgConsumedRdBW = (bytesConsumedRd / 1000000) / simSeconds; |
| |
| avgConsumedWrBW |
| .name(name() + ".avgConsumedWrBW") |
| .desc("Average consumed write bandwidth in MB/s") |
| .precision(2); |
| |
| avgConsumedWrBW = (bytesConsumedWr / 1000000) / simSeconds; |
| |
| peakBW |
| .name(name() + ".peakBW") |
| .desc("Theoretical peak bandwidth in MB/s") |
| .precision(2); |
| |
| peakBW = (SimClock::Frequency / tBURST) * bytesPerCacheLine / 1000000; |
| |
| busUtil |
| .name(name() + ".busUtil") |
| .desc("Data bus utilization in percentage") |
| .precision(2); |
| |
| busUtil = (avgRdBW + avgWrBW) / peakBW * 100; |
| |
| totGap |
| .name(name() + ".totGap") |
| .desc("Total gap between requests"); |
| |
| avgGap |
| .name(name() + ".avgGap") |
| .desc("Average gap between requests") |
| .precision(2); |
| |
| avgGap = totGap / (readReqs + writeReqs); |
| } |
| |
| void |
| SimpleDRAM::recvFunctional(PacketPtr pkt) |
| { |
| // rely on the abstract memory |
| functionalAccess(pkt); |
| } |
| |
| BaseSlavePort& |
| SimpleDRAM::getSlavePort(const string &if_name, PortID idx) |
| { |
| if (if_name != "port") { |
| return MemObject::getSlavePort(if_name, idx); |
| } else { |
| return port; |
| } |
| } |
| |
| unsigned int |
| SimpleDRAM::drain(DrainManager *dm) |
| { |
| unsigned int count = port.drain(dm); |
| |
| // if there is anything in any of our internal queues, keep track |
| // of that as well |
| if (!(writeQueue.empty() && readQueue.empty() && |
| respQueue.empty())) { |
| DPRINTF(Drain, "DRAM controller not drained, write: %d, read: %d," |
| " resp: %d\n", writeQueue.size(), readQueue.size(), |
| respQueue.size()); |
| ++count; |
| drainManager = dm; |
| // the only part that is not drained automatically over time |
| // is the write queue, thus trigger writes if there are any |
| // waiting and no reads waiting, otherwise wait until the |
| // reads are done |
| if (readQueue.empty() && !writeQueue.empty() && |
| !writeEvent.scheduled()) |
| triggerWrites(); |
| } |
| |
| if (count) |
| setDrainState(Drainable::Draining); |
| else |
| setDrainState(Drainable::Drained); |
| return count; |
| } |
| |
| SimpleDRAM::MemoryPort::MemoryPort(const std::string& name, SimpleDRAM& _memory) |
| : QueuedSlavePort(name, &_memory, queue), queue(_memory, *this), |
| memory(_memory) |
| { } |
| |
| AddrRangeList |
| SimpleDRAM::MemoryPort::getAddrRanges() const |
| { |
| AddrRangeList ranges; |
| ranges.push_back(memory.getAddrRange()); |
| return ranges; |
| } |
| |
| void |
| SimpleDRAM::MemoryPort::recvFunctional(PacketPtr pkt) |
| { |
| pkt->pushLabel(memory.name()); |
| |
| if (!queue.checkFunctional(pkt)) { |
| // Default implementation of SimpleTimingPort::recvFunctional() |
| // calls recvAtomic() and throws away the latency; we can save a |
| // little here by just not calculating the latency. |
| memory.recvFunctional(pkt); |
| } |
| |
| pkt->popLabel(); |
| } |
| |
| Tick |
| SimpleDRAM::MemoryPort::recvAtomic(PacketPtr pkt) |
| { |
| return memory.recvAtomic(pkt); |
| } |
| |
| bool |
| SimpleDRAM::MemoryPort::recvTimingReq(PacketPtr pkt) |
| { |
| // pass it to the memory controller |
| return memory.recvTimingReq(pkt); |
| } |
| |
| SimpleDRAM* |
| SimpleDRAMParams::create() |
| { |
| return new SimpleDRAM(this); |
| } |