mem: Token port implementation

Adds a TokenPort which uses tokens for flow control rather than the
standard retry mechanism in gem5. The port is intended to be used
for flow control where speculatively sending packets is not possible.
For example, GPU instructions require this to send memory requests
to the cache coalescer.

Change-Id: Id0d55ab65b7c773e97752b8514a780cdf7d88707
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/27428
Reviewed-by: Anthony Gutierrez <anthony.gutierrez@amd.com>
Maintainer: Anthony Gutierrez <anthony.gutierrez@amd.com>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/src/mem/SConscript b/src/mem/SConscript
index 2eb64e0..b77dbb1 100644
--- a/src/mem/SConscript
+++ b/src/mem/SConscript
@@ -73,6 +73,7 @@
 Source('simple_mem.cc')
 Source('snoop_filter.cc')
 Source('stack_dist_calc.cc')
+Source('token_port.cc')
 Source('tport.cc')
 Source('xbar.cc')
 Source('hmc_controller.cc')
@@ -115,6 +116,7 @@
 DebugFlag("DRAMSim2")
 DebugFlag('HMCController')
 DebugFlag('SerialLink')
+DebugFlag('TokenPort')
 
 DebugFlag("MemChecker")
 DebugFlag("MemCheckerMonitor")
diff --git a/src/mem/token_port.cc b/src/mem/token_port.cc
new file mode 100644
index 0000000..1a8439d
--- /dev/null
+++ b/src/mem/token_port.cc
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2016-2020 Advanced Micro Devices, Inc.
+ * All rights reserved.
+ *
+ * For use for simulation and test purposes only
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the copyright holder 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 HOLDER 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.
+ *
+ * Authors: Matthew Poremba
+ */
+
+
+#include "mem/token_port.hh"
+
+#include "base/trace.hh"
+#include "debug/TokenPort.hh"
+
+void
+TokenMasterPort::bind(Port &peer)
+{
+    MasterPort::bind(peer);
+}
+
+void
+TokenMasterPort::recvTokens(int num_tokens)
+{
+    panic_if(!tokenManager, "TokenManager not set for %s.\n", name());
+
+    tokenManager->recvTokens(num_tokens);
+}
+
+bool
+TokenMasterPort::haveTokens(int num_tokens)
+{
+    panic_if(!tokenManager, "TokenManager not set for %s.\n", name());
+
+    return tokenManager->haveTokens(num_tokens);
+}
+
+void
+TokenMasterPort::acquireTokens(int num_tokens)
+{
+    panic_if(!tokenManager, "TokenManager not set for %s.\n", name());
+
+    tokenManager->acquireTokens(num_tokens);
+}
+
+void
+TokenMasterPort::setTokenManager(TokenManager *_tokenManager)
+{
+    tokenManager = _tokenManager;
+}
+
+void
+TokenSlavePort::sendTokens(int num_tokens)
+{
+    fatal_if(!tokenMasterPort, "Tried sendTokens to non-token master!\n");
+
+    // Send tokens to a master
+    tokenMasterPort->recvTokens(num_tokens);
+}
+
+void
+TokenSlavePort::bind(Port& peer)
+{
+    // TokenSlavePort is allowed to bind to either TokenMasterPort or a
+    // MasterPort as fallback. If the type is a MasterPort, tokenMasterPort
+    // is set to nullptr to indicate tokens should not be exchanged.
+    auto *token_master_port = dynamic_cast<TokenMasterPort*>(&peer);
+    auto *master_port = dynamic_cast<MasterPort*>(&peer);
+    if (!token_master_port && !master_port) {
+        fatal("Attempt to bind port %s to unsupported slave port %s.",
+              name(), peer.name());
+    } else if (token_master_port) {
+        // slave port keeps track of the master port
+        tokenMasterPort = token_master_port;
+
+        // master port also keeps track of slave port
+        tokenMasterPort->bind(*this);
+    } else if (master_port) {
+        tokenMasterPort = nullptr;
+    }
+}
+
+void
+TokenSlavePort::unbind()
+{
+    SlavePort::slaveUnbind();
+    tokenMasterPort = nullptr;
+}
+
+void
+TokenSlavePort::recvRespRetry()
+{
+    // fallback to QueuedSlavePort-like impl for now
+    panic_if(respQueue.empty(),
+             "Attempted to retry a response when no retry was queued!\n");
+
+    PacketPtr pkt = respQueue.front();
+    bool success = SlavePort::sendTimingResp(pkt);
+
+    if (success) {
+        respQueue.pop_front();
+    }
+}
+
+bool
+TokenSlavePort::sendTimingResp(PacketPtr pkt)
+{
+    bool success = SlavePort::sendTimingResp(pkt);
+
+    if (!success) {
+        respQueue.push_back(pkt);
+    }
+
+    return success;
+}
+
+TokenManager::TokenManager(int init_tokens)
+{
+    availableTokens = init_tokens;
+    maxTokens = init_tokens;
+}
+
+int
+TokenManager::getMaxTokenCount() const
+{
+    return maxTokens;
+}
+
+void
+TokenManager::recvTokens(int num_tokens)
+{
+    availableTokens += num_tokens;
+
+    DPRINTF(TokenPort, "Received %d tokens, have %d\n",
+                       num_tokens, availableTokens);
+
+    panic_if(availableTokens > maxTokens,
+             "More tokens available than the maximum after recvTokens!\n");
+}
+
+bool
+TokenManager::haveTokens(int num_tokens)
+{
+    return (availableTokens >= num_tokens);
+}
+
+void
+TokenManager::acquireTokens(int num_tokens)
+{
+    panic_if(!haveTokens(num_tokens),
+             "Attempted to acquire more tokens than are available!\n");
+
+    availableTokens -= num_tokens;
+
+    DPRINTF(TokenPort, "Acquired %d tokens, have %d\n",
+                       num_tokens, availableTokens);
+}
diff --git a/src/mem/token_port.hh b/src/mem/token_port.hh
new file mode 100644
index 0000000..3d74315
--- /dev/null
+++ b/src/mem/token_port.hh
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2016-2020 Advanced Micro Devices, Inc.
+ * All rights reserved.
+ *
+ * For use for simulation and test purposes only
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the copyright holder 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 HOLDER 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_TOKEN_PORT_HH__
+#define __MEM_TOKEN_PORT_HH__
+
+#include "mem/port.hh"
+#include "sim/clocked_object.hh"
+
+class TokenManager;
+class TokenSlavePort;
+
+class TokenMasterPort : public MasterPort
+{
+  private:
+    /* Manager to track tokens between this token port pair. */
+    TokenManager *tokenManager;
+
+  public:
+    TokenMasterPort(const std::string& name, SimObject* owner,
+                    PortID id = InvalidPortID) :
+        MasterPort(name, owner, id), tokenManager(nullptr)
+    { }
+
+    /**
+     * Bind this master port to slave port. Called by the slave port in
+     * this token implementation.
+     */
+    void bind(Port &peer) override;
+
+    /**
+     * Unbind port. Handled by slave port in token implementation.
+     */
+    void unbind() override {}
+
+    /**
+     * Receive tokens returned by the slave port. This increments the number
+     * or available tokens across the port.
+     */
+    void recvTokens(int num_tokens);
+
+    /**
+     * Query if there are at least num_tokens tokens available to acquire.
+     */
+    bool haveTokens(int num_tokens);
+
+    /**
+     * Acquire tokens by decrementing the number of available tokens across
+     * the port. This does the opposite of recvTokens.
+     */
+    void acquireTokens(int num_tokens);
+
+    /**
+     * Specify a token manger, which will handle tracking of tokens for a
+     * TokenMasterPort/SlaveMasterPort pair.
+     */
+    void setTokenManager(TokenManager *_tokenManager);
+};
+
+class TokenSlavePort : public SlavePort
+{
+  private:
+    TokenMasterPort *tokenMasterPort;
+
+    std::deque<PacketPtr> respQueue;
+
+    void recvRespRetry();
+
+  public:
+    TokenSlavePort(const std::string& name, ClockedObject *owner,
+                   PortID id = InvalidPortID) :
+        SlavePort(name, owner, id), tokenMasterPort(nullptr)
+    { }
+    ~TokenSlavePort() { }
+
+    /**
+     * Bind this slave port to a master port. This also does the mirror
+     * action and bainds the master port to the slave port as well as
+     * binding the base class types.
+     */
+    void bind(Port &peer) override;
+
+    /**
+     * Unbind this slave port and associated master port.
+     */
+    void unbind() override;
+
+    /**
+     * Return num_tokens tokens back to the master port.
+     */
+    void sendTokens(int num_tokens);
+
+    bool sendTimingResp(PacketPtr pkt);
+
+    /* There is no storage here so the packet will not be found. */
+    bool trySatisfyFunctional(PacketPtr) { return false; }
+};
+
+class TokenManager
+{
+  protected:
+    /* Maximum tokens possible */
+    int maxTokens;
+
+    /* Number of currently available tokens */
+    int availableTokens;
+
+  public:
+    TokenManager(int init_tokens);
+    ~TokenManager() { }
+
+    /**
+     * Return the maximum possible tokens.
+     */
+    int getMaxTokenCount() const;
+
+    /**
+     * Increment the number of available tokens by num_tokens.
+     */
+    void recvTokens(int num_tokens);
+
+    /**
+     * Query is num_tokens tokens are available.
+     */
+    bool haveTokens(int num_tokens);
+
+    /**
+     * Decrement the number of available tokens by num_tokens.
+     */
+    void acquireTokens(int num_tokens);
+};
+
+#endif