From 7a28c82c6e62ba7e454cdd6e903d2abbea8d0bc2 Mon Sep 17 00:00:00 2001 From: Wendy Elsasser Date: Mon, 20 Jul 2020 23:09:21 -0500 Subject: [PATCH] mem: Clean up Memory Controller Make the actual controller more generic - Rename DRAMCtrl to MemCtrl - Rename DRAMacket to MemPacket - Rename dram_ctrl.cc to mem_ctrl.cc - Rename dram_ctrl.hh to mem_ctrl.hh - Create MemCtrl debug flag Move the memory interface classes/functions to separate files - mem_interface.cc - mem_interface.hh Change-Id: I1acba44c855776343e205e7733a7d8bbba92a82c Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/31654 Reviewed-by: Jason Lowe-Power Maintainer: Jason Lowe-Power Tested-by: kokoro --- configs/common/MemConfig.py | 6 +- configs/common/Options.py | 2 +- configs/dram/lat_mem_rd.py | 2 +- configs/dram/low_power_sweep.py | 4 +- configs/dram/sweep.py | 4 +- configs/example/memcheck.py | 2 +- configs/learning_gem5/part1/simple.py | 2 +- configs/learning_gem5/part1/two_level.py | 2 +- configs/learning_gem5/part2/simple_cache.py | 2 +- configs/learning_gem5/part2/simple_memobj.py | 2 +- configs/learning_gem5/part3/simple_ruby.py | 2 +- configs/nvm/sweep.py | 4 +- configs/nvm/sweep_hybrid.py | 4 +- configs/ruby/Ruby.py | 2 +- src/mem/DRAMInterface.py | 2 +- src/mem/{DRAMCtrl.py => MemCtrl.py} | 12 +- src/mem/MemInterface.py | 2 +- src/mem/NVMInterface.py | 2 +- src/mem/SConscript | 6 +- src/mem/mem_ctrl.cc | 1475 +++++++++++++++++ src/mem/mem_ctrl.hh | 709 ++++++++ src/mem/{dram_ctrl.cc => mem_interface.cc} | 1547 +----------------- src/mem/{dram_ctrl.hh => mem_interface.hh} | 759 +-------- tests/gem5/configs/base_config.py | 4 +- 24 files changed, 2328 insertions(+), 2230 deletions(-) rename src/mem/{DRAMCtrl.py => MemCtrl.py} (94%) create mode 100644 src/mem/mem_ctrl.cc create mode 100644 src/mem/mem_ctrl.hh rename src/mem/{dram_ctrl.cc => mem_interface.cc} (65%) rename src/mem/{dram_ctrl.hh => mem_interface.hh} (61%) diff --git a/configs/common/MemConfig.py b/configs/common/MemConfig.py index 7aa6761b0..941b381d4 100644 --- a/configs/common/MemConfig.py +++ b/configs/common/MemConfig.py @@ -224,11 +224,11 @@ def config_mem(options, system): if opt_mem_type == "HMC_2500_1x32": # The static latency of the vault controllers is estimated # to be smaller than a full DRAM channel controller - mem_ctrl = m5.objects.DRAMCtrl(min_writes_per_switch = 8, + mem_ctrl = m5.objects.MemCtrl(min_writes_per_switch = 8, static_backend_latency = '4ns', static_frontend_latency = '4ns') else: - mem_ctrl = m5.objects.DRAMCtrl() + mem_ctrl = m5.objects.MemCtrl() # Hookup the controller to the interface and add to the list mem_ctrl.dram = dram_intf @@ -246,7 +246,7 @@ def config_mem(options, system): # Create a controller if not sharing a channel with DRAM # in which case the controller has already been created if not opt_hybrid_channel: - mem_ctrl = m5.objects.DRAMCtrl() + mem_ctrl = m5.objects.MemCtrl() mem_ctrl.nvm = nvm_intf mem_ctrls.append(mem_ctrl) diff --git a/configs/common/Options.py b/configs/common/Options.py index 0409fb809..32f8dd94b 100644 --- a/configs/common/Options.py +++ b/configs/common/Options.py @@ -109,7 +109,7 @@ def addNoISAOptions(parser): default="512MB", help="Specify the physical memory size (single memory)") parser.add_option("--enable-dram-powerdown", action="store_true", - help="Enable low-power states in DRAMCtrl") + help="Enable low-power states in DRAMInterface") parser.add_option("--mem-channels-intlv", type="int", default=0, help="Memory channels interleave") diff --git a/configs/dram/lat_mem_rd.py b/configs/dram/lat_mem_rd.py index 9b04e4bd2..4183d4aec 100644 --- a/configs/dram/lat_mem_rd.py +++ b/configs/dram/lat_mem_rd.py @@ -130,7 +130,7 @@ for ctrl in system.mem_ctrls: # the following assumes that we are using the native DRAM # controller, check to be sure - if isinstance(ctrl, m5.objects.DRAMCtrl): + if isinstance(ctrl, m5.objects.MemCtrl): # make the DRAM refresh interval sufficiently infinite to avoid # latency spikes ctrl.tREFI = '100s' diff --git a/configs/dram/low_power_sweep.py b/configs/dram/low_power_sweep.py index 0da2b935b..292b0fa73 100644 --- a/configs/dram/low_power_sweep.py +++ b/configs/dram/low_power_sweep.py @@ -110,8 +110,8 @@ args.elastic_trace_en = 0 MemConfig.config_mem(args, system) # Sanity check for memory controller class. -if not isinstance(system.mem_ctrls[0], m5.objects.DRAMCtrl): - fatal("This script assumes the controller is a DRAMCtrl subclass") +if not isinstance(system.mem_ctrls[0], m5.objects.MemCtrl): + fatal("This script assumes the controller is a MemCtrl subclass") if not isinstance(system.mem_ctrls[0].dram, m5.objects.DRAMInterface): fatal("This script assumes the memory is a DRAMInterface subclass") diff --git a/configs/dram/sweep.py b/configs/dram/sweep.py index a771c5c9f..2f3837339 100644 --- a/configs/dram/sweep.py +++ b/configs/dram/sweep.py @@ -115,8 +115,8 @@ MemConfig.config_mem(options, system) # the following assumes that we are using the native DRAM # controller, check to be sure -if not isinstance(system.mem_ctrls[0], m5.objects.DRAMCtrl): - fatal("This script assumes the controller is a DRAMCtrl subclass") +if not isinstance(system.mem_ctrls[0], m5.objects.MemCtrl): + fatal("This script assumes the controller is a MemCtrl subclass") if not isinstance(system.mem_ctrls[0].dram, m5.objects.DRAMInterface): fatal("This script assumes the memory is a DRAMInterface subclass") diff --git a/configs/example/memcheck.py b/configs/example/memcheck.py index 6bccd54db..bffd5a050 100644 --- a/configs/example/memcheck.py +++ b/configs/example/memcheck.py @@ -217,7 +217,7 @@ cfg_file.close() proto_tester = TrafficGen(config_file = cfg_file_path) # Set up the system along with a DRAM controller -system = System(physmem = DRAMCtrl(dram = DDR3_1600_8x8())) +system = System(physmem = MemCtrl(dram = DDR3_1600_8x8())) system.voltage_domain = VoltageDomain(voltage = '1V') diff --git a/configs/learning_gem5/part1/simple.py b/configs/learning_gem5/part1/simple.py index cfd15bebb..22b2cf7b4 100644 --- a/configs/learning_gem5/part1/simple.py +++ b/configs/learning_gem5/part1/simple.py @@ -77,7 +77,7 @@ if m5.defines.buildEnv['TARGET_ISA'] == "x86": system.cpu.interrupts[0].int_slave = system.membus.master # Create a DDR3 memory controller and connect it to the membus -system.mem_ctrl = DRAMCtrl() +system.mem_ctrl = MemCtrl() system.mem_ctrl.dram = DDR3_1600_8x8() system.mem_ctrl.dram.range = system.mem_ranges[0] system.mem_ctrl.port = system.membus.master diff --git a/configs/learning_gem5/part1/two_level.py b/configs/learning_gem5/part1/two_level.py index 0dbcfc7ff..53e11378b 100644 --- a/configs/learning_gem5/part1/two_level.py +++ b/configs/learning_gem5/part1/two_level.py @@ -132,7 +132,7 @@ if m5.defines.buildEnv['TARGET_ISA'] == "x86": system.system_port = system.membus.slave # Create a DDR3 memory controller -system.mem_ctrl = DRAMCtrl() +system.mem_ctrl = MemCtrl() system.mem_ctrl.dram = DDR3_1600_8x8() system.mem_ctrl.dram.range = system.mem_ranges[0] system.mem_ctrl.port = system.membus.master diff --git a/configs/learning_gem5/part2/simple_cache.py b/configs/learning_gem5/part2/simple_cache.py index fbea73d97..533aa23ad 100644 --- a/configs/learning_gem5/part2/simple_cache.py +++ b/configs/learning_gem5/part2/simple_cache.py @@ -76,7 +76,7 @@ system.cpu.interrupts[0].int_master = system.membus.slave system.cpu.interrupts[0].int_slave = system.membus.master # Create a DDR3 memory controller and connect it to the membus -system.mem_ctrl = DRAMCtrl() +system.mem_ctrl = MemCtrl() system.mem_ctrl.dram = DDR3_1600_8x8() system.mem_ctrl.dram.range = system.mem_ranges[0] system.mem_ctrl.port = system.membus.master diff --git a/configs/learning_gem5/part2/simple_memobj.py b/configs/learning_gem5/part2/simple_memobj.py index e792eb9bb..b7d256189 100644 --- a/configs/learning_gem5/part2/simple_memobj.py +++ b/configs/learning_gem5/part2/simple_memobj.py @@ -74,7 +74,7 @@ system.cpu.interrupts[0].int_master = system.membus.slave system.cpu.interrupts[0].int_slave = system.membus.master # Create a DDR3 memory controller and connect it to the membus -system.mem_ctrl = DRAMCtrl() +system.mem_ctrl = MemCtrl() system.mem_ctrl.dram = DDR3_1600_8x8() system.mem_ctrl.dram.range = system.mem_ranges[0] system.mem_ctrl.port = system.membus.master diff --git a/configs/learning_gem5/part3/simple_ruby.py b/configs/learning_gem5/part3/simple_ruby.py index 7f70a8c7d..760a16892 100644 --- a/configs/learning_gem5/part3/simple_ruby.py +++ b/configs/learning_gem5/part3/simple_ruby.py @@ -68,7 +68,7 @@ system.mem_ranges = [AddrRange('512MB')] # Create an address range system.cpu = [TimingSimpleCPU() for i in range(2)] # Create a DDR3 memory controller and connect it to the membus -system.mem_ctrl = DRAMCtrl() +system.mem_ctrl = MemCtrl() system.mem_ctrl.dram = DDR3_1600_8x8() system.mem_ctrl.dram.range = system.mem_ranges[0] diff --git a/configs/nvm/sweep.py b/configs/nvm/sweep.py index 5bc5819cf..7e0bd9e7f 100644 --- a/configs/nvm/sweep.py +++ b/configs/nvm/sweep.py @@ -113,8 +113,8 @@ MemConfig.config_mem(options, system) # the following assumes that we are using the native memory # controller with an NVM interface, check to be sure -if not isinstance(system.mem_ctrls[0], m5.objects.DRAMCtrl): - fatal("This script assumes the controller is a DRAMCtrl subclass") +if not isinstance(system.mem_ctrls[0], m5.objects.MemCtrl): + fatal("This script assumes the controller is a MemCtrl subclass") if not isinstance(system.mem_ctrls[0].nvm, m5.objects.NVMInterface): fatal("This script assumes the memory is a NVMInterface class") diff --git a/configs/nvm/sweep_hybrid.py b/configs/nvm/sweep_hybrid.py index a2513df91..94edfd452 100644 --- a/configs/nvm/sweep_hybrid.py +++ b/configs/nvm/sweep_hybrid.py @@ -126,8 +126,8 @@ MemConfig.config_mem(options, system) # the following assumes that we are using the native controller # with NVM and DRAM interfaces, check to be sure -if not isinstance(system.mem_ctrls[0], m5.objects.DRAMCtrl): - fatal("This script assumes the controller is a DRAMCtrl subclass") +if not isinstance(system.mem_ctrls[0], m5.objects.MemCtrl): + fatal("This script assumes the controller is a MemCtrl subclass") if not isinstance(system.mem_ctrls[0].dram, m5.objects.DRAMInterface): fatal("This script assumes the first memory is a DRAMInterface subclass") if not isinstance(system.mem_ctrls[0].nvm, m5.objects.NVMInterface): diff --git a/configs/ruby/Ruby.py b/configs/ruby/Ruby.py index 4e382af05..622771aa1 100644 --- a/configs/ruby/Ruby.py +++ b/configs/ruby/Ruby.py @@ -133,7 +133,7 @@ def setup_memory_controllers(system, ruby, dir_cntrls, options): dram_intf = MemConfig.create_mem_intf(mem_type, r, index, options.num_dirs, int(math.log(options.num_dirs, 2)), intlv_size, options.xor_low_bit) - mem_ctrl = m5.objects.DRAMCtrl(dram = dram_intf) + mem_ctrl = m5.objects.MemCtrl(dram = dram_intf) if options.access_backing_store: mem_ctrl.kvm_map=False diff --git a/src/mem/DRAMInterface.py b/src/mem/DRAMInterface.py index aa415feb4..85a609223 100644 --- a/src/mem/DRAMInterface.py +++ b/src/mem/DRAMInterface.py @@ -47,7 +47,7 @@ class PageManage(Enum): vals = ['open', 'open_adaptive', 'close', class DRAMInterface(MemInterface): type = 'DRAMInterface' - cxx_header = "mem/dram_ctrl.hh" + cxx_header = "mem/mem_interface.hh" # scheduler page policy page_policy = Param.PageManage('open_adaptive', "Page management policy") diff --git a/src/mem/DRAMCtrl.py b/src/mem/MemCtrl.py similarity index 94% rename from src/mem/DRAMCtrl.py rename to src/mem/MemCtrl.py index 4ef421bde..e0f34241e 100644 --- a/src/mem/DRAMCtrl.py +++ b/src/mem/MemCtrl.py @@ -46,13 +46,13 @@ from m5.objects.QoSMemCtrl import * # First-Served and a First-Row Hit then First-Come First-Served class MemSched(Enum): vals = ['fcfs', 'frfcfs'] -# DRAMCtrl is a single-channel single-ported DRAM controller model +# MemCtrl is a single-channel single-ported Memory controller model # that aims to model the most important system-level performance -# effects of a DRAM without getting into too much detail of the DRAM -# itself. -class DRAMCtrl(QoSMemCtrl): - type = 'DRAMCtrl' - cxx_header = "mem/dram_ctrl.hh" +# effects of a memory controller, interfacing with media specific +# interfaces +class MemCtrl(QoSMemCtrl): + type = 'MemCtrl' + cxx_header = "mem/mem_ctrl.hh" # single-ported on the system interface side, instantiate with a # bus in front of the controller for multiple ports diff --git a/src/mem/MemInterface.py b/src/mem/MemInterface.py index 3a8b917c3..85fe0a058 100644 --- a/src/mem/MemInterface.py +++ b/src/mem/MemInterface.py @@ -54,7 +54,7 @@ class AddrMap(Enum): vals = ['RoRaBaChCo', 'RoRaBaCoCh', 'RoCoRaBaCh'] class MemInterface(AbstractMemory): type = 'MemInterface' abstract = True - cxx_header = "mem/dram_ctrl.hh" + cxx_header = "mem/mem_interface.hh" # Allow the interface to set required controller buffer sizes # each entry corresponds to a burst for the specific memory channel diff --git a/src/mem/NVMInterface.py b/src/mem/NVMInterface.py index f28dd81a3..3f6fbc43f 100644 --- a/src/mem/NVMInterface.py +++ b/src/mem/NVMInterface.py @@ -43,7 +43,7 @@ from m5.objects.DRAMInterface import AddrMap # are modeled without getting into too much detail of the media itself. class NVMInterface(MemInterface): type = 'NVMInterface' - cxx_header = "mem/dram_ctrl.hh" + cxx_header = "mem/mem_interface.hh" # NVM DIMM could have write buffer to offload writes # define buffer depth, which will limit the number of pending writes diff --git a/src/mem/SConscript b/src/mem/SConscript index 409116c40..cf7adc866 100644 --- a/src/mem/SConscript +++ b/src/mem/SConscript @@ -46,7 +46,7 @@ Source('comm_monitor.cc') SimObject('AbstractMemory.py') SimObject('AddrMapper.py') SimObject('Bridge.py') -SimObject('DRAMCtrl.py') +SimObject('MemCtrl.py') SimObject('MemInterface.py') SimObject('DRAMInterface.py') SimObject('NVMInterface.py') @@ -64,9 +64,10 @@ Source('addr_mapper.cc') Source('bridge.cc') Source('coherent_xbar.cc') Source('drampower.cc') -Source('dram_ctrl.cc') Source('external_master.cc') Source('external_slave.cc') +Source('mem_ctrl.cc') +Source('mem_interface.cc') Source('noncoherent_xbar.cc') Source('packet.cc') Source('port.cc') @@ -120,6 +121,7 @@ DebugFlag('NVM') DebugFlag('ExternalPort') DebugFlag('HtmMem', 'Hardware Transactional Memory (Mem side)') DebugFlag('LLSC') +DebugFlag('MemCtrl') DebugFlag('MMU') DebugFlag('MemoryAccess') DebugFlag('PacketQueue') diff --git a/src/mem/mem_ctrl.cc b/src/mem/mem_ctrl.cc new file mode 100644 index 000000000..66d3c2ab9 --- /dev/null +++ b/src/mem/mem_ctrl.cc @@ -0,0 +1,1475 @@ +/* + * Copyright (c) 2010-2020 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Copyright (c) 2013 Amin Farmahini-Farahani + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "mem/mem_ctrl.hh" + +#include "base/trace.hh" +#include "debug/DRAM.hh" +#include "debug/Drain.hh" +#include "debug/MemCtrl.hh" +#include "debug/NVM.hh" +#include "debug/QOS.hh" +#include "mem/mem_interface.hh" +#include "sim/system.hh" + +using namespace std; + +MemCtrl::MemCtrl(const MemCtrlParams* p) : + QoS::MemCtrl(p), + port(name() + ".port", *this), isTimingMode(false), + retryRdReq(false), retryWrReq(false), + nextReqEvent([this]{ processNextReqEvent(); }, name()), + respondEvent([this]{ processRespondEvent(); }, name()), + dram(p->dram), nvm(p->nvm), + readBufferSize((dram ? dram->readBufferSize : 0) + + (nvm ? nvm->readBufferSize : 0)), + writeBufferSize((dram ? dram->writeBufferSize : 0) + + (nvm ? nvm->writeBufferSize : 0)), + writeHighThreshold(writeBufferSize * p->write_high_thresh_perc / 100.0), + writeLowThreshold(writeBufferSize * p->write_low_thresh_perc / 100.0), + minWritesPerSwitch(p->min_writes_per_switch), + writesThisTime(0), readsThisTime(0), + memSchedPolicy(p->mem_sched_policy), + frontendLatency(p->static_frontend_latency), + backendLatency(p->static_backend_latency), + commandWindow(p->command_window), + nextBurstAt(0), prevArrival(0), + nextReqTime(0), + stats(*this) +{ + DPRINTF(MemCtrl, "Setting up controller\n"); + readQueue.resize(p->qos_priorities); + writeQueue.resize(p->qos_priorities); + + // Hook up interfaces to the controller + if (dram) + dram->setCtrl(this, commandWindow); + if (nvm) + nvm->setCtrl(this, commandWindow); + + fatal_if(!dram && !nvm, "Memory controller must have an interface"); + + // perform a basic check of the write thresholds + if (p->write_low_thresh_perc >= p->write_high_thresh_perc) + fatal("Write buffer low threshold %d must be smaller than the " + "high threshold %d\n", p->write_low_thresh_perc, + p->write_high_thresh_perc); +} + +void +MemCtrl::init() +{ + if (!port.isConnected()) { + fatal("MemCtrl %s is unconnected!\n", name()); + } else { + port.sendRangeChange(); + } +} + +void +MemCtrl::startup() +{ + // remember the memory system mode of operation + isTimingMode = system()->isTimingMode(); + + if (isTimingMode) { + // shift the bus busy time sufficiently far ahead that we never + // have to worry about negative values when computing the time for + // the next request, this will add an insignificant bubble at the + // start of simulation + nextBurstAt = curTick() + (dram ? dram->commandOffset() : + nvm->commandOffset()); + } +} + +Tick +MemCtrl::recvAtomic(PacketPtr pkt) +{ + DPRINTF(MemCtrl, "recvAtomic: %s 0x%x\n", + pkt->cmdString(), pkt->getAddr()); + + panic_if(pkt->cacheResponding(), "Should not see packets where cache " + "is responding"); + + Tick latency = 0; + // do the actual memory access and turn the packet into a response + if (dram && dram->getAddrRange().contains(pkt->getAddr())) { + dram->access(pkt); + + if (pkt->hasData()) { + // this value is not supposed to be accurate, just enough to + // keep things going, mimic a closed page + latency = dram->accessLatency(); + } + } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) { + nvm->access(pkt); + + if (pkt->hasData()) { + // this value is not supposed to be accurate, just enough to + // keep things going, mimic a closed page + latency = nvm->accessLatency(); + } + } else { + panic("Can't handle address range for packet %s\n", + pkt->print()); + } + + return latency; +} + +bool +MemCtrl::readQueueFull(unsigned int neededEntries) const +{ + DPRINTF(MemCtrl, + "Read queue limit %d, current size %d, entries needed %d\n", + readBufferSize, totalReadQueueSize + respQueue.size(), + neededEntries); + + auto rdsize_new = totalReadQueueSize + respQueue.size() + neededEntries; + return rdsize_new > readBufferSize; +} + +bool +MemCtrl::writeQueueFull(unsigned int neededEntries) const +{ + DPRINTF(MemCtrl, + "Write queue limit %d, current size %d, entries needed %d\n", + writeBufferSize, totalWriteQueueSize, neededEntries); + + auto wrsize_new = (totalWriteQueueSize + neededEntries); + return wrsize_new > writeBufferSize; +} + +void +MemCtrl::addToReadQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram) +{ + // only add to the read queue here. whenever the request is + // eventually done, set the readyTime, and call schedule() + assert(!pkt->isWrite()); + + assert(pkt_count != 0); + + // if the request size is larger than burst size, the pkt is split into + // multiple packets + // Note if the pkt starting address is not aligened to burst size, the + // address of first packet is kept unaliged. Subsequent packets + // are aligned to burst size boundaries. This is to ensure we accurately + // check read packets against packets in write queue. + const Addr base_addr = pkt->getAddr(); + Addr addr = base_addr; + unsigned pktsServicedByWrQ = 0; + BurstHelper* burst_helper = NULL; + + uint32_t burst_size = is_dram ? dram->bytesPerBurst() : + nvm->bytesPerBurst(); + for (int cnt = 0; cnt < pkt_count; ++cnt) { + unsigned size = std::min((addr | (burst_size - 1)) + 1, + base_addr + pkt->getSize()) - addr; + stats.readPktSize[ceilLog2(size)]++; + stats.readBursts++; + stats.masterReadAccesses[pkt->masterId()]++; + + // First check write buffer to see if the data is already at + // the controller + bool foundInWrQ = false; + Addr burst_addr = burstAlign(addr, is_dram); + // if the burst address is not present then there is no need + // looking any further + if (isInWriteQueue.find(burst_addr) != isInWriteQueue.end()) { + for (const auto& vec : writeQueue) { + for (const auto& p : vec) { + // check if the read is subsumed in the write queue + // packet we are looking at + if (p->addr <= addr && + ((addr + size) <= (p->addr + p->size))) { + + foundInWrQ = true; + stats.servicedByWrQ++; + pktsServicedByWrQ++; + DPRINTF(MemCtrl, + "Read to addr %lld with size %d serviced by " + "write queue\n", + addr, size); + stats.bytesReadWrQ += burst_size; + break; + } + } + } + } + + // If not found in the write q, make a memory packet and + // push it onto the read queue + if (!foundInWrQ) { + + // Make the burst helper for split packets + if (pkt_count > 1 && burst_helper == NULL) { + DPRINTF(MemCtrl, "Read to addr %lld translates to %d " + "memory requests\n", pkt->getAddr(), pkt_count); + burst_helper = new BurstHelper(pkt_count); + } + + MemPacket* mem_pkt; + if (is_dram) { + mem_pkt = dram->decodePacket(pkt, addr, size, true, true); + // increment read entries of the rank + dram->setupRank(mem_pkt->rank, true); + } else { + mem_pkt = nvm->decodePacket(pkt, addr, size, true, false); + // Increment count to trigger issue of non-deterministic read + nvm->setupRank(mem_pkt->rank, true); + // Default readyTime to Max; will be reset once read is issued + mem_pkt->readyTime = MaxTick; + } + mem_pkt->burstHelper = burst_helper; + + assert(!readQueueFull(1)); + stats.rdQLenPdf[totalReadQueueSize + respQueue.size()]++; + + DPRINTF(MemCtrl, "Adding to read queue\n"); + + readQueue[mem_pkt->qosValue()].push_back(mem_pkt); + + // log packet + logRequest(MemCtrl::READ, pkt->masterId(), pkt->qosValue(), + mem_pkt->addr, 1); + + // Update stats + stats.avgRdQLen = totalReadQueueSize + respQueue.size(); + } + + // Starting address of next memory pkt (aligned to burst boundary) + addr = (addr | (burst_size - 1)) + 1; + } + + // If all packets are serviced by write queue, we send the repsonse back + if (pktsServicedByWrQ == pkt_count) { + accessAndRespond(pkt, frontendLatency); + return; + } + + // Update how many split packets are serviced by write queue + if (burst_helper != NULL) + burst_helper->burstsServiced = pktsServicedByWrQ; + + // If we are not already scheduled to get a request out of the + // queue, do so now + if (!nextReqEvent.scheduled()) { + DPRINTF(MemCtrl, "Request scheduled immediately\n"); + schedule(nextReqEvent, curTick()); + } +} + +void +MemCtrl::addToWriteQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram) +{ + // only add to the write queue here. whenever the request is + // eventually done, set the readyTime, and call schedule() + assert(pkt->isWrite()); + + // if the request size is larger than burst size, the pkt is split into + // multiple packets + const Addr base_addr = pkt->getAddr(); + Addr addr = base_addr; + uint32_t burst_size = is_dram ? dram->bytesPerBurst() : + nvm->bytesPerBurst(); + for (int cnt = 0; cnt < pkt_count; ++cnt) { + unsigned size = std::min((addr | (burst_size - 1)) + 1, + base_addr + pkt->getSize()) - addr; + stats.writePktSize[ceilLog2(size)]++; + stats.writeBursts++; + stats.masterWriteAccesses[pkt->masterId()]++; + + // see if we can merge with an existing item in the write + // queue and keep track of whether we have merged or not + bool merged = isInWriteQueue.find(burstAlign(addr, is_dram)) != + isInWriteQueue.end(); + + // if the item was not merged we need to create a new write + // and enqueue it + if (!merged) { + MemPacket* mem_pkt; + if (is_dram) { + mem_pkt = dram->decodePacket(pkt, addr, size, false, true); + dram->setupRank(mem_pkt->rank, false); + } else { + mem_pkt = nvm->decodePacket(pkt, addr, size, false, false); + nvm->setupRank(mem_pkt->rank, false); + } + assert(totalWriteQueueSize < writeBufferSize); + stats.wrQLenPdf[totalWriteQueueSize]++; + + DPRINTF(MemCtrl, "Adding to write queue\n"); + + writeQueue[mem_pkt->qosValue()].push_back(mem_pkt); + isInWriteQueue.insert(burstAlign(addr, is_dram)); + + // log packet + logRequest(MemCtrl::WRITE, pkt->masterId(), pkt->qosValue(), + mem_pkt->addr, 1); + + assert(totalWriteQueueSize == isInWriteQueue.size()); + + // Update stats + stats.avgWrQLen = totalWriteQueueSize; + + } else { + DPRINTF(MemCtrl, + "Merging write burst with existing queue entry\n"); + + // keep track of the fact that this burst effectively + // disappeared as it was merged with an existing one + stats.mergedWrBursts++; + } + + // Starting address of next memory pkt (aligned to burst_size boundary) + addr = (addr | (burst_size - 1)) + 1; + } + + // we do not wait for the writes to be send to the actual memory, + // but instead take responsibility for the consistency here and + // snoop the write queue for any upcoming reads + // @todo, if a pkt size is larger than burst size, we might need a + // different front end latency + accessAndRespond(pkt, frontendLatency); + + // If we are not already scheduled to get a request out of the + // queue, do so now + if (!nextReqEvent.scheduled()) { + DPRINTF(MemCtrl, "Request scheduled immediately\n"); + schedule(nextReqEvent, curTick()); + } +} + +void +MemCtrl::printQs() const +{ +#if TRACING_ON + DPRINTF(MemCtrl, "===READ QUEUE===\n\n"); + for (const auto& queue : readQueue) { + for (const auto& packet : queue) { + DPRINTF(MemCtrl, "Read %lu\n", packet->addr); + } + } + + DPRINTF(MemCtrl, "\n===RESP QUEUE===\n\n"); + for (const auto& packet : respQueue) { + DPRINTF(MemCtrl, "Response %lu\n", packet->addr); + } + + DPRINTF(MemCtrl, "\n===WRITE QUEUE===\n\n"); + for (const auto& queue : writeQueue) { + for (const auto& packet : queue) { + DPRINTF(MemCtrl, "Write %lu\n", packet->addr); + } + } +#endif // TRACING_ON +} + +bool +MemCtrl::recvTimingReq(PacketPtr pkt) +{ + // This is where we enter from the outside world + DPRINTF(MemCtrl, "recvTimingReq: request %s addr %lld size %d\n", + pkt->cmdString(), pkt->getAddr(), pkt->getSize()); + + panic_if(pkt->cacheResponding(), "Should not see packets where cache " + "is responding"); + + panic_if(!(pkt->isRead() || pkt->isWrite()), + "Should only see read and writes at memory controller\n"); + + // Calc avg gap between requests + if (prevArrival != 0) { + stats.totGap += curTick() - prevArrival; + } + prevArrival = curTick(); + + // What type of media does this packet access? + bool is_dram; + if (dram && dram->getAddrRange().contains(pkt->getAddr())) { + is_dram = true; + } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) { + is_dram = false; + } else { + panic("Can't handle address range for packet %s\n", + pkt->print()); + } + + + // Find out how many memory packets a pkt translates to + // If the burst size is equal or larger than the pkt size, then a pkt + // translates to only one memory packet. Otherwise, a pkt translates to + // multiple memory packets + unsigned size = pkt->getSize(); + uint32_t burst_size = is_dram ? dram->bytesPerBurst() : + nvm->bytesPerBurst(); + unsigned offset = pkt->getAddr() & (burst_size - 1); + unsigned int pkt_count = divCeil(offset + size, burst_size); + + // run the QoS scheduler and assign a QoS priority value to the packet + qosSchedule( { &readQueue, &writeQueue }, burst_size, pkt); + + // check local buffers and do not accept if full + if (pkt->isWrite()) { + assert(size != 0); + if (writeQueueFull(pkt_count)) { + DPRINTF(MemCtrl, "Write queue full, not accepting\n"); + // remember that we have to retry this port + retryWrReq = true; + stats.numWrRetry++; + return false; + } else { + addToWriteQueue(pkt, pkt_count, is_dram); + stats.writeReqs++; + stats.bytesWrittenSys += size; + } + } else { + assert(pkt->isRead()); + assert(size != 0); + if (readQueueFull(pkt_count)) { + DPRINTF(MemCtrl, "Read queue full, not accepting\n"); + // remember that we have to retry this port + retryRdReq = true; + stats.numRdRetry++; + return false; + } else { + addToReadQueue(pkt, pkt_count, is_dram); + stats.readReqs++; + stats.bytesReadSys += size; + } + } + + return true; +} + +void +MemCtrl::processRespondEvent() +{ + DPRINTF(MemCtrl, + "processRespondEvent(): Some req has reached its readyTime\n"); + + MemPacket* mem_pkt = respQueue.front(); + + if (mem_pkt->isDram()) { + // media specific checks and functions when read response is complete + dram->respondEvent(mem_pkt->rank); + } + + if (mem_pkt->burstHelper) { + // it is a split packet + mem_pkt->burstHelper->burstsServiced++; + if (mem_pkt->burstHelper->burstsServiced == + mem_pkt->burstHelper->burstCount) { + // we have now serviced all children packets of a system packet + // so we can now respond to the requester + // @todo we probably want to have a different front end and back + // end latency for split packets + accessAndRespond(mem_pkt->pkt, frontendLatency + backendLatency); + delete mem_pkt->burstHelper; + mem_pkt->burstHelper = NULL; + } + } else { + // it is not a split packet + accessAndRespond(mem_pkt->pkt, frontendLatency + backendLatency); + } + + delete respQueue.front(); + respQueue.pop_front(); + + if (!respQueue.empty()) { + assert(respQueue.front()->readyTime >= curTick()); + assert(!respondEvent.scheduled()); + schedule(respondEvent, respQueue.front()->readyTime); + } else { + // if there is nothing left in any queue, signal a drain + if (drainState() == DrainState::Draining && + !totalWriteQueueSize && !totalReadQueueSize && + allIntfDrained()) { + + DPRINTF(Drain, "Controller done draining\n"); + signalDrainDone(); + } else if (mem_pkt->isDram()) { + // check the refresh state and kick the refresh event loop + // into action again if banks already closed and just waiting + // for read to complete + dram->checkRefreshState(mem_pkt->rank); + } + } + + // We have made a location in the queue available at this point, + // so if there is a read that was forced to wait, retry now + if (retryRdReq) { + retryRdReq = false; + port.sendRetryReq(); + } +} + +MemPacketQueue::iterator +MemCtrl::chooseNext(MemPacketQueue& queue, Tick extra_col_delay) +{ + // This method does the arbitration between requests. + + MemPacketQueue::iterator ret = queue.end(); + + if (!queue.empty()) { + if (queue.size() == 1) { + // available rank corresponds to state refresh idle + MemPacket* mem_pkt = *(queue.begin()); + if (packetReady(mem_pkt)) { + ret = queue.begin(); + DPRINTF(MemCtrl, "Single request, going to a free rank\n"); + } else { + DPRINTF(MemCtrl, "Single request, going to a busy rank\n"); + } + } else if (memSchedPolicy == Enums::fcfs) { + // check if there is a packet going to a free rank + for (auto i = queue.begin(); i != queue.end(); ++i) { + MemPacket* mem_pkt = *i; + if (packetReady(mem_pkt)) { + ret = i; + break; + } + } + } else if (memSchedPolicy == Enums::frfcfs) { + ret = chooseNextFRFCFS(queue, extra_col_delay); + } else { + panic("No scheduling policy chosen\n"); + } + } + return ret; +} + +MemPacketQueue::iterator +MemCtrl::chooseNextFRFCFS(MemPacketQueue& queue, Tick extra_col_delay) +{ + auto selected_pkt_it = queue.end(); + Tick col_allowed_at = MaxTick; + + // time we need to issue a column command to be seamless + const Tick min_col_at = std::max(nextBurstAt + extra_col_delay, curTick()); + + // find optimal packet for each interface + if (dram && nvm) { + // create 2nd set of parameters for NVM + auto nvm_pkt_it = queue.end(); + Tick nvm_col_at = MaxTick; + + // Select packet by default to give priority if both + // can issue at the same time or seamlessly + std::tie(selected_pkt_it, col_allowed_at) = + dram->chooseNextFRFCFS(queue, min_col_at); + std::tie(nvm_pkt_it, nvm_col_at) = + nvm->chooseNextFRFCFS(queue, min_col_at); + + // Compare DRAM and NVM and select NVM if it can issue + // earlier than the DRAM packet + if (col_allowed_at > nvm_col_at) { + selected_pkt_it = nvm_pkt_it; + } + } else if (dram) { + std::tie(selected_pkt_it, col_allowed_at) = + dram->chooseNextFRFCFS(queue, min_col_at); + } else if (nvm) { + std::tie(selected_pkt_it, col_allowed_at) = + nvm->chooseNextFRFCFS(queue, min_col_at); + } + + if (selected_pkt_it == queue.end()) { + DPRINTF(MemCtrl, "%s no available packets found\n", __func__); + } + + return selected_pkt_it; +} + +void +MemCtrl::accessAndRespond(PacketPtr pkt, Tick static_latency) +{ + DPRINTF(MemCtrl, "Responding to Address %lld.. \n",pkt->getAddr()); + + bool needsResponse = pkt->needsResponse(); + // do the actual memory access which also turns the packet into a + // response + if (dram && dram->getAddrRange().contains(pkt->getAddr())) { + dram->access(pkt); + } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) { + nvm->access(pkt); + } else { + panic("Can't handle address range for packet %s\n", + pkt->print()); + } + + // turn packet around to go back to requester if response expected + if (needsResponse) { + // access already turned the packet into a response + assert(pkt->isResponse()); + // response_time consumes the static latency and is charged also + // with headerDelay that takes into account the delay provided by + // the xbar and also the payloadDelay that takes into account the + // number of data beats. + Tick response_time = curTick() + static_latency + pkt->headerDelay + + pkt->payloadDelay; + // Here we reset the timing of the packet before sending it out. + pkt->headerDelay = pkt->payloadDelay = 0; + + // queue the packet in the response queue to be sent out after + // the static latency has passed + port.schedTimingResp(pkt, response_time); + } else { + // @todo the packet is going to be deleted, and the MemPacket + // is still having a pointer to it + pendingDelete.reset(pkt); + } + + DPRINTF(MemCtrl, "Done\n"); + + return; +} + +void +MemCtrl::pruneBurstTick() +{ + auto it = burstTicks.begin(); + while (it != burstTicks.end()) { + auto current_it = it++; + if (curTick() > *current_it) { + DPRINTF(MemCtrl, "Removing burstTick for %d\n", *current_it); + burstTicks.erase(current_it); + } + } +} + +Tick +MemCtrl::getBurstWindow(Tick cmd_tick) +{ + // get tick aligned to burst window + Tick burst_offset = cmd_tick % commandWindow; + return (cmd_tick - burst_offset); +} + +Tick +MemCtrl::verifySingleCmd(Tick cmd_tick, Tick max_cmds_per_burst) +{ + // start with assumption that there is no contention on command bus + Tick cmd_at = cmd_tick; + + // get tick aligned to burst window + Tick burst_tick = getBurstWindow(cmd_tick); + + // verify that we have command bandwidth to issue the command + // if not, iterate over next window(s) until slot found + while (burstTicks.count(burst_tick) >= max_cmds_per_burst) { + DPRINTF(MemCtrl, "Contention found on command bus at %d\n", + burst_tick); + burst_tick += commandWindow; + cmd_at = burst_tick; + } + + // add command into burst window and return corresponding Tick + burstTicks.insert(burst_tick); + return cmd_at; +} + +Tick +MemCtrl::verifyMultiCmd(Tick cmd_tick, Tick max_cmds_per_burst, + Tick max_multi_cmd_split) +{ + // start with assumption that there is no contention on command bus + Tick cmd_at = cmd_tick; + + // get tick aligned to burst window + Tick burst_tick = getBurstWindow(cmd_tick); + + // Command timing requirements are from 2nd command + // Start with assumption that 2nd command will issue at cmd_at and + // find prior slot for 1st command to issue + // Given a maximum latency of max_multi_cmd_split between the commands, + // find the burst at the maximum latency prior to cmd_at + Tick burst_offset = 0; + Tick first_cmd_offset = cmd_tick % commandWindow; + while (max_multi_cmd_split > (first_cmd_offset + burst_offset)) { + burst_offset += commandWindow; + } + // get the earliest burst aligned address for first command + // ensure that the time does not go negative + Tick first_cmd_tick = burst_tick - std::min(burst_offset, burst_tick); + + // Can required commands issue? + bool first_can_issue = false; + bool second_can_issue = false; + // verify that we have command bandwidth to issue the command(s) + while (!first_can_issue || !second_can_issue) { + bool same_burst = (burst_tick == first_cmd_tick); + auto first_cmd_count = burstTicks.count(first_cmd_tick); + auto second_cmd_count = same_burst ? first_cmd_count + 1 : + burstTicks.count(burst_tick); + + first_can_issue = first_cmd_count < max_cmds_per_burst; + second_can_issue = second_cmd_count < max_cmds_per_burst; + + if (!second_can_issue) { + DPRINTF(MemCtrl, "Contention (cmd2) found on command bus at %d\n", + burst_tick); + burst_tick += commandWindow; + cmd_at = burst_tick; + } + + // Verify max_multi_cmd_split isn't violated when command 2 is shifted + // If commands initially were issued in same burst, they are + // now in consecutive bursts and can still issue B2B + bool gap_violated = !same_burst && + ((burst_tick - first_cmd_tick) > max_multi_cmd_split); + + if (!first_can_issue || (!second_can_issue && gap_violated)) { + DPRINTF(MemCtrl, "Contention (cmd1) found on command bus at %d\n", + first_cmd_tick); + first_cmd_tick += commandWindow; + } + } + + // Add command to burstTicks + burstTicks.insert(burst_tick); + burstTicks.insert(first_cmd_tick); + + return cmd_at; +} + +bool +MemCtrl::inReadBusState(bool next_state) const +{ + // check the bus state + if (next_state) { + // use busStateNext to get the state that will be used + // for the next burst + return (busStateNext == MemCtrl::READ); + } else { + return (busState == MemCtrl::READ); + } +} + +bool +MemCtrl::inWriteBusState(bool next_state) const +{ + // check the bus state + if (next_state) { + // use busStateNext to get the state that will be used + // for the next burst + return (busStateNext == MemCtrl::WRITE); + } else { + return (busState == MemCtrl::WRITE); + } +} + +void +MemCtrl::doBurstAccess(MemPacket* mem_pkt) +{ + // first clean up the burstTick set, removing old entries + // before adding new entries for next burst + pruneBurstTick(); + + // When was command issued? + Tick cmd_at; + + // Issue the next burst and update bus state to reflect + // when previous command was issued + if (mem_pkt->isDram()) { + std::vector& queue = selQueue(mem_pkt->isRead()); + std::tie(cmd_at, nextBurstAt) = + dram->doBurstAccess(mem_pkt, nextBurstAt, queue); + + // Update timing for NVM ranks if NVM is configured on this channel + if (nvm) + nvm->addRankToRankDelay(cmd_at); + + } else { + std::tie(cmd_at, nextBurstAt) = + nvm->doBurstAccess(mem_pkt, nextBurstAt); + + // Update timing for NVM ranks if NVM is configured on this channel + if (dram) + dram->addRankToRankDelay(cmd_at); + + } + + DPRINTF(MemCtrl, "Access to %lld, ready at %lld next burst at %lld.\n", + mem_pkt->addr, mem_pkt->readyTime, nextBurstAt); + + // Update the minimum timing between the requests, this is a + // conservative estimate of when we have to schedule the next + // request to not introduce any unecessary bubbles. In most cases + // we will wake up sooner than we have to. + nextReqTime = nextBurstAt - (dram ? dram->commandOffset() : + nvm->commandOffset()); + + + // Update the common bus stats + if (mem_pkt->isRead()) { + ++readsThisTime; + // Update latency stats + stats.masterReadTotalLat[mem_pkt->masterId()] += + mem_pkt->readyTime - mem_pkt->entryTime; + stats.masterReadBytes[mem_pkt->masterId()] += mem_pkt->size; + } else { + ++writesThisTime; + stats.masterWriteBytes[mem_pkt->masterId()] += mem_pkt->size; + stats.masterWriteTotalLat[mem_pkt->masterId()] += + mem_pkt->readyTime - mem_pkt->entryTime; + } +} + +void +MemCtrl::processNextReqEvent() +{ + // transition is handled by QoS algorithm if enabled + if (turnPolicy) { + // select bus state - only done if QoS algorithms are in use + busStateNext = selectNextBusState(); + } + + // detect bus state change + bool switched_cmd_type = (busState != busStateNext); + // record stats + recordTurnaroundStats(); + + DPRINTF(MemCtrl, "QoS Turnarounds selected state %s %s\n", + (busState==MemCtrl::READ)?"READ":"WRITE", + switched_cmd_type?"[turnaround triggered]":""); + + if (switched_cmd_type) { + if (busState == MemCtrl::READ) { + DPRINTF(MemCtrl, + "Switching to writes after %d reads with %d reads " + "waiting\n", readsThisTime, totalReadQueueSize); + stats.rdPerTurnAround.sample(readsThisTime); + readsThisTime = 0; + } else { + DPRINTF(MemCtrl, + "Switching to reads after %d writes with %d writes " + "waiting\n", writesThisTime, totalWriteQueueSize); + stats.wrPerTurnAround.sample(writesThisTime); + writesThisTime = 0; + } + } + + // updates current state + busState = busStateNext; + + if (nvm) { + for (auto queue = readQueue.rbegin(); + queue != readQueue.rend(); ++queue) { + // select non-deterministic NVM read to issue + // assume that we have the command bandwidth to issue this along + // with additional RD/WR burst with needed bank operations + if (nvm->readsWaitingToIssue()) { + // select non-deterministic NVM read to issue + nvm->chooseRead(*queue); + } + } + } + + // check ranks for refresh/wakeup - uses busStateNext, so done after + // turnaround decisions + // Default to busy status and update based on interface specifics + bool dram_busy = dram ? dram->isBusy() : true; + bool nvm_busy = true; + bool all_writes_nvm = false; + if (nvm) { + all_writes_nvm = nvm->numWritesQueued == totalWriteQueueSize; + bool read_queue_empty = totalReadQueueSize == 0; + nvm_busy = nvm->isBusy(read_queue_empty, all_writes_nvm); + } + // Default state of unused interface is 'true' + // Simply AND the busy signals to determine if system is busy + if (dram_busy && nvm_busy) { + // if all ranks are refreshing wait for them to finish + // and stall this state machine without taking any further + // action, and do not schedule a new nextReqEvent + return; + } + + // when we get here it is either a read or a write + if (busState == READ) { + + // track if we should switch or not + bool switch_to_writes = false; + + if (totalReadQueueSize == 0) { + // In the case there is no read request to go next, + // trigger writes if we have passed the low threshold (or + // if we are draining) + if (!(totalWriteQueueSize == 0) && + (drainState() == DrainState::Draining || + totalWriteQueueSize > writeLowThreshold)) { + + DPRINTF(MemCtrl, + "Switching to writes due to read queue empty\n"); + switch_to_writes = true; + } else { + // check if we are drained + // not done draining until in PWR_IDLE state + // ensuring all banks are closed and + // have exited low power states + if (drainState() == DrainState::Draining && + respQueue.empty() && allIntfDrained()) { + + DPRINTF(Drain, "MemCtrl controller done draining\n"); + signalDrainDone(); + } + + // nothing to do, not even any point in scheduling an + // event for the next request + return; + } + } else { + + bool read_found = false; + MemPacketQueue::iterator to_read; + uint8_t prio = numPriorities(); + + for (auto queue = readQueue.rbegin(); + queue != readQueue.rend(); ++queue) { + + prio--; + + DPRINTF(QOS, + "Checking READ queue [%d] priority [%d elements]\n", + prio, queue->size()); + + // Figure out which read request goes next + // If we are changing command type, incorporate the minimum + // bus turnaround delay which will be rank to rank delay + to_read = chooseNext((*queue), switched_cmd_type ? + minWriteToReadDataGap() : 0); + + if (to_read != queue->end()) { + // candidate read found + read_found = true; + break; + } + } + + // if no read to an available rank is found then return + // at this point. There could be writes to the available ranks + // which are above the required threshold. However, to + // avoid adding more complexity to the code, return and wait + // for a refresh event to kick things into action again. + if (!read_found) { + DPRINTF(MemCtrl, "No Reads Found - exiting\n"); + return; + } + + auto mem_pkt = *to_read; + + doBurstAccess(mem_pkt); + + // sanity check + assert(mem_pkt->size <= (mem_pkt->isDram() ? + dram->bytesPerBurst() : + nvm->bytesPerBurst()) ); + assert(mem_pkt->readyTime >= curTick()); + + // log the response + logResponse(MemCtrl::READ, (*to_read)->masterId(), + mem_pkt->qosValue(), mem_pkt->getAddr(), 1, + mem_pkt->readyTime - mem_pkt->entryTime); + + + // Insert into response queue. It will be sent back to the + // requester at its readyTime + if (respQueue.empty()) { + assert(!respondEvent.scheduled()); + schedule(respondEvent, mem_pkt->readyTime); + } else { + assert(respQueue.back()->readyTime <= mem_pkt->readyTime); + assert(respondEvent.scheduled()); + } + + respQueue.push_back(mem_pkt); + + // we have so many writes that we have to transition + // don't transition if the writeRespQueue is full and + // there are no other writes that can issue + if ((totalWriteQueueSize > writeHighThreshold) && + !(nvm && all_writes_nvm && nvm->writeRespQueueFull())) { + switch_to_writes = true; + } + + // remove the request from the queue + // the iterator is no longer valid . + readQueue[mem_pkt->qosValue()].erase(to_read); + } + + // switching to writes, either because the read queue is empty + // and the writes have passed the low threshold (or we are + // draining), or because the writes hit the hight threshold + if (switch_to_writes) { + // transition to writing + busStateNext = WRITE; + } + } else { + + bool write_found = false; + MemPacketQueue::iterator to_write; + uint8_t prio = numPriorities(); + + for (auto queue = writeQueue.rbegin(); + queue != writeQueue.rend(); ++queue) { + + prio--; + + DPRINTF(QOS, + "Checking WRITE queue [%d] priority [%d elements]\n", + prio, queue->size()); + + // If we are changing command type, incorporate the minimum + // bus turnaround delay + to_write = chooseNext((*queue), + switched_cmd_type ? minReadToWriteDataGap() : 0); + + if (to_write != queue->end()) { + write_found = true; + break; + } + } + + // if there are no writes to a rank that is available to service + // requests (i.e. rank is in refresh idle state) are found then + // return. There could be reads to the available ranks. However, to + // avoid adding more complexity to the code, return at this point and + // wait for a refresh event to kick things into action again. + if (!write_found) { + DPRINTF(MemCtrl, "No Writes Found - exiting\n"); + return; + } + + auto mem_pkt = *to_write; + + // sanity check + assert(mem_pkt->size <= (mem_pkt->isDram() ? + dram->bytesPerBurst() : + nvm->bytesPerBurst()) ); + + doBurstAccess(mem_pkt); + + isInWriteQueue.erase(burstAlign(mem_pkt->addr, mem_pkt->isDram())); + + // log the response + logResponse(MemCtrl::WRITE, mem_pkt->masterId(), + mem_pkt->qosValue(), mem_pkt->getAddr(), 1, + mem_pkt->readyTime - mem_pkt->entryTime); + + + // remove the request from the queue - the iterator is no longer valid + writeQueue[mem_pkt->qosValue()].erase(to_write); + + delete mem_pkt; + + // If we emptied the write queue, or got sufficiently below the + // threshold (using the minWritesPerSwitch as the hysteresis) and + // are not draining, or we have reads waiting and have done enough + // writes, then switch to reads. + // If we are interfacing to NVM and have filled the writeRespQueue, + // with only NVM writes in Q, then switch to reads + bool below_threshold = + totalWriteQueueSize + minWritesPerSwitch < writeLowThreshold; + + if (totalWriteQueueSize == 0 || + (below_threshold && drainState() != DrainState::Draining) || + (totalReadQueueSize && writesThisTime >= minWritesPerSwitch) || + (totalReadQueueSize && nvm && nvm->writeRespQueueFull() && + all_writes_nvm)) { + + // turn the bus back around for reads again + busStateNext = MemCtrl::READ; + + // note that the we switch back to reads also in the idle + // case, which eventually will check for any draining and + // also pause any further scheduling if there is really + // nothing to do + } + } + // It is possible that a refresh to another rank kicks things back into + // action before reaching this point. + if (!nextReqEvent.scheduled()) + schedule(nextReqEvent, std::max(nextReqTime, curTick())); + + // If there is space available and we have writes waiting then let + // them retry. This is done here to ensure that the retry does not + // cause a nextReqEvent to be scheduled before we do so as part of + // the next request processing + if (retryWrReq && totalWriteQueueSize < writeBufferSize) { + retryWrReq = false; + port.sendRetryReq(); + } +} + +bool +MemCtrl::packetReady(MemPacket* pkt) +{ + return (pkt->isDram() ? + dram->burstReady(pkt) : nvm->burstReady(pkt)); +} + +Tick +MemCtrl::minReadToWriteDataGap() +{ + Tick dram_min = dram ? dram->minReadToWriteDataGap() : MaxTick; + Tick nvm_min = nvm ? nvm->minReadToWriteDataGap() : MaxTick; + return std::min(dram_min, nvm_min); +} + +Tick +MemCtrl::minWriteToReadDataGap() +{ + Tick dram_min = dram ? dram->minWriteToReadDataGap() : MaxTick; + Tick nvm_min = nvm ? nvm->minWriteToReadDataGap() : MaxTick; + return std::min(dram_min, nvm_min); +} + +Addr +MemCtrl::burstAlign(Addr addr, bool is_dram) const +{ + if (is_dram) + return (addr & ~(Addr(dram->bytesPerBurst() - 1))); + else + return (addr & ~(Addr(nvm->bytesPerBurst() - 1))); +} + +MemCtrl::CtrlStats::CtrlStats(MemCtrl &_ctrl) + : Stats::Group(&_ctrl), + ctrl(_ctrl), + + ADD_STAT(readReqs, "Number of read requests accepted"), + ADD_STAT(writeReqs, "Number of write requests accepted"), + + ADD_STAT(readBursts, + "Number of controller read bursts, " + "including those serviced by the write queue"), + ADD_STAT(writeBursts, + "Number of controller write bursts, " + "including those merged in the write queue"), + ADD_STAT(servicedByWrQ, + "Number of controller read bursts serviced by the write queue"), + ADD_STAT(mergedWrBursts, + "Number of controller write bursts merged with an existing one"), + + ADD_STAT(neitherReadNorWriteReqs, + "Number of requests that are neither read nor write"), + + ADD_STAT(avgRdQLen, "Average read queue length when enqueuing"), + ADD_STAT(avgWrQLen, "Average write queue length when enqueuing"), + + ADD_STAT(numRdRetry, "Number of times read queue was full causing retry"), + ADD_STAT(numWrRetry, "Number of times write queue was full causing retry"), + + ADD_STAT(readPktSize, "Read request sizes (log2)"), + ADD_STAT(writePktSize, "Write request sizes (log2)"), + + ADD_STAT(rdQLenPdf, "What read queue length does an incoming req see"), + ADD_STAT(wrQLenPdf, "What write queue length does an incoming req see"), + + ADD_STAT(rdPerTurnAround, + "Reads before turning the bus around for writes"), + ADD_STAT(wrPerTurnAround, + "Writes before turning the bus around for reads"), + + ADD_STAT(bytesReadWrQ, "Total number of bytes read from write queue"), + ADD_STAT(bytesReadSys, "Total read bytes from the system interface side"), + ADD_STAT(bytesWrittenSys, + "Total written bytes from the system interface side"), + + ADD_STAT(avgRdBWSys, "Average system read bandwidth in MiByte/s"), + ADD_STAT(avgWrBWSys, "Average system write bandwidth in MiByte/s"), + + ADD_STAT(totGap, "Total gap between requests"), + ADD_STAT(avgGap, "Average gap between requests"), + + ADD_STAT(masterReadBytes, "Per-master bytes read from memory"), + ADD_STAT(masterWriteBytes, "Per-master bytes write to memory"), + ADD_STAT(masterReadRate, + "Per-master bytes read from memory rate (Bytes/sec)"), + ADD_STAT(masterWriteRate, + "Per-master bytes write to memory rate (Bytes/sec)"), + ADD_STAT(masterReadAccesses, + "Per-master read serviced memory accesses"), + ADD_STAT(masterWriteAccesses, + "Per-master write serviced memory accesses"), + ADD_STAT(masterReadTotalLat, + "Per-master read total memory access latency"), + ADD_STAT(masterWriteTotalLat, + "Per-master write total memory access latency"), + ADD_STAT(masterReadAvgLat, + "Per-master read average memory access latency"), + ADD_STAT(masterWriteAvgLat, + "Per-master write average memory access latency") + +{ +} + +void +MemCtrl::CtrlStats::regStats() +{ + using namespace Stats; + + assert(ctrl.system()); + const auto max_masters = ctrl.system()->maxMasters(); + + avgRdQLen.precision(2); + avgWrQLen.precision(2); + + readPktSize.init(ceilLog2(ctrl.system()->cacheLineSize()) + 1); + writePktSize.init(ceilLog2(ctrl.system()->cacheLineSize()) + 1); + + rdQLenPdf.init(ctrl.readBufferSize); + wrQLenPdf.init(ctrl.writeBufferSize); + + rdPerTurnAround + .init(ctrl.readBufferSize) + .flags(nozero); + wrPerTurnAround + .init(ctrl.writeBufferSize) + .flags(nozero); + + avgRdBWSys.precision(2); + avgWrBWSys.precision(2); + avgGap.precision(2); + + // per-master bytes read and written to memory + masterReadBytes + .init(max_masters) + .flags(nozero | nonan); + + masterWriteBytes + .init(max_masters) + .flags(nozero | nonan); + + // per-master bytes read and written to memory rate + masterReadRate + .flags(nozero | nonan) + .precision(12); + + masterReadAccesses + .init(max_masters) + .flags(nozero); + + masterWriteAccesses + .init(max_masters) + .flags(nozero); + + masterReadTotalLat + .init(max_masters) + .flags(nozero | nonan); + + masterReadAvgLat + .flags(nonan) + .precision(2); + + masterWriteRate + .flags(nozero | nonan) + .precision(12); + + masterWriteTotalLat + .init(max_masters) + .flags(nozero | nonan); + + masterWriteAvgLat + .flags(nonan) + .precision(2); + + for (int i = 0; i < max_masters; i++) { + const std::string master = ctrl.system()->getMasterName(i); + masterReadBytes.subname(i, master); + masterReadRate.subname(i, master); + masterWriteBytes.subname(i, master); + masterWriteRate.subname(i, master); + masterReadAccesses.subname(i, master); + masterWriteAccesses.subname(i, master); + masterReadTotalLat.subname(i, master); + masterReadAvgLat.subname(i, master); + masterWriteTotalLat.subname(i, master); + masterWriteAvgLat.subname(i, master); + } + + // Formula stats + avgRdBWSys = (bytesReadSys / 1000000) / simSeconds; + avgWrBWSys = (bytesWrittenSys / 1000000) / simSeconds; + + avgGap = totGap / (readReqs + writeReqs); + + masterReadRate = masterReadBytes / simSeconds; + masterWriteRate = masterWriteBytes / simSeconds; + masterReadAvgLat = masterReadTotalLat / masterReadAccesses; + masterWriteAvgLat = masterWriteTotalLat / masterWriteAccesses; +} + +void +MemCtrl::recvFunctional(PacketPtr pkt) +{ + if (dram && dram->getAddrRange().contains(pkt->getAddr())) { + // rely on the abstract memory + dram->functionalAccess(pkt); + } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) { + // rely on the abstract memory + nvm->functionalAccess(pkt); + } else { + panic("Can't handle address range for packet %s\n", + pkt->print()); + } +} + +Port & +MemCtrl::getPort(const string &if_name, PortID idx) +{ + if (if_name != "port") { + return QoS::MemCtrl::getPort(if_name, idx); + } else { + return port; + } +} + +bool +MemCtrl::allIntfDrained() const +{ + // ensure dram is in power down and refresh IDLE states + bool dram_drained = !dram || dram->allRanksDrained(); + // No outstanding NVM writes + // All other queues verified as needed with calling logic + bool nvm_drained = !nvm || nvm->allRanksDrained(); + return (dram_drained && nvm_drained); +} + +DrainState +MemCtrl::drain() +{ + // if there is anything in any of our internal queues, keep track + // of that as well + if (!(!totalWriteQueueSize && !totalReadQueueSize && respQueue.empty() && + allIntfDrained())) { + + DPRINTF(Drain, "Memory controller not drained, write: %d, read: %d," + " resp: %d\n", totalWriteQueueSize, totalReadQueueSize, + respQueue.size()); + + // the only queue that is not drained automatically over time + // is the write queue, thus kick things into action if needed + if (!totalWriteQueueSize && !nextReqEvent.scheduled()) { + schedule(nextReqEvent, curTick()); + } + + if (dram) + dram->drainRanks(); + + return DrainState::Draining; + } else { + return DrainState::Drained; + } +} + +void +MemCtrl::drainResume() +{ + if (!isTimingMode && system()->isTimingMode()) { + // if we switched to timing mode, kick things into action, + // and behave as if we restored from a checkpoint + startup(); + dram->startup(); + } else if (isTimingMode && !system()->isTimingMode()) { + // if we switch from timing mode, stop the refresh events to + // not cause issues with KVM + if (dram) + dram->suspend(); + } + + // update the mode + isTimingMode = system()->isTimingMode(); +} + +MemCtrl::MemoryPort::MemoryPort(const std::string& name, MemCtrl& _ctrl) + : QueuedSlavePort(name, &_ctrl, queue), queue(_ctrl, *this, true), + ctrl(_ctrl) +{ } + +AddrRangeList +MemCtrl::MemoryPort::getAddrRanges() const +{ + AddrRangeList ranges; + if (ctrl.dram) { + DPRINTF(DRAM, "Pushing DRAM ranges to port\n"); + ranges.push_back(ctrl.dram->getAddrRange()); + } + if (ctrl.nvm) { + DPRINTF(NVM, "Pushing NVM ranges to port\n"); + ranges.push_back(ctrl.nvm->getAddrRange()); + } + return ranges; +} + +void +MemCtrl::MemoryPort::recvFunctional(PacketPtr pkt) +{ + pkt->pushLabel(ctrl.name()); + + if (!queue.trySatisfyFunctional(pkt)) { + // Default implementation of SimpleTimingPort::recvFunctional() + // calls recvAtomic() and throws away the latency; we can save a + // little here by just not calculating the latency. + ctrl.recvFunctional(pkt); + } + + pkt->popLabel(); +} + +Tick +MemCtrl::MemoryPort::recvAtomic(PacketPtr pkt) +{ + return ctrl.recvAtomic(pkt); +} + +bool +MemCtrl::MemoryPort::recvTimingReq(PacketPtr pkt) +{ + // pass it to the memory controller + return ctrl.recvTimingReq(pkt); +} + +MemCtrl* +MemCtrlParams::create() +{ + return new MemCtrl(this); +} diff --git a/src/mem/mem_ctrl.hh b/src/mem/mem_ctrl.hh new file mode 100644 index 000000000..834cb5c5e --- /dev/null +++ b/src/mem/mem_ctrl.hh @@ -0,0 +1,709 @@ +/* + * Copyright (c) 2012-2020 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Copyright (c) 2013 Amin Farmahini-Farahani + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * MemCtrl declaration + */ + +#ifndef __MEM_CTRL_HH__ +#define __MEM_CTRL_HH__ + +#include +#include +#include +#include +#include + +#include "base/callback.hh" +#include "base/statistics.hh" +#include "enums/MemSched.hh" +#include "mem/qos/mem_ctrl.hh" +#include "mem/qport.hh" +#include "params/MemCtrl.hh" +#include "sim/eventq.hh" + +class DRAMInterface; +class NVMInterface; + +/** + * A burst helper helps organize and manage a packet that is larger than + * the memory burst size. A system packet that is larger than the burst size + * is split into multiple packets and all those packets point to + * a single burst helper such that we know when the whole packet is served. + */ +class BurstHelper +{ + public: + + /** Number of bursts requred for a system packet **/ + const unsigned int burstCount; + + /** Number of bursts serviced so far for a system packet **/ + unsigned int burstsServiced; + + BurstHelper(unsigned int _burstCount) + : burstCount(_burstCount), burstsServiced(0) + { } +}; + +/** + * A memory packet stores packets along with the timestamp of when + * the packet entered the queue, and also the decoded address. + */ +class MemPacket +{ + public: + + /** When did request enter the controller */ + const Tick entryTime; + + /** When will request leave the controller */ + Tick readyTime; + + /** This comes from the outside world */ + const PacketPtr pkt; + + /** MasterID associated with the packet */ + const MasterID _masterId; + + const bool read; + + /** Does this packet access DRAM?*/ + const bool dram; + + /** Will be populated by address decoder */ + const uint8_t rank; + const uint8_t bank; + const uint32_t row; + + /** + * Bank id is calculated considering banks in all the ranks + * eg: 2 ranks each with 8 banks, then bankId = 0 --> rank0, bank0 and + * bankId = 8 --> rank1, bank0 + */ + const uint16_t bankId; + + /** + * The starting address of the packet. + * This address could be unaligned to burst size boundaries. The + * reason is to keep the address offset so we can accurately check + * incoming read packets with packets in the write queue. + */ + Addr addr; + + /** + * The size of this dram packet in bytes + * It is always equal or smaller than the burst size + */ + unsigned int size; + + /** + * A pointer to the BurstHelper if this MemPacket is a split packet + * If not a split packet (common case), this is set to NULL + */ + BurstHelper* burstHelper; + + /** + * QoS value of the encapsulated packet read at queuing time + */ + uint8_t _qosValue; + + /** + * Set the packet QoS value + * (interface compatibility with Packet) + */ + inline void qosValue(const uint8_t qv) { _qosValue = qv; } + + /** + * Get the packet QoS value + * (interface compatibility with Packet) + */ + inline uint8_t qosValue() const { return _qosValue; } + + /** + * Get the packet MasterID + * (interface compatibility with Packet) + */ + inline MasterID masterId() const { return _masterId; } + + /** + * Get the packet size + * (interface compatibility with Packet) + */ + inline unsigned int getSize() const { return size; } + + /** + * Get the packet address + * (interface compatibility with Packet) + */ + inline Addr getAddr() const { return addr; } + + /** + * Return true if its a read packet + * (interface compatibility with Packet) + */ + inline bool isRead() const { return read; } + + /** + * Return true if its a write packet + * (interface compatibility with Packet) + */ + inline bool isWrite() const { return !read; } + + /** + * Return true if its a DRAM access + */ + inline bool isDram() const { return dram; } + + MemPacket(PacketPtr _pkt, bool is_read, bool is_dram, uint8_t _rank, + uint8_t _bank, uint32_t _row, uint16_t bank_id, Addr _addr, + unsigned int _size) + : entryTime(curTick()), readyTime(curTick()), pkt(_pkt), + _masterId(pkt->masterId()), + read(is_read), dram(is_dram), rank(_rank), bank(_bank), row(_row), + bankId(bank_id), addr(_addr), size(_size), burstHelper(NULL), + _qosValue(_pkt->qosValue()) + { } + +}; + +// The memory packets are store in a multiple dequeue structure, +// based on their QoS priority +typedef std::deque MemPacketQueue; + + +/** + * The memory controller is a single-channel memory controller capturing + * the most important timing constraints associated with a + * contemporary controller. For multi-channel memory systems, the controller + * is combined with a crossbar model, with the channel address + * interleaving taking part in the crossbar. + * + * As a basic design principle, this controller + * model is not cycle callable, but instead uses events to: 1) decide + * when new decisions can be made, 2) when resources become available, + * 3) when things are to be considered done, and 4) when to send + * things back. The controller interfaces to media specific interfaces + * to enable flexible topoloties. + * Through these simple principles, the model delivers + * high performance, and lots of flexibility, allowing users to + * evaluate the system impact of a wide range of memory technologies. + * + * For more details, please see Hansson et al, "Simulating DRAM + * controllers for future system architecture exploration", + * Proc. ISPASS, 2014. If you use this model as part of your research + * please cite the paper. + * + */ +class MemCtrl : public QoS::MemCtrl +{ + private: + + // For now, make use of a queued slave port to avoid dealing with + // flow control for the responses being sent back + class MemoryPort : public QueuedSlavePort + { + + RespPacketQueue queue; + MemCtrl& ctrl; + + public: + + MemoryPort(const std::string& name, MemCtrl& _ctrl); + + protected: + + Tick recvAtomic(PacketPtr pkt); + + void recvFunctional(PacketPtr pkt); + + bool recvTimingReq(PacketPtr); + + virtual AddrRangeList getAddrRanges() const; + + }; + + /** + * Our incoming port, for a multi-ported controller add a crossbar + * in front of it + */ + MemoryPort port; + + /** + * Remember if the memory system is in timing mode + */ + bool isTimingMode; + + /** + * Remember if we have to retry a request when available. + */ + bool retryRdReq; + bool retryWrReq; + + /** + * Bunch of things requires to setup "events" in gem5 + * When event "respondEvent" occurs for example, the method + * processRespondEvent is called; no parameters are allowed + * in these methods + */ + void processNextReqEvent(); + EventFunctionWrapper nextReqEvent; + + void processRespondEvent(); + EventFunctionWrapper respondEvent; + + /** + * Check if the read queue has room for more entries + * + * @param pkt_count The number of entries needed in the read queue + * @return true if read queue is full, false otherwise + */ + bool readQueueFull(unsigned int pkt_count) const; + + /** + * Check if the write queue has room for more entries + * + * @param pkt_count The number of entries needed in the write queue + * @return true if write queue is full, false otherwise + */ + bool writeQueueFull(unsigned int pkt_count) const; + + /** + * When a new read comes in, first check if the write q has a + * pending request to the same address.\ If not, decode the + * address to populate rank/bank/row, create one or mutliple + * "mem_pkt", and push them to the back of the read queue.\ + * If this is the only + * read request in the system, schedule an event to start + * servicing it. + * + * @param pkt The request packet from the outside world + * @param pkt_count The number of memory bursts the pkt + * @param is_dram Does this packet access DRAM? + * translate to. If pkt size is larger then one full burst, + * then pkt_count is greater than one. + */ + void addToReadQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram); + + /** + * Decode the incoming pkt, create a mem_pkt and push to the + * back of the write queue. \If the write q length is more than + * the threshold specified by the user, ie the queue is beginning + * to get full, stop reads, and start draining writes. + * + * @param pkt The request packet from the outside world + * @param pkt_count The number of memory bursts the pkt + * @param is_dram Does this packet access DRAM? + * translate to. If pkt size is larger then one full burst, + * then pkt_count is greater than one. + */ + void addToWriteQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram); + + /** + * Actually do the burst based on media specific access function. + * Update bus statistics when complete. + * + * @param mem_pkt The memory packet created from the outside world pkt + */ + void doBurstAccess(MemPacket* mem_pkt); + + /** + * When a packet reaches its "readyTime" in the response Q, + * use the "access()" method in AbstractMemory to actually + * create the response packet, and send it back to the outside + * world requestor. + * + * @param pkt The packet from the outside world + * @param static_latency Static latency to add before sending the packet + */ + void accessAndRespond(PacketPtr pkt, Tick static_latency); + + /** + * Determine if there is a packet that can issue. + * + * @param pkt The packet to evaluate + */ + bool packetReady(MemPacket* pkt); + + /** + * Calculate the minimum delay used when scheduling a read-to-write + * transision. + * @param return minimum delay + */ + Tick minReadToWriteDataGap(); + + /** + * Calculate the minimum delay used when scheduling a write-to-read + * transision. + * @param return minimum delay + */ + Tick minWriteToReadDataGap(); + + /** + * The memory schduler/arbiter - picks which request needs to + * go next, based on the specified policy such as FCFS or FR-FCFS + * and moves it to the head of the queue. + * Prioritizes accesses to the same rank as previous burst unless + * controller is switching command type. + * + * @param queue Queued requests to consider + * @param extra_col_delay Any extra delay due to a read/write switch + * @return an iterator to the selected packet, else queue.end() + */ + MemPacketQueue::iterator chooseNext(MemPacketQueue& queue, + Tick extra_col_delay); + + /** + * For FR-FCFS policy reorder the read/write queue depending on row buffer + * hits and earliest bursts available in memory + * + * @param queue Queued requests to consider + * @param extra_col_delay Any extra delay due to a read/write switch + * @return an iterator to the selected packet, else queue.end() + */ + MemPacketQueue::iterator chooseNextFRFCFS(MemPacketQueue& queue, + Tick extra_col_delay); + + /** + * Calculate burst window aligned tick + * + * @param cmd_tick Initial tick of command + * @return burst window aligned tick + */ + Tick getBurstWindow(Tick cmd_tick); + + /** + * Used for debugging to observe the contents of the queues. + */ + void printQs() const; + + /** + * Burst-align an address. + * + * @param addr The potentially unaligned address + * @param is_dram Does this packet access DRAM? + * + * @return An address aligned to a memory burst + */ + Addr burstAlign(Addr addr, bool is_dram) const; + + /** + * The controller's main read and write queues, + * with support for QoS reordering + */ + std::vector readQueue; + std::vector writeQueue; + + /** + * To avoid iterating over the write queue to check for + * overlapping transactions, maintain a set of burst addresses + * that are currently queued. Since we merge writes to the same + * location we never have more than one address to the same burst + * address. + */ + std::unordered_set isInWriteQueue; + + /** + * Response queue where read packets wait after we're done working + * with them, but it's not time to send the response yet. The + * responses are stored separately mostly to keep the code clean + * and help with events scheduling. For all logical purposes such + * as sizing the read queue, this and the main read queue need to + * be added together. + */ + std::deque respQueue; + + /** + * Holds count of commands issued in burst window starting at + * defined Tick. This is used to ensure that the command bandwidth + * does not exceed the allowable media constraints. + */ + std::unordered_multiset burstTicks; + + /** + * Create pointer to interface of the actual dram media when connected + */ + DRAMInterface* const dram; + + /** + * Create pointer to interface of the actual nvm media when connected + */ + NVMInterface* const nvm; + + /** + * The following are basic design parameters of the memory + * controller, and are initialized based on parameter values. + * The rowsPerBank is determined based on the capacity, number of + * ranks and banks, the burst size, and the row buffer size. + */ + const uint32_t readBufferSize; + const uint32_t writeBufferSize; + const uint32_t writeHighThreshold; + const uint32_t writeLowThreshold; + const uint32_t minWritesPerSwitch; + uint32_t writesThisTime; + uint32_t readsThisTime; + + /** + * Memory controller configuration initialized based on parameter + * values. + */ + Enums::MemSched memSchedPolicy; + + /** + * Pipeline latency of the controller frontend. The frontend + * contribution is added to writes (that complete when they are in + * the write buffer) and reads that are serviced the write buffer. + */ + const Tick frontendLatency; + + /** + * Pipeline latency of the backend and PHY. Along with the + * frontend contribution, this latency is added to reads serviced + * by the memory. + */ + const Tick backendLatency; + + /** + * Length of a command window, used to check + * command bandwidth + */ + const Tick commandWindow; + + /** + * Till when must we wait before issuing next RD/WR burst? + */ + Tick nextBurstAt; + + Tick prevArrival; + + /** + * The soonest you have to start thinking about the next request + * is the longest access time that can occur before + * nextBurstAt. Assuming you need to precharge, open a new row, + * and access, it is tRP + tRCD + tCL. + */ + Tick nextReqTime; + + struct CtrlStats : public Stats::Group + { + CtrlStats(MemCtrl &ctrl); + + void regStats() override; + + MemCtrl &ctrl; + + // All statistics that the model needs to capture + Stats::Scalar readReqs; + Stats::Scalar writeReqs; + Stats::Scalar readBursts; + Stats::Scalar writeBursts; + Stats::Scalar servicedByWrQ; + Stats::Scalar mergedWrBursts; + Stats::Scalar neitherReadNorWriteReqs; + // Average queue lengths + Stats::Average avgRdQLen; + Stats::Average avgWrQLen; + + Stats::Scalar numRdRetry; + Stats::Scalar numWrRetry; + Stats::Vector readPktSize; + Stats::Vector writePktSize; + Stats::Vector rdQLenPdf; + Stats::Vector wrQLenPdf; + Stats::Histogram rdPerTurnAround; + Stats::Histogram wrPerTurnAround; + + Stats::Scalar bytesReadWrQ; + Stats::Scalar bytesReadSys; + Stats::Scalar bytesWrittenSys; + // Average bandwidth + Stats::Formula avgRdBWSys; + Stats::Formula avgWrBWSys; + + Stats::Scalar totGap; + Stats::Formula avgGap; + + // per-master bytes read and written to memory + Stats::Vector masterReadBytes; + Stats::Vector masterWriteBytes; + + // per-master bytes read and written to memory rate + Stats::Formula masterReadRate; + Stats::Formula masterWriteRate; + + // per-master read and write serviced memory accesses + Stats::Vector masterReadAccesses; + Stats::Vector masterWriteAccesses; + + // per-master read and write total memory access latency + Stats::Vector masterReadTotalLat; + Stats::Vector masterWriteTotalLat; + + // per-master raed and write average memory access latency + Stats::Formula masterReadAvgLat; + Stats::Formula masterWriteAvgLat; + }; + + CtrlStats stats; + + /** + * Upstream caches need this packet until true is returned, so + * hold it for deletion until a subsequent call + */ + std::unique_ptr pendingDelete; + + /** + * Select either the read or write queue + * + * @param is_read The current burst is a read, select read queue + * @return a reference to the appropriate queue + */ + std::vector& selQueue(bool is_read) + { + return (is_read ? readQueue : writeQueue); + }; + + /** + * Remove commands that have already issued from burstTicks + */ + void pruneBurstTick(); + + public: + + MemCtrl(const MemCtrlParams* p); + + /** + * Ensure that all interfaced have drained commands + * + * @return bool flag, set once drain complete + */ + bool allIntfDrained() const; + + DrainState drain() override; + + /** + * Check for command bus contention for single cycle command. + * If there is contention, shift command to next burst. + * Check verifies that the commands issued per burst is less + * than a defined max number, maxCommandsPerWindow. + * Therefore, contention per cycle is not verified and instead + * is done based on a burst window. + * + * @param cmd_tick Initial tick of command, to be verified + * @param max_cmds_per_burst Number of commands that can issue + * in a burst window + * @return tick for command issue without contention + */ + Tick verifySingleCmd(Tick cmd_tick, Tick max_cmds_per_burst); + + /** + * Check for command bus contention for multi-cycle (2 currently) + * command. If there is contention, shift command(s) to next burst. + * Check verifies that the commands issued per burst is less + * than a defined max number, maxCommandsPerWindow. + * Therefore, contention per cycle is not verified and instead + * is done based on a burst window. + * + * @param cmd_tick Initial tick of command, to be verified + * @param max_multi_cmd_split Maximum delay between commands + * @param max_cmds_per_burst Number of commands that can issue + * in a burst window + * @return tick for command issue without contention + */ + Tick verifyMultiCmd(Tick cmd_tick, Tick max_cmds_per_burst, + Tick max_multi_cmd_split = 0); + + /** + * Is there a respondEvent scheduled? + * + * @return true if event is scheduled + */ + bool respondEventScheduled() const { return respondEvent.scheduled(); } + + /** + * Is there a read/write burst Event scheduled? + * + * @return true if event is scheduled + */ + bool requestEventScheduled() const { return nextReqEvent.scheduled(); } + + /** + * restart the controller + * This can be used by interfaces to restart the + * scheduler after maintainence commands complete + * + * @param Tick to schedule next event + */ + void restartScheduler(Tick tick) { schedule(nextReqEvent, tick); } + + /** + * Check the current direction of the memory channel + * + * @param next_state Check either the current or next bus state + * @return True when bus is currently in a read state + */ + bool inReadBusState(bool next_state) const; + + /** + * Check the current direction of the memory channel + * + * @param next_state Check either the current or next bus state + * @return True when bus is currently in a write state + */ + bool inWriteBusState(bool next_state) const; + + Port &getPort(const std::string &if_name, + PortID idx=InvalidPortID) override; + + virtual void init() override; + virtual void startup() override; + virtual void drainResume() override; + + protected: + + Tick recvAtomic(PacketPtr pkt); + void recvFunctional(PacketPtr pkt); + bool recvTimingReq(PacketPtr pkt); + +}; + +#endif //__MEM_CTRL_HH__ diff --git a/src/mem/dram_ctrl.cc b/src/mem/mem_interface.cc similarity index 65% rename from src/mem/dram_ctrl.cc rename to src/mem/mem_interface.cc index 55451a2e1..7817c4ac3 100644 --- a/src/mem/dram_ctrl.cc +++ b/src/mem/mem_interface.cc @@ -38,147 +38,48 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "mem/dram_ctrl.hh" +#include "mem/mem_interface.hh" #include "base/bitfield.hh" #include "base/trace.hh" #include "debug/DRAM.hh" #include "debug/DRAMPower.hh" #include "debug/DRAMState.hh" -#include "debug/Drain.hh" #include "debug/NVM.hh" -#include "debug/QOS.hh" -#include "params/DRAMInterface.hh" -#include "params/NVMInterface.hh" #include "sim/system.hh" using namespace std; using namespace Data; -DRAMCtrl::DRAMCtrl(const DRAMCtrlParams* p) : - QoS::MemCtrl(p), - port(name() + ".port", *this), isTimingMode(false), - retryRdReq(false), retryWrReq(false), - nextReqEvent([this]{ processNextReqEvent(); }, name()), - respondEvent([this]{ processRespondEvent(); }, name()), - dram(p->dram), nvm(p->nvm), - readBufferSize((dram ? dram->readBufferSize : 0) + - (nvm ? nvm->readBufferSize : 0)), - writeBufferSize((dram ? dram->writeBufferSize : 0) + - (nvm ? nvm->writeBufferSize : 0)), - writeHighThreshold(writeBufferSize * p->write_high_thresh_perc / 100.0), - writeLowThreshold(writeBufferSize * p->write_low_thresh_perc / 100.0), - minWritesPerSwitch(p->min_writes_per_switch), - writesThisTime(0), readsThisTime(0), - memSchedPolicy(p->mem_sched_policy), - frontendLatency(p->static_frontend_latency), - backendLatency(p->static_backend_latency), - commandWindow(p->command_window), - nextBurstAt(0), prevArrival(0), - nextReqTime(0), - stats(*this) -{ - DPRINTF(DRAM, "Setting up controller\n"); - readQueue.resize(p->qos_priorities); - writeQueue.resize(p->qos_priorities); - - // Hook up interfaces to the controller - if (dram) - dram->setCtrl(this, commandWindow); - if (nvm) - nvm->setCtrl(this, commandWindow); - - fatal_if(!dram && !nvm, "Memory controller must have an interface"); - - // perform a basic check of the write thresholds - if (p->write_low_thresh_perc >= p->write_high_thresh_perc) - fatal("Write buffer low threshold %d must be smaller than the " - "high threshold %d\n", p->write_low_thresh_perc, - p->write_high_thresh_perc); -} - -void -DRAMCtrl::init() -{ - if (!port.isConnected()) { - fatal("DRAMCtrl %s is unconnected!\n", name()); - } else { - port.sendRangeChange(); - } -} +MemInterface::MemInterface(const MemInterfaceParams* _p) + : AbstractMemory(_p), + addrMapping(_p->addr_mapping), + burstSize((_p->devices_per_rank * _p->burst_length * + _p->device_bus_width) / 8), + deviceSize(_p->device_size), + deviceRowBufferSize(_p->device_rowbuffer_size), + devicesPerRank(_p->devices_per_rank), + rowBufferSize(devicesPerRank * deviceRowBufferSize), + burstsPerRowBuffer(rowBufferSize / burstSize), + burstsPerStripe(range.interleaved() ? + range.granularity() / burstSize : 1), + ranksPerChannel(_p->ranks_per_channel), + banksPerRank(_p->banks_per_rank), rowsPerBank(0), + tCK(_p->tCK), tCS(_p->tCS), tBURST(_p->tBURST), + tRTW(_p->tRTW), + tWTR(_p->tWTR), + readBufferSize(_p->read_buffer_size), + writeBufferSize(_p->write_buffer_size) +{} void -DRAMCtrl::startup() +MemInterface::setCtrl(MemCtrl* _ctrl, unsigned int command_window) { - // remember the memory system mode of operation - isTimingMode = system()->isTimingMode(); - - if (isTimingMode) { - // shift the bus busy time sufficiently far ahead that we never - // have to worry about negative values when computing the time for - // the next request, this will add an insignificant bubble at the - // start of simulation - nextBurstAt = curTick() + (dram ? dram->commandOffset() : - nvm->commandOffset()); - } -} - -Tick -DRAMCtrl::recvAtomic(PacketPtr pkt) -{ - DPRINTF(DRAM, "recvAtomic: %s 0x%x\n", pkt->cmdString(), pkt->getAddr()); - - panic_if(pkt->cacheResponding(), "Should not see packets where cache " - "is responding"); - - Tick latency = 0; - // do the actual memory access and turn the packet into a response - if (dram && dram->getAddrRange().contains(pkt->getAddr())) { - dram->access(pkt); - - if (pkt->hasData()) { - // this value is not supposed to be accurate, just enough to - // keep things going, mimic a closed page - latency = dram->accessLatency(); - } - } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) { - nvm->access(pkt); - - if (pkt->hasData()) { - // this value is not supposed to be accurate, just enough to - // keep things going, mimic a closed page - latency = nvm->accessLatency(); - } - } else { - panic("Can't handle address range for packet %s\n", - pkt->print()); - } - - return latency; -} - -bool -DRAMCtrl::readQueueFull(unsigned int neededEntries) const -{ - DPRINTF(DRAM, "Read queue limit %d, current size %d, entries needed %d\n", - readBufferSize, totalReadQueueSize + respQueue.size(), - neededEntries); - - auto rdsize_new = totalReadQueueSize + respQueue.size() + neededEntries; - return rdsize_new > readBufferSize; -} - -bool -DRAMCtrl::writeQueueFull(unsigned int neededEntries) const -{ - DPRINTF(DRAM, "Write queue limit %d, current size %d, entries needed %d\n", - writeBufferSize, totalWriteQueueSize, neededEntries); - - auto wrsize_new = (totalWriteQueueSize + neededEntries); - return wrsize_new > writeBufferSize; + ctrl = _ctrl; + maxCommandsPerWindow = command_window / tCK; } -DRAMPacket* +MemPacket* MemInterface::decodePacket(const PacketPtr pkt, Addr pkt_addr, unsigned size, bool is_read, bool is_dram) { @@ -260,465 +161,12 @@ MemInterface::decodePacket(const PacketPtr pkt, Addr pkt_addr, // later uint16_t bank_id = banksPerRank * rank + bank; - return new DRAMPacket(pkt, is_read, is_dram, rank, bank, row, bank_id, + return new MemPacket(pkt, is_read, is_dram, rank, bank, row, bank_id, pkt_addr, size); } -void -DRAMCtrl::addToReadQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram) -{ - // only add to the read queue here. whenever the request is - // eventually done, set the readyTime, and call schedule() - assert(!pkt->isWrite()); - - assert(pkt_count != 0); - - // if the request size is larger than burst size, the pkt is split into - // multiple packets - // Note if the pkt starting address is not aligened to burst size, the - // address of first packet is kept unaliged. Subsequent packets - // are aligned to burst size boundaries. This is to ensure we accurately - // check read packets against packets in write queue. - const Addr base_addr = pkt->getAddr(); - Addr addr = base_addr; - unsigned pktsServicedByWrQ = 0; - BurstHelper* burst_helper = NULL; - - uint32_t burst_size = is_dram ? dram->bytesPerBurst() : - nvm->bytesPerBurst(); - for (int cnt = 0; cnt < pkt_count; ++cnt) { - unsigned size = std::min((addr | (burst_size - 1)) + 1, - base_addr + pkt->getSize()) - addr; - stats.readPktSize[ceilLog2(size)]++; - stats.readBursts++; - stats.masterReadAccesses[pkt->masterId()]++; - - // First check write buffer to see if the data is already at - // the controller - bool foundInWrQ = false; - Addr burst_addr = burstAlign(addr, is_dram); - // if the burst address is not present then there is no need - // looking any further - if (isInWriteQueue.find(burst_addr) != isInWriteQueue.end()) { - for (const auto& vec : writeQueue) { - for (const auto& p : vec) { - // check if the read is subsumed in the write queue - // packet we are looking at - if (p->addr <= addr && - ((addr + size) <= (p->addr + p->size))) { - - foundInWrQ = true; - stats.servicedByWrQ++; - pktsServicedByWrQ++; - DPRINTF(DRAM, - "Read to addr %lld with size %d serviced by " - "write queue\n", - addr, size); - stats.bytesReadWrQ += burst_size; - break; - } - } - } - } - - // If not found in the write q, make a DRAM packet and - // push it onto the read queue - if (!foundInWrQ) { - - // Make the burst helper for split packets - if (pkt_count > 1 && burst_helper == NULL) { - DPRINTF(DRAM, "Read to addr %lld translates to %d " - "memory requests\n", pkt->getAddr(), pkt_count); - burst_helper = new BurstHelper(pkt_count); - } - - DRAMPacket* dram_pkt; - if (is_dram) { - dram_pkt = dram->decodePacket(pkt, addr, size, true, true); - // increment read entries of the rank - dram->setupRank(dram_pkt->rank, true); - } else { - dram_pkt = nvm->decodePacket(pkt, addr, size, true, false); - // Increment count to trigger issue of non-deterministic read - nvm->setupRank(dram_pkt->rank, true); - // Default readyTime to Max; will be reset once read is issued - dram_pkt->readyTime = MaxTick; - } - dram_pkt->burstHelper = burst_helper; - - assert(!readQueueFull(1)); - stats.rdQLenPdf[totalReadQueueSize + respQueue.size()]++; - - DPRINTF(DRAM, "Adding to read queue\n"); - - readQueue[dram_pkt->qosValue()].push_back(dram_pkt); - - // log packet - logRequest(MemCtrl::READ, pkt->masterId(), pkt->qosValue(), - dram_pkt->addr, 1); - - // Update stats - stats.avgRdQLen = totalReadQueueSize + respQueue.size(); - } - - // Starting address of next memory pkt (aligned to burst boundary) - addr = (addr | (burst_size - 1)) + 1; - } - - // If all packets are serviced by write queue, we send the repsonse back - if (pktsServicedByWrQ == pkt_count) { - accessAndRespond(pkt, frontendLatency); - return; - } - - // Update how many split packets are serviced by write queue - if (burst_helper != NULL) - burst_helper->burstsServiced = pktsServicedByWrQ; - - // If we are not already scheduled to get a request out of the - // queue, do so now - if (!nextReqEvent.scheduled()) { - DPRINTF(DRAM, "Request scheduled immediately\n"); - schedule(nextReqEvent, curTick()); - } -} - -void -DRAMCtrl::addToWriteQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram) -{ - // only add to the write queue here. whenever the request is - // eventually done, set the readyTime, and call schedule() - assert(pkt->isWrite()); - - // if the request size is larger than burst size, the pkt is split into - // multiple packets - const Addr base_addr = pkt->getAddr(); - Addr addr = base_addr; - uint32_t burst_size = is_dram ? dram->bytesPerBurst() : - nvm->bytesPerBurst(); - for (int cnt = 0; cnt < pkt_count; ++cnt) { - unsigned size = std::min((addr | (burst_size - 1)) + 1, - base_addr + pkt->getSize()) - addr; - stats.writePktSize[ceilLog2(size)]++; - stats.writeBursts++; - stats.masterWriteAccesses[pkt->masterId()]++; - - // see if we can merge with an existing item in the write - // queue and keep track of whether we have merged or not - bool merged = isInWriteQueue.find(burstAlign(addr, is_dram)) != - isInWriteQueue.end(); - - // if the item was not merged we need to create a new write - // and enqueue it - if (!merged) { - DRAMPacket* dram_pkt; - if (is_dram) { - dram_pkt = dram->decodePacket(pkt, addr, size, false, true); - dram->setupRank(dram_pkt->rank, false); - } else { - dram_pkt = nvm->decodePacket(pkt, addr, size, false, false); - nvm->setupRank(dram_pkt->rank, false); - } - assert(totalWriteQueueSize < writeBufferSize); - stats.wrQLenPdf[totalWriteQueueSize]++; - - DPRINTF(DRAM, "Adding to write queue\n"); - - writeQueue[dram_pkt->qosValue()].push_back(dram_pkt); - isInWriteQueue.insert(burstAlign(addr, is_dram)); - - // log packet - logRequest(MemCtrl::WRITE, pkt->masterId(), pkt->qosValue(), - dram_pkt->addr, 1); - - assert(totalWriteQueueSize == isInWriteQueue.size()); - - // Update stats - stats.avgWrQLen = totalWriteQueueSize; - - } else { - DPRINTF(DRAM, "Merging write burst with existing queue entry\n"); - - // keep track of the fact that this burst effectively - // disappeared as it was merged with an existing one - stats.mergedWrBursts++; - } - - // Starting address of next memory pkt (aligned to burst_size boundary) - addr = (addr | (burst_size - 1)) + 1; - } - - // we do not wait for the writes to be send to the actual memory, - // but instead take responsibility for the consistency here and - // snoop the write queue for any upcoming reads - // @todo, if a pkt size is larger than burst size, we might need a - // different front end latency - accessAndRespond(pkt, frontendLatency); - - // If we are not already scheduled to get a request out of the - // queue, do so now - if (!nextReqEvent.scheduled()) { - DPRINTF(DRAM, "Request scheduled immediately\n"); - schedule(nextReqEvent, curTick()); - } -} - -void -DRAMCtrl::printQs() const -{ -#if TRACING_ON - DPRINTF(DRAM, "===READ QUEUE===\n\n"); - for (const auto& queue : readQueue) { - for (const auto& packet : queue) { - DPRINTF(DRAM, "Read %lu\n", packet->addr); - } - } - - DPRINTF(DRAM, "\n===RESP QUEUE===\n\n"); - for (const auto& packet : respQueue) { - DPRINTF(DRAM, "Response %lu\n", packet->addr); - } - - DPRINTF(DRAM, "\n===WRITE QUEUE===\n\n"); - for (const auto& queue : writeQueue) { - for (const auto& packet : queue) { - DPRINTF(DRAM, "Write %lu\n", packet->addr); - } - } -#endif // TRACING_ON -} - -bool -DRAMCtrl::recvTimingReq(PacketPtr pkt) -{ - // This is where we enter from the outside world - DPRINTF(DRAM, "recvTimingReq: request %s addr %lld size %d\n", - pkt->cmdString(), pkt->getAddr(), pkt->getSize()); - - panic_if(pkt->cacheResponding(), "Should not see packets where cache " - "is responding"); - - panic_if(!(pkt->isRead() || pkt->isWrite()), - "Should only see read and writes at memory controller\n"); - - // Calc avg gap between requests - if (prevArrival != 0) { - stats.totGap += curTick() - prevArrival; - } - prevArrival = curTick(); - - // What type of media does this packet access? - bool is_dram; - if (dram && dram->getAddrRange().contains(pkt->getAddr())) { - is_dram = true; - } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) { - is_dram = false; - } else { - panic("Can't handle address range for packet %s\n", - pkt->print()); - } - - - // Find out how many memory packets a pkt translates to - // If the burst size is equal or larger than the pkt size, then a pkt - // translates to only one memory packet. Otherwise, a pkt translates to - // multiple memory packets - unsigned size = pkt->getSize(); - uint32_t burst_size = is_dram ? dram->bytesPerBurst() : - nvm->bytesPerBurst(); - unsigned offset = pkt->getAddr() & (burst_size - 1); - unsigned int pkt_count = divCeil(offset + size, burst_size); - - // run the QoS scheduler and assign a QoS priority value to the packet - qosSchedule( { &readQueue, &writeQueue }, burst_size, pkt); - - // check local buffers and do not accept if full - if (pkt->isWrite()) { - assert(size != 0); - if (writeQueueFull(pkt_count)) { - DPRINTF(DRAM, "Write queue full, not accepting\n"); - // remember that we have to retry this port - retryWrReq = true; - stats.numWrRetry++; - return false; - } else { - addToWriteQueue(pkt, pkt_count, is_dram); - stats.writeReqs++; - stats.bytesWrittenSys += size; - } - } else { - assert(pkt->isRead()); - assert(size != 0); - if (readQueueFull(pkt_count)) { - DPRINTF(DRAM, "Read queue full, not accepting\n"); - // remember that we have to retry this port - retryRdReq = true; - stats.numRdRetry++; - return false; - } else { - addToReadQueue(pkt, pkt_count, is_dram); - stats.readReqs++; - stats.bytesReadSys += size; - } - } - - return true; -} - -void -DRAMCtrl::processRespondEvent() -{ - DPRINTF(DRAM, - "processRespondEvent(): Some req has reached its readyTime\n"); - - DRAMPacket* dram_pkt = respQueue.front(); - - if (dram_pkt->isDram()) { - // media specific checks and functions when read response is complete - dram->respondEvent(dram_pkt->rank); - } - - if (dram_pkt->burstHelper) { - // it is a split packet - dram_pkt->burstHelper->burstsServiced++; - if (dram_pkt->burstHelper->burstsServiced == - dram_pkt->burstHelper->burstCount) { - // we have now serviced all children packets of a system packet - // so we can now respond to the requester - // @todo we probably want to have a different front end and back - // end latency for split packets - accessAndRespond(dram_pkt->pkt, frontendLatency + backendLatency); - delete dram_pkt->burstHelper; - dram_pkt->burstHelper = NULL; - } - } else { - // it is not a split packet - accessAndRespond(dram_pkt->pkt, frontendLatency + backendLatency); - } - - assert(respQueue.front() == dram_pkt); - respQueue.pop_front(); - - if (!respQueue.empty()) { - assert(respQueue.front()->readyTime >= curTick()); - assert(!respondEvent.scheduled()); - schedule(respondEvent, respQueue.front()->readyTime); - } else { - // if there is nothing left in any queue, signal a drain - if (drainState() == DrainState::Draining && - !totalWriteQueueSize && !totalReadQueueSize && - allIntfDrained()) { - - DPRINTF(Drain, "DRAM controller done draining\n"); - signalDrainDone(); - } else if (dram_pkt->isDram()) { - // check the refresh state and kick the refresh event loop - // into action again if banks already closed and just waiting - // for read to complete - dram->checkRefreshState(dram_pkt->rank); - } - } - - delete dram_pkt; - - // We have made a location in the queue available at this point, - // so if there is a read that was forced to wait, retry now - if (retryRdReq) { - retryRdReq = false; - port.sendRetryReq(); - } -} - -DRAMPacketQueue::iterator -DRAMCtrl::chooseNext(DRAMPacketQueue& queue, Tick extra_col_delay) -{ - // This method does the arbitration between requests. - - DRAMPacketQueue::iterator ret = queue.end(); - - if (!queue.empty()) { - if (queue.size() == 1) { - // available rank corresponds to state refresh idle - DRAMPacket* dram_pkt = *(queue.begin()); - if (packetReady(dram_pkt)) { - ret = queue.begin(); - DPRINTF(DRAM, "Single request, going to a free rank\n"); - } else { - DPRINTF(DRAM, "Single request, going to a busy rank\n"); - } - } else if (memSchedPolicy == Enums::fcfs) { - // check if there is a packet going to a free rank - for (auto i = queue.begin(); i != queue.end(); ++i) { - DRAMPacket* dram_pkt = *i; - if (packetReady(dram_pkt)) { - ret = i; - break; - } - } - } else if (memSchedPolicy == Enums::frfcfs) { - ret = chooseNextFRFCFS(queue, extra_col_delay); - } else { - panic("No scheduling policy chosen\n"); - } - } - return ret; -} - -DRAMPacketQueue::iterator -DRAMCtrl::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick extra_col_delay) -{ - auto selected_pkt_it = queue.end(); - Tick col_allowed_at = MaxTick; - - // time we need to issue a column command to be seamless - const Tick min_col_at = std::max(nextBurstAt + extra_col_delay, curTick()); - - // find optimal packet for each interface - if (dram && nvm) { - // create 2nd set of parameters for NVM - auto nvm_pkt_it = queue.end(); - Tick nvm_col_at = MaxTick; - - // Select DRAM packet by default to give priority if both - // can issue at the same time or seamlessly - std::tie(selected_pkt_it, col_allowed_at) = - dram->chooseNextFRFCFS(queue, min_col_at); - if (selected_pkt_it == queue.end()) { - DPRINTF(DRAM, "%s no available DRAM ranks found\n", __func__); - } - - std::tie(nvm_pkt_it, nvm_col_at) = - nvm->chooseNextFRFCFS(queue, min_col_at); - if (nvm_pkt_it == queue.end()) { - DPRINTF(NVM, "%s no available NVM ranks found\n", __func__); - } - - // Compare DRAM and NVM and select NVM if it can issue - // earlier than the DRAM packet - if (col_allowed_at > nvm_col_at) { - selected_pkt_it = nvm_pkt_it; - } - } else if (dram) { - std::tie(selected_pkt_it, col_allowed_at) = - dram->chooseNextFRFCFS(queue, min_col_at); - - if (selected_pkt_it == queue.end()) { - DPRINTF(DRAM, "%s no available DRAM ranks found\n", __func__); - } - } else if (nvm) { - std::tie(selected_pkt_it, col_allowed_at) = - nvm->chooseNextFRFCFS(queue, min_col_at); - - if (selected_pkt_it == queue.end()) { - DPRINTF(NVM, "%s no available NVM ranks found\n", __func__); - } - } - - return selected_pkt_it; -} - -pair -DRAMInterface::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const +pair +DRAMInterface::chooseNextFRFCFS(MemPacketQueue& queue, Tick min_col_at) const { vector earliest_banks(ranksPerChannel, 0); @@ -746,7 +194,7 @@ DRAMInterface::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const auto selected_pkt_it = queue.end(); for (auto i = queue.begin(); i != queue.end() ; ++i) { - DRAMPacket* pkt = *i; + MemPacket* pkt = *i; // select optimal DRAM packet in Q if (pkt->isDram()) { @@ -832,157 +280,6 @@ DRAMInterface::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const return make_pair(selected_pkt_it, selected_col_at); } -void -DRAMCtrl::accessAndRespond(PacketPtr pkt, Tick static_latency) -{ - DPRINTF(DRAM, "Responding to Address %lld.. \n",pkt->getAddr()); - - bool needsResponse = pkt->needsResponse(); - // do the actual memory access which also turns the packet into a - // response - if (dram && dram->getAddrRange().contains(pkt->getAddr())) { - dram->access(pkt); - } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) { - nvm->access(pkt); - } else { - panic("Can't handle address range for packet %s\n", - pkt->print()); - } - - // turn packet around to go back to requester if response expected - if (needsResponse) { - // access already turned the packet into a response - assert(pkt->isResponse()); - // response_time consumes the static latency and is charged also - // with headerDelay that takes into account the delay provided by - // the xbar and also the payloadDelay that takes into account the - // number of data beats. - Tick response_time = curTick() + static_latency + pkt->headerDelay + - pkt->payloadDelay; - // Here we reset the timing of the packet before sending it out. - pkt->headerDelay = pkt->payloadDelay = 0; - - // queue the packet in the response queue to be sent out after - // the static latency has passed - port.schedTimingResp(pkt, response_time); - } else { - // @todo the packet is going to be deleted, and the DRAMPacket - // is still having a pointer to it - pendingDelete.reset(pkt); - } - - DPRINTF(DRAM, "Done\n"); - - return; -} - -void -DRAMCtrl::pruneBurstTick() -{ - auto it = burstTicks.begin(); - while (it != burstTicks.end()) { - auto current_it = it++; - if (curTick() > *current_it) { - DPRINTF(DRAM, "Removing burstTick for %d\n", *current_it); - burstTicks.erase(current_it); - } - } -} - -Tick -DRAMCtrl::getBurstWindow(Tick cmd_tick) -{ - // get tick aligned to burst window - Tick burst_offset = cmd_tick % commandWindow; - return (cmd_tick - burst_offset); -} - -Tick -DRAMCtrl::verifySingleCmd(Tick cmd_tick, Tick max_cmds_per_burst) -{ - // start with assumption that there is no contention on command bus - Tick cmd_at = cmd_tick; - - // get tick aligned to burst window - Tick burst_tick = getBurstWindow(cmd_tick); - - // verify that we have command bandwidth to issue the command - // if not, iterate over next window(s) until slot found - while (burstTicks.count(burst_tick) >= max_cmds_per_burst) { - DPRINTF(DRAM, "Contention found on command bus at %d\n", burst_tick); - burst_tick += commandWindow; - cmd_at = burst_tick; - } - - // add command into burst window and return corresponding Tick - burstTicks.insert(burst_tick); - return cmd_at; -} - -Tick -DRAMCtrl::verifyMultiCmd(Tick cmd_tick, Tick max_cmds_per_burst, - Tick max_multi_cmd_split) -{ - // start with assumption that there is no contention on command bus - Tick cmd_at = cmd_tick; - - // get tick aligned to burst window - Tick burst_tick = getBurstWindow(cmd_tick); - - // Command timing requirements are from 2nd command - // Start with assumption that 2nd command will issue at cmd_at and - // find prior slot for 1st command to issue - // Given a maximum latency of max_multi_cmd_split between the commands, - // find the burst at the maximum latency prior to cmd_at - Tick burst_offset = 0; - Tick first_cmd_offset = cmd_tick % commandWindow; - while (max_multi_cmd_split > (first_cmd_offset + burst_offset)) { - burst_offset += commandWindow; - } - // get the earliest burst aligned address for first command - // ensure that the time does not go negative - Tick first_cmd_tick = burst_tick - std::min(burst_offset, burst_tick); - - // Can required commands issue? - bool first_can_issue = false; - bool second_can_issue = false; - // verify that we have command bandwidth to issue the command(s) - while (!first_can_issue || !second_can_issue) { - bool same_burst = (burst_tick == first_cmd_tick); - auto first_cmd_count = burstTicks.count(first_cmd_tick); - auto second_cmd_count = same_burst ? first_cmd_count + 1 : - burstTicks.count(burst_tick); - - first_can_issue = first_cmd_count < max_cmds_per_burst; - second_can_issue = second_cmd_count < max_cmds_per_burst; - - if (!second_can_issue) { - DPRINTF(DRAM, "Contention (cmd2) found on command bus at %d\n", - burst_tick); - burst_tick += commandWindow; - cmd_at = burst_tick; - } - - // Verify max_multi_cmd_split isn't violated when command 2 is shifted - // If commands initially were issued in same burst, they are - // now in consecutive bursts and can still issue B2B - bool gap_violated = !same_burst && - ((burst_tick - first_cmd_tick) > max_multi_cmd_split); - - if (!first_can_issue || (!second_can_issue && gap_violated)) { - DPRINTF(DRAM, "Contention (cmd1) found on command bus at %d\n", - first_cmd_tick); - first_cmd_tick += commandWindow; - } - } - - // Add command to burstTicks - burstTicks.insert(burst_tick); - burstTicks.insert(first_cmd_tick); - - return cmd_at; -} - void DRAMInterface::activateBank(Rank& rank_ref, Bank& bank_ref, Tick act_tick, uint32_t row) @@ -1156,14 +453,14 @@ DRAMInterface::prechargeBank(Rank& rank_ref, Bank& bank, Tick pre_tick, } pair -DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, - const std::vector& queue) +DRAMInterface::doBurstAccess(MemPacket* mem_pkt, Tick next_burst_at, + const std::vector& queue) { DPRINTF(DRAM, "Timing access to addr %lld, rank/bank/row %d %d %d\n", - dram_pkt->addr, dram_pkt->rank, dram_pkt->bank, dram_pkt->row); + mem_pkt->addr, mem_pkt->rank, mem_pkt->bank, mem_pkt->row); // get the rank - Rank& rank_ref = *ranks[dram_pkt->rank]; + Rank& rank_ref = *ranks[mem_pkt->rank]; assert(rank_ref.inRefIdleState()); @@ -1176,13 +473,13 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, } // get the bank - Bank& bank_ref = rank_ref.banks[dram_pkt->bank]; + Bank& bank_ref = rank_ref.banks[mem_pkt->bank]; // for the state we need to track if it is a row hit or not bool row_hit = true; // Determine the access latency and update the bank state - if (bank_ref.openRow == dram_pkt->row) { + if (bank_ref.openRow == mem_pkt->row) { // nothing to do } else { row_hit = false; @@ -1198,11 +495,11 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, // Record the activation and deal with all the global timing // constraints caused be a new activation (tRRD and tXAW) - activateBank(rank_ref, bank_ref, act_tick, dram_pkt->row); + activateBank(rank_ref, bank_ref, act_tick, mem_pkt->row); } // respect any constraints on the command (e.g. tRCD or tCCD) - const Tick col_allowed_at = dram_pkt->isRead() ? + const Tick col_allowed_at = mem_pkt->isRead() ? bank_ref.rdAllowedAt : bank_ref.wrAllowedAt; // we need to wait until the bus is available before we can issue @@ -1235,7 +532,7 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, DPRINTF(DRAM, "Schedule RD/WR burst at tick %d\n", cmd_at); // update the packet ready time - dram_pkt->readyTime = cmd_at + tCL + tBURST; + mem_pkt->readyTime = cmd_at + tCL + tBURST; rank_ref.lastBurstTick = cmd_at; @@ -1245,7 +542,7 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, Tick dly_to_wr_cmd; for (int j = 0; j < ranksPerChannel; j++) { for (int i = 0; i < banksPerRank; i++) { - if (dram_pkt->rank == j) { + if (mem_pkt->rank == j) { if (bankGroupArch && (bank_ref.bankgr == ranks[j]->banks[i].bankgr)) { // bank group architecture requires longer delays between @@ -1253,17 +550,17 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, // tCCD_L is default requirement for same BG timing // tCCD_L_WR is required for write-to-write // Need to also take bus turnaround delays into account - dly_to_rd_cmd = dram_pkt->isRead() ? + dly_to_rd_cmd = mem_pkt->isRead() ? tCCD_L : std::max(tCCD_L, wrToRdDlySameBG); - dly_to_wr_cmd = dram_pkt->isRead() ? + dly_to_wr_cmd = mem_pkt->isRead() ? std::max(tCCD_L, rdToWrDlySameBG) : tCCD_L_WR; } else { // tBURST is default requirement for diff BG timing // Need to also take bus turnaround delays into account - dly_to_rd_cmd = dram_pkt->isRead() ? burst_gap : + dly_to_rd_cmd = mem_pkt->isRead() ? burst_gap : writeToReadDelay(); - dly_to_wr_cmd = dram_pkt->isRead() ? readToWriteDelay() : + dly_to_wr_cmd = mem_pkt->isRead() ? readToWriteDelay() : burst_gap; } } else { @@ -1281,14 +578,14 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, } // Save rank of current access - activeRank = dram_pkt->rank; + activeRank = mem_pkt->rank; // If this is a write, we also need to respect the write recovery // time before a precharge, in the case of a read, respect the // read to precharge constraint bank_ref.preAllowedAt = std::max(bank_ref.preAllowedAt, - dram_pkt->isRead() ? cmd_at + tRTP : - dram_pkt->readyTime + tWR); + mem_pkt->isRead() ? cmd_at + tRTP : + mem_pkt->readyTime + tWR); // increment the bytes accessed and the accesses per row bank_ref.bytesAccessed += burstSize; @@ -1325,11 +622,11 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, // 3) make sure we are not considering the packet that we are // currently dealing with while (!got_more_hits && p != queue[i].end()) { - if (dram_pkt != (*p)) { - bool same_rank_bank = (dram_pkt->rank == (*p)->rank) && - (dram_pkt->bank == (*p)->bank); + if (mem_pkt != (*p)) { + bool same_rank_bank = (mem_pkt->rank == (*p)->rank) && + (mem_pkt->bank == (*p)->bank); - bool same_row = dram_pkt->row == (*p)->row; + bool same_row = mem_pkt->row == (*p)->row; got_more_hits |= same_rank_bank && same_row; got_bank_conflict |= same_rank_bank && !same_row; } @@ -1349,16 +646,16 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, } // DRAMPower trace command to be written - std::string mem_cmd = dram_pkt->isRead() ? "RD" : "WR"; + std::string mem_cmd = mem_pkt->isRead() ? "RD" : "WR"; // MemCommand required for DRAMPower library MemCommand::cmds command = (mem_cmd == "RD") ? MemCommand::RD : MemCommand::WR; - rank_ref.cmdList.push_back(Command(command, dram_pkt->bank, cmd_at)); + rank_ref.cmdList.push_back(Command(command, mem_pkt->bank, cmd_at)); DPRINTF(DRAMPower, "%llu,%s,%d,%d\n", divCeil(cmd_at, tCK) - - timeStampOffset, mem_cmd, dram_pkt->bank, dram_pkt->rank); + timeStampOffset, mem_cmd, mem_pkt->bank, mem_pkt->rank); // if this access should use auto-precharge, then we are // closing the row after the read/write burst @@ -1368,11 +665,11 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, prechargeBank(rank_ref, bank_ref, std::max(curTick(), bank_ref.preAllowedAt), true); - DPRINTF(DRAM, "Auto-precharged bank: %d\n", dram_pkt->bankId); + DPRINTF(DRAM, "Auto-precharged bank: %d\n", mem_pkt->bankId); } // Update the stats and schedule the next request - if (dram_pkt->isRead()) { + if (mem_pkt->isRead()) { // Every respQueue which will generate an event, increment count ++rank_ref.outstandingEvents; @@ -1380,11 +677,11 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, if (row_hit) stats.readRowHits++; stats.bytesRead += burstSize; - stats.perBankRdBursts[dram_pkt->bankId]++; + stats.perBankRdBursts[mem_pkt->bankId]++; // Update latency stats - stats.totMemAccLat += dram_pkt->readyTime - dram_pkt->entryTime; - stats.totQLat += cmd_at - dram_pkt->entryTime; + stats.totMemAccLat += mem_pkt->readyTime - mem_pkt->entryTime; + stats.totQLat += cmd_at - mem_pkt->entryTime; stats.totBusLat += tBURST; } else { // Schedule write done event to decrement event count @@ -1394,12 +691,12 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, // the time that writes are outstanding and bus is active // to holdoff power-down entry events if (!rank_ref.writeDoneEvent.scheduled()) { - schedule(rank_ref.writeDoneEvent, dram_pkt->readyTime); + schedule(rank_ref.writeDoneEvent, mem_pkt->readyTime); // New event, increment count ++rank_ref.outstandingEvents; - } else if (rank_ref.writeDoneEvent.when() < dram_pkt->readyTime) { - reschedule(rank_ref.writeDoneEvent, dram_pkt->readyTime); + } else if (rank_ref.writeDoneEvent.when() < mem_pkt->readyTime) { + reschedule(rank_ref.writeDoneEvent, mem_pkt->readyTime); } // will remove write from queue when returned to parent function // decrement count for DRAM rank @@ -1409,7 +706,7 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, if (row_hit) stats.writeRowHits++; stats.bytesWritten += burstSize; - stats.perBankWrBursts[dram_pkt->bankId]++; + stats.perBankWrBursts[mem_pkt->bankId]++; } // Update bus state to reflect when previous command was issued @@ -1433,399 +730,6 @@ DRAMInterface::addRankToRankDelay(Tick cmd_at) } } -bool -DRAMCtrl::inReadBusState(bool next_state) const -{ - // check the bus state - if (next_state) { - // use busStateNext to get the state that will be used - // for the next burst - return (busStateNext == DRAMCtrl::READ); - } else { - return (busState == DRAMCtrl::READ); - } -} - -bool -DRAMCtrl::inWriteBusState(bool next_state) const -{ - // check the bus state - if (next_state) { - // use busStateNext to get the state that will be used - // for the next burst - return (busStateNext == DRAMCtrl::WRITE); - } else { - return (busState == DRAMCtrl::WRITE); - } -} - -void -DRAMCtrl::doBurstAccess(DRAMPacket* dram_pkt) -{ - // first clean up the burstTick set, removing old entries - // before adding new entries for next burst - pruneBurstTick(); - - // When was command issued? - Tick cmd_at; - - // Issue the next burst and update bus state to reflect - // when previous command was issued - if (dram_pkt->isDram()) { - std::vector& queue = selQueue(dram_pkt->isRead()); - std::tie(cmd_at, nextBurstAt) = - dram->doBurstAccess(dram_pkt, nextBurstAt, queue); - - // Update timing for NVM ranks if NVM is configured on this channel - if (nvm) - nvm->addRankToRankDelay(cmd_at); - - } else { - std::tie(cmd_at, nextBurstAt) = - nvm->doBurstAccess(dram_pkt, nextBurstAt); - - // Update timing for NVM ranks if NVM is configured on this channel - if (dram) - dram->addRankToRankDelay(cmd_at); - - } - - DPRINTF(DRAM, "Access to %lld, ready at %lld next burst at %lld.\n", - dram_pkt->addr, dram_pkt->readyTime, nextBurstAt); - - // Update the minimum timing between the requests, this is a - // conservative estimate of when we have to schedule the next - // request to not introduce any unecessary bubbles. In most cases - // we will wake up sooner than we have to. - nextReqTime = nextBurstAt - (dram ? dram->commandOffset() : - nvm->commandOffset()); - - - // Update the common bus stats - if (dram_pkt->isRead()) { - ++readsThisTime; - // Update latency stats - stats.masterReadTotalLat[dram_pkt->masterId()] += - dram_pkt->readyTime - dram_pkt->entryTime; - stats.masterReadBytes[dram_pkt->masterId()] += dram_pkt->size; - } else { - ++writesThisTime; - stats.masterWriteBytes[dram_pkt->masterId()] += dram_pkt->size; - stats.masterWriteTotalLat[dram_pkt->masterId()] += - dram_pkt->readyTime - dram_pkt->entryTime; - } -} - -void -DRAMCtrl::processNextReqEvent() -{ - // transition is handled by QoS algorithm if enabled - if (turnPolicy) { - // select bus state - only done if QoS algorithms are in use - busStateNext = selectNextBusState(); - } - - // detect bus state change - bool switched_cmd_type = (busState != busStateNext); - // record stats - recordTurnaroundStats(); - - DPRINTF(DRAM, "QoS Turnarounds selected state %s %s\n", - (busState==MemCtrl::READ)?"READ":"WRITE", - switched_cmd_type?"[turnaround triggered]":""); - - if (switched_cmd_type) { - if (busState == MemCtrl::READ) { - DPRINTF(DRAM, - "Switching to writes after %d reads with %d reads " - "waiting\n", readsThisTime, totalReadQueueSize); - stats.rdPerTurnAround.sample(readsThisTime); - readsThisTime = 0; - } else { - DPRINTF(DRAM, - "Switching to reads after %d writes with %d writes " - "waiting\n", writesThisTime, totalWriteQueueSize); - stats.wrPerTurnAround.sample(writesThisTime); - writesThisTime = 0; - } - } - - // updates current state - busState = busStateNext; - - if (nvm) { - for (auto queue = readQueue.rbegin(); - queue != readQueue.rend(); ++queue) { - // select non-deterministic NVM read to issue - // assume that we have the command bandwidth to issue this along - // with additional RD/WR burst with needed bank operations - if (nvm->readsWaitingToIssue()) { - // select non-deterministic NVM read to issue - nvm->chooseRead(*queue); - } - } - } - - // check ranks for refresh/wakeup - uses busStateNext, so done after - // turnaround decisions - // Default to busy status and update based on interface specifics - bool dram_busy = dram ? dram->isBusy() : true; - bool nvm_busy = true; - bool all_writes_nvm = false; - if (nvm) { - all_writes_nvm = nvm->numWritesQueued == totalWriteQueueSize; - bool read_queue_empty = totalReadQueueSize == 0; - nvm_busy = nvm->isBusy(read_queue_empty, all_writes_nvm); - } - // Default state of unused interface is 'true' - // Simply AND the busy signals to determine if system is busy - if (dram_busy && nvm_busy) { - // if all ranks are refreshing wait for them to finish - // and stall this state machine without taking any further - // action, and do not schedule a new nextReqEvent - return; - } - - // when we get here it is either a read or a write - if (busState == READ) { - - // track if we should switch or not - bool switch_to_writes = false; - - if (totalReadQueueSize == 0) { - // In the case there is no read request to go next, - // trigger writes if we have passed the low threshold (or - // if we are draining) - if (!(totalWriteQueueSize == 0) && - (drainState() == DrainState::Draining || - totalWriteQueueSize > writeLowThreshold)) { - - DPRINTF(DRAM, "Switching to writes due to read queue empty\n"); - switch_to_writes = true; - } else { - // check if we are drained - // not done draining until in PWR_IDLE state - // ensuring all banks are closed and - // have exited low power states - if (drainState() == DrainState::Draining && - respQueue.empty() && allIntfDrained()) { - - DPRINTF(Drain, "DRAM controller done draining\n"); - signalDrainDone(); - } - - // nothing to do, not even any point in scheduling an - // event for the next request - return; - } - } else { - - bool read_found = false; - DRAMPacketQueue::iterator to_read; - uint8_t prio = numPriorities(); - - for (auto queue = readQueue.rbegin(); - queue != readQueue.rend(); ++queue) { - - prio--; - - DPRINTF(QOS, - "DRAM controller checking READ queue [%d] priority [%d elements]\n", - prio, queue->size()); - - // Figure out which read request goes next - // If we are changing command type, incorporate the minimum - // bus turnaround delay which will be rank to rank delay - to_read = chooseNext((*queue), switched_cmd_type ? - minWriteToReadDataGap() : 0); - - if (to_read != queue->end()) { - // candidate read found - read_found = true; - break; - } - } - - // if no read to an available rank is found then return - // at this point. There could be writes to the available ranks - // which are above the required threshold. However, to - // avoid adding more complexity to the code, return and wait - // for a refresh event to kick things into action again. - if (!read_found) { - DPRINTF(DRAM, "No Reads Found - exiting\n"); - return; - } - - auto dram_pkt = *to_read; - - doBurstAccess(dram_pkt); - - // sanity check - assert(dram_pkt->size <= (dram_pkt->isDram() ? - dram->bytesPerBurst() : - nvm->bytesPerBurst()) ); - assert(dram_pkt->readyTime >= curTick()); - - // log the response - logResponse(MemCtrl::READ, (*to_read)->masterId(), - dram_pkt->qosValue(), dram_pkt->getAddr(), 1, - dram_pkt->readyTime - dram_pkt->entryTime); - - - // Insert into response queue. It will be sent back to the - // requester at its readyTime - if (respQueue.empty()) { - assert(!respondEvent.scheduled()); - schedule(respondEvent, dram_pkt->readyTime); - } else { - assert(respQueue.back()->readyTime <= dram_pkt->readyTime); - assert(respondEvent.scheduled()); - } - - respQueue.push_back(dram_pkt); - - // we have so many writes that we have to transition - // don't transition if the writeRespQueue is full and - // there are no other writes that can issue - if ((totalWriteQueueSize > writeHighThreshold) && - !(nvm && all_writes_nvm && nvm->writeRespQueueFull())) { - switch_to_writes = true; - } - - // remove the request from the queue - the iterator is no longer valid . - readQueue[dram_pkt->qosValue()].erase(to_read); - } - - // switching to writes, either because the read queue is empty - // and the writes have passed the low threshold (or we are - // draining), or because the writes hit the hight threshold - if (switch_to_writes) { - // transition to writing - busStateNext = WRITE; - } - } else { - - bool write_found = false; - DRAMPacketQueue::iterator to_write; - uint8_t prio = numPriorities(); - - for (auto queue = writeQueue.rbegin(); - queue != writeQueue.rend(); ++queue) { - - prio--; - - DPRINTF(QOS, - "DRAM controller checking WRITE queue [%d] priority [%d elements]\n", - prio, queue->size()); - - // If we are changing command type, incorporate the minimum - // bus turnaround delay - to_write = chooseNext((*queue), - switched_cmd_type ? minReadToWriteDataGap() : 0); - - if (to_write != queue->end()) { - write_found = true; - break; - } - } - - // if there are no writes to a rank that is available to service - // requests (i.e. rank is in refresh idle state) are found then - // return. There could be reads to the available ranks. However, to - // avoid adding more complexity to the code, return at this point and - // wait for a refresh event to kick things into action again. - if (!write_found) { - DPRINTF(DRAM, "No Writes Found - exiting\n"); - return; - } - - auto dram_pkt = *to_write; - - // sanity check - assert(dram_pkt->size <= (dram_pkt->isDram() ? - dram->bytesPerBurst() : - nvm->bytesPerBurst()) ); - - doBurstAccess(dram_pkt); - - isInWriteQueue.erase(burstAlign(dram_pkt->addr, dram_pkt->isDram())); - - // log the response - logResponse(MemCtrl::WRITE, dram_pkt->masterId(), - dram_pkt->qosValue(), dram_pkt->getAddr(), 1, - dram_pkt->readyTime - dram_pkt->entryTime); - - - // remove the request from the queue - the iterator is no longer valid - writeQueue[dram_pkt->qosValue()].erase(to_write); - - delete dram_pkt; - - // If we emptied the write queue, or got sufficiently below the - // threshold (using the minWritesPerSwitch as the hysteresis) and - // are not draining, or we have reads waiting and have done enough - // writes, then switch to reads. - // If we are interfacing to NVM and have filled the writeRespQueue, - // with only NVM writes in Q, then switch to reads - bool below_threshold = - totalWriteQueueSize + minWritesPerSwitch < writeLowThreshold; - - if (totalWriteQueueSize == 0 || - (below_threshold && drainState() != DrainState::Draining) || - (totalReadQueueSize && writesThisTime >= minWritesPerSwitch) || - (totalReadQueueSize && nvm && nvm->writeRespQueueFull() && - all_writes_nvm)) { - - // turn the bus back around for reads again - busStateNext = MemCtrl::READ; - - // note that the we switch back to reads also in the idle - // case, which eventually will check for any draining and - // also pause any further scheduling if there is really - // nothing to do - } - } - // It is possible that a refresh to another rank kicks things back into - // action before reaching this point. - if (!nextReqEvent.scheduled()) - schedule(nextReqEvent, std::max(nextReqTime, curTick())); - - // If there is space available and we have writes waiting then let - // them retry. This is done here to ensure that the retry does not - // cause a nextReqEvent to be scheduled before we do so as part of - // the next request processing - if (retryWrReq && totalWriteQueueSize < writeBufferSize) { - retryWrReq = false; - port.sendRetryReq(); - } -} - -MemInterface::MemInterface(const MemInterfaceParams* _p) - : AbstractMemory(_p), - addrMapping(_p->addr_mapping), - burstSize((_p->devices_per_rank * _p->burst_length * - _p->device_bus_width) / 8), - deviceSize(_p->device_size), - deviceRowBufferSize(_p->device_rowbuffer_size), - devicesPerRank(_p->devices_per_rank), - rowBufferSize(devicesPerRank * deviceRowBufferSize), - burstsPerRowBuffer(rowBufferSize / burstSize), - burstsPerStripe(range.interleaved() ? - range.granularity() / burstSize : 1), - ranksPerChannel(_p->ranks_per_channel), - banksPerRank(_p->banks_per_rank), rowsPerBank(0), - tCK(_p->tCK), tCS(_p->tCS), tBURST(_p->tBURST), - tRTW(_p->tRTW), - tWTR(_p->tWTR) -{} - -void -MemInterface::setCtrl(DRAMCtrl* _ctrl, unsigned int command_window) -{ - ctrl = _ctrl; - maxCommandsPerWindow = command_window / tCK; -} - DRAMInterface::DRAMInterface(const DRAMInterfaceParams* _p) : MemInterface(_p), bankGroupsPerRank(_p->bank_groups_per_rank), @@ -1849,9 +753,7 @@ DRAMInterface::DRAMInterface(const DRAMInterfaceParams* _p) timeStampOffset(0), activeRank(0), enableDRAMPowerdown(_p->enable_dram_powerdown), lastStatsResetTick(0), - stats(*this), - readBufferSize(_p->read_buffer_size), - writeBufferSize(_p->write_buffer_size) + stats(*this) { DPRINTF(DRAM, "Setting up DRAM Interface\n"); @@ -2132,7 +1034,7 @@ DRAMInterface::suspend() } pair, bool> -DRAMInterface::minBankPrep(const DRAMPacketQueue& queue, +DRAMInterface::minBankPrep(const MemPacketQueue& queue, Tick min_col_at) const { Tick min_act_at = MaxTick; @@ -2943,173 +1845,6 @@ DRAMInterface::Rank::forceSelfRefreshExit() const { (dram.ctrl->inWriteBusState(true) && (writeEntries != 0)); } -DRAMCtrl::CtrlStats::CtrlStats(DRAMCtrl &_ctrl) - : Stats::Group(&_ctrl), - ctrl(_ctrl), - - ADD_STAT(readReqs, "Number of read requests accepted"), - ADD_STAT(writeReqs, "Number of write requests accepted"), - - ADD_STAT(readBursts, - "Number of controller read bursts, " - "including those serviced by the write queue"), - ADD_STAT(writeBursts, - "Number of controller write bursts, " - "including those merged in the write queue"), - ADD_STAT(servicedByWrQ, - "Number of controller read bursts serviced by the write queue"), - ADD_STAT(mergedWrBursts, - "Number of controller write bursts merged with an existing one"), - - ADD_STAT(neitherReadNorWriteReqs, - "Number of requests that are neither read nor write"), - - ADD_STAT(avgRdQLen, "Average read queue length when enqueuing"), - ADD_STAT(avgWrQLen, "Average write queue length when enqueuing"), - - ADD_STAT(numRdRetry, "Number of times read queue was full causing retry"), - ADD_STAT(numWrRetry, "Number of times write queue was full causing retry"), - - ADD_STAT(readPktSize, "Read request sizes (log2)"), - ADD_STAT(writePktSize, "Write request sizes (log2)"), - - ADD_STAT(rdQLenPdf, "What read queue length does an incoming req see"), - ADD_STAT(wrQLenPdf, "What write queue length does an incoming req see"), - - ADD_STAT(rdPerTurnAround, - "Reads before turning the bus around for writes"), - ADD_STAT(wrPerTurnAround, - "Writes before turning the bus around for reads"), - - ADD_STAT(bytesReadWrQ, "Total number of bytes read from write queue"), - ADD_STAT(bytesReadSys, "Total read bytes from the system interface side"), - ADD_STAT(bytesWrittenSys, - "Total written bytes from the system interface side"), - - ADD_STAT(avgRdBWSys, "Average system read bandwidth in MiByte/s"), - ADD_STAT(avgWrBWSys, "Average system write bandwidth in MiByte/s"), - - ADD_STAT(totGap, "Total gap between requests"), - ADD_STAT(avgGap, "Average gap between requests"), - - ADD_STAT(masterReadBytes, "Per-master bytes read from memory"), - ADD_STAT(masterWriteBytes, "Per-master bytes write to memory"), - ADD_STAT(masterReadRate, - "Per-master bytes read from memory rate (Bytes/sec)"), - ADD_STAT(masterWriteRate, - "Per-master bytes write to memory rate (Bytes/sec)"), - ADD_STAT(masterReadAccesses, - "Per-master read serviced memory accesses"), - ADD_STAT(masterWriteAccesses, - "Per-master write serviced memory accesses"), - ADD_STAT(masterReadTotalLat, - "Per-master read total memory access latency"), - ADD_STAT(masterWriteTotalLat, - "Per-master write total memory access latency"), - ADD_STAT(masterReadAvgLat, - "Per-master read average memory access latency"), - ADD_STAT(masterWriteAvgLat, - "Per-master write average memory access latency") - -{ -} - -void -DRAMCtrl::CtrlStats::regStats() -{ - using namespace Stats; - - assert(ctrl.system()); - const auto max_masters = ctrl.system()->maxMasters(); - - avgRdQLen.precision(2); - avgWrQLen.precision(2); - - readPktSize.init(ceilLog2(ctrl.system()->cacheLineSize()) + 1); - writePktSize.init(ceilLog2(ctrl.system()->cacheLineSize()) + 1); - - rdQLenPdf.init(ctrl.readBufferSize); - wrQLenPdf.init(ctrl.writeBufferSize); - - rdPerTurnAround - .init(ctrl.readBufferSize) - .flags(nozero); - wrPerTurnAround - .init(ctrl.writeBufferSize) - .flags(nozero); - - avgRdBWSys.precision(2); - avgWrBWSys.precision(2); - avgGap.precision(2); - - // per-master bytes read and written to memory - masterReadBytes - .init(max_masters) - .flags(nozero | nonan); - - masterWriteBytes - .init(max_masters) - .flags(nozero | nonan); - - // per-master bytes read and written to memory rate - masterReadRate - .flags(nozero | nonan) - .precision(12); - - masterReadAccesses - .init(max_masters) - .flags(nozero); - - masterWriteAccesses - .init(max_masters) - .flags(nozero); - - masterReadTotalLat - .init(max_masters) - .flags(nozero | nonan); - - masterReadAvgLat - .flags(nonan) - .precision(2); - - masterWriteRate - .flags(nozero | nonan) - .precision(12); - - masterWriteTotalLat - .init(max_masters) - .flags(nozero | nonan); - - masterWriteAvgLat - .flags(nonan) - .precision(2); - - for (int i = 0; i < max_masters; i++) { - const std::string master = ctrl.system()->getMasterName(i); - masterReadBytes.subname(i, master); - masterReadRate.subname(i, master); - masterWriteBytes.subname(i, master); - masterWriteRate.subname(i, master); - masterReadAccesses.subname(i, master); - masterWriteAccesses.subname(i, master); - masterReadTotalLat.subname(i, master); - masterReadAvgLat.subname(i, master); - masterWriteTotalLat.subname(i, master); - masterWriteAvgLat.subname(i, master); - } - - // Formula stats - avgRdBWSys = (bytesReadSys / 1000000) / simSeconds; - avgWrBWSys = (bytesWrittenSys / 1000000) / simSeconds; - - avgGap = totGap / (readReqs + writeReqs); - - masterReadRate = masterReadBytes / simSeconds; - masterWriteRate = masterWriteBytes / simSeconds; - masterReadAvgLat = masterReadTotalLat / masterReadAccesses; - masterWriteAvgLat = masterWriteTotalLat / masterWriteAccesses; -} - void DRAMInterface::DRAMStats::resetStats() { @@ -3261,142 +1996,6 @@ DRAMInterface::RankStats::preDumpStats() rank.computeStats(); } -void -DRAMCtrl::recvFunctional(PacketPtr pkt) -{ - if (dram && dram->getAddrRange().contains(pkt->getAddr())) { - // rely on the abstract memory - dram->functionalAccess(pkt); - } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) { - // rely on the abstract memory - nvm->functionalAccess(pkt); - } else { - panic("Can't handle address range for packet %s\n", - pkt->print()); - } -} - -Port & -DRAMCtrl::getPort(const string &if_name, PortID idx) -{ - if (if_name != "port") { - return QoS::MemCtrl::getPort(if_name, idx); - } else { - return port; - } -} - -bool -DRAMCtrl::allIntfDrained() const -{ - // ensure dram is in power down and refresh IDLE states - bool dram_drained = !dram || dram->allRanksDrained(); - // No outstanding NVM writes - // All other queues verified as needed with calling logic - bool nvm_drained = !nvm || nvm->allRanksDrained(); - return (dram_drained && nvm_drained); -} - -DrainState -DRAMCtrl::drain() -{ - // if there is anything in any of our internal queues, keep track - // of that as well - if (!(!totalWriteQueueSize && !totalReadQueueSize && respQueue.empty() && - allIntfDrained())) { - - DPRINTF(Drain, "DRAM controller not drained, write: %d, read: %d," - " resp: %d\n", totalWriteQueueSize, totalReadQueueSize, - respQueue.size()); - - // the only queue that is not drained automatically over time - // is the write queue, thus kick things into action if needed - if (!totalWriteQueueSize && !nextReqEvent.scheduled()) { - schedule(nextReqEvent, curTick()); - } - - if (dram) - dram->drainRanks(); - - return DrainState::Draining; - } else { - return DrainState::Drained; - } -} - -void -DRAMCtrl::drainResume() -{ - if (!isTimingMode && system()->isTimingMode()) { - // if we switched to timing mode, kick things into action, - // and behave as if we restored from a checkpoint - startup(); - dram->startup(); - } else if (isTimingMode && !system()->isTimingMode()) { - // if we switch from timing mode, stop the refresh events to - // not cause issues with KVM - if (dram) - dram->suspend(); - } - - // update the mode - isTimingMode = system()->isTimingMode(); -} - -DRAMCtrl::MemoryPort::MemoryPort(const std::string& name, DRAMCtrl& _ctrl) - : QueuedSlavePort(name, &_ctrl, queue), queue(_ctrl, *this, true), - ctrl(_ctrl) -{ } - -AddrRangeList -DRAMCtrl::MemoryPort::getAddrRanges() const -{ - AddrRangeList ranges; - if (ctrl.dram) { - DPRINTF(DRAM, "Pushing DRAM ranges to port\n"); - ranges.push_back(ctrl.dram->getAddrRange()); - } - if (ctrl.nvm) { - DPRINTF(DRAM, "Pushing NVM ranges to port\n"); - ranges.push_back(ctrl.nvm->getAddrRange()); - } - return ranges; -} - -void -DRAMCtrl::MemoryPort::recvFunctional(PacketPtr pkt) -{ - pkt->pushLabel(ctrl.name()); - - if (!queue.trySatisfyFunctional(pkt)) { - // Default implementation of SimpleTimingPort::recvFunctional() - // calls recvAtomic() and throws away the latency; we can save a - // little here by just not calculating the latency. - ctrl.recvFunctional(pkt); - } - - pkt->popLabel(); -} - -Tick -DRAMCtrl::MemoryPort::recvAtomic(PacketPtr pkt) -{ - return ctrl.recvAtomic(pkt); -} - -bool -DRAMCtrl::MemoryPort::recvTimingReq(PacketPtr pkt) -{ - // pass it to the memory controller - return ctrl.recvTimingReq(pkt); -} - -DRAMCtrl* -DRAMCtrlParams::create() -{ - return new DRAMCtrl(this); -} - NVMInterface::NVMInterface(const NVMInterfaceParams* _p) : MemInterface(_p), maxPendingWrites(_p->max_pending_writes), @@ -3407,9 +2006,7 @@ NVMInterface::NVMInterface(const NVMInterfaceParams* _p) writeRespondEvent([this]{ processWriteRespondEvent(); }, name()), readReadyEvent([this]{ processReadReadyEvent(); }, name()), nextReadAt(0), numPendingReads(0), numReadDataReady(0), - numReadsToIssue(0), numWritesQueued(0), - readBufferSize(_p->read_buffer_size), - writeBufferSize(_p->write_buffer_size) + numReadsToIssue(0), numWritesQueued(0) { DPRINTF(NVM, "Setting up NVM Interface\n"); @@ -3473,8 +2070,8 @@ void NVMInterface::setupRank(const uint8_t rank, const bool is_read) } } -pair -NVMInterface::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const +pair +NVMInterface::chooseNextFRFCFS(MemPacketQueue& queue, Tick min_col_at) const { // remember if we found a hit, but one that cannit issue seamlessly bool found_prepped_pkt = false; @@ -3483,7 +2080,7 @@ NVMInterface::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const Tick selected_col_at = MaxTick; for (auto i = queue.begin(); i != queue.end() ; ++i) { - DRAMPacket* pkt = *i; + MemPacket* pkt = *i; // select optimal NVM packet in Q if (!pkt->isDram()) { @@ -3530,7 +2127,7 @@ NVMInterface::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const } void -NVMInterface::chooseRead(DRAMPacketQueue& queue) +NVMInterface::chooseRead(MemPacketQueue& queue) { Tick cmd_at = std::max(curTick(), nextReadAt); @@ -3545,7 +2142,7 @@ NVMInterface::chooseRead(DRAMPacketQueue& queue) numReadsToIssue--; // For simplicity, issue non-deterministic reads in order (fcfs) for (auto i = queue.begin(); i != queue.end() ; ++i) { - DRAMPacket* pkt = *i; + MemPacket* pkt = *i; // Find 1st NVM read packet that hasn't issued read command if (pkt->readyTime == MaxTick && !pkt->isDram() && pkt->isRead()) { @@ -3671,7 +2268,7 @@ NVMInterface::processReadReadyEvent() bool -NVMInterface::burstReady(DRAMPacket* pkt) const { +NVMInterface::burstReady(MemPacket* pkt) const { bool read_rdy = pkt->isRead() && (ctrl->inReadBusState(true)) && (pkt->readyTime <= curTick()) && (numReadDataReady > 0); bool write_rdy = !pkt->isRead() && !ctrl->inReadBusState(true) && @@ -3680,7 +2277,7 @@ NVMInterface::burstReady(DRAMPacket* pkt) const { } pair -NVMInterface::doBurstAccess(DRAMPacket* pkt, Tick next_burst_at) +NVMInterface::doBurstAccess(MemPacket* pkt, Tick next_burst_at) { DPRINTF(NVM, "NVM Timing access to addr %lld, rank/bank/row %d %d %d\n", pkt->addr, pkt->rank, pkt->bank, pkt->row); diff --git a/src/mem/dram_ctrl.hh b/src/mem/mem_interface.hh similarity index 61% rename from src/mem/dram_ctrl.hh rename to src/mem/mem_interface.hh index 7fc499f0d..98440020a 100644 --- a/src/mem/dram_ctrl.hh +++ b/src/mem/mem_interface.hh @@ -40,11 +40,11 @@ /** * @file - * DRAMCtrl declaration + * MemInterface declaration */ -#ifndef __MEM_DRAM_CTRL_HH__ -#define __MEM_DRAM_CTRL_HH__ +#ifndef __MEM_INTERFACE_HH__ +#define __MEM_INTERFACE_HH__ #include #include @@ -54,168 +54,15 @@ #include "base/statistics.hh" #include "enums/AddrMap.hh" -#include "enums/MemSched.hh" #include "enums/PageManage.hh" #include "mem/abstract_mem.hh" #include "mem/drampower.hh" -#include "mem/qos/mem_ctrl.hh" -#include "mem/qport.hh" -#include "params/DRAMCtrl.hh" +#include "mem/mem_ctrl.hh" #include "params/DRAMInterface.hh" #include "params/MemInterface.hh" #include "params/NVMInterface.hh" #include "sim/eventq.hh" -class DRAMInterfaceParams; -class NVMInterfaceParams; - -/** - * A burst helper helps organize and manage a packet that is larger than - * the DRAM burst size. A system packet that is larger than the burst size - * is split into multiple DRAM packets and all those DRAM packets point to - * a single burst helper such that we know when the whole packet is served. - */ -class BurstHelper -{ - public: - - /** Number of DRAM bursts requred for a system packet **/ - const unsigned int burstCount; - - /** Number of DRAM bursts serviced so far for a system packet **/ - unsigned int burstsServiced; - - BurstHelper(unsigned int _burstCount) - : burstCount(_burstCount), burstsServiced(0) - { } -}; - -/** - * A DRAM packet stores packets along with the timestamp of when - * the packet entered the queue, and also the decoded address. - */ -class DRAMPacket -{ - public: - - /** When did request enter the controller */ - const Tick entryTime; - - /** When will request leave the controller */ - Tick readyTime; - - /** This comes from the outside world */ - const PacketPtr pkt; - - /** MasterID associated with the packet */ - const MasterID _masterId; - - const bool read; - - /** Does this packet access DRAM?*/ - const bool dram; - - /** Will be populated by address decoder */ - const uint8_t rank; - const uint8_t bank; - const uint32_t row; - - /** - * Bank id is calculated considering banks in all the ranks - * eg: 2 ranks each with 8 banks, then bankId = 0 --> rank0, bank0 and - * bankId = 8 --> rank1, bank0 - */ - const uint16_t bankId; - - /** - * The starting address of the DRAM packet. - * This address could be unaligned to burst size boundaries. The - * reason is to keep the address offset so we can accurately check - * incoming read packets with packets in the write queue. - */ - Addr addr; - - /** - * The size of this dram packet in bytes - * It is always equal or smaller than DRAM burst size - */ - unsigned int size; - - /** - * A pointer to the BurstHelper if this DRAMPacket is a split packet - * If not a split packet (common case), this is set to NULL - */ - BurstHelper* burstHelper; - - /** - * QoS value of the encapsulated packet read at queuing time - */ - uint8_t _qosValue; - - /** - * Set the packet QoS value - * (interface compatibility with Packet) - */ - inline void qosValue(const uint8_t qv) { _qosValue = qv; } - - /** - * Get the packet QoS value - * (interface compatibility with Packet) - */ - inline uint8_t qosValue() const { return _qosValue; } - - /** - * Get the packet MasterID - * (interface compatibility with Packet) - */ - inline MasterID masterId() const { return _masterId; } - - /** - * Get the packet size - * (interface compatibility with Packet) - */ - inline unsigned int getSize() const { return size; } - - /** - * Get the packet address - * (interface compatibility with Packet) - */ - inline Addr getAddr() const { return addr; } - - /** - * Return true if its a read packet - * (interface compatibility with Packet) - */ - inline bool isRead() const { return read; } - - /** - * Return true if its a write packet - * (interface compatibility with Packet) - */ - inline bool isWrite() const { return !read; } - - /** - * Return true if its a DRAM access - */ - inline bool isDram() const { return dram; } - - DRAMPacket(PacketPtr _pkt, bool is_read, bool is_dram, uint8_t _rank, - uint8_t _bank, uint32_t _row, uint16_t bank_id, Addr _addr, - unsigned int _size) - : entryTime(curTick()), readyTime(curTick()), pkt(_pkt), - _masterId(pkt->masterId()), - read(is_read), dram(is_dram), rank(_rank), bank(_bank), row(_row), - bankId(bank_id), addr(_addr), size(_size), burstHelper(NULL), - _qosValue(_pkt->qosValue()) - { } - -}; - -// The DRAM packets are store in a multiple dequeue structure, -// based on their QoS priority -typedef std::deque DRAMPacketQueue; - - /** * General interface to memory device * Includes functions and parameters shared across media types @@ -259,9 +106,9 @@ class MemInterface : public AbstractMemory }; /** - * A pointer to the parent DRAMCtrl instance + * A pointer to the parent MemCtrl instance */ - DRAMCtrl* ctrl; + MemCtrl* ctrl; /** * Number of commands that can issue in the defined controller @@ -317,13 +164,23 @@ class MemInterface : public AbstractMemory public: + + /** + * Buffer sizes for read and write queues in the controller + * These are passed to the controller on instantiation + * Defining them here allows for buffers to be resized based + * on memory type / configuration. + */ + const uint32_t readBufferSize; + const uint32_t writeBufferSize; + /** Set a pointer to the controller and initialize * interface based on controller parameters * @param _ctrl pointer to the parent controller * @param command_window size of command window used to * check command bandwidth */ - void setCtrl(DRAMCtrl* _ctrl, unsigned int command_window); + void setCtrl(MemCtrl* _ctrl, unsigned int command_window); /** * Get an address in a dense range which starts from 0. The input @@ -363,8 +220,8 @@ class MemInterface : public AbstractMemory * @return an iterator to the selected packet, else queue.end() * @return the tick when the packet selected will issue */ - virtual std::pair - chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const = 0; + virtual std::pair + chooseNextFRFCFS(MemPacketQueue& queue, Tick min_col_at) const = 0; /* * Function to calulate unloaded latency @@ -386,7 +243,7 @@ class MemInterface : public AbstractMemory * * @param Return true if RD/WR can issue */ - virtual bool burstReady(DRAMPacket* pkt) const = 0; + virtual bool burstReady(MemPacket* pkt) const = 0; /** * Determine the required delay for an access to a different rank @@ -414,13 +271,13 @@ class MemInterface : public AbstractMemory * pkt_addr is used for the offset within the packet. * * @param pkt The packet from the outside world - * @param pkt_addr The starting address of the DRAM packet - * @param size The size of the DRAM packet in bytes + * @param pkt_addr The starting address of the packet + * @param size The size of the packet in bytes * @param is_read Is the request for a read or a write to memory * @param is_dram Is the request to a DRAM interface - * @return A DRAMPacket pointer with the decoded information + * @return A MemPacket pointer with the decoded information */ - DRAMPacket* decodePacket(const PacketPtr pkt, Addr pkt_addr, + MemPacket* decodePacket(const PacketPtr pkt, Addr pkt_addr, unsigned int size, bool is_read, bool is_dram); /** @@ -997,17 +854,6 @@ class DRAMInterface : public MemInterface */ std::vector ranks; - public: - - /** - * Buffer sizes for read and write queues in the controller - * These are passed to the controller on instantiation - * Defining them here allows for buffers to be resized based - * on memory type / configuration. - */ - const uint32_t readBufferSize; - const uint32_t writeBufferSize; - /* * @return delay between write and read commands */ @@ -1024,7 +870,7 @@ class DRAMInterface : public MemInterface * @return boolean indicating burst can issue seamlessly, with no gaps */ std::pair, bool> - minBankPrep(const DRAMPacketQueue& queue, Tick min_col_at) const; + minBankPrep(const MemPacketQueue& queue, Tick min_col_at) const; /* * @return time to send a burst of data without gaps @@ -1093,8 +939,8 @@ class DRAMInterface : public MemInterface * @return an iterator to the selected packet, else queue.end() * @return the tick when the packet selected will issue */ - std::pair - chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const override; + std::pair + chooseNextFRFCFS(MemPacketQueue& queue, Tick min_col_at) const override; /** * Actually do the burst - figure out the latency it @@ -1104,15 +950,15 @@ class DRAMInterface : public MemInterface * response q from where it will eventually go back to the outside * world. * - * @param dram_pkt The DRAM packet created from the outside world pkt + * @param mem_pkt The packet created from the outside world pkt * @param next_burst_at Minimum bus timing requirement from controller * @param queue Reference to the read or write queue with the packet * @return pair, tick when current burst is issued and * tick when next burst can issue */ std::pair - doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at, - const std::vector& queue); + doBurstAccess(MemPacket* mem_pkt, Tick next_burst_at, + const std::vector& queue); /** * Check if a burst operation can be issued to the DRAM @@ -1122,7 +968,7 @@ class DRAMInterface : public MemInterface * REF IDLE state */ bool - burstReady(DRAMPacket* pkt) const override + burstReady(MemPacket* pkt) const override { return ranks[pkt->rank]->inRefIdleState(); } @@ -1309,15 +1155,6 @@ class NVMInterface : public MemInterface // number of writes in the writeQueue for the NVM interface uint32_t numWritesQueued; - /** - * Buffer sizes for read and write queues in the controller - * These are passed to the controller on instantiation - * Defining them here allows for buffers to be resized based - * on memory type / configuration. - */ - const uint32_t readBufferSize; - const uint32_t writeBufferSize; - /** * Initialize the NVM interface and verify parameters */ @@ -1352,7 +1189,7 @@ class NVMInterface : public MemInterface * has been updated to a non-zero value to * account for race conditions between events */ - bool burstReady(DRAMPacket* pkt) const override; + bool burstReady(MemPacket* pkt) const override; /** * This function checks if ranks are busy. @@ -1375,8 +1212,8 @@ class NVMInterface : public MemInterface * @return an iterator to the selected packet, else queue.end() * @return the tick when the packet selected will issue */ - std::pair - chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const override; + std::pair + chooseNextFRFCFS(MemPacketQueue& queue, Tick min_col_at) const override; /** * Add rank to rank delay to bus timing to all NVM banks in alli ranks @@ -1391,7 +1228,7 @@ class NVMInterface : public MemInterface /** * Select read command to issue asynchronously */ - void chooseRead(DRAMPacketQueue& queue); + void chooseRead(MemPacketQueue& queue); /* * Function to calulate unloaded access latency @@ -1425,531 +1262,9 @@ class NVMInterface : public MemInterface * tick when next burst can issue */ std::pair - doBurstAccess(DRAMPacket* pkt, Tick next_burst_at); + doBurstAccess(MemPacket* pkt, Tick next_burst_at); NVMInterface(const NVMInterfaceParams* _p); }; -/** - * The DRAM controller is a single-channel memory controller capturing - * the most important timing constraints associated with a - * contemporary DRAM. For multi-channel memory systems, the controller - * is combined with a crossbar model, with the channel address - * interleaving taking part in the crossbar. - * - * As a basic design principle, this controller - * model is not cycle callable, but instead uses events to: 1) decide - * when new decisions can be made, 2) when resources become available, - * 3) when things are to be considered done, and 4) when to send - * things back. Through these simple principles, the model delivers - * high performance, and lots of flexibility, allowing users to - * evaluate the system impact of a wide range of memory technologies, - * such as DDR3/4, LPDDR2/3/4, WideIO1/2, HBM and HMC. - * - * For more details, please see Hansson et al, "Simulating DRAM - * controllers for future system architecture exploration", - * Proc. ISPASS, 2014. If you use this model as part of your research - * please cite the paper. - * - * The low-power functionality implements a staggered powerdown - * similar to that described in "Optimized Active and Power-Down Mode - * Refresh Control in 3D-DRAMs" by Jung et al, VLSI-SoC, 2014. - */ -class DRAMCtrl : public QoS::MemCtrl -{ - private: - - // For now, make use of a queued slave port to avoid dealing with - // flow control for the responses being sent back - class MemoryPort : public QueuedSlavePort - { - - RespPacketQueue queue; - DRAMCtrl& ctrl; - - public: - - MemoryPort(const std::string& name, DRAMCtrl& _ctrl); - - protected: - - Tick recvAtomic(PacketPtr pkt); - - void recvFunctional(PacketPtr pkt); - - bool recvTimingReq(PacketPtr); - - virtual AddrRangeList getAddrRanges() const; - - }; - - /** - * Our incoming port, for a multi-ported controller add a crossbar - * in front of it - */ - MemoryPort port; - - /** - * Remember if the memory system is in timing mode - */ - bool isTimingMode; - - /** - * Remember if we have to retry a request when available. - */ - bool retryRdReq; - bool retryWrReq; - - /** - * Bunch of things requires to setup "events" in gem5 - * When event "respondEvent" occurs for example, the method - * processRespondEvent is called; no parameters are allowed - * in these methods - */ - void processNextReqEvent(); - EventFunctionWrapper nextReqEvent; - - void processRespondEvent(); - EventFunctionWrapper respondEvent; - - /** - * Check if the read queue has room for more entries - * - * @param pkt_count The number of entries needed in the read queue - * @return true if read queue is full, false otherwise - */ - bool readQueueFull(unsigned int pkt_count) const; - - /** - * Check if the write queue has room for more entries - * - * @param pkt_count The number of entries needed in the write queue - * @return true if write queue is full, false otherwise - */ - bool writeQueueFull(unsigned int pkt_count) const; - - /** - * When a new read comes in, first check if the write q has a - * pending request to the same address.\ If not, decode the - * address to populate rank/bank/row, create one or mutliple - * "dram_pkt", and push them to the back of the read queue.\ - * If this is the only - * read request in the system, schedule an event to start - * servicing it. - * - * @param pkt The request packet from the outside world - * @param pkt_count The number of DRAM bursts the pkt - * @param is_dram Does this packet access DRAM? - * translate to. If pkt size is larger then one full burst, - * then pkt_count is greater than one. - */ - void addToReadQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram); - - /** - * Decode the incoming pkt, create a dram_pkt and push to the - * back of the write queue. \If the write q length is more than - * the threshold specified by the user, ie the queue is beginning - * to get full, stop reads, and start draining writes. - * - * @param pkt The request packet from the outside world - * @param pkt_count The number of DRAM bursts the pkt - * @param is_dram Does this packet access DRAM? - * translate to. If pkt size is larger then one full burst, - * then pkt_count is greater than one. - */ - void addToWriteQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram); - - /** - * Actually do the burst based on media specific access function. - * Update bus statistics when complete. - * - * @param pkt The DRAM packet created from the outside world pkt - */ - void doBurstAccess(DRAMPacket* dram_pkt); - - /** - * When a packet reaches its "readyTime" in the response Q, - * use the "access()" method in AbstractMemory to actually - * create the response packet, and send it back to the outside - * world requestor. - * - * @param pkt The packet from the outside world - * @param static_latency Static latency to add before sending the packet - */ - void accessAndRespond(PacketPtr pkt, Tick static_latency); - - /** - * Determine if there is a packet that can issue. - * - * @param pkt The packet to evaluate - */ - bool - packetReady(DRAMPacket* pkt) - { - return (pkt->isDram() ? - dram->burstReady(pkt) : nvm->burstReady(pkt)); - } - - /** - * Calculate the minimum delay used when scheduling a read-to-write - * transision. - * @param return minimum delay - */ - Tick - minReadToWriteDataGap() - { - Tick dram_min = dram ? dram->minReadToWriteDataGap() : MaxTick; - Tick nvm_min = nvm ? nvm->minReadToWriteDataGap() : MaxTick; - return std::min(dram_min, nvm_min); - } - - /** - * Calculate the minimum delay used when scheduling a write-to-read - * transision. - * @param return minimum delay - */ - Tick - minWriteToReadDataGap() - { - Tick dram_min = dram ? dram->minWriteToReadDataGap() : MaxTick; - Tick nvm_min = nvm ? nvm->minWriteToReadDataGap() : MaxTick; - return std::min(dram_min, nvm_min); - } - - /** - * The memory schduler/arbiter - picks which request needs to - * go next, based on the specified policy such as FCFS or FR-FCFS - * and moves it to the head of the queue. - * Prioritizes accesses to the same rank as previous burst unless - * controller is switching command type. - * - * @param queue Queued requests to consider - * @param extra_col_delay Any extra delay due to a read/write switch - * @return an iterator to the selected packet, else queue.end() - */ - DRAMPacketQueue::iterator chooseNext(DRAMPacketQueue& queue, - Tick extra_col_delay); - - /** - * For FR-FCFS policy reorder the read/write queue depending on row buffer - * hits and earliest bursts available in DRAM - * - * @param queue Queued requests to consider - * @param extra_col_delay Any extra delay due to a read/write switch - * @return an iterator to the selected packet, else queue.end() - */ - DRAMPacketQueue::iterator chooseNextFRFCFS(DRAMPacketQueue& queue, - Tick extra_col_delay); - - /** - * Calculate burst window aligned tick - * - * @param cmd_tick Initial tick of command - * @return burst window aligned tick - */ - Tick getBurstWindow(Tick cmd_tick); - - /** - * Used for debugging to observe the contents of the queues. - */ - void printQs() const; - - /** - * Burst-align an address. - * - * @param addr The potentially unaligned address - * @param is_dram Does this packet access DRAM? - * - * @return An address aligned to a memory burst - */ - Addr - burstAlign(Addr addr, bool is_dram) const - { - if (is_dram) - return (addr & ~(Addr(dram->bytesPerBurst() - 1))); - else - return (addr & ~(Addr(nvm->bytesPerBurst() - 1))); - } - - /** - * The controller's main read and write queues, with support for QoS reordering - */ - std::vector readQueue; - std::vector writeQueue; - - /** - * To avoid iterating over the write queue to check for - * overlapping transactions, maintain a set of burst addresses - * that are currently queued. Since we merge writes to the same - * location we never have more than one address to the same burst - * address. - */ - std::unordered_set isInWriteQueue; - - /** - * Response queue where read packets wait after we're done working - * with them, but it's not time to send the response yet. The - * responses are stored separately mostly to keep the code clean - * and help with events scheduling. For all logical purposes such - * as sizing the read queue, this and the main read queue need to - * be added together. - */ - std::deque respQueue; - - /** - * Holds count of commands issued in burst window starting at - * defined Tick. This is used to ensure that the command bandwidth - * does not exceed the allowable media constraints. - */ - std::unordered_multiset burstTicks; - - /** - * Create pointer to interface of the actual dram media when connected - */ - DRAMInterface* const dram; - - /** - * Create pointer to interface of the actual nvm media when connected - */ - NVMInterface* const nvm; - - /** - * The following are basic design parameters of the memory - * controller, and are initialized based on parameter values. - * The rowsPerBank is determined based on the capacity, number of - * ranks and banks, the burst size, and the row buffer size. - */ - const uint32_t readBufferSize; - const uint32_t writeBufferSize; - const uint32_t writeHighThreshold; - const uint32_t writeLowThreshold; - const uint32_t minWritesPerSwitch; - uint32_t writesThisTime; - uint32_t readsThisTime; - - /** - * Memory controller configuration initialized based on parameter - * values. - */ - Enums::MemSched memSchedPolicy; - - /** - * Pipeline latency of the controller frontend. The frontend - * contribution is added to writes (that complete when they are in - * the write buffer) and reads that are serviced the write buffer. - */ - const Tick frontendLatency; - - /** - * Pipeline latency of the backend and PHY. Along with the - * frontend contribution, this latency is added to reads serviced - * by the DRAM. - */ - const Tick backendLatency; - - /** - * Length of a command window, used to check - * command bandwidth - */ - const Tick commandWindow; - - /** - * Till when must we wait before issuing next RD/WR burst? - */ - Tick nextBurstAt; - - Tick prevArrival; - - /** - * The soonest you have to start thinking about the next request - * is the longest access time that can occur before - * nextBurstAt. Assuming you need to precharge, open a new row, - * and access, it is tRP + tRCD + tCL. - */ - Tick nextReqTime; - - struct CtrlStats : public Stats::Group - { - CtrlStats(DRAMCtrl &ctrl); - - void regStats() override; - - DRAMCtrl &ctrl; - - // All statistics that the model needs to capture - Stats::Scalar readReqs; - Stats::Scalar writeReqs; - Stats::Scalar readBursts; - Stats::Scalar writeBursts; - Stats::Scalar servicedByWrQ; - Stats::Scalar mergedWrBursts; - Stats::Scalar neitherReadNorWriteReqs; - // Average queue lengths - Stats::Average avgRdQLen; - Stats::Average avgWrQLen; - - Stats::Scalar numRdRetry; - Stats::Scalar numWrRetry; - Stats::Vector readPktSize; - Stats::Vector writePktSize; - Stats::Vector rdQLenPdf; - Stats::Vector wrQLenPdf; - Stats::Histogram rdPerTurnAround; - Stats::Histogram wrPerTurnAround; - - Stats::Scalar bytesReadWrQ; - Stats::Scalar bytesReadSys; - Stats::Scalar bytesWrittenSys; - // Average bandwidth - Stats::Formula avgRdBWSys; - Stats::Formula avgWrBWSys; - - Stats::Scalar totGap; - Stats::Formula avgGap; - - // per-master bytes read and written to memory - Stats::Vector masterReadBytes; - Stats::Vector masterWriteBytes; - - // per-master bytes read and written to memory rate - Stats::Formula masterReadRate; - Stats::Formula masterWriteRate; - - // per-master read and write serviced memory accesses - Stats::Vector masterReadAccesses; - Stats::Vector masterWriteAccesses; - - // per-master read and write total memory access latency - Stats::Vector masterReadTotalLat; - Stats::Vector masterWriteTotalLat; - - // per-master raed and write average memory access latency - Stats::Formula masterReadAvgLat; - Stats::Formula masterWriteAvgLat; - }; - - CtrlStats stats; - - /** - * Upstream caches need this packet until true is returned, so - * hold it for deletion until a subsequent call - */ - std::unique_ptr pendingDelete; - - /** - * Select either the read or write queue - * - * @param is_read The current burst is a read, select read queue - * @return a reference to the appropriate queue - */ - std::vector& - selQueue(bool is_read) - { - return (is_read ? readQueue : writeQueue); - }; - - /** - * Remove commands that have already issued from burstTicks - */ - void pruneBurstTick(); - - public: - - DRAMCtrl(const DRAMCtrlParams* p); - - /** - * Ensure that all interfaced have drained commands - * - * @return bool flag, set once drain complete - */ - bool allIntfDrained() const; - - DrainState drain() override; - - /** - * Check for command bus contention for single cycle command. - * If there is contention, shift command to next burst. - * Check verifies that the commands issued per burst is less - * than a defined max number, maxCommandsPerWindow. - * Therefore, contention per cycle is not verified and instead - * is done based on a burst window. - * - * @param cmd_tick Initial tick of command, to be verified - * @param max_cmds_per_burst Number of commands that can issue - * in a burst window - * @return tick for command issue without contention - */ - Tick verifySingleCmd(Tick cmd_tick, Tick max_cmds_per_burst); - - /** - * Check for command bus contention for multi-cycle (2 currently) - * command. If there is contention, shift command(s) to next burst. - * Check verifies that the commands issued per burst is less - * than a defined max number, maxCommandsPerWindow. - * Therefore, contention per cycle is not verified and instead - * is done based on a burst window. - * - * @param cmd_tick Initial tick of command, to be verified - * @param max_multi_cmd_split Maximum delay between commands - * @param max_cmds_per_burst Number of commands that can issue - * in a burst window - * @return tick for command issue without contention - */ - Tick verifyMultiCmd(Tick cmd_tick, Tick max_cmds_per_burst, - Tick max_multi_cmd_split = 0); - - /** - * Is there a respondEvent scheduled? - * - * @return true if event is scheduled - */ - bool respondEventScheduled() const { return respondEvent.scheduled(); } - - /** - * Is there a read/write burst Event scheduled? - * - * @return true if event is scheduled - */ - bool requestEventScheduled() const { return nextReqEvent.scheduled(); } - - /** - * restart the controller - * This can be used by interfaces to restart the - * scheduler after maintainence commands complete - * - * @param Tick to schedule next event - */ - void restartScheduler(Tick tick) { schedule(nextReqEvent, tick); } - - /** - * Check the current direction of the memory channel - * - * @param next_state Check either the current or next bus state - * @return True when bus is currently in a read state - */ - bool inReadBusState(bool next_state) const; - - /** - * Check the current direction of the memory channel - * - * @param next_state Check either the current or next bus state - * @return True when bus is currently in a write state - */ - bool inWriteBusState(bool next_state) const; - - Port &getPort(const std::string &if_name, - PortID idx=InvalidPortID) override; - - virtual void init() override; - virtual void startup() override; - virtual void drainResume() override; - - protected: - - Tick recvAtomic(PacketPtr pkt); - void recvFunctional(PacketPtr pkt); - bool recvTimingReq(PacketPtr pkt); - -}; - -#endif //__MEM_DRAM_CTRL_HH__ +#endif //__MEM_INTERFACE_HH__ diff --git a/tests/gem5/configs/base_config.py b/tests/gem5/configs/base_config.py index cbea76874..fbedbaf68 100644 --- a/tests/gem5/configs/base_config.py +++ b/tests/gem5/configs/base_config.py @@ -221,7 +221,7 @@ class BaseSESystem(BaseSystem): def create_system(self): if issubclass(self.mem_class, m5.objects.DRAMInterface): - mem_ctrl = DRAMCtrl() + mem_ctrl = MemCtrl() mem_ctrl.dram = self.mem_class() else: mem_ctrl = self.mem_class() @@ -280,7 +280,7 @@ class BaseFSSystem(BaseSystem): if issubclass(self.mem_class, m5.objects.DRAMInterface): mem_ctrls = [] for r in system.mem_ranges: - mem_ctrl = DRAMCtrl() + mem_ctrl = MemCtrl() mem_ctrl.dram = self.mem_class(range = r) mem_ctrls.append(mem_ctrl) system.physmem = mem_ctrls -- 2.30.2