ruby: memory controllers now inherit from an abstract "MemoryControl" class
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
|
||||
* Copyright (c) 2012 Advanced Micro Devices, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -26,86 +27,9 @@
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "base/cast.hh"
|
||||
#include "base/cprintf.hh"
|
||||
#include "mem/ruby/common/Address.hh"
|
||||
#include "mem/ruby/common/Consumer.hh"
|
||||
#include "mem/ruby/common/Global.hh"
|
||||
#include "mem/ruby/network/Network.hh"
|
||||
@@ -113,564 +37,17 @@
|
||||
#include "mem/ruby/slicc_interface/NetworkMessage.hh"
|
||||
#include "mem/ruby/slicc_interface/RubySlicc_ComponentMapping.hh"
|
||||
#include "mem/ruby/system/MemoryControl.hh"
|
||||
#include "mem/ruby/system/RubyMemoryControl.hh"
|
||||
#include "mem/ruby/system/System.hh"
|
||||
|
||||
using namespace std;
|
||||
MemoryControl::MemoryControl(const Params *p) : SimObject(p), m_event(this) {};
|
||||
MemoryControl::~MemoryControl() {};
|
||||
|
||||
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(const Params *p)
|
||||
: SimObject(p), m_event(this)
|
||||
{
|
||||
m_mem_bus_cycle_multiplier = p->mem_bus_cycle_multiplier;
|
||||
m_banks_per_rank = p->banks_per_rank;
|
||||
m_ranks_per_dimm = p->ranks_per_dimm;
|
||||
m_dimms_per_channel = p->dimms_per_channel;
|
||||
m_bank_bit_0 = p->bank_bit_0;
|
||||
m_rank_bit_0 = p->rank_bit_0;
|
||||
m_dimm_bit_0 = p->dimm_bit_0;
|
||||
m_bank_queue_size = p->bank_queue_size;
|
||||
m_bank_busy_time = p->bank_busy_time;
|
||||
m_rank_rank_delay = p->rank_rank_delay;
|
||||
m_read_write_delay = p->read_write_delay;
|
||||
m_basic_bus_busy_time = p->basic_bus_busy_time;
|
||||
m_mem_ctl_latency = p->mem_ctl_latency;
|
||||
m_refresh_period = p->refresh_period;
|
||||
m_tFaw = p->tFaw;
|
||||
m_mem_random_arbitrate = p->mem_random_arbitrate;
|
||||
m_mem_fixed_delay = p->mem_fixed_delay;
|
||||
|
||||
m_profiler_ptr = new MemCntrlProfiler(name(),
|
||||
m_banks_per_rank,
|
||||
m_ranks_per_dimm,
|
||||
m_dimms_per_channel);
|
||||
}
|
||||
|
||||
void
|
||||
MemoryControl::init()
|
||||
{
|
||||
m_msg_counter = 0;
|
||||
|
||||
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_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;
|
||||
}
|
||||
}
|
||||
|
||||
MemoryControl::~MemoryControl()
|
||||
{
|
||||
delete [] m_bankQueues;
|
||||
delete [] m_bankBusyCounter;
|
||||
delete [] m_oldRequest;
|
||||
delete m_profiler_ptr;
|
||||
}
|
||||
|
||||
// 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 = safe_cast<const MemoryMsg*>(message.get());
|
||||
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;
|
||||
physical_address_t addr = memRef.m_addr;
|
||||
int bank = getBank(addr);
|
||||
|
||||
DPRINTF(RubyMemory,
|
||||
"New memory request%7d: %#08x %c arrived at %10d bank = %3x sched %c\n",
|
||||
m_msg_counter, addr, memRef.m_is_mem_read ? 'R':'W',
|
||||
memRef.m_time * g_eventQueue_ptr->getClock(),
|
||||
bank, m_event.scheduled() ? 'Y':'N');
|
||||
|
||||
m_profiler_ptr->profileMemReq(bank);
|
||||
m_input_queue.push_back(memRef);
|
||||
|
||||
if (!m_event.scheduled()) {
|
||||
schedule(m_event, curTick() + 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.get();
|
||||
assert(msg_ptr != NULL);
|
||||
return msg_ptr;
|
||||
}
|
||||
|
||||
MemoryNode
|
||||
MemoryControl::peekNode()
|
||||
{
|
||||
assert(isReady());
|
||||
MemoryNode req = m_response_queue.front();
|
||||
DPRINTF(RubyMemory, "Peek: memory request%7d: %#08x %c sched %c\n",
|
||||
req.m_msg_counter, req.m_addr, req.m_is_mem_read ? 'R':'W',
|
||||
m_event.scheduled() ? 'Y':'N');
|
||||
|
||||
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 " << name() << ":" << endl;
|
||||
out << " Ruby cycles per memory cycle: " << m_mem_bus_cycle_multiplier
|
||||
<< endl;
|
||||
out << " Basic read latency: " << m_mem_ctl_latency << endl;
|
||||
if (m_mem_fixed_delay) {
|
||||
out << " Fixed Latency mode: Added cycles = " << m_mem_fixed_delay
|
||||
<< endl;
|
||||
} else {
|
||||
out << " Bank busy time: " << m_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_mem_random_arbitrate << endl;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryControl::clearStats() const
|
||||
{
|
||||
m_profiler_ptr->clearStats();
|
||||
}
|
||||
|
||||
void
|
||||
MemoryControl::printStats(ostream& out) const
|
||||
{
|
||||
m_profiler_ptr->printStats(out);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
DPRINTF(RubyMemory, "Enqueueing msg %#08x %c back to directory at %15d\n",
|
||||
req.m_addr, req.m_is_mem_read ? 'R':'W',
|
||||
arrival_time * g_eventQueue_ptr->getClock());
|
||||
|
||||
// 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_mem_fixed_delay) {
|
||||
m_profiler_ptr->profileMemBankBusy();
|
||||
|
||||
DPRINTF(RubyMemory, "bank %x busy %d\n", bank, m_bankBusyCounter[bank]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_mem_random_arbitrate >= 2) {
|
||||
if ((random() % 100) < m_mem_random_arbitrate) {
|
||||
m_profiler_ptr->profileMemRandBusy();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_mem_fixed_delay)
|
||||
return true;
|
||||
|
||||
if ((m_ageCounter > (2 * m_bank_busy_time)) && !m_oldRequest[bank]) {
|
||||
m_profiler_ptr->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.
|
||||
m_profiler_ptr->profileMemArbWait(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_busBusyCounter_Basic > 0) {
|
||||
m_profiler_ptr->profileMemBusBusy();
|
||||
return false;
|
||||
}
|
||||
|
||||
int rank = getRank(bank);
|
||||
if (m_tfaw_count[rank] >= ACTIVATE_PER_TFAW) {
|
||||
m_profiler_ptr->profileMemTfawBusy();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool write = !m_bankQueues[bank].front().m_is_mem_read;
|
||||
if (write && (m_busBusyCounter_Write > 0)) {
|
||||
m_profiler_ptr->profileMemReadWriteBusy();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!write && (rank != m_busBusy_WhichRank)
|
||||
&& (m_busBusyCounter_ReadNewRank > 0)) {
|
||||
m_profiler_ptr->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:
|
||||
DPRINTF(RubyMemory, "Refresh bank %3x\n", bank);
|
||||
|
||||
m_profiler_ptr->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();
|
||||
|
||||
DPRINTF(RubyMemory, "Mem issue request%7d: %#08x %c "
|
||||
"bank=%3x sched %c\n", req.m_msg_counter, req.m_addr,
|
||||
req.m_is_mem_read? 'R':'W',
|
||||
bank, m_event.scheduled() ? 'Y':'N');
|
||||
|
||||
if (req.m_msgptr) { // don't enqueue L3 writebacks
|
||||
enqueueToDirectory(req, m_mem_ctl_latency + m_mem_fixed_delay);
|
||||
}
|
||||
m_oldRequest[bank] = 0;
|
||||
markTfaw(rank);
|
||||
m_bankBusyCounter[bank] = m_bank_busy_time;
|
||||
m_busBusy_WhichRank = rank;
|
||||
if (req.m_is_mem_read) {
|
||||
m_profiler_ptr->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 {
|
||||
m_profiler_ptr->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 mem_fixed_delay mode.
|
||||
if (!m_mem_fixed_delay) m_refresh_count--;
|
||||
if (m_refresh_count == 0) {
|
||||
m_refresh_count = m_refresh_period_system;
|
||||
|
||||
// Are we overrunning our ability to refresh?
|
||||
assert(m_need_refresh < 10);
|
||||
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_mem_random_arbitrate) {
|
||||
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 mem_fixed_delay 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) {
|
||||
m_profiler_ptr->profileMemBankQ(qs-1);
|
||||
}
|
||||
if (qs > 0) {
|
||||
// we're not idle if anything is queued
|
||||
m_idleCount = IDLECOUNT_MAX_VALUE;
|
||||
queueHeads++;
|
||||
if (queueReady(m_roundRobin)) {
|
||||
issueRequest(m_roundRobin);
|
||||
banksIssued++;
|
||||
if (m_mem_fixed_delay) {
|
||||
m_profiler_ptr->profileMemWaitCycles(m_mem_fixed_delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// memWaitCycles is a redundant catch-all for the specific
|
||||
// counters in queueReady
|
||||
m_profiler_ptr->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()) {
|
||||
// we're not idle if anything is pending
|
||||
m_idleCount = IDLECOUNT_MAX_VALUE;
|
||||
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);
|
||||
}
|
||||
m_profiler_ptr->profileMemInputQ(m_input_queue.size());
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int
|
||||
MemoryControl::drain(Event *de)
|
||||
{
|
||||
DPRINTF(RubyMemory, "MemoryController drain\n");
|
||||
if(m_event.scheduled()) {
|
||||
deschedule(m_event);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// wakeup: This function is called once per memory controller clock cycle.
|
||||
void
|
||||
MemoryControl::wakeup()
|
||||
{
|
||||
DPRINTF(RubyMemory, "MemoryController wakeup\n");
|
||||
// execute everything
|
||||
executeCycle();
|
||||
|
||||
m_idleCount--;
|
||||
if (m_idleCount > 0) {
|
||||
assert(!m_event.scheduled());
|
||||
schedule(m_event, curTick() + m_mem_bus_cycle_multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryControl *
|
||||
RubyMemoryControl *
|
||||
RubyMemoryControlParams::create()
|
||||
{
|
||||
return new MemoryControl(this);
|
||||
return new RubyMemoryControl(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
|
||||
* Copyright (c) 2012 Advanced Micro Devices, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -26,8 +27,8 @@
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __MEM_RUBY_SYSTEM_MEMORY_CONTROL_HH__
|
||||
#define __MEM_RUBY_SYSTEM_MEMORY_CONTROL_HH__
|
||||
#ifndef __MEM_RUBY_SYSTEM_ABSTRACT_MEMORY_CONTROL_HH__
|
||||
#define __MEM_RUBY_SYSTEM_ABSTRACT_MEMORY_CONTROL_HH__
|
||||
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
@@ -39,13 +40,9 @@
|
||||
#include "mem/ruby/slicc_interface/Message.hh"
|
||||
#include "mem/ruby/system/AbstractMemOrCache.hh"
|
||||
#include "mem/ruby/system/MemoryNode.hh"
|
||||
#include "params/RubyMemoryControl.hh"
|
||||
#include "mem/ruby/system/System.hh"
|
||||
#include "sim/sim_object.hh"
|
||||
|
||||
// This constant is part of the definition of tFAW; see
|
||||
// the comments in header to MemoryControl.cc
|
||||
#define ACTIVATE_PER_TFAW 4
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Consumer;
|
||||
@@ -54,45 +51,50 @@ class MemoryControl :
|
||||
public SimObject, public Consumer, public AbstractMemOrCache
|
||||
{
|
||||
public:
|
||||
|
||||
typedef RubyMemoryControlParams Params;
|
||||
MemoryControl(const Params *p);
|
||||
void init();
|
||||
virtual void init() = 0;
|
||||
|
||||
~MemoryControl();
|
||||
|
||||
unsigned int drain(Event *de);
|
||||
unsigned int drain(Event *de) = 0;
|
||||
|
||||
void wakeup();
|
||||
virtual void wakeup() = 0;
|
||||
|
||||
void setConsumer(Consumer* consumer_ptr);
|
||||
Consumer* getConsumer() { return m_consumer_ptr; };
|
||||
void setDescription(const std::string& name) { m_description = name; };
|
||||
std::string getDescription() { return m_description; };
|
||||
virtual void setConsumer(Consumer* consumer_ptr) = 0;
|
||||
virtual Consumer* getConsumer() = 0;
|
||||
virtual void setDescription(const std::string& name) = 0;
|
||||
virtual std::string getDescription() = 0;
|
||||
|
||||
// Called from the directory:
|
||||
void enqueue(const MsgPtr& message, int latency );
|
||||
void enqueueMemRef(MemoryNode& memRef);
|
||||
void dequeue();
|
||||
const Message* peek();
|
||||
MemoryNode peekNode();
|
||||
bool isReady();
|
||||
bool areNSlotsAvailable(int n) { return true; }; // infinite queue length
|
||||
virtual void enqueue(const MsgPtr& message, int latency ) = 0;
|
||||
virtual void enqueueMemRef(MemoryNode& memRef) = 0;
|
||||
virtual void dequeue() = 0;
|
||||
virtual const Message* peek() = 0;
|
||||
virtual MemoryNode peekNode() = 0;
|
||||
virtual bool isReady() = 0;
|
||||
virtual bool areNSlotsAvailable(int n) = 0; // infinite queue length
|
||||
|
||||
//// Called from L3 cache:
|
||||
//void writeBack(physical_address_t addr);
|
||||
|
||||
void printConfig(std::ostream& out);
|
||||
void print(std::ostream& out) const;
|
||||
void clearStats() const;
|
||||
void printStats(std::ostream& out) const;
|
||||
virtual void printConfig(std::ostream& out) = 0;
|
||||
virtual void print(std::ostream& out) const = 0;
|
||||
virtual void clearStats() const = 0;
|
||||
virtual void printStats(std::ostream& out) const = 0;
|
||||
|
||||
virtual void regStats() {};
|
||||
|
||||
virtual const int getChannel(const physical_address_t addr) const = 0;
|
||||
virtual const int getBank(const physical_address_t addr) const = 0;
|
||||
virtual const int getRank(const physical_address_t addr) const = 0;
|
||||
virtual const int getRow(const physical_address_t addr) const = 0;
|
||||
|
||||
//added by SS
|
||||
int getBanksPerRank() { return m_banks_per_rank; };
|
||||
int getRanksPerDimm() { return m_ranks_per_dimm; };
|
||||
int getDimmsPerChannel() { return m_dimms_per_channel; }
|
||||
virtual int getBanksPerRank() = 0;
|
||||
virtual int getRanksPerDimm() = 0;
|
||||
virtual int getDimmsPerChannel() = 0;
|
||||
|
||||
private:
|
||||
protected:
|
||||
class MemCntrlEvent : public Event
|
||||
{
|
||||
public:
|
||||
@@ -106,76 +108,7 @@ class MemoryControl :
|
||||
MemoryControl* mem_cntrl;
|
||||
};
|
||||
|
||||
void enqueueToDirectory(MemoryNode req, int latency);
|
||||
int getBank(physical_address_t addr);
|
||||
int getRank(int bank);
|
||||
bool queueReady(int bank);
|
||||
void issueRequest(int bank);
|
||||
bool issueRefresh(int bank);
|
||||
void markTfaw(int rank);
|
||||
void executeCycle();
|
||||
|
||||
// Private copy constructor and assignment operator
|
||||
MemoryControl (const MemoryControl& obj);
|
||||
MemoryControl& operator=(const MemoryControl& obj);
|
||||
|
||||
// data members
|
||||
Consumer* m_consumer_ptr; // Consumer to signal a wakeup()
|
||||
std::string m_description;
|
||||
int m_msg_counter;
|
||||
|
||||
int m_mem_bus_cycle_multiplier;
|
||||
int m_banks_per_rank;
|
||||
int m_ranks_per_dimm;
|
||||
int m_dimms_per_channel;
|
||||
int m_bank_bit_0;
|
||||
int m_rank_bit_0;
|
||||
int m_dimm_bit_0;
|
||||
unsigned int m_bank_queue_size;
|
||||
int m_bank_busy_time;
|
||||
int m_rank_rank_delay;
|
||||
int m_read_write_delay;
|
||||
int m_basic_bus_busy_time;
|
||||
int m_mem_ctl_latency;
|
||||
int m_refresh_period;
|
||||
int m_mem_random_arbitrate;
|
||||
int m_tFaw;
|
||||
int m_mem_fixed_delay;
|
||||
|
||||
int m_total_banks;
|
||||
int m_total_ranks;
|
||||
int m_refresh_period_system;
|
||||
|
||||
// queues where memory requests live
|
||||
std::list<MemoryNode> m_response_queue;
|
||||
std::list<MemoryNode> m_input_queue;
|
||||
std::list<MemoryNode>* m_bankQueues;
|
||||
|
||||
// Each entry indicates number of address-bus cycles until bank
|
||||
// is reschedulable:
|
||||
int* m_bankBusyCounter;
|
||||
int* m_oldRequest;
|
||||
|
||||
uint64* m_tfaw_shift;
|
||||
int* m_tfaw_count;
|
||||
|
||||
// Each of these indicates number of address-bus cycles until
|
||||
// we can issue a new request of the corresponding type:
|
||||
int m_busBusyCounter_Write;
|
||||
int m_busBusyCounter_ReadNewRank;
|
||||
int m_busBusyCounter_Basic;
|
||||
|
||||
int m_busBusy_WhichRank; // which rank last granted
|
||||
int m_roundRobin; // which bank queue was last granted
|
||||
int m_refresh_count; // cycles until next refresh
|
||||
int m_need_refresh; // set whenever m_refresh_count goes to zero
|
||||
int m_refresh_bank; // which bank to refresh next
|
||||
int m_ageCounter; // age of old requests; to detect starvation
|
||||
int m_idleCount; // watchdog timer for shutting down
|
||||
|
||||
MemCntrlProfiler* m_profiler_ptr;
|
||||
|
||||
MemCntrlEvent m_event;
|
||||
};
|
||||
|
||||
#endif // __MEM_RUBY_SYSTEM_MEMORY_CONTROL_HH__
|
||||
#endif // __MEM_RUBY_SYSTEM_ABSTRACT_MEMORY_CONTROL_HH__
|
||||
|
||||
@@ -30,24 +30,10 @@
|
||||
from m5.params import *
|
||||
from m5.SimObject import SimObject
|
||||
|
||||
class RubyMemoryControl(SimObject):
|
||||
type = 'RubyMemoryControl'
|
||||
class MemoryControl(SimObject):
|
||||
abstract = True
|
||||
type = 'MemoryControl'
|
||||
cxx_class = 'MemoryControl'
|
||||
version = Param.Int("");
|
||||
|
||||
mem_bus_cycle_multiplier = Param.Int(10, "");
|
||||
banks_per_rank = Param.Int(8, "");
|
||||
ranks_per_dimm = Param.Int(2, "");
|
||||
dimms_per_channel = Param.Int(2, "");
|
||||
bank_bit_0 = Param.Int(8, "");
|
||||
rank_bit_0 = Param.Int(11, "");
|
||||
dimm_bit_0 = Param.Int(12, "");
|
||||
bank_queue_size = Param.Int(12, "");
|
||||
bank_busy_time = Param.Int(11, "");
|
||||
rank_rank_delay = Param.Int(1, "");
|
||||
read_write_delay = Param.Int(2, "");
|
||||
basic_bus_busy_time = Param.Int(2, "");
|
||||
mem_ctl_latency = Param.Int(12, "");
|
||||
refresh_period = Param.Int(1560, "");
|
||||
tFaw = Param.Int(0, "");
|
||||
mem_random_arbitrate = Param.Int(0, "");
|
||||
mem_fixed_delay = Param.Int(0, "");
|
||||
|
||||
698
src/mem/ruby/system/RubyMemoryControl.cc
Normal file
698
src/mem/ruby/system/RubyMemoryControl.cc
Normal file
@@ -0,0 +1,698 @@
|
||||
/*
|
||||
* Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
|
||||
* Copyright (c) 2012 Advanced Micro Devices, Inc.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "base/cast.hh"
|
||||
#include "base/cprintf.hh"
|
||||
#include "mem/ruby/common/Address.hh"
|
||||
#include "mem/ruby/common/Consumer.hh"
|
||||
#include "mem/ruby/common/Global.hh"
|
||||
#include "mem/ruby/network/Network.hh"
|
||||
#include "mem/ruby/profiler/Profiler.hh"
|
||||
#include "mem/ruby/slicc_interface/NetworkMessage.hh"
|
||||
#include "mem/ruby/slicc_interface/RubySlicc_ComponentMapping.hh"
|
||||
#include "mem/ruby/system/RubyMemoryControl.hh"
|
||||
#include "mem/ruby/system/System.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
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 RubyMemoryControl& obj)
|
||||
{
|
||||
obj.print(out);
|
||||
out << flush;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
// ****************************************************************
|
||||
|
||||
// CONSTRUCTOR
|
||||
RubyMemoryControl::RubyMemoryControl(const Params *p)
|
||||
: MemoryControl(p)
|
||||
{
|
||||
m_mem_bus_cycle_multiplier = p->mem_bus_cycle_multiplier;
|
||||
m_banks_per_rank = p->banks_per_rank;
|
||||
m_ranks_per_dimm = p->ranks_per_dimm;
|
||||
m_dimms_per_channel = p->dimms_per_channel;
|
||||
m_bank_bit_0 = p->bank_bit_0;
|
||||
m_rank_bit_0 = p->rank_bit_0;
|
||||
m_dimm_bit_0 = p->dimm_bit_0;
|
||||
m_bank_queue_size = p->bank_queue_size;
|
||||
m_bank_busy_time = p->bank_busy_time;
|
||||
m_rank_rank_delay = p->rank_rank_delay;
|
||||
m_read_write_delay = p->read_write_delay;
|
||||
m_basic_bus_busy_time = p->basic_bus_busy_time;
|
||||
m_mem_ctl_latency = p->mem_ctl_latency;
|
||||
m_refresh_period = p->refresh_period;
|
||||
m_tFaw = p->tFaw;
|
||||
m_mem_random_arbitrate = p->mem_random_arbitrate;
|
||||
m_mem_fixed_delay = p->mem_fixed_delay;
|
||||
|
||||
m_profiler_ptr = new MemCntrlProfiler(name(),
|
||||
m_banks_per_rank,
|
||||
m_ranks_per_dimm,
|
||||
m_dimms_per_channel);
|
||||
}
|
||||
|
||||
void
|
||||
RubyMemoryControl::init()
|
||||
{
|
||||
m_msg_counter = 0;
|
||||
|
||||
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_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;
|
||||
}
|
||||
}
|
||||
|
||||
RubyMemoryControl::~RubyMemoryControl()
|
||||
{
|
||||
delete [] m_bankQueues;
|
||||
delete [] m_bankBusyCounter;
|
||||
delete [] m_oldRequest;
|
||||
delete m_profiler_ptr;
|
||||
}
|
||||
|
||||
// enqueue new request from directory
|
||||
void
|
||||
RubyMemoryControl::enqueue(const MsgPtr& message, int latency)
|
||||
{
|
||||
Time current_time = g_eventQueue_ptr->getTime();
|
||||
Time arrival_time = current_time + latency;
|
||||
const MemoryMsg* memMess = safe_cast<const MemoryMsg*>(message.get());
|
||||
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
|
||||
RubyMemoryControl::enqueueMemRef(MemoryNode& memRef)
|
||||
{
|
||||
m_msg_counter++;
|
||||
memRef.m_msg_counter = m_msg_counter;
|
||||
physical_address_t addr = memRef.m_addr;
|
||||
int bank = getBank(addr);
|
||||
|
||||
DPRINTF(RubyMemory,
|
||||
"New memory request%7d: %#08x %c arrived at %10d bank = %3x sched %c\n",
|
||||
m_msg_counter, addr, memRef.m_is_mem_read ? 'R':'W',
|
||||
memRef.m_time * g_eventQueue_ptr->getClock(),
|
||||
bank, m_event.scheduled() ? 'Y':'N');
|
||||
|
||||
m_profiler_ptr->profileMemReq(bank);
|
||||
m_input_queue.push_back(memRef);
|
||||
|
||||
if (!m_event.scheduled()) {
|
||||
schedule(m_event, curTick() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// dequeue, peek, and isReady are used to transfer completed requests
|
||||
// back to the directory
|
||||
void
|
||||
RubyMemoryControl::dequeue()
|
||||
{
|
||||
assert(isReady());
|
||||
m_response_queue.pop_front();
|
||||
}
|
||||
|
||||
const Message*
|
||||
RubyMemoryControl::peek()
|
||||
{
|
||||
MemoryNode node = peekNode();
|
||||
Message* msg_ptr = node.m_msgptr.get();
|
||||
assert(msg_ptr != NULL);
|
||||
return msg_ptr;
|
||||
}
|
||||
|
||||
MemoryNode
|
||||
RubyMemoryControl::peekNode()
|
||||
{
|
||||
assert(isReady());
|
||||
MemoryNode req = m_response_queue.front();
|
||||
DPRINTF(RubyMemory, "Peek: memory request%7d: %#08x %c sched %c\n",
|
||||
req.m_msg_counter, req.m_addr, req.m_is_mem_read ? 'R':'W',
|
||||
m_event.scheduled() ? 'Y':'N');
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
bool
|
||||
RubyMemoryControl::isReady()
|
||||
{
|
||||
return ((!m_response_queue.empty()) &&
|
||||
(m_response_queue.front().m_time <= g_eventQueue_ptr->getTime()));
|
||||
}
|
||||
|
||||
void
|
||||
RubyMemoryControl::setConsumer(Consumer* consumer_ptr)
|
||||
{
|
||||
m_consumer_ptr = consumer_ptr;
|
||||
}
|
||||
|
||||
void
|
||||
RubyMemoryControl::print(ostream& out) const
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
RubyMemoryControl::printConfig(ostream& out)
|
||||
{
|
||||
out << "Memory Control " << name() << ":" << endl;
|
||||
out << " Ruby cycles per memory cycle: " << m_mem_bus_cycle_multiplier
|
||||
<< endl;
|
||||
out << " Basic read latency: " << m_mem_ctl_latency << endl;
|
||||
if (m_mem_fixed_delay) {
|
||||
out << " Fixed Latency mode: Added cycles = " << m_mem_fixed_delay
|
||||
<< endl;
|
||||
} else {
|
||||
out << " Bank busy time: " << m_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_mem_random_arbitrate << endl;
|
||||
}
|
||||
|
||||
void
|
||||
RubyMemoryControl::clearStats() const
|
||||
{
|
||||
m_profiler_ptr->clearStats();
|
||||
}
|
||||
|
||||
void
|
||||
RubyMemoryControl::printStats(ostream& out) const
|
||||
{
|
||||
m_profiler_ptr->printStats(out);
|
||||
}
|
||||
|
||||
// Queue up a completed request to send back to directory
|
||||
void
|
||||
RubyMemoryControl::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);
|
||||
|
||||
DPRINTF(RubyMemory, "Enqueueing msg %#08x %c back to directory at %15d\n",
|
||||
req.m_addr, req.m_is_mem_read ? 'R':'W',
|
||||
arrival_time * g_eventQueue_ptr->getClock());
|
||||
|
||||
// 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.
|
||||
const int
|
||||
RubyMemoryControl::getBank(const physical_address_t addr) const
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
const int
|
||||
RubyMemoryControl::getRank(const physical_address_t addr) const
|
||||
{
|
||||
int bank = getBank(addr);
|
||||
int rank = (bank / m_banks_per_rank);
|
||||
assert (rank < (m_ranks_per_dimm * m_dimms_per_channel));
|
||||
return rank;
|
||||
}
|
||||
|
||||
// getRank returns an integer that is unique for each rank
|
||||
// and independent of individual bank.
|
||||
const int
|
||||
RubyMemoryControl::getRank(int bank) const
|
||||
{
|
||||
int rank = (bank / m_banks_per_rank);
|
||||
assert (rank < (m_ranks_per_dimm * m_dimms_per_channel));
|
||||
return rank;
|
||||
}
|
||||
|
||||
// Not used!
|
||||
const int
|
||||
RubyMemoryControl::getChannel(const physical_address_t addr) const
|
||||
{
|
||||
assert(false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Not used!
|
||||
const int
|
||||
RubyMemoryControl::getRow(const physical_address_t addr) const
|
||||
{
|
||||
assert(false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// queueReady determines if the head item in a bank queue
|
||||
// can be issued this cycle
|
||||
bool
|
||||
RubyMemoryControl::queueReady(int bank)
|
||||
{
|
||||
if ((m_bankBusyCounter[bank] > 0) && !m_mem_fixed_delay) {
|
||||
m_profiler_ptr->profileMemBankBusy();
|
||||
|
||||
DPRINTF(RubyMemory, "bank %x busy %d\n", bank, m_bankBusyCounter[bank]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_mem_random_arbitrate >= 2) {
|
||||
if ((random() % 100) < m_mem_random_arbitrate) {
|
||||
m_profiler_ptr->profileMemRandBusy();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_mem_fixed_delay)
|
||||
return true;
|
||||
|
||||
if ((m_ageCounter > (2 * m_bank_busy_time)) && !m_oldRequest[bank]) {
|
||||
m_profiler_ptr->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.
|
||||
m_profiler_ptr->profileMemArbWait(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_busBusyCounter_Basic > 0) {
|
||||
m_profiler_ptr->profileMemBusBusy();
|
||||
return false;
|
||||
}
|
||||
|
||||
int rank = getRank(bank);
|
||||
if (m_tfaw_count[rank] >= ACTIVATE_PER_TFAW) {
|
||||
m_profiler_ptr->profileMemTfawBusy();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool write = !m_bankQueues[bank].front().m_is_mem_read;
|
||||
if (write && (m_busBusyCounter_Write > 0)) {
|
||||
m_profiler_ptr->profileMemReadWriteBusy();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!write && (rank != m_busBusy_WhichRank)
|
||||
&& (m_busBusyCounter_ReadNewRank > 0)) {
|
||||
m_profiler_ptr->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
|
||||
RubyMemoryControl::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:
|
||||
DPRINTF(RubyMemory, "Refresh bank %3x\n", bank);
|
||||
|
||||
m_profiler_ptr->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
|
||||
RubyMemoryControl::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
|
||||
RubyMemoryControl::issueRequest(int bank)
|
||||
{
|
||||
int rank = getRank(bank);
|
||||
MemoryNode req = m_bankQueues[bank].front();
|
||||
m_bankQueues[bank].pop_front();
|
||||
|
||||
DPRINTF(RubyMemory, "Mem issue request%7d: %#08x %c "
|
||||
"bank=%3x sched %c\n", req.m_msg_counter, req.m_addr,
|
||||
req.m_is_mem_read? 'R':'W',
|
||||
bank, m_event.scheduled() ? 'Y':'N');
|
||||
|
||||
if (req.m_msgptr) { // don't enqueue L3 writebacks
|
||||
enqueueToDirectory(req, m_mem_ctl_latency + m_mem_fixed_delay);
|
||||
}
|
||||
m_oldRequest[bank] = 0;
|
||||
markTfaw(rank);
|
||||
m_bankBusyCounter[bank] = m_bank_busy_time;
|
||||
m_busBusy_WhichRank = rank;
|
||||
if (req.m_is_mem_read) {
|
||||
m_profiler_ptr->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 {
|
||||
m_profiler_ptr->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
|
||||
RubyMemoryControl::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 mem_fixed_delay mode.
|
||||
if (!m_mem_fixed_delay) m_refresh_count--;
|
||||
if (m_refresh_count == 0) {
|
||||
m_refresh_count = m_refresh_period_system;
|
||||
|
||||
// Are we overrunning our ability to refresh?
|
||||
assert(m_need_refresh < 10);
|
||||
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_mem_random_arbitrate) {
|
||||
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 mem_fixed_delay 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) {
|
||||
m_profiler_ptr->profileMemBankQ(qs-1);
|
||||
}
|
||||
if (qs > 0) {
|
||||
// we're not idle if anything is queued
|
||||
m_idleCount = IDLECOUNT_MAX_VALUE;
|
||||
queueHeads++;
|
||||
if (queueReady(m_roundRobin)) {
|
||||
issueRequest(m_roundRobin);
|
||||
banksIssued++;
|
||||
if (m_mem_fixed_delay) {
|
||||
m_profiler_ptr->profileMemWaitCycles(m_mem_fixed_delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// memWaitCycles is a redundant catch-all for the specific
|
||||
// counters in queueReady
|
||||
m_profiler_ptr->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()) {
|
||||
// we're not idle if anything is pending
|
||||
m_idleCount = IDLECOUNT_MAX_VALUE;
|
||||
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);
|
||||
}
|
||||
m_profiler_ptr->profileMemInputQ(m_input_queue.size());
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int
|
||||
RubyMemoryControl::drain(Event *de)
|
||||
{
|
||||
DPRINTF(RubyMemory, "MemoryController drain\n");
|
||||
if(m_event.scheduled()) {
|
||||
deschedule(m_event);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// wakeup: This function is called once per memory controller clock cycle.
|
||||
void
|
||||
RubyMemoryControl::wakeup()
|
||||
{
|
||||
DPRINTF(RubyMemory, "MemoryController wakeup\n");
|
||||
// execute everything
|
||||
executeCycle();
|
||||
|
||||
m_idleCount--;
|
||||
if (m_idleCount > 0) {
|
||||
assert(!m_event.scheduled());
|
||||
schedule(m_event, curTick() + m_mem_bus_cycle_multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
174
src/mem/ruby/system/RubyMemoryControl.hh
Normal file
174
src/mem/ruby/system/RubyMemoryControl.hh
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
|
||||
* Copyright (c) 2012 Advanced Micro Devices, Inc.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __MEM_RUBY_SYSTEM_MEMORY_CONTROL_HH__
|
||||
#define __MEM_RUBY_SYSTEM_MEMORY_CONTROL_HH__
|
||||
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include "mem/protocol/MemoryMsg.hh"
|
||||
#include "mem/ruby/common/Address.hh"
|
||||
#include "mem/ruby/common/Consumer.hh"
|
||||
#include "mem/ruby/common/Global.hh"
|
||||
#include "mem/ruby/profiler/MemCntrlProfiler.hh"
|
||||
#include "mem/ruby/slicc_interface/Message.hh"
|
||||
#include "mem/ruby/system/AbstractMemOrCache.hh"
|
||||
#include "mem/ruby/system/MemoryControl.hh"
|
||||
#include "mem/ruby/system/MemoryNode.hh"
|
||||
#include "mem/ruby/system/System.hh"
|
||||
#include "params/RubyMemoryControl.hh"
|
||||
#include "sim/sim_object.hh"
|
||||
|
||||
// This constant is part of the definition of tFAW; see
|
||||
// the comments in header to RubyMemoryControl.cc
|
||||
#define ACTIVATE_PER_TFAW 4
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class RubyMemoryControl : public MemoryControl
|
||||
{
|
||||
public:
|
||||
typedef RubyMemoryControlParams Params;
|
||||
RubyMemoryControl(const Params *p);
|
||||
void init();
|
||||
|
||||
~RubyMemoryControl();
|
||||
|
||||
unsigned int drain(Event *de);
|
||||
|
||||
void wakeup();
|
||||
|
||||
void setConsumer(Consumer* consumer_ptr);
|
||||
Consumer* getConsumer() { return m_consumer_ptr; };
|
||||
void setDescription(const std::string& name) { m_description = name; };
|
||||
std::string getDescription() { return m_description; };
|
||||
|
||||
// Called from the directory:
|
||||
void enqueue(const MsgPtr& message, int latency );
|
||||
void enqueueMemRef(MemoryNode& memRef);
|
||||
void dequeue();
|
||||
const Message* peek();
|
||||
MemoryNode peekNode();
|
||||
bool isReady();
|
||||
bool areNSlotsAvailable(int n) { return true; }; // infinite queue length
|
||||
|
||||
//// Called from L3 cache:
|
||||
//void writeBack(physical_address_t addr);
|
||||
|
||||
void printConfig(std::ostream& out);
|
||||
void print(std::ostream& out) const;
|
||||
void clearStats() const;
|
||||
void printStats(std::ostream& out) const;
|
||||
|
||||
const int getBank(const physical_address_t addr) const;
|
||||
const int getRank(const physical_address_t addr) const;
|
||||
|
||||
// not used in Ruby memory controller
|
||||
const int getChannel(const physical_address_t addr) const;
|
||||
const int getRow(const physical_address_t addr) const;
|
||||
|
||||
//added by SS
|
||||
int getBanksPerRank() { return m_banks_per_rank; };
|
||||
int getRanksPerDimm() { return m_ranks_per_dimm; };
|
||||
int getDimmsPerChannel() { return m_dimms_per_channel; }
|
||||
|
||||
|
||||
private:
|
||||
void enqueueToDirectory(MemoryNode req, int latency);
|
||||
const int getRank(int bank) const;
|
||||
bool queueReady(int bank);
|
||||
void issueRequest(int bank);
|
||||
bool issueRefresh(int bank);
|
||||
void markTfaw(int rank);
|
||||
void executeCycle();
|
||||
|
||||
// Private copy constructor and assignment operator
|
||||
RubyMemoryControl (const RubyMemoryControl& obj);
|
||||
RubyMemoryControl& operator=(const RubyMemoryControl& obj);
|
||||
|
||||
// data members
|
||||
Consumer* m_consumer_ptr; // Consumer to signal a wakeup()
|
||||
std::string m_description;
|
||||
int m_msg_counter;
|
||||
|
||||
int m_mem_bus_cycle_multiplier;
|
||||
int m_banks_per_rank;
|
||||
int m_ranks_per_dimm;
|
||||
int m_dimms_per_channel;
|
||||
int m_bank_bit_0;
|
||||
int m_rank_bit_0;
|
||||
int m_dimm_bit_0;
|
||||
unsigned int m_bank_queue_size;
|
||||
int m_bank_busy_time;
|
||||
int m_rank_rank_delay;
|
||||
int m_read_write_delay;
|
||||
int m_basic_bus_busy_time;
|
||||
int m_mem_ctl_latency;
|
||||
int m_refresh_period;
|
||||
int m_mem_random_arbitrate;
|
||||
int m_tFaw;
|
||||
int m_mem_fixed_delay;
|
||||
|
||||
int m_total_banks;
|
||||
int m_total_ranks;
|
||||
int m_refresh_period_system;
|
||||
|
||||
// queues where memory requests live
|
||||
std::list<MemoryNode> m_response_queue;
|
||||
std::list<MemoryNode> m_input_queue;
|
||||
std::list<MemoryNode>* m_bankQueues;
|
||||
|
||||
// Each entry indicates number of address-bus cycles until bank
|
||||
// is reschedulable:
|
||||
int* m_bankBusyCounter;
|
||||
int* m_oldRequest;
|
||||
|
||||
uint64* m_tfaw_shift;
|
||||
int* m_tfaw_count;
|
||||
|
||||
// Each of these indicates number of address-bus cycles until
|
||||
// we can issue a new request of the corresponding type:
|
||||
int m_busBusyCounter_Write;
|
||||
int m_busBusyCounter_ReadNewRank;
|
||||
int m_busBusyCounter_Basic;
|
||||
|
||||
int m_busBusy_WhichRank; // which rank last granted
|
||||
int m_roundRobin; // which bank queue was last granted
|
||||
int m_refresh_count; // cycles until next refresh
|
||||
int m_need_refresh; // set whenever m_refresh_count goes to zero
|
||||
int m_refresh_bank; // which bank to refresh next
|
||||
int m_ageCounter; // age of old requests; to detect starvation
|
||||
int m_idleCount; // watchdog timer for shutting down
|
||||
|
||||
MemCntrlProfiler* m_profiler_ptr;
|
||||
};
|
||||
|
||||
#endif // __MEM_RUBY_SYSTEM_MEMORY_CONTROL_HH__
|
||||
54
src/mem/ruby/system/RubyMemoryControl.py
Normal file
54
src/mem/ruby/system/RubyMemoryControl.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2009 Advanced Micro Devices, Inc.
|
||||
# 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.
|
||||
#
|
||||
# Authors: Steve Reinhardt
|
||||
# Brad Beckmann
|
||||
|
||||
from m5.params import *
|
||||
from m5.SimObject import SimObject
|
||||
from MemoryControl import MemoryControl
|
||||
|
||||
class RubyMemoryControl(MemoryControl):
|
||||
type = 'RubyMemoryControl'
|
||||
cxx_class = 'RubyMemoryControl'
|
||||
version = Param.Int("");
|
||||
|
||||
banks_per_rank = Param.Int(8, "");
|
||||
ranks_per_dimm = Param.Int(2, "");
|
||||
dimms_per_channel = Param.Int(2, "");
|
||||
bank_bit_0 = Param.Int(8, "");
|
||||
rank_bit_0 = Param.Int(11, "");
|
||||
dimm_bit_0 = Param.Int(12, "");
|
||||
bank_queue_size = Param.Int(12, "");
|
||||
bank_busy_time = Param.Int(11, "");
|
||||
rank_rank_delay = Param.Int(1, "");
|
||||
read_write_delay = Param.Int(2, "");
|
||||
basic_bus_busy_time = Param.Int(2, "");
|
||||
mem_ctl_latency = Param.Int(12, "");
|
||||
refresh_period = Param.Int(1560, "");
|
||||
tFaw = Param.Int(0, "");
|
||||
mem_random_arbitrate = Param.Int(0, "");
|
||||
mem_fixed_delay = Param.Int(0, "");
|
||||
@@ -39,6 +39,7 @@ SimObject('DirectoryMemory.py')
|
||||
SimObject('MemoryControl.py')
|
||||
SimObject('WireBuffer.py')
|
||||
SimObject('RubySystem.py')
|
||||
SimObject('RubyMemoryControl.py')
|
||||
|
||||
Source('DMASequencer.cc')
|
||||
Source('DirectoryMemory.cc')
|
||||
@@ -46,6 +47,7 @@ Source('SparseMemory.cc')
|
||||
Source('CacheMemory.cc')
|
||||
Source('MemoryControl.cc')
|
||||
Source('WireBuffer.cc')
|
||||
Source('RubyMemoryControl.cc')
|
||||
Source('MemoryNode.cc')
|
||||
Source('PersistentTable.cc')
|
||||
Source('RubyPort.cc')
|
||||
|
||||
@@ -39,7 +39,7 @@ python_class_map = {"int": "Int",
|
||||
"WireBuffer": "RubyWireBuffer",
|
||||
"Sequencer": "RubySequencer",
|
||||
"DirectoryMemory": "RubyDirectoryMemory",
|
||||
"MemoryControl": "RubyMemoryControl",
|
||||
"MemoryControl": "MemoryControl",
|
||||
"DMASequencer": "DMASequencer"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user