Mem: Add a maximum bandwidth to SimpleMemory
authorAndreas Hansson <andreas.hansson@arm.com>
Tue, 18 Sep 2012 14:30:02 +0000 (10:30 -0400)
committerAndreas Hansson <andreas.hansson@arm.com>
Tue, 18 Sep 2012 14:30:02 +0000 (10:30 -0400)
This patch makes a minor addition to the SimpleMemory by enforcing a
maximum data rate. The bandwidth is configurable, and a reasonable
value (12.8GB/s) has been choosen as the default.

The changes do add some complexity to the SimpleMemory, but they
should definitely be justifiable as this enables a far more realistic
setup using even this simple memory controller.

The rate regulation is done for reads and writes combined to reflect
the bidirectional data busses used by most (if not all) relevant
memories. Moreover, the regulation is done per packet as opposed to
long term, as it is the short term data rate (data bus width times
frequency) that is the limiting factor.

A follow-up patch bumps the stats for the regressions.

src/mem/SimpleMemory.py
src/mem/simple_mem.cc
src/mem/simple_mem.hh

index c47d04222a42a5585aaa15304cce87f0eab9b5af..9361b45d8caf5cd1734ffbaf5d2a167484e3f949 100644 (file)
@@ -47,3 +47,7 @@ class SimpleMemory(AbstractMemory):
     port = SlavePort("Slave ports")
     latency = Param.Latency('30ns', "Request to response latency")
     latency_var = Param.Latency('0ns', "Request to response latency variance")
+    # The memory bandwidth limit default is set to 12.8GB/s which is
+    # representative of a x64 DDR3-1600 channel.
+    bandwidth = Param.MemoryBandwidth('12.8GB/s',
+                                      "Combined read and write bandwidth")
index b9db642769ffad1cde0db7fda5a17df06f59836f..a67b4825fb9e3978b19889ce3ca785291996e55e 100644 (file)
 
 using namespace std;
 
-SimpleMemory::SimpleMemory(const Params* p) :
+SimpleMemory::SimpleMemory(const SimpleMemoryParams* p) :
     AbstractMemory(p),
-    port(name() + ".port", *this), lat(p->latency), lat_var(p->latency_var)
+    port(name() + ".port", *this), lat(p->latency),
+    lat_var(p->latency_var), bandwidth(p->bandwidth),
+    isBusy(false), retryReq(false), releaseEvent(this)
 {
 }
 
@@ -89,6 +91,80 @@ SimpleMemory::doFunctionalAccess(PacketPtr pkt)
     functionalAccess(pkt);
 }
 
+bool
+SimpleMemory::recvTimingReq(PacketPtr pkt)
+{
+    if (pkt->memInhibitAsserted()) {
+        // snooper will supply based on copy of packet
+        // still target's responsibility to delete packet
+        delete pkt;
+        return true;
+    }
+
+    // we should never get a new request after committing to retry the
+    // current one, the bus violates the rule as it simply sends a
+    // retry to the next one waiting on the retry list, so simply
+    // ignore it
+    if (retryReq)
+        return false;
+
+    // if we are busy with a read or write, remember that we have to
+    // retry
+    if (isBusy) {
+        retryReq = true;
+        return false;
+    }
+
+    // update the release time according to the bandwidth limit, and
+    // do so with respect to the time it takes to finish this request
+    // rather than long term as it is the short term data rate that is
+    // limited for any real memory
+
+    // only look at reads and writes when determining if we are busy,
+    // and for how long, as it is not clear what to regulate for the
+    // other types of commands
+    if (pkt->isRead() || pkt->isWrite()) {
+        // calculate an appropriate tick to release to not exceed
+        // the bandwidth limit
+        Tick duration = pkt->getSize() * bandwidth;
+
+        // only consider ourselves busy if there is any need to wait
+        // to avoid extra events being scheduled for (infinitely) fast
+        // memories
+        if (duration != 0) {
+            schedule(releaseEvent, curTick() + duration);
+            isBusy = true;
+        }
+    }
+
+    // go ahead and deal with the packet and put the response in the
+    // queue if there is one
+    bool needsResponse = pkt->needsResponse();
+    Tick latency = doAtomicAccess(pkt);
+    // turn packet around to go back to requester if response expected
+    if (needsResponse) {
+        // doAtomicAccess() should already have turned packet into
+        // atomic response
+        assert(pkt->isResponse());
+        port.schedTimingResp(pkt, curTick() + latency);
+    } else {
+        delete pkt;
+    }
+
+    return true;
+}
+
+void
+SimpleMemory::release()
+{
+    assert(isBusy);
+    isBusy = false;
+    if (retryReq) {
+        retryReq = false;
+        port.sendRetry();
+    }
+}
+
 SlavePort &
 SimpleMemory::getSlavePort(const std::string &if_name, int idx)
 {
@@ -113,7 +189,8 @@ SimpleMemory::drain(Event *de)
 
 SimpleMemory::MemoryPort::MemoryPort(const std::string& _name,
                                      SimpleMemory& _memory)
-    : SimpleTimingPort(_name, &_memory), memory(_memory)
+    : QueuedSlavePort(_name, &_memory, queueImpl),
+      queueImpl(_memory, *this), memory(_memory)
 { }
 
 AddrRangeList
@@ -145,6 +222,12 @@ SimpleMemory::MemoryPort::recvFunctional(PacketPtr pkt)
     pkt->popLabel();
 }
 
