| /* |
| * Copyright (c) 2010-2020 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. |
| * |
| * Copyright (c) 2013 Amin Farmahini-Farahani |
| * All rights reserved. |
| * |
| * 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. |
| */ |
| |
| #include "mem/nvm_interface.hh" |
| |
| #include "base/bitfield.hh" |
| #include "base/cprintf.hh" |
| #include "base/trace.hh" |
| #include "debug/NVM.hh" |
| #include "sim/system.hh" |
| |
| namespace gem5 |
| { |
| |
| namespace memory |
| { |
| |
| NVMInterface::NVMInterface(const NVMInterfaceParams &_p) |
| : MemInterface(_p), |
| maxPendingWrites(_p.max_pending_writes), |
| maxPendingReads(_p.max_pending_reads), |
| twoCycleRdWr(_p.two_cycle_rdwr), |
| tREAD(_p.tREAD), tWRITE(_p.tWRITE), tSEND(_p.tSEND), |
| stats(*this), |
| writeRespondEvent([this]{ processWriteRespondEvent(); }, name()), |
| readReadyEvent([this]{ processReadReadyEvent(); }, name()), |
| nextReadAt(0), numPendingReads(0), numReadDataReady(0), |
| numReadsToIssue(0) |
| { |
| DPRINTF(NVM, "Setting up NVM Interface\n"); |
| |
| fatal_if(!isPowerOf2(burstSize), "NVM burst size %d is not allowed, " |
| "must be a power of two\n", burstSize); |
| |
| // sanity check the ranks since we rely on bit slicing for the |
| // address decoding |
| fatal_if(!isPowerOf2(ranksPerChannel), "NVM rank count of %d is " |
| "not allowed, must be a power of two\n", ranksPerChannel); |
| |
| for (int i =0; i < ranksPerChannel; i++) { |
| // Add NVM ranks to the system |
| DPRINTF(NVM, "Creating NVM rank %d \n", i); |
| Rank* rank = new Rank(_p, i, *this); |
| ranks.push_back(rank); |
| } |
| |
| uint64_t capacity = 1ULL << ceilLog2(AbstractMemory::size()); |
| |
| DPRINTF(NVM, "NVM capacity %lld (%lld) bytes\n", capacity, |
| AbstractMemory::size()); |
| |
| rowsPerBank = capacity / (rowBufferSize * |
| banksPerRank * ranksPerChannel); |
| } |
| |
| NVMInterface::Rank::Rank(const NVMInterfaceParams &_p, |
| int _rank, NVMInterface& _nvm) |
| : EventManager(&_nvm), rank(_rank), banks(_p.banks_per_rank) |
| { |
| for (int b = 0; b < _p.banks_per_rank; b++) { |
| banks[b].bank = b; |
| // No bank groups; simply assign to bank number |
| banks[b].bankgr = b; |
| } |
| } |
| |
| void |
| NVMInterface::init() |
| { |
| AbstractMemory::init(); |
| } |
| |
| void NVMInterface::setupRank(const uint8_t rank, const bool is_read) |
| { |
| if (is_read) { |
| // increment count to trigger read and track number of reads in Q |
| numReadsToIssue++; |
| } else { |
| // increment count to track number of writes in Q |
| numWritesQueued++; |
| } |
| } |
| |
| MemPacket* |
| NVMInterface::decodePacket(const PacketPtr pkt, Addr pkt_addr, |
| unsigned size, bool is_read, uint8_t pseudo_channel) |
| { |
| // decode the address based on the address mapping scheme, with |
| // Ro, Ra, Co, Ba and Ch denoting row, rank, column, bank and |
| // channel, respectively |
| uint8_t rank; |
| uint8_t bank; |
| // use a 64-bit unsigned during the computations as the row is |
| // always the top bits, and check before creating the packet |
| uint64_t row; |
| |
| // Get packed address, starting at 0 |
| Addr addr = getCtrlAddr(pkt_addr); |
| |
| // truncate the address to a memory burst, which makes it unique to |
| // a specific buffer, row, bank, rank and channel |
| addr = addr / burstSize; |
| |
| // we have removed the lowest order address bits that denote the |
| // position within the column |
| if (addrMapping == enums::RoRaBaChCo || addrMapping == enums::RoRaBaCoCh) { |
| // the lowest order bits denote the column to ensure that |
| // sequential cache lines occupy the same row |
| addr = addr / burstsPerRowBuffer; |
| |
| // after the channel bits, 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, no need to remove them from addr |
| row = addr % rowsPerBank; |
| } else if (addrMapping == enums::RoCoRaBaCh) { |
| // with emerging technologies, could have small page size with |
| // interleaving granularity greater than row buffer |
| if (burstsPerStripe > burstsPerRowBuffer) { |
| // remove column bits which are a subset of burstsPerStripe |
| addr = addr / burstsPerRowBuffer; |
| } else { |
| // remove lower column bits below channel bits |
| addr = addr / burstsPerStripe; |
| } |
| |
| // 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 higher-order column bites |
| if (burstsPerStripe < burstsPerRowBuffer) { |
| addr = addr / (burstsPerRowBuffer / burstsPerStripe); |
| } |
| |
| // lastly, get the row bits, no need to remove them from addr |
| row = addr % rowsPerBank; |
| } else |
| panic("Unknown address mapping policy chosen!"); |
| |
| assert(rank < ranksPerChannel); |
| assert(bank < banksPerRank); |
| assert(row < rowsPerBank); |
| assert(row < Bank::NO_ROW); |
| |
| DPRINTF(NVM, "Address: %#x Rank %d Bank %d Row %d\n", |
| pkt_addr, rank, bank, row); |
| |
| // create the corresponding memory packet with the entry time and |
| // ready time set to the current tick, the latter will be updated |
| // later |
| uint16_t bank_id = banksPerRank * rank + bank; |
| |
| return new MemPacket(pkt, is_read, false, pseudo_channel, rank, bank, row, |
| bank_id, pkt_addr, size); |
| } |
| |
| std::pair<MemPacketQueue::iterator, Tick> |
| NVMInterface::chooseNextFRFCFS(MemPacketQueue& queue, Tick min_col_at) const |
| { |
| // remember if we found a hit, but one that cannit issue seamlessly |
| bool found_prepped_pkt = false; |
| |
| auto selected_pkt_it = queue.end(); |
| Tick selected_col_at = MaxTick; |
| |
| for (auto i = queue.begin(); i != queue.end() ; ++i) { |
| MemPacket* pkt = *i; |
| |
| // select optimal NVM packet in Q |
| if (!pkt->isDram()) { |
| const Bank& bank = ranks[pkt->rank]->banks[pkt->bank]; |
| const Tick col_allowed_at = pkt->isRead() ? bank.rdAllowedAt : |
| bank.wrAllowedAt; |
| |
| // check if rank is not doing a refresh and thus is available, |
| // if not, jump to the next packet |
| if (burstReady(pkt)) { |
| DPRINTF(NVM, "%s bank %d - Rank %d available\n", __func__, |
| pkt->bank, pkt->rank); |
| |
| // no additional rank-to-rank or media delays |
| if (col_allowed_at <= min_col_at) { |
| // FCFS within entries that can issue without |
| // additional delay, such as same rank accesses |
| // or media delay requirements |
| selected_pkt_it = i; |
| selected_col_at = col_allowed_at; |
| // no need to look through the remaining queue entries |
| DPRINTF(NVM, "%s Seamless buffer hit\n", __func__); |
| break; |
| } else if (!found_prepped_pkt) { |
| // packet is to prepped region but cannnot issue |
| // seamlessly; remember this one and continue |
| selected_pkt_it = i; |
| selected_col_at = col_allowed_at; |
| DPRINTF(NVM, "%s Prepped packet found \n", __func__); |
| found_prepped_pkt = true; |
| } |
| } else { |
| DPRINTF(NVM, "%s bank %d - Rank %d not available\n", __func__, |
| pkt->bank, pkt->rank); |
| } |
| } |
| } |
| |
| if (selected_pkt_it == queue.end()) { |
| DPRINTF(NVM, "%s no available NVM ranks found\n", __func__); |
| } |
| |
| return std::make_pair(selected_pkt_it, selected_col_at); |
| } |
| |
| void |
| NVMInterface::chooseRead(MemPacketQueue& queue) |
| { |
| Tick cmd_at = std::max(curTick(), nextReadAt); |
| |
| // This method does the arbitration between non-deterministic read |
| // requests to NVM. The chosen packet is not removed from the queue |
| // at this time. Removal from the queue will occur when the data is |
| // ready and a separate SEND command is issued to retrieve it via the |
| // chooseNext function in the top-level controller. |
| assert(!queue.empty()); |
| |
| assert(numReadsToIssue > 0); |
| numReadsToIssue--; |
| // For simplicity, issue non-deterministic reads in order (fcfs) |
| for (auto i = queue.begin(); i != queue.end() ; ++i) { |
| MemPacket* pkt = *i; |
| |
| // Find 1st NVM read packet that hasn't issued read command |
| if (pkt->readyTime == MaxTick && !pkt->isDram() && pkt->isRead()) { |
| // get the bank |
| Bank& bank_ref = ranks[pkt->rank]->banks[pkt->bank]; |
| |
| // issueing a read, inc counter and verify we haven't overrun |
| numPendingReads++; |
| assert(numPendingReads <= maxPendingReads); |
| |
| // increment the bytes accessed and the accesses per row |
| bank_ref.bytesAccessed += burstSize; |
| |
| // Verify command bandiwth to issue |
| // Host can issue read immediately uith buffering closer |
| // to the NVM. The actual execution at the NVM may be delayed |
| // due to busy resources |
| if (twoCycleRdWr) { |
| cmd_at = ctrl->verifyMultiCmd(cmd_at, |
| maxCommandsPerWindow, tCK); |
| } else { |
| cmd_at = ctrl->verifySingleCmd(cmd_at, |
| maxCommandsPerWindow, false); |
| } |
| |
| // Update delay to next read |
| // Ensures single read command issued per cycle |
| nextReadAt = cmd_at + tCK; |
| |
| // If accessing a new location in this bank, update timing |
| // and stats |
| if (bank_ref.openRow != pkt->row) { |
| // update the open bank, re-using row field |
| bank_ref.openRow = pkt->row; |
| |
| // sample the bytes accessed to a buffer in this bank |
| // here when we are re-buffering the data |
| stats.bytesPerBank.sample(bank_ref.bytesAccessed); |
| // start counting anew |
| bank_ref.bytesAccessed = 0; |
| |
| // holdoff next command to this bank until the read completes |
| // and the data has been successfully buffered |
| // can pipeline accesses to the same bank, sending them |
| // across the interface B2B, but will incur full access |
| // delay between data ready responses to different buffers |
| // in a bank |
| bank_ref.actAllowedAt = std::max(cmd_at, |
| bank_ref.actAllowedAt) + tREAD; |
| } |
| // update per packet readyTime to holdoff burst read operation |
| // overloading readyTime, which will be updated again when the |
| // burst is issued |
| pkt->readyTime = std::max(cmd_at, bank_ref.actAllowedAt); |
| |
| DPRINTF(NVM, "Issuing NVM Read to bank %d at tick %d. " |
| "Data ready at %d\n", |
| bank_ref.bank, cmd_at, pkt->readyTime); |
| |
| // Insert into read ready queue. It will be handled after |
| // the media delay has been met |
| if (readReadyQueue.empty()) { |
| assert(!readReadyEvent.scheduled()); |
| schedule(readReadyEvent, pkt->readyTime); |
| } else if (readReadyEvent.when() > pkt->readyTime) { |
| // move it sooner in time, to the first read with data |
| reschedule(readReadyEvent, pkt->readyTime); |
| } else { |
| assert(readReadyEvent.scheduled()); |
| } |
| readReadyQueue.push_back(pkt->readyTime); |
| |
| // found an NVM read to issue - break out |
| break; |
| } |
| } |
| } |
| |
| void |
| NVMInterface::processReadReadyEvent() |
| { |
| // signal that there is read data ready to be transmitted |
| numReadDataReady++; |
| |
| DPRINTF(NVM, |
| "processReadReadyEvent(): Data for an NVM read is ready. " |
| "numReadDataReady is %d\t numPendingReads is %d\n", |
| numReadDataReady, numPendingReads); |
| |
| // Find lowest ready time and verify it is equal to curTick |
| // also find the next lowest to schedule next event |
| // Done with this response, erase entry |
| auto ready_it = readReadyQueue.begin(); |
| Tick next_ready_at = MaxTick; |
| for (auto i = readReadyQueue.begin(); i != readReadyQueue.end() ; ++i) { |
| if (*ready_it > *i) { |
| next_ready_at = *ready_it; |
| ready_it = i; |
| } else if ((next_ready_at > *i) && (i != ready_it)) { |
| next_ready_at = *i; |
| } |
| } |
| |
| // Verify we found the time of this event and remove it |
| assert(*ready_it == curTick()); |
| readReadyQueue.erase(ready_it); |
| |
| if (!readReadyQueue.empty()) { |
| assert(readReadyQueue.front() >= curTick()); |
| assert(!readReadyEvent.scheduled()); |
| schedule(readReadyEvent, next_ready_at); |
| } |
| |
| // It is possible that a new command kicks things back into |
| // action before reaching this point but need to ensure that we |
| // continue to process new commands as read data becomes ready |
| // This will also trigger a drain if needed |
| if (!ctrl->requestEventScheduled()) { |
| DPRINTF(NVM, "Restart controller scheduler immediately\n"); |
| ctrl->restartScheduler(curTick()); |
| } |
| } |
| |
| bool |
| NVMInterface::burstReady(MemPacket* pkt) const { |
| bool read_rdy = pkt->isRead() && (ctrl->inReadBusState(true)) && |
| (pkt->readyTime <= curTick()) && (numReadDataReady > 0); |
| bool write_rdy = !pkt->isRead() && !ctrl->inReadBusState(true) && |
| !writeRespQueueFull(); |
| return (read_rdy || write_rdy); |
| } |
| |
| std::pair<Tick, Tick> |
| NVMInterface::doBurstAccess(MemPacket* pkt, Tick next_burst_at, |
| const std::vector<MemPacketQueue>& queue) |
| { |
| DPRINTF(NVM, "NVM Timing access to addr %#x, rank/bank/row %d %d %d\n", |
| pkt->addr, pkt->rank, pkt->bank, pkt->row); |
| |
| // get the bank |
| Bank& bank_ref = ranks[pkt->rank]->banks[pkt->bank]; |
| |
| // respect any constraints on the command |
| const Tick bst_allowed_at = pkt->isRead() ? |
| bank_ref.rdAllowedAt : bank_ref.wrAllowedAt; |
| |
| // we need to wait until the bus is available before we can issue |
| // the command; need minimum of tBURST between commands |
| Tick cmd_at = std::max(bst_allowed_at, curTick()); |
| |
| // we need to wait until the bus is available before we can issue |
| // the command; need minimum of tBURST between commands |
| cmd_at = std::max(cmd_at, next_burst_at); |
| |
| // Verify there is command bandwidth to issue |
| // Read burst (send command) is a simple data access and only requires |
| // one command cycle |
| // Write command may require multiple cycles to enable larger address space |
| if (pkt->isRead() || !twoCycleRdWr) { |
| cmd_at = ctrl->verifySingleCmd(cmd_at, maxCommandsPerWindow, false); |
| } else { |
| cmd_at = ctrl->verifyMultiCmd(cmd_at, maxCommandsPerWindow, tCK); |
| } |
| // update the packet ready time to reflect when data will be transferred |
| // Use the same bus delays defined for NVM |
| pkt->readyTime = cmd_at + tSEND + tBURST; |
| |
| Tick dly_to_rd_cmd; |
| Tick dly_to_wr_cmd; |
| for (auto n : ranks) { |
| for (int i = 0; i < banksPerRank; i++) { |
| // base delay is a function of tBURST and bus turnaround |
| dly_to_rd_cmd = pkt->isRead() ? tBURST : writeToReadDelay(); |
| dly_to_wr_cmd = pkt->isRead() ? readToWriteDelay() : tBURST; |
| |
| if (pkt->rank != n->rank) { |
| // adjust timing for different ranks |
| // Need to account for rank-to-rank switching with tCS |
| dly_to_wr_cmd = rankToRankDelay(); |
| dly_to_rd_cmd = rankToRankDelay(); |
| } |
| n->banks[i].rdAllowedAt = std::max(cmd_at + dly_to_rd_cmd, |
| n->banks[i].rdAllowedAt); |
| |
| n->banks[i].wrAllowedAt = std::max(cmd_at + dly_to_wr_cmd, |
| n->banks[i].wrAllowedAt); |
| } |
| } |
| |
| DPRINTF(NVM, "NVM Access to %#x, ready at %lld.\n", |
| pkt->addr, pkt->readyTime); |
| |
| if (pkt->isRead()) { |
| // completed the read, decrement counters |
| assert(numPendingReads != 0); |
| assert(numReadDataReady != 0); |
| |
| numPendingReads--; |
| numReadDataReady--; |
| } else { |
| // Adjust number of NVM writes in Q |
| assert(numWritesQueued > 0); |
| numWritesQueued--; |
| |
| // increment the bytes accessed and the accesses per row |
| // only increment for writes as the reads are handled when |
| // the non-deterministic read is issued, before the data transfer |
| bank_ref.bytesAccessed += burstSize; |
| |
| // Commands will be issued serially when accessing the same bank |
| // Commands can issue in parallel to different banks |
| if ((bank_ref.bank == pkt->bank) && |
| (bank_ref.openRow != pkt->row)) { |
| // update the open buffer, re-using row field |
| bank_ref.openRow = pkt->row; |
| |
| // sample the bytes accessed to a buffer in this bank |
| // here when we are re-buffering the data |
| stats.bytesPerBank.sample(bank_ref.bytesAccessed); |
| // start counting anew |
| bank_ref.bytesAccessed = 0; |
| } |
| |
| // Determine when write will actually complete, assuming it is |
| // scheduled to push to NVM immediately |
| // update actAllowedAt to serialize next command completion that |
| // accesses this bank; must wait until this write completes |
| // Data accesses to the same buffer in this bank |
| // can issue immediately after actAllowedAt expires, without |
| // waiting additional delay of tWRITE. Can revisit this |
| // assumption/simplification in the future. |
| bank_ref.actAllowedAt = std::max(pkt->readyTime, |
| bank_ref.actAllowedAt) + tWRITE; |
| |
| // Need to track number of outstanding writes to |
| // ensure 'buffer' on media controller does not overflow |
| assert(!writeRespQueueFull()); |
| |
| // Insert into write done queue. It will be handled after |
| // the media delay has been met |
| if (writeRespQueueEmpty()) { |
| assert(!writeRespondEvent.scheduled()); |
| schedule(writeRespondEvent, bank_ref.actAllowedAt); |
| } else { |
| assert(writeRespondEvent.scheduled()); |
| } |
| writeRespQueue.push_back(bank_ref.actAllowedAt); |
| writeRespQueue.sort(); |
| if (writeRespondEvent.when() > bank_ref.actAllowedAt) { |
| DPRINTF(NVM, "Rescheduled respond event from %lld to %11d\n", |
| writeRespondEvent.when(), bank_ref.actAllowedAt); |
| DPRINTF(NVM, "Front of response queue is %11d\n", |
| writeRespQueue.front()); |
| reschedule(writeRespondEvent, bank_ref.actAllowedAt); |
| } |
| |
| } |
| |
| // Update the stats |
| if (pkt->isRead()) { |
| stats.readBursts++; |
| stats.bytesRead += burstSize; |
| stats.perBankRdBursts[pkt->bankId]++; |
| stats.pendingReads.sample(numPendingReads); |
| |
| // Update latency stats |
| stats.totMemAccLat += pkt->readyTime - pkt->entryTime; |
| stats.totBusLat += tBURST; |
| stats.totQLat += cmd_at - pkt->entryTime; |
| } else { |
| stats.writeBursts++; |
| stats.bytesWritten += burstSize; |
| stats.perBankWrBursts[pkt->bankId]++; |
| } |
| |
| return std::make_pair(cmd_at, cmd_at + tBURST); |
| } |
| |
| void |
| NVMInterface::processWriteRespondEvent() |
| { |
| DPRINTF(NVM, |
| "processWriteRespondEvent(): A NVM write reached its readyTime. " |
| "%d remaining pending NVM writes\n", writeRespQueue.size()); |
| |
| // Update stat to track histogram of pending writes |
| stats.pendingWrites.sample(writeRespQueue.size()); |
| |
| // Done with this response, pop entry |
| writeRespQueue.pop_front(); |
| |
| if (!writeRespQueue.empty()) { |
| assert(writeRespQueue.front() >= curTick()); |
| assert(!writeRespondEvent.scheduled()); |
| schedule(writeRespondEvent, writeRespQueue.front()); |
| } |
| |
| // It is possible that a new command kicks things back into |
| // action before reaching this point but need to ensure that we |
| // continue to process new commands as writes complete at the media and |
| // credits become available. This will also trigger a drain if needed |
| if (!ctrl->requestEventScheduled()) { |
| DPRINTF(NVM, "Restart controller scheduler immediately\n"); |
| ctrl->restartScheduler(curTick()); |
| } |
| } |
| |
| void |
| NVMInterface::addRankToRankDelay(Tick cmd_at) |
| { |
| // update timing for NVM ranks due to bursts issued |
| // to ranks for other media interfaces |
| for (auto n : ranks) { |
| for (int i = 0; i < banksPerRank; i++) { |
| // different rank by default |
| // Need to only account for rank-to-rank switching |
| n->banks[i].rdAllowedAt = std::max(cmd_at + rankToRankDelay(), |
| n->banks[i].rdAllowedAt); |
| n->banks[i].wrAllowedAt = std::max(cmd_at + rankToRankDelay(), |
| n->banks[i].wrAllowedAt); |
| } |
| } |
| } |
| |
| bool |
| NVMInterface::isBusy(bool read_queue_empty, bool all_writes_nvm) |
| { |
| DPRINTF(NVM,"isBusy: numReadDataReady = %d\n", numReadDataReady); |
| // Determine NVM is busy and cannot issue a burst |
| // A read burst cannot issue when data is not ready from the NVM |
| // Also check that we have reads queued to ensure we can change |
| // bus direction to service potential write commands. |
| // A write cannot issue once we've reached MAX pending writes |
| // Only assert busy for the write case when there are also |
| // no reads in Q and the write queue only contains NVM commands |
| // This allows the bus state to switch and service reads |
| return (ctrl->inReadBusState(true) ? |
| (numReadDataReady == 0) && !read_queue_empty : |
| writeRespQueueFull() && read_queue_empty && |
| all_writes_nvm); |
| } |
| |
| NVMInterface::NVMStats::NVMStats(NVMInterface &_nvm) |
| : statistics::Group(&_nvm), |
| nvm(_nvm), |
| |
| ADD_STAT(readBursts, statistics::units::Count::get(), |
| "Number of NVM read bursts"), |
| ADD_STAT(writeBursts, statistics::units::Count::get(), |
| "Number of NVM write bursts"), |
| |
| ADD_STAT(perBankRdBursts, statistics::units::Count::get(), |
| "Per bank write bursts"), |
| ADD_STAT(perBankWrBursts, statistics::units::Count::get(), |
| "Per bank write bursts"), |
| |
| ADD_STAT(totQLat, statistics::units::Tick::get(), |
| "Total ticks spent queuing"), |
| ADD_STAT(totBusLat, statistics::units::Tick::get(), |
| "Total ticks spent in databus transfers"), |
| ADD_STAT(totMemAccLat, statistics::units::Tick::get(), |
| "Total ticks spent from burst creation until serviced " |
| "by the NVM"), |
| ADD_STAT(avgQLat, statistics::units::Rate< |
| statistics::units::Tick, statistics::units::Count>::get(), |
| "Average queueing delay per NVM burst"), |
| ADD_STAT(avgBusLat, statistics::units::Rate< |
| statistics::units::Tick, statistics::units::Count>::get(), |
| "Average bus latency per NVM burst"), |
| ADD_STAT(avgMemAccLat, statistics::units::Rate< |
| statistics::units::Tick, statistics::units::Count>::get(), |
| "Average memory access latency per NVM burst"), |
| |
| ADD_STAT(avgRdBW, statistics::units::Rate< |
| statistics::units::Byte, statistics::units::Second>::get(), |
| "Average DRAM read bandwidth in MiBytes/s"), |
| ADD_STAT(avgWrBW, statistics::units::Rate< |
| statistics::units::Byte, statistics::units::Second>::get(), |
| "Average DRAM write bandwidth in MiBytes/s"), |
| ADD_STAT(peakBW, statistics::units::Rate< |
| statistics::units::Byte, statistics::units::Second>::get(), |
| "Theoretical peak bandwidth in MiByte/s"), |
| ADD_STAT(busUtil, statistics::units::Ratio::get(), |
| "NVM Data bus utilization in percentage"), |
| ADD_STAT(busUtilRead, statistics::units::Ratio::get(), |
| "NVM Data bus read utilization in percentage"), |
| ADD_STAT(busUtilWrite, statistics::units::Ratio::get(), |
| "NVM Data bus write utilization in percentage"), |
| |
| ADD_STAT(pendingReads, statistics::units::Count::get(), |
| "Reads issued to NVM for which data has not been transferred"), |
| ADD_STAT(pendingWrites, statistics::units::Count::get(), |
| "Number of outstanding writes to NVM"), |
| ADD_STAT(bytesPerBank, statistics::units::Byte::get(), |
| "Bytes read within a bank before loading new bank") |
| |
| { |
| } |
| |
| void |
| NVMInterface::NVMStats::regStats() |
| { |
| using namespace statistics; |
| |
| perBankRdBursts.init(nvm.ranksPerChannel == 0 ? 1 : |
| nvm.banksPerRank * nvm.ranksPerChannel); |
| |
| perBankWrBursts.init(nvm.ranksPerChannel == 0 ? 1 : |
| nvm.banksPerRank * nvm.ranksPerChannel); |
| |
| avgQLat.precision(2); |
| avgBusLat.precision(2); |
| avgMemAccLat.precision(2); |
| |
| avgRdBW.precision(2); |
| avgWrBW.precision(2); |
| peakBW.precision(2); |
| |
| busUtil.precision(2); |
| busUtilRead.precision(2); |
| busUtilWrite.precision(2); |
| |
| pendingReads |
| .init(nvm.maxPendingReads) |
| .flags(nozero); |
| |
| pendingWrites |
| .init(nvm.maxPendingWrites) |
| .flags(nozero); |
| |
| bytesPerBank |
| .init(nvm.rowBufferSize) |
| .flags(nozero); |
| |
| avgQLat = totQLat / readBursts; |
| avgBusLat = totBusLat / readBursts; |
| avgMemAccLat = totMemAccLat / readBursts; |
| |
| avgRdBW = (bytesRead / 1000000) / simSeconds; |
| avgWrBW = (bytesWritten / 1000000) / simSeconds; |
| peakBW = (sim_clock::Frequency / nvm.tBURST) * |
| nvm.burstSize / 1000000; |
| |
| busUtil = (avgRdBW + avgWrBW) / peakBW * 100; |
| busUtilRead = avgRdBW / peakBW * 100; |
| busUtilWrite = avgWrBW / peakBW * 100; |
| } |
| |
| } // namespace memory |
| } // namespace gem5 |