| /* |
| * Copyright (c) 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 |
| */ |
| |
| /** |
| * @file |
| * SimpleDRAM declaration |
| */ |
| |
| #ifndef __MEM_SIMPLE_DRAM_HH__ |
| #define __MEM_SIMPLE_DRAM_HH__ |
| |
| #include <deque> |
| |
| #include "base/statistics.hh" |
| #include "enums/AddrMap.hh" |
| #include "enums/MemSched.hh" |
| #include "enums/PageManage.hh" |
| #include "mem/abstract_mem.hh" |
| #include "mem/qport.hh" |
| #include "params/SimpleDRAM.hh" |
| #include "sim/eventq.hh" |
| |
| /** |
| * The simple DRAM is a basic single-channel memory controller aiming |
| * to mimic a high-level DRAM controller and the most important timing |
| * constraints associated with the DRAM. The focus is really on |
| * modelling the impact on the system rather than the DRAM itself, |
| * hence the focus is on the controller model and not on the |
| * memory. By adhering to the correct timing constraints, ultimately |
| * there is no need for a memory model in addition to the controller |
| * model. |
| * |
| * As a basic design principle, this controller is not cycle callable, |
| * but instead uses events to decide when new decisions can be made, |
| * when resources become available, when things are to be considered |
| * done, and when to send things back. Through these simple |
| * principles, we achieve a performant model that is not |
| * cycle-accurate, but enables us to evaluate the system impact of a |
| * wide range of memory technologies, and also collect statistics |
| * about the use of the memory. |
| */ |
| class SimpleDRAM : public AbstractMemory |
| { |
| |
| private: |
| |
| // For now, make use of a queued slave port to avoid dealing with |
| // flow control for the responses being sent back |
| class MemoryPort : public QueuedSlavePort |
| { |
| |
| SlavePacketQueue queue; |
| SimpleDRAM& memory; |
| |
| public: |
| |
| MemoryPort(const std::string& name, SimpleDRAM& _memory); |
| |
| protected: |
| |
| Tick recvAtomic(PacketPtr pkt); |
| |
| void recvFunctional(PacketPtr pkt); |
| |
| bool recvTimingReq(PacketPtr); |
| |
| virtual AddrRangeList getAddrRanges() const; |
| |
| }; |
| |
| /** |
| * Our incoming port, for a multi-ported controller add a crossbar |
| * in front of it |
| */ |
| MemoryPort port; |
| |
| /** |
| * Remember if we have to retry a request when available. |
| */ |
| bool retryRdReq; |
| bool retryWrReq; |
| |
| /** |
| * Remember that a row buffer hit occured |
| */ |
| bool rowHitFlag; |
| |
| /** |
| * Use this flag to shutoff reads, i.e. do not schedule any reads |
| * beyond those already done so that we can turn the bus around |
| * and do a few writes, or refresh, or whatever |
| */ |
| bool stopReads; |
| |
| /** List to keep track of activate ticks */ |
| std::deque<Tick> actTicks; |
| |
| /** |
| * A basic class to track the bank state indirectly via |
| * times "freeAt" and "tRASDoneAt" and what page is currently open |
| */ |
| class Bank |
| { |
| |
| public: |
| |
| static const uint32_t INVALID_ROW = -1; |
| |
| uint32_t openRow; |
| |
| Tick freeAt; |
| Tick tRASDoneAt; |
| |
| Bank() : openRow(INVALID_ROW), freeAt(0), tRASDoneAt(0) |
| { } |
| }; |
| |
| /** |
| * A DRAM packet stores packets along with the timestamp of when |
| * the packet entered the queue, and also the decoded address. |
| */ |
| class DRAMPacket { |
| |
| public: |
| |
| /** When did request enter the controller */ |
| const Tick entryTime; |
| |
| /** When will request leave the controller */ |
| Tick readyTime; |
| |
| /** This comes from the outside world */ |
| const PacketPtr pkt; |
| |
| /** Will be populated by address decoder */ |
| const uint8_t rank; |
| const uint16_t bank; |
| const uint16_t row; |
| const Addr addr; |
| Bank& bank_ref; |
| |
| DRAMPacket(PacketPtr _pkt, uint8_t _rank, |
| uint16_t _bank, uint16_t _row, Addr _addr, Bank& _bank_ref) |
| : entryTime(curTick()), readyTime(curTick()), |
| pkt(_pkt), rank(_rank), bank(_bank), row(_row), addr(_addr), |
| bank_ref(_bank_ref) |
| { } |
| |
| }; |
| |
| /** |
| * Bunch of things requires to setup "events" in gem5 |
| * When event "writeEvent" occurs for example, the method |
| * processWriteEvent is called; no parameters are allowed |
| * in these methods |
| */ |
| void processWriteEvent(); |
| EventWrapper<SimpleDRAM, &SimpleDRAM::processWriteEvent> writeEvent; |
| |
| void processRespondEvent(); |
| EventWrapper<SimpleDRAM, &SimpleDRAM::processRespondEvent> respondEvent; |
| |
| void processRefreshEvent(); |
| EventWrapper<SimpleDRAM, &SimpleDRAM::processRefreshEvent> refreshEvent; |
| |
| void processNextReqEvent(); |
| EventWrapper<SimpleDRAM,&SimpleDRAM::processNextReqEvent> nextReqEvent; |
| |
| |
| /** |
| * Check if the read queue has room for more entries |
| * |
| * @return true if read queue is full, false otherwise |
| */ |
| bool readQueueFull() const; |
| |
| /** |
| * Check if the write queue has room for more entries |
| * |
| * @return true if write queue is full, false otherwise |
| */ |
| bool writeQueueFull() const; |
| |
| /** |
| * When a new read comes in, first check if the write q has a |
| * pending request to the same address.\ If not, decode the |
| * address to populate rank/bank/row, create a "dram_pkt", and |
| * push it to the back of the read queue.\ If this is the only |
| * read request in the system, schedule an event to start |
| * servicing it. |
| * |
| * @param pkt The request packet from the outside world |
| */ |
| void addToReadQueue(PacketPtr pkt); |
| |
| /** |
| * Decode the incoming pkt, create a dram_pkt and push to the |
| * back of the write queue. \If the write q length is more than |
| * the threshold specified by the user, ie the queue is beginning |
| * to get full, stop reads, and start draining writes. |
| * |
| * @param pkt The request packet from the outside world |
| */ |
| void addToWriteQueue(PacketPtr pkt); |
| |
| /** |
| * Actually do the DRAM access - figure out the latency it |
| * will take to service the req based on bank state, channel state etc |
| * and then update those states to account for this request.\ Based |
| * on this, update the packet's "readyTime" and move it to the |
| * response q from where it will eventually go back to the outside |
| * world. |
| * |
| * @param pkt The DRAM packet created from the outside world pkt |
| */ |
| void doDRAMAccess(DRAMPacket* dram_pkt); |
| |
| /** |
| * Check when the channel is free to turnaround, add turnaround |
| * delay and schedule a whole bunch of writes. |
| */ |
| void triggerWrites(); |
| |
| /** |
| * When a packet reaches its "readyTime" in the response Q, |
| * use the "access()" method in AbstractMemory to actually |
| * create the response packet, and send it back to the outside |
| * world requestor. |
| * |
| * @param pkt The packet from the outside world |
| */ |
| void accessAndRespond(PacketPtr pkt); |
| |
| /** |
| * Address decoder to figure out physical mapping onto ranks, |
| * banks, and rows. |
| * |
| * @param pkt The packet from the outside world |
| * @return A DRAMPacket pointer with the decoded information |
| */ |
| DRAMPacket* decodeAddr(PacketPtr pkt); |
| |
| /** |
| * The memory schduler/arbiter - picks which read request needs to |
| * go next, based on the specified policy such as FCFS or FR-FCFS |
| * and moves it to the head of the read queue. |
| * |
| * @return True if a request was chosen and false if queue is empty |
| */ |
| bool chooseNextRead(); |
| |
| /** |
| * Calls chooseNextReq() to pick the right request, then calls |
| * doDRAMAccess on that request in order to actually service |
| * that request |
| */ |
| void scheduleNextReq(); |
| |
| /** |
| *Looks at the state of the banks, channels, row buffer hits etc |
| * to estimate how long a request will take to complete. |
| * |
| * @param dram_pkt The request for which we want to estimate latency |
| * @param inTime The tick at which you want to probe the memory |
| * |
| * @return A pair of ticks, one indicating how many ticks *after* |
| * inTime the request require, and the other indicating how |
| * much of that was just the bank access time, ignoring the |
| * ticks spent simply waiting for resources to become free |
| */ |
| std::pair<Tick, Tick> estimateLatency(DRAMPacket* dram_pkt, Tick inTime); |
| |
| /** |
| * Move the request at the head of the read queue to the response |
| * queue, sorting by readyTime.\ If it is the only packet in the |
| * response queue, schedule a respond event to send it back to the |
| * outside world |
| */ |
| void moveToRespQ(); |
| |
| /** |
| * Scheduling policy within the write queue |
| */ |
| void chooseNextWrite(); |
| |
| /** |
| * Looking at all banks, determine the moment in time when they |
| * are all free. |
| * |
| * @return The tick when all banks are free |
| */ |
| Tick maxBankFreeAt() const; |
| |
| |
| /** |
| * Keep track of when row activations happen, in order to enforce |
| * the maximum number of activations in the activation window. The |
| * method updates the time that the banks become available based |
| * on the current limits. |
| */ |
| void recordActivate(Tick act_tick); |
| |
| void printParams() const; |
| void printQs() const; |
| |
| /** |
| * The controller's main read and write queues |
| */ |
| std::list<DRAMPacket*> readQueue; |
| std::list<DRAMPacket*> writeQueue; |
| |
| /** |
| * Response queue where read packets wait after we're done working |
| * with them, but it's not time to send the response yet. The |
| * responses are stored seperately mostly to keep the code clean |
| * and help with events scheduling. For all logical purposes such |
| * as sizing the read queue, this and the main read queue need to |
| * be added together. |
| */ |
| std::list<DRAMPacket*> respQueue; |
| |
| /** |
| * If we need to drain, keep the drain manager around until we're |
| * done here. |
| */ |
| DrainManager *drainManager; |
| |
| /** |
| * Multi-dimensional vector of banks, first dimension is ranks, |
| * second is bank |
| */ |
| std::vector<std::vector<Bank> > banks; |
| |
| /** |
| * The following are basic design parameters of the memory |
| * controller, and are initialized based on parameter values. The |
| * bytesPerCacheLine is based on the neighbouring ports cache line |
| * size and thus determined outside the constructor. Similarly, |
| * the rowsPerBank is determined based on the capacity, number of |
| * ranks and banks, the cache line size, and the row buffer size. |
| */ |
| uint32_t bytesPerCacheLine; |
| const uint32_t linesPerRowBuffer; |
| const uint32_t ranksPerChannel; |
| const uint32_t banksPerRank; |
| const uint32_t channels; |
| uint32_t rowsPerBank; |
| const uint32_t readBufferSize; |
| const uint32_t writeBufferSize; |
| const double writeThresholdPerc; |
| uint32_t writeThreshold; |
| |
| /** |
| * Basic memory timing parameters initialized based on parameter |
| * values. |
| */ |
| const Tick tWTR; |
| const Tick tBURST; |
| const Tick tRCD; |
| const Tick tCL; |
| const Tick tRP; |
| const Tick tRFC; |
| const Tick tREFI; |
| const Tick tXAW; |
| const uint32_t activationLimit; |
| |
| /** |
| * Memory controller configuration initialized based on parameter |
| * values. |
| */ |
| Enums::MemSched memSchedPolicy; |
| Enums::AddrMap addrMapping; |
| Enums::PageManage pageMgmt; |
| |
| /** |
| * Till when has the main data bus been spoken for already? |
| */ |
| Tick busBusyUntil; |
| |
| Tick writeStartTime; |
| Tick prevArrival; |
| int numReqs; |
| |
| // All statistics that the model needs to capture |
| Stats::Scalar readReqs; |
| Stats::Scalar writeReqs; |
| Stats::Scalar cpuReqs; |
| Stats::Scalar bytesRead; |
| Stats::Scalar bytesWritten; |
| Stats::Scalar bytesConsumedRd; |
| Stats::Scalar bytesConsumedWr; |
| Stats::Scalar servicedByWrQ; |
| Stats::Scalar neitherReadNorWrite; |
| Stats::Vector perBankRdReqs; |
| Stats::Vector perBankWrReqs; |
| Stats::Scalar numRdRetry; |
| Stats::Scalar numWrRetry; |
| Stats::Scalar totGap; |
| Stats::Vector readPktSize; |
| Stats::Vector writePktSize; |
| Stats::Vector rdQLenPdf; |
| Stats::Vector wrQLenPdf; |
| |
| |
| // Latencies summed over all requests |
| Stats::Scalar totQLat; |
| Stats::Scalar totMemAccLat; |
| Stats::Scalar totBusLat; |
| Stats::Scalar totBankLat; |
| |
| // Average latencies per request |
| Stats::Formula avgQLat; |
| Stats::Formula avgBankLat; |
| Stats::Formula avgBusLat; |
| Stats::Formula avgMemAccLat; |
| |
| // Average bandwidth |
| Stats::Formula avgRdBW; |
| Stats::Formula avgWrBW; |
| Stats::Formula avgConsumedRdBW; |
| Stats::Formula avgConsumedWrBW; |
| Stats::Formula peakBW; |
| Stats::Formula busUtil; |
| |
| // Average queue lengths |
| Stats::Average avgRdQLen; |
| Stats::Average avgWrQLen; |
| |
| // Row hit count and rate |
| Stats::Scalar readRowHits; |
| Stats::Scalar writeRowHits; |
| Stats::Formula readRowHitRate; |
| Stats::Formula writeRowHitRate; |
| Stats::Formula avgGap; |
| |
| /** @todo this is a temporary workaround until the 4-phase code is |
| * committed. upstream caches needs this packet until true is returned, so |
| * hold onto it for deletion until a subsequent call |
| */ |
| std::vector<PacketPtr> pendingDelete; |
| |
| public: |
| |
| void regStats(); |
| |
| SimpleDRAM(const SimpleDRAMParams* p); |
| |
| unsigned int drain(DrainManager* dm); |
| |
| virtual BaseSlavePort& getSlavePort(const std::string& if_name, |
| PortID idx = InvalidPortID); |
| |
| virtual void init(); |
| virtual void startup(); |
| |
| protected: |
| |
| Tick recvAtomic(PacketPtr pkt); |
| void recvFunctional(PacketPtr pkt); |
| bool recvTimingReq(PacketPtr pkt); |
| |
| }; |
| |
| #endif //__MEM_SIMPLE_DRAM_HH__ |