| /* |
| * Copyright (c) 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. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer; |
| * redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution; |
| * neither the name of the copyright holders nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #ifndef __MEM_QOS_MEM_CTRL_HH__ |
| #define __MEM_QOS_MEM_CTRL_HH__ |
| |
| #include <cstdint> |
| #include <deque> |
| #include <memory> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/compiler.hh" |
| #include "base/logging.hh" |
| #include "base/statistics.hh" |
| #include "base/trace.hh" |
| #include "base/types.hh" |
| #include "debug/QOS.hh" |
| #include "mem/packet.hh" |
| #include "mem/request.hh" |
| #include "params/QoSMemCtrl.hh" |
| #include "sim/clocked_object.hh" |
| #include "sim/cur_tick.hh" |
| #include "sim/system.hh" |
| |
| namespace gem5 |
| { |
| |
| namespace memory |
| { |
| |
| namespace qos |
| { |
| |
| class Policy; |
| class QueuePolicy; |
| class TurnaroundPolicy; |
| |
| /** |
| * The qos::MemCtrl is a base class for Memory objects |
| * which support QoS - it provides access to a set of QoS |
| * scheduling policies |
| */ |
| class MemCtrl : public ClockedObject |
| { |
| public: |
| /** Bus Direction */ |
| enum BusState { READ, WRITE }; |
| |
| protected: |
| /** QoS Policy, assigns QoS priority to the incoming packets */ |
| const std::unique_ptr<Policy> policy; |
| |
| /** QoS Bus Turnaround Policy: selects the bus direction (READ/WRITE) */ |
| const std::unique_ptr<TurnaroundPolicy> turnPolicy; |
| |
| /** QoS Queue Policy: selects packet among same-priority queue */ |
| const std::unique_ptr<QueuePolicy> queuePolicy; |
| |
| /** Number of configured QoS priorities */ |
| const uint8_t _numPriorities; |
| |
| /** Enables QoS priority escalation */ |
| const bool qosPriorityEscalation; |
| |
| /** |
| * Enables QoS synchronized scheduling invokes the QoS scheduler |
| * on all requestors, at every packet arrival. |
| */ |
| const bool qosSyncroScheduler; |
| |
| /** Hash of requestor ID - requestor name */ |
| std::unordered_map<RequestorID, const std::string> requestors; |
| |
| /** Hash of requestors - number of packets queued per priority */ |
| std::unordered_map<RequestorID, std::vector<uint64_t> > packetPriorities; |
| |
| /** Hash of requestors - address of request - queue of times of request */ |
| std::unordered_map<RequestorID, |
| std::unordered_map<uint64_t, std::deque<uint64_t>> > requestTimes; |
| |
| /** |
| * Vector of QoS priorities/last service time. Refreshed at every |
| * qosSchedule call. |
| */ |
| std::vector<Tick> serviceTick; |
| |
| /** Read request packets queue length in #packets, per QoS priority */ |
| std::vector<uint64_t> readQueueSizes; |
| |
| /** Write request packets queue length in #packets, per QoS priority */ |
| std::vector<uint64_t> writeQueueSizes; |
| |
| /** Total read request packets queue length in #packets */ |
| uint64_t totalReadQueueSize; |
| |
| /** Total write request packets queue length in #packets */ |
| uint64_t totalWriteQueueSize; |
| |
| /** |
| * Bus state used to control the read/write switching and drive |
| * the scheduling of the next request. |
| */ |
| BusState busState; |
| |
| /** bus state for next request event triggered */ |
| BusState busStateNext; |
| |
| struct MemCtrlStats : public statistics::Group |
| { |
| MemCtrlStats(MemCtrl &mc); |
| |
| void regStats() override; |
| |
| const MemCtrl &memCtrl; |
| |
| /** per-requestor average QoS priority */ |
| statistics::VectorStandardDeviation avgPriority; |
| /** |
| * per-requestor average QoS distance between assigned and |
| * queued values |
| */ |
| statistics::VectorStandardDeviation avgPriorityDistance; |
| |
| /** per-priority minimum latency */ |
| statistics::Vector priorityMinLatency; |
| /** per-priority maximum latency */ |
| statistics::Vector priorityMaxLatency; |
| /** Count the number of turnarounds READ to WRITE */ |
| statistics::Scalar numReadWriteTurnArounds; |
| /** Count the number of turnarounds WRITE to READ */ |
| statistics::Scalar numWriteReadTurnArounds; |
| /** Count the number of times bus staying in READ state */ |
| statistics::Scalar numStayReadState; |
| /** Count the number of times bus staying in WRITE state */ |
| statistics::Scalar numStayWriteState; |
| } stats; |
| |
| /** Pointer to the System object */ |
| System* _system; |
| |
| /** |
| * Initializes dynamically counters and |
| * statistics for a given Requestor |
| * |
| * @param id the requestor's ID |
| */ |
| void addRequestor(const RequestorID id); |
| |
| /** |
| * Called upon receiving a request or |
| * updates statistics and updates queues status |
| * |
| * @param dir request direction |
| * @param id requestor id |
| * @param _qos packet QoS value |
| * @param addr packet address |
| * @param entries number of entries to record |
| */ |
| void logRequest(BusState dir, RequestorID id, uint8_t _qos, |
| Addr addr, uint64_t entries); |
| |
| /** |
| * Called upon receiving a response, |
| * updates statistics and updates queues status |
| * |
| * @param dir response direction |
| * @param id requestor id |
| * @param _qos packet QoS value |
| * @param addr packet address |
| * @param entries number of entries to record |
| * @param delay response delay |
| */ |
| void logResponse(BusState dir, RequestorID id, uint8_t _qos, |
| Addr addr, uint64_t entries, double delay); |
| |
| /** |
| * Assign priority to a packet by executing |
| * the configured QoS policy. |
| * |
| * @param queues_ptr list of pointers to packet queues |
| * @param queue_entry_size size in bytes per each packet in the queue |
| * @param pkt pointer to the Packet |
| * @return a QoS priority value |
| */ |
| template<typename Queues> |
| uint8_t qosSchedule(std::initializer_list<Queues*> queues_ptr, |
| uint64_t queue_entry_size, const PacketPtr pkt); |
| |
| using SimObject::schedule; |
| uint8_t schedule(RequestorID id, uint64_t data); |
| uint8_t schedule(const PacketPtr pkt); |
| |
| /** |
| * Returns next bus direction (READ or WRITE) |
| * based on configured policy. |
| */ |
| BusState selectNextBusState(); |
| |
| /** |
| * Set current bus direction (READ or WRITE) |
| * from next selected one |
| */ |
| void setCurrentBusState() { busState = busStateNext; } |
| |
| /** |
| * Record statistics on turnarounds based on |
| * busStateNext and busState values |
| */ |
| void recordTurnaroundStats(); |
| |
| /** |
| * Escalates/demotes priority of all packets |
| * belonging to the passed requestor to given |
| * priority value |
| * |
| * @param queues list of pointers to packet queues |
| * @param queue_entry_size size of an entry in the queue |
| * @param id requestor whose packets priority will change |
| * @param tgt_prio target priority value |
| */ |
| template<typename Queues> |
| void escalate(std::initializer_list<Queues*> queues, |
| uint64_t queue_entry_size, |
| RequestorID id, uint8_t tgt_prio); |
| |
| /** |
| * Escalates/demotes priority of all packets |
| * belonging to the passed requestor to given |
| * priority value in a specified cluster of queues |
| * (e.g. read queues or write queues) which is passed |
| * as an argument to the function. |
| * The curr_prio/tgt_prio parameters are queue selectors in the |
| * queue cluster. |
| * |
| * @param queues reference to packet queues |
| * @param queue_entry_size size of an entry in the queue |
| * @param id requestor whose packets priority will change |
| * @param curr_prio source queue priority value |
| * @param tgt_prio target queue priority value |
| */ |
| template<typename Queues> |
| void escalateQueues(Queues& queues, uint64_t queue_entry_size, |
| RequestorID id, uint8_t curr_prio, uint8_t tgt_prio); |
| |
| public: |
| /** |
| * QoS Memory base class |
| * |
| * @param p pointer to QoSMemCtrl parameters |
| */ |
| MemCtrl(const QoSMemCtrlParams &); |
| |
| virtual ~MemCtrl(); |
| |
| /** |
| * Gets the current bus state |
| * |
| * @return current bus state |
| */ |
| BusState getBusState() const { return busState; } |
| |
| /** |
| * Gets the next bus state |
| * |
| * @return next bus state |
| */ |
| BusState getBusStateNext() const { return busStateNext; } |
| |
| /** |
| * hasRequestor returns true if the selected requestor(ID) has |
| * been registered in the memory controller, which happens if |
| * the memory controller has received at least a packet from |
| * that requestor. |
| * |
| * @param id requestor id to lookup |
| * @return true if the memory controller has received a packet |
| * from the requestor, false otherwise. |
| */ |
| bool hasRequestor(RequestorID id) const |
| { |
| return requestors.find(id) != requestors.end(); |
| } |
| |
| /** |
| * Gets a READ queue size |
| * |
| * @param prio QoS Priority of the queue |
| * @return queue size in packets |
| */ |
| uint64_t getReadQueueSize(const uint8_t prio) const |
| { return readQueueSizes[prio]; } |
| |
| /** |
| * Gets a WRITE queue size |
| * |
| * @param prio QoS Priority of the queue |
| * @return queue size in packets |
| */ |
| uint64_t getWriteQueueSize(const uint8_t prio) const |
| { return writeQueueSizes[prio]; } |
| |
| /** |
| * Gets the total combined READ queues size |
| * |
| * @return total queues size in packets |
| */ |
| uint64_t getTotalReadQueueSize() const { return totalReadQueueSize; } |
| |
| /** |
| * Gets the total combined WRITE queues size |
| * |
| * @return total queues size in packets |
| */ |
| uint64_t getTotalWriteQueueSize() const { return totalWriteQueueSize; } |
| |
| /** |
| * Gets the last service tick related to a QoS Priority |
| * |
| * @param prio QoS Priority |
| * @return tick |
| */ |
| Tick getServiceTick(const uint8_t prio) const { return serviceTick[prio]; } |
| |
| /** |
| * Gets the total number of priority levels in the |
| * QoS memory controller. |
| * |
| * @return total number of priority levels |
| */ |
| uint8_t numPriorities() const { return _numPriorities; } |
| |
| /** read the system pointer |
| * @return pointer to the system object */ |
| System* system() const { return _system; } |
| }; |
| |
| template<typename Queues> |
| void |
| MemCtrl::escalateQueues(Queues& queues, uint64_t queue_entry_size, |
| RequestorID id, uint8_t curr_prio, uint8_t tgt_prio) |
| { |
| auto it = queues[curr_prio].begin(); |
| while (it != queues[curr_prio].end()) { |
| // No packets left to move |
| if (packetPriorities[id][curr_prio] == 0) |
| break; |
| |
| auto pkt = *it; |
| |
| DPRINTF(QOS, |
| "qos::MemCtrl::escalateQueues checking priority %d packet " |
| "id %d address %d\n", curr_prio, |
| pkt->requestorId(), pkt->getAddr()); |
| |
| // Found a packet to move |
| if (pkt->requestorId() == id) { |
| |
| uint64_t moved_entries = divCeil(pkt->getSize(), |
| queue_entry_size); |
| |
| DPRINTF(QOS, |
| "qos::MemCtrl::escalateQueues Requestor %s [id %d] moving " |
| "packet addr %d size %d (p size %d) from priority %d " |
| "to priority %d - " |
| "this requestor packets %d (entries to move %d)\n", |
| requestors[id], id, pkt->getAddr(), |
| pkt->getSize(), |
| queue_entry_size, curr_prio, tgt_prio, |
| packetPriorities[id][curr_prio], moved_entries); |
| |
| |
| if (pkt->isRead()) { |
| panic_if(readQueueSizes[curr_prio] < moved_entries, |
| "qos::MemCtrl::escalateQueues requestor %s negative " |
| "READ packets for priority %d", |
| requestors[id], tgt_prio); |
| readQueueSizes[curr_prio] -= moved_entries; |
| readQueueSizes[tgt_prio] += moved_entries; |
| } else if (pkt->isWrite()) { |
| panic_if(writeQueueSizes[curr_prio] < moved_entries, |
| "qos::MemCtrl::escalateQueues requestor %s negative " |
| "WRITE packets for priority %d", |
| requestors[id], tgt_prio); |
| writeQueueSizes[curr_prio] -= moved_entries; |
| writeQueueSizes[tgt_prio] += moved_entries; |
| } |
| |
| // Change QoS priority and move packet |
| pkt->qosValue(tgt_prio); |
| queues[tgt_prio].push_back(pkt); |
| |
| // Erase element from source packet queue, this will |
| // increment the iterator |
| it = queues[curr_prio].erase(it); |
| panic_if(packetPriorities[id][curr_prio] < moved_entries, |
| "qos::MemCtrl::escalateQueues requestor %s negative " |
| "packets for priority %d", |
| requestors[id], tgt_prio); |
| |
| packetPriorities[id][curr_prio] -= moved_entries; |
| packetPriorities[id][tgt_prio] += moved_entries; |
| } else { |
| // Increment iterator to next location in the queue |
| it++; |
| } |
| } |
| } |
| |
| template<typename Queues> |
| void |
| MemCtrl::escalate(std::initializer_list<Queues*> queues, |
| uint64_t queue_entry_size, |
| RequestorID id, uint8_t tgt_prio) |
| { |
| // If needed, initialize all counters and statistics |
| // for this requestor |
| addRequestor(id); |
| |
| DPRINTF(QOS, |
| "qos::MemCtrl::escalate Requestor %s [id %d] to priority " |
| "%d (currently %d packets)\n",requestors[id], id, tgt_prio, |
| packetPriorities[id][tgt_prio]); |
| |
| for (uint8_t curr_prio = 0; curr_prio < numPriorities(); ++curr_prio) { |
| // Skip target priority |
| if (curr_prio == tgt_prio) |
| continue; |
| |
| // Process other priority packet |
| while (packetPriorities[id][curr_prio] > 0) { |
| DPRINTF(QOS, |
| "qos::MemCtrl::escalate MID %d checking priority %d " |
| "(packets %d)- current packets in prio %d: %d\n" |
| "\t(source read %d source write %d target read %d, " |
| "target write %d)\n", |
| id, curr_prio, packetPriorities[id][curr_prio], |
| tgt_prio, packetPriorities[id][tgt_prio], |
| readQueueSizes[curr_prio], |
| writeQueueSizes[curr_prio], readQueueSizes[tgt_prio], |
| writeQueueSizes[tgt_prio]); |
| |
| // Check both read and write queue |
| for (auto q : queues) { |
| escalateQueues(*q, queue_entry_size, id, |
| curr_prio, tgt_prio); |
| } |
| } |
| } |
| |
| DPRINTF(QOS, |
| "qos::MemCtrl::escalate Completed requestor %s [id %d] to " |
| "priority %d (now %d packets)\n\t(total read %d, total write %d)" |
| "\n", requestors[id], id, tgt_prio, packetPriorities[id][tgt_prio], |
| readQueueSizes[tgt_prio], writeQueueSizes[tgt_prio]); |
| } |
| |
| template<typename Queues> |
| uint8_t |
| MemCtrl::qosSchedule(std::initializer_list<Queues*> queues, |
| const uint64_t queue_entry_size, |
| const PacketPtr pkt) |
| { |
| // Schedule packet. |
| uint8_t pkt_priority = schedule(pkt); |
| |
| assert(pkt_priority < numPriorities()); |
| |
| pkt->qosValue(pkt_priority); |
| |
| if (qosSyncroScheduler) { |
| // Call the scheduling function on all other requestors. |
| for (const auto& requestor : requestors) { |
| |
| if (requestor.first == pkt->requestorId()) |
| continue; |
| |
| uint8_t prio = schedule(requestor.first, 0); |
| |
| if (qosPriorityEscalation) { |
| DPRINTF(QOS, |
| "qos::MemCtrl::qosSchedule: (syncro) escalating " |
| "REQUESTOR %s to assigned priority %d\n", |
| _system->getRequestorName(requestor.first), |
| prio); |
| escalate(queues, queue_entry_size, requestor.first, prio); |
| } |
| } |
| } |
| |
| if (qosPriorityEscalation) { |
| DPRINTF(QOS, |
| "qos::MemCtrl::qosSchedule: escalating " |
| "REQUESTOR %s to assigned priority %d\n", |
| _system->getRequestorName(pkt->requestorId()), |
| pkt_priority); |
| escalate(queues, queue_entry_size, pkt->requestorId(), pkt_priority); |
| } |
| |
| // Update last service tick for selected priority |
| serviceTick[pkt_priority] = curTick(); |
| |
| return pkt_priority; |
| } |
| |
| } // namespace qos |
| } // namespace memory |
| } // namespace gem5 |
| |
| #endif /* __MEM_QOS_MEM_CTRL_HH__ */ |