From aa103566d6413bfdbfab7177734f0894a751a856 Mon Sep 17 00:00:00 2001 From: Matteo Andreozzi Date: Thu, 10 May 2018 21:59:08 +0100 Subject: mem: Add a simple QoS-aware Memory Controller This patch implements QoSMemorySink: a simple generic QoS-aware memory controller which inherits from QoS::MemCtrl. Change-Id: I537a4e2d4cb8f54fa0002eb088b2c6957afb9973 Signed-off-by: Giacomo Travaglini Reviewed-on: https://gem5-review.googlesource.com/11971 Maintainer: Nikos Nikoleris Reviewed-by: Nikos Nikoleris Reviewed-by: Matthew Poremba --- src/mem/qos/QoSMemSinkCtrl.py | 63 +++++++ src/mem/qos/SConscript | 2 + src/mem/qos/mem_sink.cc | 392 ++++++++++++++++++++++++++++++++++++++++++ src/mem/qos/mem_sink.hh | 248 ++++++++++++++++++++++++++ 4 files changed, 705 insertions(+) create mode 100644 src/mem/qos/QoSMemSinkCtrl.py create mode 100644 src/mem/qos/mem_sink.cc create mode 100644 src/mem/qos/mem_sink.hh diff --git a/src/mem/qos/QoSMemSinkCtrl.py b/src/mem/qos/QoSMemSinkCtrl.py new file mode 100644 index 000000000..00f19ef7d --- /dev/null +++ b/src/mem/qos/QoSMemSinkCtrl.py @@ -0,0 +1,63 @@ +# Copyright (c) 2018 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Author: Matteo Andreozzi + +from m5.params import * +from QoSMemCtrl import * + +class QoSMemSinkCtrl(QoSMemCtrl): + type = 'QoSMemSinkCtrl' + cxx_header = "mem/qos/mem_sink.hh" + cxx_class = "QoS::MemSinkCtrl" + port = SlavePort("Slave ports") + + # the basic configuration of the controller architecture, note + # that each entry corresponds to a burst for the specific DRAM + # configuration (e.g. x32 with burst length 8 is 32 bytes) and not + # the cacheline size or request/packet size + write_buffer_size = Param.Unsigned(64, "Number of write queue entries") + read_buffer_size = Param.Unsigned(32, "Number of read queue entries") + + # memory packet size + memory_packet_size = Param.MemorySize("32B", "Memory packet size") + + # request latency - minimum timing between requests + request_latency = Param.Latency("20ns", "Memory latency between requests") + + # response latency - time to issue a response once a request is serviced + response_latency = Param.Latency("20ns", "Memory response latency") + + diff --git a/src/mem/qos/SConscript b/src/mem/qos/SConscript index 9d44337ab..9a9343fc0 100644 --- a/src/mem/qos/SConscript +++ b/src/mem/qos/SConscript @@ -38,9 +38,11 @@ Import('*') SimObject('QoSMemCtrl.py') +SimObject('QoSMemSinkCtrl.py') SimObject('QoSPolicy.py') SimObject('QoSTurnaround.py') Source('policy.cc') Source('q_policy.cc') Source('mem_ctrl.cc') +Source('mem_sink.cc') diff --git a/src/mem/qos/mem_sink.cc b/src/mem/qos/mem_sink.cc new file mode 100644 index 000000000..a951daee2 --- /dev/null +++ b/src/mem/qos/mem_sink.cc @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2018 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Matteo Andreozzi + */ + +#include "debug/Drain.hh" +#include "debug/QOS.hh" +#include "mem_sink.hh" +#include "sim/system.hh" + +namespace QoS { + +MemSinkCtrl::MemSinkCtrl(const QoSMemSinkCtrlParams* p) + : MemCtrl(p), requestLatency(p->request_latency), + responseLatency(p->response_latency), + memoryPacketSize(p->memory_packet_size), + readBufferSize(p->read_buffer_size), + writeBufferSize(p->write_buffer_size), port(name() + ".port", *this), + retryRdReq(false), retryWrReq(false), nextRequest(0), nextReqEvent(this) +{ + // Resize read and write queue to allocate space + // for configured QoS priorities + readQueue.resize(numPriorities()); + writeQueue.resize(numPriorities()); +} + +MemSinkCtrl::~MemSinkCtrl() +{} + +void +MemSinkCtrl::init() +{ + MemCtrl::init(); + + // Allow unconnected memories as this is used in several ruby + // systems at the moment + if (port.isConnected()) { + port.sendRangeChange(); + } +} + +bool +MemSinkCtrl::readQueueFull(const uint64_t packets) const +{ + return (totalReadQueueSize + packets > readBufferSize); +} + +bool +MemSinkCtrl::writeQueueFull(const uint64_t packets) const +{ + return (totalWriteQueueSize + packets > writeBufferSize); +} + +Tick +MemSinkCtrl::recvAtomic(PacketPtr pkt) +{ + panic_if(pkt->cacheResponding(), + "%s Should not see packets where cache is responding\n", + __func__); + + access(pkt); + return responseLatency; +} + +void +MemSinkCtrl::recvFunctional(PacketPtr pkt) +{ + pkt->pushLabel(name()); + + functionalAccess(pkt); + + pkt->popLabel(); +} + +BaseSlavePort & +MemSinkCtrl::getSlavePort(const std::string &interface, PortID idx) +{ + if (interface != "port") { + return MemObject::getSlavePort(interface, idx); + } else { + return port; + } +} + +bool +MemSinkCtrl::recvTimingReq(PacketPtr pkt) +{ + // Request accepted + bool req_accepted = true; + + panic_if(!(pkt->isRead() || pkt->isWrite()), + "%s. Should only see " + "read and writes at memory controller\n", + __func__); + + panic_if(pkt->cacheResponding(), + "%s. Should not see packets where cache is responding\n", + __func__); + + DPRINTF(QOS, + "%s: MASTER %s request %s addr %lld size %d\n", + __func__, + _system->getMasterName(pkt->req->masterId()), + pkt->cmdString(), pkt->getAddr(), pkt->getSize()); + + uint64_t required_entries = divCeil(pkt->getSize(), memoryPacketSize); + + assert(required_entries); + + // Schedule packet + uint8_t pkt_priority = qosSchedule({&readQueue, &writeQueue}, + memoryPacketSize, pkt); + + if (pkt->isRead()) { + if (readQueueFull(required_entries)) { + DPRINTF(QOS, + "%s Read queue full, not accepting\n", __func__); + // Remember that we have to retry this port + retryRdReq = true; + numReadRetries++; + req_accepted = false; + } else { + // Enqueue the incoming packet into corresponding + // QoS priority queue + readQueue.at(pkt_priority).push_back(pkt); + queuePolicy->enqueuePacket(pkt); + } + } else { + if (writeQueueFull(required_entries)) { + DPRINTF(QOS, + "%s Write queue full, not accepting\n", __func__); + // Remember that we have to retry this port + retryWrReq = true; + numWriteRetries++; + req_accepted = false; + } else { + // Enqueue the incoming packet into corresponding QoS + // priority queue + writeQueue.at(pkt_priority).push_back(pkt); + queuePolicy->enqueuePacket(pkt); + } + } + + if (req_accepted) { + // The packet is accepted - log it + logRequest(pkt->isRead()? READ : WRITE, + pkt->req->masterId(), + pkt->qosValue(), + pkt->getAddr(), + required_entries); + } + + // Check if we have to process next request event + if (!nextReqEvent.scheduled()) { + DPRINTF(QOS, + "%s scheduling next request at " + "time %d (next is %d)\n", __func__, + std::max(curTick(), nextRequest), nextRequest); + schedule(nextReqEvent, std::max(curTick(), nextRequest)); + } + return req_accepted; +} + +void +MemSinkCtrl::processNextReqEvent() +{ + PacketPtr pkt = nullptr; + + // Evaluate bus direction + busStateNext = selectNextBusState(); + + // Record turnaround stats and update current state direction + recordTurnaroundStats(); + + // Set current bus state + setCurrentBusState(); + + // Access current direction buffer + std::vector* queue_ptr = (busState == READ ? &readQueue : + &writeQueue); + + DPRINTF(QOS, + "%s DUMPING %s queues status\n", __func__, + (busState == WRITE ? "WRITE" : "READ")); + + if (DTRACE(QOS)) { + for (uint8_t i = 0; i < numPriorities(); ++i) { + std::string plist = ""; + for (auto& e : (busState == WRITE ? writeQueue[i]: readQueue[i])) { + plist += (std::to_string(e->req->masterId())) + " "; + } + DPRINTF(QOS, + "%s priority Queue [%i] contains %i elements, " + "packets are: [%s]\n", __func__, i, + busState == WRITE ? writeQueueSizes[i] : + readQueueSizes[i], + plist); + } + } + + uint8_t curr_prio = numPriorities(); + + for (auto queue = (*queue_ptr).rbegin(); + queue != (*queue_ptr).rend(); ++queue) { + + curr_prio--; + + DPRINTF(QOS, + "%s checking %s queue [%d] priority [%d packets]\n", + __func__, (busState == READ? "READ" : "WRITE"), + curr_prio, queue->size()); + + if (!queue->empty()) { + // Call the queue policy to select packet from priority queue + auto p_it = queuePolicy->selectPacket(&(*queue)); + pkt = *p_it; + queue->erase(p_it); + + DPRINTF(QOS, + "%s scheduling packet address %d for master %s from " + "priority queue %d\n", __func__, pkt->getAddr(), + _system->getMasterName(pkt->req->masterId()), + curr_prio); + break; + } + } + + assert(pkt); + + // Setup next request service time - do it here as retry request + // hands over control to the port + nextRequest = curTick() + requestLatency; + + uint64_t removed_entries = divCeil(pkt->getSize(), memoryPacketSize); + + DPRINTF(QOS, + "%s scheduled packet address %d for master %s size is %d, " + "corresponds to %d memory packets\n", __func__, pkt->getAddr(), + _system->getMasterName(pkt->req->masterId()), + pkt->getSize(), removed_entries); + + // Schedule response + panic_if(!pkt->needsResponse(), + "%s response not required\n", __func__); + + // Do the actual memory access which also turns the packet + // into a response + access(pkt); + + // Log the response + logResponse(pkt->isRead()? READ : WRITE, + pkt->req->masterId(), + pkt->qosValue(), + pkt->getAddr(), + removed_entries, responseLatency); + + // Schedule the response + port.schedTimingResp(pkt, curTick() + responseLatency, true); + DPRINTF(QOS, + "%s response scheduled at time %d\n", + __func__, curTick() + responseLatency); + + // Finally - handle retry requests - this handles control + // to the port, so do it last + if (busState == READ && retryRdReq) { + retryRdReq = false; + port.sendRetryReq(); + } else if (busState == WRITE && retryWrReq) { + retryWrReq = false; + port.sendRetryReq(); + } + + // Check if we have to schedule another request event + if ((totalReadQueueSize || totalWriteQueueSize) && + !nextReqEvent.scheduled()) { + + schedule(nextReqEvent, curTick() + requestLatency); + DPRINTF(QOS, + "%s scheduling next request event at tick %d\n", + __func__, curTick() + requestLatency); + } +} + +DrainState +MemSinkCtrl::drain() +{ + if (totalReadQueueSize || totalWriteQueueSize) { + DPRINTF(Drain, + "%s queues have requests, waiting to drain\n", + __func__); + return DrainState::Draining; + } else { + return DrainState::Drained; + } +} + +void +MemSinkCtrl::regStats() +{ + MemCtrl::regStats(); + + // Initialize all the stats + using namespace Stats; + + numReadRetries.name(name() + ".numReadRetries") + .desc("Number of read retries"); + numWriteRetries.name(name() + ".numWriteRetries") + .desc("Number of write retries"); +} + +MemSinkCtrl::MemoryPort::MemoryPort(const std::string& n, + MemSinkCtrl& m) + : QueuedSlavePort(n, &m, queue), memory(m), queue(memory, *this) +{} + +AddrRangeList +MemSinkCtrl::MemoryPort::getAddrRanges() const +{ + AddrRangeList ranges; + ranges.push_back(memory.getAddrRange()); + return ranges; +} + +Tick +MemSinkCtrl::MemoryPort::recvAtomic(PacketPtr pkt) +{ + return memory.recvAtomic(pkt); +} + +void +MemSinkCtrl::MemoryPort::recvFunctional(PacketPtr pkt) +{ + pkt->pushLabel(memory.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. + memory.recvFunctional(pkt); + } + + pkt->popLabel(); +} + +bool +MemSinkCtrl::MemoryPort::recvTimingReq(PacketPtr pkt) +{ + return memory.recvTimingReq(pkt); +} + +} // namespace QoS + +QoS::MemSinkCtrl* +QoSMemSinkCtrlParams::create() +{ + return new QoS::MemSinkCtrl(this); +} + diff --git a/src/mem/qos/mem_sink.hh b/src/mem/qos/mem_sink.hh new file mode 100644 index 000000000..84258e0ac --- /dev/null +++ b/src/mem/qos/mem_sink.hh @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2018 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Matteo Andreozzi + */ + + +#ifndef __MEM_QOS_MEM_SINK_HH__ +#define __MEM_QOS_MEM_SINK_HH__ + +#include "mem/qos/mem_ctrl.hh" +#include "mem/qport.hh" +#include "params/QoSMemSinkCtrl.hh" + +namespace QoS { + +/** + * QoS Memory Sink + * + * The QoS Memory Sink is a lightweight memory controller with QoS + * support. It is meant to provide a QoS aware simple memory system + * without the need of using a complex DRAM memory controller + */ +class MemSinkCtrl : public MemCtrl +{ + protected: + /** + * The Request packets are store in a multiple dequeue structure, + * based on their QoS priority + */ + using PacketQueue = std::deque; + + private: + class MemoryPort : public QueuedSlavePort + { + private: + /** reference to parent memory object */ + MemSinkCtrl& memory; + + /** Outgoing packet responses queue */ + RespPacketQueue queue; + + public: + /** + * Constructor + * + * @param n port name + * @param m reference to ProfileGen parent object + */ + MemoryPort(const std::string&, MemSinkCtrl&); + + protected: + /** + * Receive a Packet in Atomic mode + * + * @param pkt pointer to memory packet + * @return packet access latency in ticks + */ + Tick recvAtomic(PacketPtr pkt); + + /** + * Receive a Packet in Functional mode + * + * @param pkt pointer to memory packet + */ + void recvFunctional(PacketPtr pkt); + + /** + * Receive a Packet in Timing mode + * + * @param pkt pointer to memory packet + * @return true if the request was accepted + */ + bool recvTimingReq(PacketPtr pkt); + + /** + * Gets the configured address ranges for this port + * @return the configured address ranges for this port + */ + AddrRangeList getAddrRanges() const; + + }; + + public: + /** + * QoS Memory Sink Constructor + * + * @param p QoS Memory Sink configuration parameters + */ + MemSinkCtrl(const QoSMemSinkCtrlParams*); + + virtual ~MemSinkCtrl(); + + /** + * Checks and return the Drain state of this SimObject + * @return current Drain state + */ + DrainState drain() override; + + /** + * Getter method to access this memory's slave port + * + * @param interface interface name + * @param idx port ID number + * @return reference to this memory's slave port + */ + BaseSlavePort& getSlavePort(const std::string&, + PortID = InvalidPortID) override; + + /** + * Initializes this object + */ + void init() override; + + protected: + /** Memory between requests latency (ticks) */ + const Tick requestLatency; + + /** Memory response latency (ticks) */ + const Tick responseLatency; + + /** Memory packet size in bytes */ + const uint64_t memoryPacketSize; + + /** Read request packets queue buffer size in #packets */ + const uint64_t readBufferSize; + + /** Write request packets queue buffer size in #packets */ + const uint64_t writeBufferSize; + + /** Memory slave port */ + MemoryPort port; + + /** Read request pending */ + bool retryRdReq; + + /** Write request pending */ + bool retryWrReq; + + /** Next request service time */ + Tick nextRequest; + + /** Count the number of read retries */ + Stats::Scalar numReadRetries; + + /** Count the number of write retries */ + Stats::Scalar numWriteRetries; + + /** + * QoS-aware (per priority) incoming read requests packets queue + */ + std::vector readQueue; + + /** + * QoS-aware (per priority) incoming read requests packets queue + */ + std::vector writeQueue; + + /** + * Processes the next Request event according to configured + * request latency + */ + void processNextReqEvent(); + + /** Event wrapper to schedule next request handler function */ + EventWrapper< + MemSinkCtrl, + &MemSinkCtrl::processNextReqEvent> nextReqEvent; + + /** + * Check if the read queue has room for more entries + * + * @param packets The number of entries needed in the read queue + * @return true if read queue is full, false otherwise + */ + inline bool readQueueFull(const uint64_t packets) const; + + /** + * Check if the write queue has room for more entries + * + * @param packets The number of entries needed in the write queue + * @return true if write queue is full, false otherwise + */ + inline bool writeQueueFull(const uint64_t packets) const; + + /** + * Receive a Packet in Atomic mode + * + * @param pkt pointer to memory packet + * @return packet access latency in ticks + */ + Tick recvAtomic(PacketPtr pkt); + + /** + * Receive a Packet in Functional mode + * + * @param pkt pointer to memory packet + */ + void recvFunctional(PacketPtr pkt); + + /** + * Receive a Packet in Timing mode + * + * @param pkt pointer to memory packet + * @return true if the request was accepted + */ + bool recvTimingReq(PacketPtr pkt); + + /** Registers statistics */ + void regStats() override; +}; + +} // namespace QoS + +#endif /* __MEM_QOS_MEM_SINK_HH__ */ -- cgit v1.2.3