/* * 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); 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, true), memory(m), queue(memory, *this, true) {} 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); }