+bool
+SimpleMemory::MemoryPort::recvTimingReq(PacketPtr pkt)
+{
+    return memory.recvTimingReq(pkt);
+}
+
 SimpleMemory*
 SimpleMemoryParams::create()
 {
index 95bfa57ad5f7d0401d513457dc4b84223549c8cf..4f7af864b8fd21a6fa61b37148505dae22b42771 100644 (file)
 
 /**
  * The simple memory is a basic single-ported memory controller with
- * an infinite throughput and a fixed latency, potentially with a
- * variance added to it. It uses a SimpleTimingPort to implement the
- * timing accesses.
+ * an configurable throughput and latency, potentially with a variance
+ * added to the latter. It uses a QueueSlavePort to avoid dealing with
+ * the flow control of sending responses.
  */
 class SimpleMemory : public AbstractMemory
 {
 
   private:
 
-    class MemoryPort : public SimpleTimingPort
+    class MemoryPort : public QueuedSlavePort
     {
+
+      private:
+
+        /// Queue holding the response packets
+        SlavePacketQueue queueImpl;
         SimpleMemory& memory;
 
       public:
@@ -74,11 +79,13 @@ class SimpleMemory : public AbstractMemory
 
       protected:
 
-        virtual Tick recvAtomic(PacketPtr pkt);
+        Tick recvAtomic(PacketPtr pkt);
+
+        void recvFunctional(PacketPtr pkt);
 
-        virtual void recvFunctional(PacketPtr pkt);
+        bool recvTimingReq(PacketPtr pkt);
 
-        virtual AddrRangeList getAddrRanges() const;
+        AddrRangeList getAddrRanges() const;
 
     };
 
@@ -87,10 +94,32 @@ class SimpleMemory : public AbstractMemory
     Tick lat;
     Tick lat_var;
 
+    /// Bandwidth in ticks per byte
+    const double bandwidth;
+
+    /**
+     * Track the state of the memory as either idle or busy, no need
+     * for an enum with only two states.
+     */
+    bool isBusy;
+
+    /**
+     * Remember if we have to retry an outstanding request that
+     * arrived while we were busy.
+     */
+    bool retryReq;
+
+    /**
+     * Release the memory after being busy and send a retry if a
+     * request was rejected in the meanwhile.
+     */
+    void release();
+
+    EventWrapper<SimpleMemory, &SimpleMemory::release> releaseEvent;
+
   public:
 
-    typedef SimpleMemoryParams Params;
-    SimpleMemory(const Params *p);
+    SimpleMemory(const SimpleMemoryParams *p);
     virtual ~SimpleMemory() { }
 
     unsigned int drain(Event* de);
@@ -98,17 +127,12 @@ class SimpleMemory : public AbstractMemory
     virtual SlavePort& getSlavePort(const std::string& if_name, int idx = -1);
     virtual void init();
 
-    const Params *
-    params() const
-    {
-        return dynamic_cast<const Params *>(_params);
-    }
-
   protected:
 
     Tick doAtomicAccess(PacketPtr pkt);
     void doFunctionalAccess(PacketPtr pkt);
-    virtual Tick calculateLatency(PacketPtr pkt);
+    bool recvTimingReq(PacketPtr pkt);
+    Tick calculateLatency(PacketPtr pkt);
 
 };