summaryrefslogtreecommitdiff
path: root/src/mem/ruby/system/MemoryControl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/mem/ruby/system/MemoryControl.cc')
-rw-r--r--src/mem/ruby/system/MemoryControl.cc632
1 files changed, 632 insertions, 0 deletions
diff --git a/src/mem/ruby/system/MemoryControl.cc b/src/mem/ruby/system/MemoryControl.cc
new file mode 100644
index 000000000..e9f8a5ca8
--- /dev/null
+++ b/src/mem/ruby/system/MemoryControl.cc
@@ -0,0 +1,632 @@
+
+/*
+ * Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
+ * 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.
+ */
+
+/*
+ * MemoryControl.C
+ *
+ * Description: This module simulates a basic DDR-style memory controller
+ * (and can easily be extended to do FB-DIMM as well).
+ *
+ * This module models a single channel, connected to any number of
+ * DIMMs with any number of ranks of DRAMs each. If you want multiple
+ * address/data channels, you need to instantiate multiple copies of
+ * this module.
+ *
+ * Each memory request is placed in a queue associated with a specific
+ * memory bank. This queue is of finite size; if the queue is full
+ * the request will back up in an (infinite) common queue and will
+ * effectively throttle the whole system. This sort of behavior is
+ * intended to be closer to real system behavior than if we had an
+ * infinite queue on each bank. If you want the latter, just make
+ * the bank queues unreasonably large.
+ *
+ * The head item on a bank queue is issued when all of the
+ * following are true:
+ * the bank is available
+ * the address path to the DIMM is available
+ * the data path to or from the DIMM is available
+ *
+ * Note that we are not concerned about fixed offsets in time. The bank
+ * will not be used at the same moment as the address path, but since
+ * there is no queue in the DIMM or the DRAM it will be used at a constant
+ * number of cycles later, so it is treated as if it is used at the same
+ * time.
+ *
+ * We are assuming closed bank policy; that is, we automatically close
+ * each bank after a single read or write. Adding an option for open
+ * bank policy is for future work.
+ *
+ * We are assuming "posted CAS"; that is, we send the READ or WRITE
+ * immediately after the ACTIVATE. This makes scheduling the address
+ * bus trivial; we always schedule a fixed set of cycles. For DDR-400,
+ * this is a set of two cycles; for some configurations such as
+ * DDR-800 the parameter tRRD forces this to be set to three cycles.
+ *
+ * We assume a four-bit-time transfer on the data wires. This is
+ * the minimum burst length for DDR-2. This would correspond
+ * to (for example) a memory where each DIMM is 72 bits wide
+ * and DIMMs are ganged in pairs to deliver 64 bytes at a shot.
+ * This gives us the same occupancy on the data wires as on the
+ * address wires (for the two-address-cycle case).
+ *
+ * The only non-trivial scheduling problem is the data wires.
+ * A write will use the wires earlier in the operation than a read
+ * will; typically one cycle earlier as seen at the DRAM, but earlier
+ * by a worst-case round-trip wire delay when seen at the memory controller.
+ * So, while reads from one rank can be scheduled back-to-back
+ * every two cycles, and writes (to any rank) scheduled every two cycles,
+ * when a read is followed by a write we need to insert a bubble.
+ * Furthermore, consecutive reads from two different ranks may need
+ * to insert a bubble due to skew between when one DRAM stops driving the
+ * wires and when the other one starts. (These bubbles are parameters.)
+ *
+ * This means that when some number of reads and writes are at the
+ * heads of their queues, reads could starve writes, and/or reads
+ * to the same rank could starve out other requests, since the others
+ * would never see the data bus ready.
+ * For this reason, we have implemented an anti-starvation feature.
+ * A group of requests is marked "old", and a counter is incremented
+ * each cycle as long as any request from that batch has not issued.
+ * if the counter reaches twice the bank busy time, we hold off any
+ * newer requests until all of the "old" requests have issued.
+ *
+ * We also model tFAW. This is an obscure DRAM parameter that says
+ * that no more than four activate requests can happen within a window
+ * of a certain size. For most configurations this does not come into play,
+ * or has very little effect, but it could be used to throttle the power
+ * consumption of the DRAM. In this implementation (unlike in a DRAM
+ * data sheet) TFAW is measured in memory bus cycles; i.e. if TFAW = 16
+ * then no more than four activates may happen within any 16 cycle window.
+ * Refreshes are included in the activates.
+ *
+ *
+ * $Id: $
+ *
+ */
+
+#include "Global.hh"
+#include "Map.hh"
+#include "Address.hh"
+#include "Profiler.hh"
+#include "AbstractChip.hh"
+#include "System.hh"
+#include "RubySlicc_ComponentMapping.hh"
+#include "NetworkMessage.hh"
+#include "Network.hh"
+
+#include "Consumer.hh"
+
+#include "MemoryControl.hh"
+
+#include <list>
+
+class Consumer;
+
+// Value to reset watchdog timer to.
+// If we're idle for this many memory control cycles,
+// shut down our clock (our rescheduling of ourselves).
+// Refresh shuts down as well.
+// When we restart, we'll be in a different phase
+// with respect to ruby cycles, so this introduces
+// a slight inaccuracy. But it is necessary or the
+// ruby tester never terminates because the event
+// queue is never empty.
+#define IDLECOUNT_MAX_VALUE 1000
+
+// Output operator definition
+
+ostream& operator<<(ostream& out, const MemoryControl& obj)
+{
+ obj.print(out);
+ out << flush;
+ return out;
+}
+
+
+// ****************************************************************
+
+// CONSTRUCTOR
+
+MemoryControl::MemoryControl (AbstractChip* chip_ptr, int version) {
+ m_chip_ptr = chip_ptr;
+ m_version = version;
+ m_msg_counter = 0;
+
+ m_debug = 0;
+ //if (m_version == 0) m_debug = 1;
+
+ m_mem_bus_cycle_multiplier = RubyConfig::memBusCycleMultiplier();
+ m_banks_per_rank = RubyConfig::banksPerRank();
+ m_ranks_per_dimm = RubyConfig::ranksPerDimm();
+ m_dimms_per_channel = RubyConfig::dimmsPerChannel();
+ m_bank_bit_0 = RubyConfig::bankBit0();
+ m_rank_bit_0 = RubyConfig::rankBit0();
+ m_dimm_bit_0 = RubyConfig::dimmBit0();
+ m_bank_queue_size = RubyConfig::bankQueueSize();
+ m_bank_busy_time = RubyConfig::bankBusyTime();
+ m_rank_rank_delay = RubyConfig::rankRankDelay();
+ m_read_write_delay = RubyConfig::readWriteDelay();
+ m_basic_bus_busy_time = RubyConfig::basicBusBusyTime();
+ m_mem_ctl_latency = RubyConfig::memCtlLatency();
+ m_refresh_period = RubyConfig::refreshPeriod();
+ m_memRandomArbitrate = RubyConfig::memRandomArbitrate();
+ m_tFaw = RubyConfig::tFaw();
+ m_memFixedDelay = RubyConfig::memFixedDelay();
+
+ assert(m_tFaw <= 62); // must fit in a uint64 shift register
+
+ m_total_banks = m_banks_per_rank * m_ranks_per_dimm * m_dimms_per_channel;
+ m_total_ranks = m_ranks_per_dimm * m_dimms_per_channel;
+ m_refresh_period_system = m_refresh_period / m_total_banks;
+
+ m_bankQueues = new list<MemoryNode> [m_total_banks];
+ assert(m_bankQueues);
+
+ m_bankBusyCounter = new int [m_total_banks];
+ assert(m_bankBusyCounter);
+
+ m_oldRequest = new int [m_total_banks];
+ assert(m_oldRequest);
+
+ for (int i=0; i<m_total_banks; i++) {
+ m_bankBusyCounter[i] = 0;
+ m_oldRequest[i] = 0;
+ }
+
+ m_busBusyCounter_Basic = 0;
+ m_busBusyCounter_Write = 0;
+ m_busBusyCounter_ReadNewRank = 0;
+ m_busBusy_WhichRank = 0;
+
+ m_roundRobin = 0;
+ m_refresh_count = 1;
+ m_need_refresh = 0;
+ m_refresh_bank = 0;
+ m_awakened = 0;
+ m_idleCount = 0;
+ m_ageCounter = 0;
+
+ // Each tfaw shift register keeps a moving bit pattern
+ // which shows when recent activates have occurred.
+ // m_tfaw_count keeps track of how many 1 bits are set
+ // in each shift register. When m_tfaw_count is >= 4,
+ // new activates are not allowed.
+ m_tfaw_shift = new uint64 [m_total_ranks];
+ m_tfaw_count = new int [m_total_ranks];
+ for (int i=0; i<m_total_ranks; i++) {
+ m_tfaw_shift[i] = 0;
+ m_tfaw_count[i] = 0;
+ }
+}
+
+
+// DESTRUCTOR
+
+MemoryControl::~MemoryControl () {
+ delete [] m_bankQueues;
+ delete [] m_bankBusyCounter;
+ delete [] m_oldRequest;
+}
+
+
+// PUBLIC METHODS
+
+// enqueue new request from directory
+
+void MemoryControl::enqueue (const MsgPtr& message, int latency) {
+ Time current_time = g_eventQueue_ptr->getTime();
+ Time arrival_time = current_time + latency;
+ const MemoryMsg* memMess = dynamic_cast<const MemoryMsg*>(message.ref());
+ physical_address_t addr = memMess->getAddress().getAddress();
+ MemoryRequestType type = memMess->getType();
+ bool is_mem_read = (type == MemoryRequestType_MEMORY_READ);
+ MemoryNode thisReq(arrival_time, message, addr, is_mem_read, !is_mem_read);
+ enqueueMemRef(thisReq);
+}
+
+// Alternate entry point used when we already have a MemoryNode structure built.
+
+void MemoryControl::enqueueMemRef (MemoryNode& memRef) {
+ m_msg_counter++;
+ memRef.m_msg_counter = m_msg_counter;
+ Time arrival_time = memRef.m_time;
+ uint64 at = arrival_time;
+ bool is_mem_read = memRef.m_is_mem_read;
+ bool dirtyWB = memRef.m_is_dirty_wb;
+ physical_address_t addr = memRef.m_addr;
+ int bank = getBank(addr);
+ if (m_debug) {
+ printf("New memory request%7d: 0x%08llx %c arrived at %10lld ", m_msg_counter, addr, is_mem_read? 'R':'W', at);
+ printf("bank =%3x\n", bank);
+ }
+ g_system_ptr->getProfiler()->profileMemReq(bank);
+ m_input_queue.push_back(memRef);
+ if (!m_awakened) {
+ g_eventQueue_ptr->scheduleEvent(this, 1);
+ m_awakened = 1;
+ }
+}
+
+
+
+// dequeue, peek, and isReady are used to transfer completed requests
+// back to the directory
+
+void MemoryControl::dequeue () {
+ assert(isReady());
+ m_response_queue.pop_front();
+}
+
+
+const Message* MemoryControl::peek () {
+ MemoryNode node = peekNode();
+ Message* msg_ptr = node.m_msgptr.ref();
+ assert(msg_ptr != NULL);
+ return msg_ptr;
+}
+
+
+MemoryNode MemoryControl::peekNode () {
+ assert(isReady());
+ MemoryNode req = m_response_queue.front();
+ uint64 returnTime = req.m_time;
+ if (m_debug) {
+ printf("Old memory request%7d: 0x%08llx %c peeked at %10lld\n",
+ req.m_msg_counter, req.m_addr, req.m_is_mem_read? 'R':'W', returnTime);
+ }
+ return req;
+}
+
+
+bool MemoryControl::isReady () {
+ return ((!m_response_queue.empty()) &&
+ (m_response_queue.front().m_time <= g_eventQueue_ptr->getTime()));
+}
+
+void MemoryControl::setConsumer (Consumer* consumer_ptr) {
+ m_consumer_ptr = consumer_ptr;
+}
+
+void MemoryControl::print (ostream& out) const {
+}
+
+
+void MemoryControl::printConfig (ostream& out) {
+ out << "Memory Control " << m_version << ":" << endl;
+ out << " Ruby cycles per memory cycle: " << m_mem_bus_cycle_multiplier << endl;
+ out << " Basic read latency: " << m_mem_ctl_latency << endl;
+ if (m_memFixedDelay) {
+ out << " Fixed Latency mode: Added cycles = " << m_memFixedDelay << endl;
+ } else {
+ out << " Bank busy time: " << BANK_BUSY_TIME << " memory cycles" << endl;
+ out << " Memory channel busy time: " << m_basic_bus_busy_time << endl;
+ out << " Dead cycles between reads to different ranks: " << m_rank_rank_delay << endl;
+ out << " Dead cycle between a read and a write: " << m_read_write_delay << endl;
+ out << " tFaw (four-activate) window: " << m_tFaw << endl;
+ }
+ out << " Banks per rank: " << m_banks_per_rank << endl;
+ out << " Ranks per DIMM: " << m_ranks_per_dimm << endl;
+ out << " DIMMs per channel: " << m_dimms_per_channel << endl;
+ out << " LSB of bank field in address: " << m_bank_bit_0 << endl;
+ out << " LSB of rank field in address: " << m_rank_bit_0 << endl;
+ out << " LSB of DIMM field in address: " << m_dimm_bit_0 << endl;
+ out << " Max size of each bank queue: " << m_bank_queue_size << endl;
+ out << " Refresh period (within one bank): " << m_refresh_period << endl;
+ out << " Arbitration randomness: " << m_memRandomArbitrate << endl;
+}
+
+
+void MemoryControl::setDebug (int debugFlag) {
+ m_debug = debugFlag;
+}
+
+
+// ****************************************************************
+
+// PRIVATE METHODS
+
+// Queue up a completed request to send back to directory
+
+void MemoryControl::enqueueToDirectory (MemoryNode req, int latency) {
+ Time arrival_time = g_eventQueue_ptr->getTime()
+ + (latency * m_mem_bus_cycle_multiplier);
+ req.m_time = arrival_time;
+ m_response_queue.push_back(req);
+
+ // schedule the wake up
+ g_eventQueue_ptr->scheduleEventAbsolute(m_consumer_ptr, arrival_time);
+}
+
+
+
+// getBank returns an integer that is unique for each
+// bank across this memory controller.
+
+int MemoryControl::getBank (physical_address_t addr) {
+ int dimm = (addr >> m_dimm_bit_0) & (m_dimms_per_channel - 1);
+ int rank = (addr >> m_rank_bit_0) & (m_ranks_per_dimm - 1);
+ int bank = (addr >> m_bank_bit_0) & (m_banks_per_rank - 1);
+ return (dimm * m_ranks_per_dimm * m_banks_per_rank)
+ + (rank * m_banks_per_rank)
+ + bank;
+}
+
+// getRank returns an integer that is unique for each rank
+// and independent of individual bank.
+
+int MemoryControl::getRank (int bank) {
+ int rank = (bank / m_banks_per_rank);
+ assert (rank < (m_ranks_per_dimm * m_dimms_per_channel));
+ return rank;
+}
+
+
+// queueReady determines if the head item in a bank queue
+// can be issued this cycle
+
+bool MemoryControl::queueReady (int bank) {
+ if ((m_bankBusyCounter[bank] > 0) && !m_memFixedDelay) {
+ g_system_ptr->getProfiler()->profileMemBankBusy();
+ //if (m_debug) printf(" bank %x busy %d\n", bank, m_bankBusyCounter[bank]);
+ return false;
+ }
+ if (m_memRandomArbitrate >= 2) {
+ if ((random() % 100) < m_memRandomArbitrate) {
+ g_system_ptr->getProfiler()->profileMemRandBusy();
+ return false;
+ }
+ }
+ if (m_memFixedDelay) return true;
+ if ((m_ageCounter > (2 * m_bank_busy_time)) && !m_oldRequest[bank]) {
+ g_system_ptr->getProfiler()->profileMemNotOld();
+ return false;
+ }
+ if (m_busBusyCounter_Basic == m_basic_bus_busy_time) {
+ // Another bank must have issued this same cycle.
+ // For profiling, we count this as an arb wait rather than
+ // a bus wait. This is a little inaccurate since it MIGHT
+ // have also been blocked waiting for a read-write or a
+ // read-read instead, but it's pretty close.
+ g_system_ptr->getProfiler()->profileMemArbWait(1);
+ return false;
+ }
+ if (m_busBusyCounter_Basic > 0) {
+ g_system_ptr->getProfiler()->profileMemBusBusy();
+ return false;
+ }
+ int rank = getRank(bank);
+ if (m_tfaw_count[rank] >= ACTIVATE_PER_TFAW) {
+ g_system_ptr->getProfiler()->profileMemTfawBusy();
+ return false;
+ }
+ bool write = !m_bankQueues[bank].front().m_is_mem_read;
+ if (write && (m_busBusyCounter_Write > 0)) {
+ g_system_ptr->getProfiler()->profileMemReadWriteBusy();
+ return false;
+ }
+ if (!write && (rank != m_busBusy_WhichRank)
+ && (m_busBusyCounter_ReadNewRank > 0)) {
+ g_system_ptr->getProfiler()->profileMemDataBusBusy();
+ return false;
+ }
+ return true;
+}
+
+
+// issueRefresh checks to see if this bank has a refresh scheduled
+// and, if so, does the refresh and returns true
+
+bool MemoryControl::issueRefresh (int bank) {
+ if (!m_need_refresh || (m_refresh_bank != bank)) return false;
+ if (m_bankBusyCounter[bank] > 0) return false;
+ // Note that m_busBusyCounter will prevent multiple issues during
+ // the same cycle, as well as on different but close cycles:
+ if (m_busBusyCounter_Basic > 0) return false;
+ int rank = getRank(bank);
+ if (m_tfaw_count[rank] >= ACTIVATE_PER_TFAW) return false;
+
+ // Issue it:
+
+ //if (m_debug) {
+ //uint64 current_time = g_eventQueue_ptr->getTime();
+ //printf(" Refresh bank %3x at %lld\n", bank, current_time);
+ //}
+ g_system_ptr->getProfiler()->profileMemRefresh();
+ m_need_refresh--;
+ m_refresh_bank++;
+ if (m_refresh_bank >= m_total_banks) m_refresh_bank = 0;
+ m_bankBusyCounter[bank] = m_bank_busy_time;
+ m_busBusyCounter_Basic = m_basic_bus_busy_time;
+ m_busBusyCounter_Write = m_basic_bus_busy_time;
+ m_busBusyCounter_ReadNewRank = m_basic_bus_busy_time;
+ markTfaw(rank);
+ return true;
+}
+
+
+// Mark the activate in the tFaw shift register
+void MemoryControl::markTfaw (int rank) {
+ if (m_tFaw) {
+ m_tfaw_shift[rank] |= (1 << (m_tFaw-1));
+ m_tfaw_count[rank]++;
+ }
+}
+
+
+// Issue a memory request: Activate the bank,
+// reserve the address and data buses, and queue
+// the request for return to the requesting
+// processor after a fixed latency.
+
+void MemoryControl::issueRequest (int bank) {
+ int rank = getRank(bank);
+ MemoryNode req = m_bankQueues[bank].front();
+ m_bankQueues[bank].pop_front();
+ if (m_debug) {
+ uint64 current_time = g_eventQueue_ptr->getTime();
+ printf(" Mem issue request%7d: 0x%08llx %c at %10lld bank =%3x\n",
+ req.m_msg_counter, req.m_addr, req.m_is_mem_read? 'R':'W', current_time, bank);
+ }
+ if (req.m_msgptr.ref() != NULL) { // don't enqueue L3 writebacks
+ enqueueToDirectory(req, m_mem_ctl_latency + m_memFixedDelay);
+ }
+ m_oldRequest[bank] = 0;
+ markTfaw(rank);
+ m_bankBusyCounter[bank] = m_bank_busy_time;
+ m_busBusy_WhichRank = rank;
+ if (req.m_is_mem_read) {
+ g_system_ptr->getProfiler()->profileMemRead();
+ m_busBusyCounter_Basic = m_basic_bus_busy_time;
+ m_busBusyCounter_Write = m_basic_bus_busy_time + m_read_write_delay;
+ m_busBusyCounter_ReadNewRank = m_basic_bus_busy_time + m_rank_rank_delay;
+ } else {
+ g_system_ptr->getProfiler()->profileMemWrite();
+ m_busBusyCounter_Basic = m_basic_bus_busy_time;
+ m_busBusyCounter_Write = m_basic_bus_busy_time;
+ m_busBusyCounter_ReadNewRank = m_basic_bus_busy_time;
+ }
+}
+
+
+// executeCycle: This function is called once per memory clock cycle
+// to simulate all the periodic hardware.
+
+void MemoryControl::executeCycle () {
+ // Keep track of time by counting down the busy counters:
+ for (int bank=0; bank < m_total_banks; bank++) {
+ if (m_bankBusyCounter[bank] > 0) m_bankBusyCounter[bank]--;
+ }
+ if (m_busBusyCounter_Write > 0) m_busBusyCounter_Write--;
+ if (m_busBusyCounter_ReadNewRank > 0) m_busBusyCounter_ReadNewRank--;
+ if (m_busBusyCounter_Basic > 0) m_busBusyCounter_Basic--;
+
+ // Count down the tFAW shift registers:
+ for (int rank=0; rank < m_total_ranks; rank++) {
+ if (m_tfaw_shift[rank] & 1) m_tfaw_count[rank]--;
+ m_tfaw_shift[rank] >>= 1;
+ }
+
+ // After time period expires, latch an indication that we need a refresh.
+ // Disable refresh if in memFixedDelay mode.
+ if (!m_memFixedDelay) m_refresh_count--;
+ if (m_refresh_count == 0) {
+ m_refresh_count = m_refresh_period_system;
+ assert (m_need_refresh < 10); // Are we overrunning our ability to refresh?
+ m_need_refresh++;
+ }
+
+ // If this batch of requests is all done, make a new batch:
+ m_ageCounter++;
+ int anyOld = 0;
+ for (int bank=0; bank < m_total_banks; bank++) {
+ anyOld |= m_oldRequest[bank];
+ }
+ if (!anyOld) {
+ for (int bank=0; bank < m_total_banks; bank++) {
+ if (!m_bankQueues[bank].empty()) m_oldRequest[bank] = 1;
+ }
+ m_ageCounter = 0;
+ }
+
+ // If randomness desired, re-randomize round-robin position each cycle
+ if (m_memRandomArbitrate) {
+ m_roundRobin = random() % m_total_banks;
+ }
+
+
+ // For each channel, scan round-robin, and pick an old, ready
+ // request and issue it. Treat a refresh request as if it
+ // were at the head of its bank queue. After we issue something,
+ // keep scanning the queues just to gather statistics about
+ // how many are waiting. If in memFixedDelay mode, we can issue
+ // more than one request per cycle.
+
+ int queueHeads = 0;
+ int banksIssued = 0;
+ for (int i = 0; i < m_total_banks; i++) {
+ m_roundRobin++;
+ if (m_roundRobin >= m_total_banks) m_roundRobin = 0;
+ issueRefresh(m_roundRobin);
+ int qs = m_bankQueues[m_roundRobin].size();
+ if (qs > 1) {
+ g_system_ptr->getProfiler()->profileMemBankQ(qs-1);
+ }
+ if (qs > 0) {
+ m_idleCount = IDLECOUNT_MAX_VALUE; // we're not idle if anything is queued
+ queueHeads++;
+ if (queueReady(m_roundRobin)) {
+ issueRequest(m_roundRobin);
+ banksIssued++;
+ if (m_memFixedDelay) {
+ g_system_ptr->getProfiler()->profileMemWaitCycles(m_memFixedDelay);
+ }
+ }
+ }
+ }
+
+ // memWaitCycles is a redundant catch-all for the specific counters in queueReady
+ g_system_ptr->getProfiler()->profileMemWaitCycles(queueHeads - banksIssued);
+
+ // Check input queue and move anything to bank queues if not full.
+ // Since this is done here at the end of the cycle, there will always
+ // be at least one cycle of latency in the bank queue.
+ // We deliberately move at most one request per cycle (to simulate
+ // typical hardware). Note that if one bank queue fills up, other
+ // requests can get stuck behind it here.
+
+ if (!m_input_queue.empty()) {
+ m_idleCount = IDLECOUNT_MAX_VALUE; // we're not idle if anything is pending
+ MemoryNode req = m_input_queue.front();
+ int bank = getBank(req.m_addr);
+ if (m_bankQueues[bank].size() < m_bank_queue_size) {
+ m_input_queue.pop_front();
+ m_bankQueues[bank].push_back(req);
+ }
+ g_system_ptr->getProfiler()->profileMemInputQ(m_input_queue.size());
+ }
+}
+
+
+// wakeup: This function is called once per memory controller clock cycle.
+
+void MemoryControl::wakeup () {
+
+ // execute everything
+ executeCycle();
+
+ m_idleCount--;
+ if (m_idleCount <= 0) {
+ m_awakened = 0;
+ } else {
+ // Reschedule ourselves so that we run every memory cycle:
+ g_eventQueue_ptr->scheduleEvent(this, m_mem_bus_cycle_multiplier);
+ }
+}
+
+