| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <meta http-equiv="x-ua-compatible" content="ie=edge"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| |
| <title>gem5</title> |
| |
| <!-- SITE FAVICON --> |
| <link rel="shortcut icon" type="image/gif" href="/assets/img/gem5ColorVert.gif"/> |
| |
| <link rel="canonical" href="http://localhost:4000/memoryobject/"> |
| <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,700,800,600' rel='stylesheet' type='text/css'> |
| <link href='https://fonts.googleapis.com/css?family=Muli:400,300' rel='stylesheet' type='text/css'> |
| |
| <!-- FAVICON --> |
| <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"> |
| |
| <!-- BOOTSTRAP --> |
| <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> |
| |
| <!-- CUSTOM CSS --> |
| <link rel="stylesheet" href="/css/main.css"> |
| </head> |
| |
| |
| <body> |
| <nav class="navbar navbar-expand-md navbar-light bg-light"> |
| <a class="navbar-brand" href="/"> |
| <img src="/assets/img/gem5ColorLong.gif" alt="gem5" height=45px> |
| </a> |
| <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> |
| <span class="navbar-toggler-icon"></span> |
| </button> |
| <div class="collapse navbar-collapse" id="navbarNavDropdown"> |
| <ul class="navbar-nav ml-auto"> |
| <li class="nav-item "> |
| <a class="nav-link" href="/">Home</a> |
| </li> |
| |
| <li class="nav-item dropdown "> |
| <a class="nav-link dropdown-toggle" href="/about" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> |
| About |
| </a> |
| <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> |
| <a class="dropdown-item" href="/about">About</a> |
| <a class="dropdown-item" href="/publications">Publications</a> |
| <a class="dropdown-item" href="/governance">Governance</a> |
| </div> |
| </li> |
| |
| <li class="nav-item dropdown active"> |
| <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> |
| Documentation |
| </a> |
| <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> |
| <!-- Pull navigation from _data/documentation.yml --> |
| |
| <a class="dropdown-item" href="/introduction">Introduction</a> |
| |
| <a class="dropdown-item" href="/building">Getting Started</a> |
| |
| <a class="dropdown-item" href="/environment">Modifying/Extending</a> |
| |
| <a class="dropdown-item" href="/MSIintro">Modeling Cache Coherence with Ruby</a> |
| |
| </div> |
| </li> |
| |
| <li class="nav-item "> |
| <a class="nav-link" href="/contributing">Contributing</a> |
| </li> |
| |
| <li class="nav-item "> |
| <a class="nav-link" href="/blog">Blog</a> |
| </li> |
| |
| <li class="nav-item "> |
| <a class="nav-link" href="/search">Search</a> |
| </li> |
| </ul> |
| </div> |
| </nav> |
| |
| <main> |
| <div class="sidenav-top"> |
| <a href="/"><img src="/assets/img/gem5ColorLong.gif" height="80"></a> |
| <div class="search"> |
| <form action="/search" method="get"> |
| <!-- <label for="search-box"><i class="fa fa-search"></i></label> --> |
| <input type="text" name="query"> |
| <button type="submit" name="submit"><i class="fa fa-search"></i></button> |
| </form> |
| </div> |
| </div> |
| <div class="sidenav"> |
| <!-- Pull navigation from _data/documentation.yml --> |
| |
| <a class="item" href="/introduction" role="button" aria-expanded="false" aria-controls="collapseExample"> |
| Introduction |
| </a> |
| <div class="collapse " id="introduction"> |
| |
| </div> |
| |
| <a class="item" data-toggle="collapse" href="#pt1" role="button" aria-expanded="false" aria-controls="collapseExample"> |
| Getting Started |
| </a> |
| <div class="collapse " id="pt1"> |
| |
| <a class="subitem " href="/building">Building gem5</a> |
| |
| <a class="subitem " href="/simple_config">Creating a simple configuration script</a> |
| |
| <a class="subitem " href="/cache_config">Adding cache to configuration script</a> |
| |
| <a class="subitem " href="/gem5_stats">Understanding gem5 statistics and output</a> |
| |
| <a class="subitem " href="/example_configs">Using the default configuration scripts</a> |
| |
| </div> |
| |
| <a class="item" data-toggle="collapse" href="#pt2" role="button" aria-expanded="false" aria-controls="collapseExample"> |
| Modifying/Extending |
| </a> |
| <div class="collapse show" id="pt2"> |
| |
| <a class="subitem " href="/environment">Setting up your development environment</a> |
| |
| <a class="subitem " href="/helloobject">Creating a very simple SimObject</a> |
| |
| <a class="subitem " href="/debugging">Debugging gem5</a> |
| |
| <a class="subitem " href="/events">Event-driven programming</a> |
| |
| <a class="subitem " href="/parameters">Adding parameters to SimObjects and more events</a> |
| |
| <a class="subitem active" href="/memoryobject">Creating SimObjects in the memory system</a> |
| |
| <a class="subitem " href="/simplecache">Creating a simple cache object</a> |
| |
| </div> |
| |
| <a class="item" data-toggle="collapse" href="#pt3" role="button" aria-expanded="false" aria-controls="collapseExample"> |
| Modeling Cache Coherence with Ruby |
| </a> |
| <div class="collapse " id="pt3"> |
| |
| <a class="subitem " href="/MSIintro">Introduction to Ruby</a> |
| |
| <a class="subitem " href="/cache-intro">MSI example cache protocol</a> |
| |
| <a class="subitem " href="/cache-declarations">Declaring a state machine</a> |
| |
| <a class="subitem " href="/cache-in-ports">In port code blocks</a> |
| |
| <a class="subitem " href="/cache-actions">Action code blocks</a> |
| |
| <a class="subitem " href="/cache-transitions">Transition code blocks</a> |
| |
| <a class="subitem " href="/directory">MSI Directory implementation</a> |
| |
| <a class="subitem " href="/MSIbuilding">Compiling a SLICC protocol</a> |
| |
| <a class="subitem " href="/configuration">Configuring a simple Ruby system</a> |
| |
| <a class="subitem " href="/running">Running the simple Ruby system</a> |
| |
| <a class="subitem " href="/MSIdebugging">Debugging SLICC Protocols</a> |
| |
| <a class="subitem " href="/simple-MI_example">Configuring for a standard protocol</a> |
| |
| </div> |
| |
| </div> |
| |
| <div class="container" id="doc-container"> |
| <div class="edit"><a href="https://github.com/gem5/new-website/tree/master/_pages/documentation/part2/memoryobject.md">Edit this page</a></div> |
| <dl> |
| <dt>authors</dt> |
| <dd>Jason Lowe-Power</dd> |
| </dl> |
| |
| <h1 id="creating-simobjects-in-the-memory-system">Creating SimObjects in the memory system</h1> |
| |
| <p>In this chapter, we will create a simple memory object that sits between |
| the CPU and the memory bus. In the next chapter <simplecache-chapter> |
| we will take this simple memory object and add some logic to it to make |
| it a very simple blocking uniprocessor cache.</p> |
| |
| <h2 id="gem5-master-and-slave-ports">gem5 master and slave ports</h2> |
| |
| <p>Before diving into the implementation of a memory object, we should |
| first understand gem5’s master and slave port interface. As previously |
| discussed in simple-config-chapter, all memory objects are connected |
| together via ports. These ports provide a rigid interface between these |
| memory objects.</p> |
| |
| <p>These ports implement three different memory system <em>modes</em>: timing, |
| atomic, and functional. The most important mode is <em>timing mode</em>. Timing |
| mode is the only mode that produces correct simulation results. The |
| other modes are only used in special circumstances.</p> |
| |
| <p><em>Atomic mode</em> is useful for fastforwarding simulation to a region of |
| interest and warming up the simulator. This mode assumes that no events |
| will be generated in the memory system. Instead, all of the memory |
| requests execute through a single long callchain. It is not required to |
| implement atomic accesses for a memory object unless it will be used |
| during fastforward or during simulator warmup.</p> |
| |
| <p><em>Functional mode</em> is better described as <em>debugging mode</em>. Functional |
| mode is used for things like reading data from the host into the |
| simulator memory. It is used heavily in syscall emulation mode. For |
| instance, functional mode is used to load the binary in the |
| <code class="highlighter-rouge">process.cmd</code> from the host into the simulated system’s memory so the |
| simulated system can access it. Functional accesses should return the |
| most up-to-date data on a read, no matter where the data is, and should |
| update all possible valid data on a write (e.g., in a system with caches |
| there may be multiple valid cache blocks with the same address).</p> |
| |
| <h3 id="packets">Packets</h3> |
| |
| <p>In gem5, <code class="highlighter-rouge">Packets</code> are sent across ports. A <code class="highlighter-rouge">Packet</code> is made up of a |
| <code class="highlighter-rouge">MemReq</code> which is the memory request object. The <code class="highlighter-rouge">MemReq</code> holds |
| information about the original request that initiated the packet such as |
| the requestor, the address, and the type of request (read, write, etc.).</p> |
| |
| <p>Packets also have a <code class="highlighter-rouge">MemCmd</code>, which is the <em>current</em> command of the |
| packet. This command can change throughout the life of the packet (e.g., |
| requests turn into responses once the memory command is satisfied). The |
| most common <code class="highlighter-rouge">MemCmd</code> are <code class="highlighter-rouge">ReadReq</code> (read request), <code class="highlighter-rouge">ReadResp</code> (read |
| response), <code class="highlighter-rouge">WriteReq</code> (write request), <code class="highlighter-rouge">WriteResp</code> (write response). |
| There are also writeback requests (<code class="highlighter-rouge">WritebackDirty</code>, <code class="highlighter-rouge">WritebackClean</code>) |
| for caches and many other command types.</p> |
| |
| <p>Packets also either keep the data for the request, or a pointer to the |
| data. There are options when creating the packet whether the data is |
| dynamic (explicitly allocated and deallocated), or static (allocated and |
| deallocated by the packet object).</p> |
| |
| <p>Finally, packets are used in the classic caches as the unit to track |
| coherency. Therefore, much of the packet code is specific to the classic |
| cache coherence protocol. However, packets are used for all |
| communication between memory objects in gem5, even if they are not |
| directly involved in coherence (e.g., DRAM controllers and the CPU |
| models).</p> |
| |
| <p>All of the port interface functions accept a <code class="highlighter-rouge">Packet</code> pointer as a |
| parameter. Since this pointer is so common, gem5 includes a typedef for |
| it: <code class="highlighter-rouge">PacketPtr</code>.</p> |
| |
| <h3 id="port-interface">Port interface</h3> |
| |
| <p>There are two types of ports in gem5: master ports and slave ports. |
| Whenever you implement a memory object, you will implement at least one |
| of these types of ports. To do this, you create a new class that |
| inherits from either <code class="highlighter-rouge">MasterPort</code> or <code class="highlighter-rouge">SlavePort</code> for master and slave |
| ports, respectively. Master ports send requests (and receive response), |
| and slave ports receive requests (and send responses).</p> |
| |
| <p>master-slave-1-fig outlines the simplest interaction between a master |
| and slave port. This figure shows the interaction in timing mode. The |
| other modes are much simpler and use a simple callchain between the |
| master and the slave.</p> |
| |
| <p><img src="../_static/figures/master_slave_1.png" alt="Simple master-slave interaction when both can accept the request and |
| the response." /></p> |
| |
| <p>As mentioned above, all of the port interfaces require a <code class="highlighter-rouge">PacketPtr</code> as |
| a parameter. Each of these functions (<code class="highlighter-rouge">sendTimingReq</code>, <code class="highlighter-rouge">recvTimingReq</code>, |
| etc.), accepts a single parameter, a <code class="highlighter-rouge">PacketPtr</code>. This packet is the |
| request or response to send or receive.</p> |
| |
| <p>To send a request packet, the master calls <code class="highlighter-rouge">sendTimingReq</code>. In turn, |
| (and in the same callchain), the function <code class="highlighter-rouge">recvTimingReq</code> is called on |
| the slave with the same <code class="highlighter-rouge">PacketPtr</code> as its sole parameter.</p> |
| |
| <p>The <code class="highlighter-rouge">recvTimingReq</code> has a return type of <code class="highlighter-rouge">bool</code>. This boolean return |
| value is directly returned to the calling master. A return value of |
| <code class="highlighter-rouge">true</code> signifies that the packet was accepted by the slave. A return |
| value of <code class="highlighter-rouge">false</code>, on the other hand, means that the slave was unable to |
| accept and the request must be retried sometime in the future.</p> |
| |
| <p>In master-slave-1-fig, first, the master sends a timing request by |
| calling <code class="highlighter-rouge">sendTimingReq</code>, which in turn calls <code class="highlighter-rouge">recvTimingResp</code>. The |
| slave, returns true from <code class="highlighter-rouge">recvTimingResp</code>, which is returned from the |
| call to <code class="highlighter-rouge">sendTimingReq</code>. The master continue executing, and the slave |
| does whatever is necessary to complete the request (e.g., if it is a |
| cache, it looks up the tags to see if there is a match to the address in |
| the request).</p> |
| |
| <p>Once the slave completes the request, it can send a response to the |
| master. The slave calls <code class="highlighter-rouge">sendTimingResp</code> with the response packet (this |
| should be the same <code class="highlighter-rouge">PacketPtr</code> as the request, but it should now be a |
| response packet). In turn, the master function <code class="highlighter-rouge">recvTimingResp</code> is |
| called. The master’s <code class="highlighter-rouge">recvTimingResp</code> function returns <code class="highlighter-rouge">true</code>, which is |
| the return value of <code class="highlighter-rouge">sendTimingResp</code> in the slave. Thus, the interaction |
| for that request is complete.</p> |
| |
| <p>Later in master-slave-example-section we will show the example code for |
| these functions.</p> |
| |
| <p>It is possible that the master or slave is busy when they receive a |
| request or a response. master-slave-2-fig shows the case where the slave |
| is busy when the original request was sent.</p> |
| |
| <p><img src="../_static/figures/master_slave_2.png" alt="Simple master-slave interaction when the slave is |
| busy" /></p> |
| |
| <p>In this case, the slave returns <code class="highlighter-rouge">false</code> from the <code class="highlighter-rouge">recvTimingReq</code> |
| function. When a master receives false after calling <code class="highlighter-rouge">sendTimingReq</code>, it |
| must wait until the its function <code class="highlighter-rouge">recvReqRetry</code> is executed. Only when |
| this function is called is the master allowed to retry calling |
| <code class="highlighter-rouge">sendTimingRequest</code>. The above figure shows the timing request failing |
| once, but it could fail any number of times. Note: it is up to the |
| master to track the packet that fails, not the slave. The slave <em>does |
| not</em> keep the pointer to the packet that fails.</p> |
| |
| <p>Similarly, master-slave-3-fig shows the case when the master is busy at |
| the time the slave tries to send a response. In this case, the slave |
| cannot call <code class="highlighter-rouge">sendTimingResp</code> until it receives a <code class="highlighter-rouge">recvRespRetry</code>.</p> |
| |
| <p><img src="../_static/figures/master_slave_3.png" alt="Simple master-slave interaction when the master is |
| busy" /></p> |
| |
| <p>Importantly, in both of these cases, the retry codepath can be a single |
| call stack. For instance, when the master calls <code class="highlighter-rouge">sendRespRetry</code>, |
| <code class="highlighter-rouge">recvTimingReq</code> can also be called in the same call stack. Therefore, it |
| is easy to incorrectly create an infinite recursion bug, or other bugs. |
| It is important that before a memory object sends a retry, that it is |
| ready <em>at that instant</em> to accept another packet.</p> |
| |
| <h2 id="simple-memory-object-example">Simple memory object example</h2> |
| |
| <p>In this section, we will build a simple memory object. Initially, it |
| will simply pass requests through from the CPU-side (a simple CPU) to |
| the memory-side (a simple memory bus). See Figure simple-memobj-figure. |
| It will have a single master port, to send requests to the memory bus, |
| and two cpu-side ports for the instruction and data cache ports of the |
| CPU. In the next chapter <simplecache-chapter>, we will add the logic |
| to make this object a cache.</p> |
| |
| <p><img src="../_static/figures/simple_memobj.png" alt="System with a simple memory object which sits between a CPU and the |
| memory bus." /></p> |
| |
| <h3 id="declare-the-simobject">Declare the SimObject</h3> |
| |
| <p>Just like when we were creating the simple SimObject in |
| hello-simobject-chapter, the first step is to create a SimObject Python |
| file. We will call this simple memory object <code class="highlighter-rouge">SimpleMemobj</code> and create |
| the SimObject Python file in <code class="highlighter-rouge">src/learning_gem5/simple_memobj</code>.</p> |
| |
| <p>``` {.sourceCode .python} |
| from m5.params import * |
| from m5.proxy import * |
| from MemObject import MemObject</p> |
| |
| <p>class SimpleMemobj(MemObject): |
| type = ‘SimpleMemobj’ |
| cxx_header = “learning_gem5/simple_memobj/simple_memobj.hh”</p> |
| |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>inst_port = SlavePort("CPU side port, receives requests") |
| data_port = SlavePort("CPU side port, receives requests") |
| mem_side = MasterPort("Memory side port, sends requests") ``` |
| </code></pre></div></div> |
| |
| <p>For this object, we inherit from <code class="highlighter-rouge">MemObject</code>, not <code class="highlighter-rouge">SimObject</code> since we |
| are creating an object that will interact with the memory system. The |
| <code class="highlighter-rouge">MemObject</code> class has two pure virtual functions that we will have to |
| define in our C++ implementation, <code class="highlighter-rouge">getMasterPort</code> and <code class="highlighter-rouge">getSlavePort</code>.</p> |
| |
| <p>This object’s parameters are three ports. Two ports for the CPU to |
| connect the instruction and data ports and a port to connect to the |
| memory bus. These ports do not have a default value, and they have a |
| simple description.</p> |
| |
| <p>It is important to remember the names of these ports. We will explicitly |
| use these names when implementing <code class="highlighter-rouge">SimpleMemobj</code> and defining the |
| <code class="highlighter-rouge">getMasterPort</code> and <code class="highlighter-rouge">getSlavePort</code> functions.</p> |
| |
| <p>You can download the SimObject file |
| here <../_static/scripts/part2/memoryobject/SimpleMemobj.py></p> |
| |
| <p>Of course, you also need to create a SConscript file in the new |
| directory as well that declares the SimObject Python file. You can |
| download the SConscript file |
| here <../_static/scripts/part2/memoryobject/SConscript></p> |
| |
| <h3 id="define-the-simplememobj-class">Define the SimpleMemobj class</h3> |
| |
| <p>Now, we create a header file for <code class="highlighter-rouge">SimpleMemobj</code>.</p> |
| |
| <p>``` {.sourceCode .c++} |
| class SimpleMemobj : public MemObject |
| { |
| private:</p> |
| |
| <p>public:</p> |
| |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/** constructor |
| */ |
| SimpleMemobj(SimpleMemobjParams *params); }; ``` |
| </code></pre></div></div> |
| |
| <h3 id="define-a-slave-port-type">Define a slave port type</h3> |
| |
| <p>Now, we need to define classes for our two kinds of ports: the CPU-side |
| and the memory-side ports. For this, we will declare these classes |
| inside the <code class="highlighter-rouge">SimpleMemobj</code> class since no other object will ever use |
| these classes.</p> |
| |
| <p>Let’s start with the slave port, or the CPU-side port. We are going to |
| inherit from the <code class="highlighter-rouge">SlavePort</code> class. The following is the required code |
| to override all of the pure virtual functions in the <code class="highlighter-rouge">SlavePort</code> class.</p> |
| |
| <p>``` {.sourceCode .c++} |
| class CPUSidePort : public SlavePort |
| { |
| private: |
| SimpleMemobj *owner;</p> |
| |
| <p>public: |
| CPUSidePort(const std::string& name, SimpleMemobj *owner) : |
| SlavePort(name, owner), owner(owner) |
| { }</p> |
| |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AddrRangeList getAddrRanges() const override; |
| </code></pre></div></div> |
| |
| <p>protected: |
| Tick recvAtomic(PacketPtr pkt) override { panic(“recvAtomic unimpl.”); } |
| void recvFunctional(PacketPtr pkt) override; |
| bool recvTimingReq(PacketPtr pkt) override; |
| void recvRespRetry() override; |
| };</p> |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> |
| This object requires five functions to be defined. |
| |
| This object also has a single member variable, its owner, so it can call |
| functions on that object. |
| |
| ### Define a master port type |
| |
| Next, we need to define a master port type. This will be the memory-side |
| port which will forward request from the CPU-side to the rest of the |
| memory system. |
| |
| ``` {.sourceCode .c++} |
| class MemSidePort : public MasterPort |
| { |
| private: |
| SimpleMemobj *owner; |
| |
| public: |
| MemSidePort(const std::string& name, SimpleMemobj *owner) : |
| MasterPort(name, owner), owner(owner) |
| { } |
| |
| protected: |
| bool recvTimingResp(PacketPtr pkt) override; |
| void recvReqRetry() override; |
| void recvRangeChange() override; |
| }; |
| </code></pre></div></div> |
| |
| <p>This class only has three pure virtual functions that we must override.</p> |
| |
| <h3 id="defining-the-memobject-interface">Defining the MemObject interface</h3> |
| |
| <p>Now that we have defined these two new types <code class="highlighter-rouge">CPUSidePort</code> and |
| <code class="highlighter-rouge">MemSidePort</code>, we can declare our three ports as part of <code class="highlighter-rouge">SimpleMemobj</code>. |
| We also need to declare the two pure virtual functions in the |
| <code class="highlighter-rouge">MemObject</code> class, <code class="highlighter-rouge">getMasterPort</code> and <code class="highlighter-rouge">getSlavePort</code>. These two |
| functions are used by gem5 during the initialization phase to connect |
| memory objects together via ports.</p> |
| |
| <p>``` {.sourceCode .c++} |
| class SimpleMemobj : public MemObject |
| { |
| private:</p> |
| |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code><CPUSidePort declaration> |
| <MemSidePort declaration> |
| |
| CPUSidePort instPort; |
| CPUSidePort dataPort; |
| |
| MemSidePort memPort; |
| </code></pre></div></div> |
| |
| <p>public: |
| SimpleMemobj(SimpleMemobjParams *params);</p> |
| |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>BaseMasterPort& getMasterPort(const std::string& if_name, |
| PortID idx = InvalidPortID) override; |
| |
| BaseSlavePort& getSlavePort(const std::string& if_name, |
| PortID idx = InvalidPortID) override; |
| </code></pre></div></div> |
| |
| <p>};</p> |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> |
| You can download the header file for the `SimpleMemobj` |
| here \<../\_static/scripts/part2/memoryobject/simple\_memobj.hh\> |
| |
| ### Implementing basic MemObject functions |
| |
| For the constructor of `SimpleMemobj`, we will simply call the |
| `MemObject` constructor. We also need to initialize all of the ports. |
| Each port's constructor takes two parameters: the name and a pointer to |
| its owner, as we defined in the header file. The name can be any string, |
| but by convention, it is the same name as in the Python SimObject file. |
| |
| ``` {.sourceCode .c++} |
| SimpleMemobj::SimpleMemobj(SimpleMemobjParams *params) : |
| MemObject(params), |
| instPort(params->name + ".inst_port", this), |
| dataPort(params->name + ".data_port", this), |
| memPort(params->name + ".mem_side", this) |
| { |
| } |
| </code></pre></div></div> |
| |
| <p>Next, we need to implement the interfaces to get the ports. This |
| interface is made of two functions <code class="highlighter-rouge">getMasterPort</code> and <code class="highlighter-rouge">getSlavePort</code>. |
| These functions take two parameters. The <code class="highlighter-rouge">if_name</code> is the Python |
| variable name of the interface for <em>this</em> object. In the case of the |
| master port it will be <code class="highlighter-rouge">mem_side</code> since this is what we declared as a |
| <code class="highlighter-rouge">MasterPort</code> in the Python SimObject file.</p> |
| |
| <p>To implement <code class="highlighter-rouge">getMasterPort</code>, we compare the <code class="highlighter-rouge">if_name</code> and check to see |
| if it is <code class="highlighter-rouge">mem_side</code> as specified in our Python SimObject file. If it is, |
| then we return the <code class="highlighter-rouge">memPort</code> object. If not, then we pass the request |
| name to our parent. However, it will be an error if we try to connect a |
| slave port to any other named port since the parent class has no ports |
| defined.</p> |
| |
| <p>``` {.sourceCode .c++} |
| BaseMasterPort& |
| SimpleMemobj::getMasterPort(const std::string& if_name, PortID idx) |
| { |
| if (if_name == “mem_side”) { |
| return memPort; |
| } else { |
| return MemObject::getMasterPort(if_name, idx); |
| } |
| }</p> |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> |
| To implement `getSlavePort`, we similarly check if the `if_name` matches |
| either of the names we defined for our slave ports in the Python |
| SimObject file. If the name is `"inst_port"`, then we return the |
| instPort, and if the name is `data_port` we return the data port. |
| |
| ``` {.sourceCode .c++} |
| BaseSlavePort& |
| SimpleMemobj::getSlavePort(const std::string& if_name, PortID idx) |
| { |
| if (if_name == "inst_port") { |
| return instPort; |
| } else if (if_name == "data_port") { |
| return dataPort; |
| } else { |
| return MemObject::getSlavePort(if_name, idx); |
| } |
| } |
| </code></pre></div></div> |
| |
| <h3 id="implementing-slave-and-master-port-functions">Implementing slave and master port functions</h3> |
| |
| <p>The implementation of both the slave and master port is relatively |
| simple. For the most part, each of the port functions just forwards the |
| information to the main memory object (<code class="highlighter-rouge">SimpleMemobj</code>).</p> |
| |
| <p>Starting with two simple functions, <code class="highlighter-rouge">getAddrRanges</code> and <code class="highlighter-rouge">recvFunctional</code> |
| simply call into the <code class="highlighter-rouge">SimpleMemobj</code>.</p> |
| |
| <p>``` {.sourceCode .c++} |
| AddrRangeList |
| SimpleMemobj::CPUSidePort::getAddrRanges() const |
| { |
| return owner->getAddrRanges(); |
| }</p> |
| |
| <p>void |
| SimpleMemobj::CPUSidePort::recvFunctional(PacketPtr pkt) |
| { |
| return owner->handleFunctional(pkt); |
| }</p> |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> |
| The implementation of these functions in the `SimpleMemobj` are equally |
| simple. These implementations just pass through the request to the |
| memory side. We can use `DPRINTF` calls here to track what is happening |
| for debug purposes as well. |
| |
| ``` {.sourceCode .c++} |
| void |
| SimpleMemobj::handleFunctional(PacketPtr pkt) |
| { |
| memPort.sendFunctional(pkt); |
| } |
| |
| AddrRangeList |
| SimpleMemobj::getAddrRanges() const |
| { |
| DPRINTF(SimpleMemobj, "Sending new ranges\n"); |
| return memPort.getAddrRanges(); |
| } |
| </code></pre></div></div> |
| |
| <p>Similarly for the <code class="highlighter-rouge">MemSidePort</code>, we need to implement <code class="highlighter-rouge">recvRangeChange</code> |
| and forward the request through the <code class="highlighter-rouge">SimpleMemobj</code> to the slave port.</p> |
| |
| <p>``` {.sourceCode .c++} |
| void |
| SimpleMemobj::MemSidePort::recvRangeChange() |
| { |
| owner->sendRangeChange(); |
| }</p> |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> |
| ``` {.sourceCode .c++} |
| void |
| SimpleMemobj::sendRangeChange() |
| { |
| instPort.sendRangeChange(); |
| dataPort.sendRangeChange(); |
| } |
| </code></pre></div></div> |
| |
| <h3 id="implementing-receiving-requests">Implementing receiving requests</h3> |
| |
| <p>The implementation of <code class="highlighter-rouge">recvTimingReq</code> is slightly more complicated. We |
| need to check to see if the <code class="highlighter-rouge">SimpleMemobj</code> can accept the request. The |
| <code class="highlighter-rouge">SimpleMemobj</code> is a very simple blocking structure; we only allow a |
| single request outstanding at a time. Therefore, if we get a request |
| while another request is outstanding, the <code class="highlighter-rouge">SimpleMemobj</code> will block the |
| second request.</p> |
| |
| <p>To simplify the implementation, the <code class="highlighter-rouge">CPUSidePort</code> stores all of the |
| flow-control information for the port interface. Thus, we need to add an |
| extra member variable, <code class="highlighter-rouge">needRetry</code>, to the <code class="highlighter-rouge">CPUSidePort</code>, a boolean that |
| stores whether we need to send a retry whenever the <code class="highlighter-rouge">SimpleMemobj</code> |
| becomes free. Then, if the <code class="highlighter-rouge">SimpleMemobj</code> is blocked on a request, we |
| set that we need to send a retry sometime in the future.</p> |
| |
| <p>``` {.sourceCode .c++} |
| bool |
| SimpleMemobj::CPUSidePort::recvTimingReq(PacketPtr pkt) |
| { |
| if (!owner->handleRequest(pkt)) { |
| needRetry = true; |
| return false; |
| } else { |
| return true; |
| } |
| }</p> |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> |
| To handle the request for the `SimpleMemobj`, we first check if the |
| `SimpleMemobj` is already blocked waiting for a response to another |
| request. If it is blocked, then we return `false` to signal the calling |
| master port that we cannot accept the request right now. Otherwise, we |
| mark the port as blocked and send the packet out of the memory port. For |
| this, we can define a helper function in the `MemSidePort` object to |
| hide the flow control from the `SimpleMemobj` implementation. We will |
| assume the `memPort` handles all of the flow control and always return |
| `true` from `handleRequest` since we were successful in consuming the |
| request. |
| |
| ``` {.sourceCode .c++} |
| bool |
| SimpleMemobj::handleRequest(PacketPtr pkt) |
| { |
| if (blocked) { |
| return false; |
| } |
| DPRINTF(SimpleMemobj, "Got request for addr %#x\n", pkt->getAddr()); |
| blocked = true; |
| memPort.sendPacket(pkt); |
| return true; |
| } |
| </code></pre></div></div> |
| |
| <p>Next, we need to implement the <code class="highlighter-rouge">sendPacket</code> function in the |
| <code class="highlighter-rouge">MemSidePort</code>. This function will handle the flow control in case its |
| peer slave port cannot accept the request. For this, we need to add a |
| member to the <code class="highlighter-rouge">MemSidePort</code> to store the packet in case it is blocked. |
| It is the responsibility of the sender to store the packet if the |
| receiver cannot receive the request (or response).</p> |
| |
| <p>This function simply send the packet by calling the function |
| <code class="highlighter-rouge">sendTimingReq</code>. If the send fails, then this object store the packet in |
| the <code class="highlighter-rouge">blockedPacket</code> member function so it can send the packet later |
| (when it receives a <code class="highlighter-rouge">recvReqRetry</code>). This function also contains some |
| defensive code to make sure there is not a bug and we never try to |
| overwrite the <code class="highlighter-rouge">blockedPacket</code> variable incorrectly.</p> |
| |
| <p>``` {.sourceCode .c++} |
| void |
| SimpleMemobj::MemSidePort::sendPacket(PacketPtr pkt) |
| { |
| panic_if(blockedPacket != nullptr, “Should never try to send if blocked!”); |
| if (!sendTimingReq(pkt)) { |
| blockedPacket = pkt; |
| } |
| }</p> |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> |
| Next, we need to implement the code to resend the packet. In this |
| function, we try to resend the packet by calling the `sendPacket` |
| function we wrote above. |
| |
| ``` {.sourceCode .c++} |
| void |
| SimpleMemobj::MemSidePort::recvReqRetry() |
| { |
| assert(blockedPacket != nullptr); |
| |
| PacketPtr pkt = blockedPacket; |
| blockedPacket = nullptr; |
| |
| sendPacket(pkt); |
| } |
| </code></pre></div></div> |
| |
| <h3 id="implementing-receiving-responses">Implementing receiving responses</h3> |
| |
| <p>The response codepath is similar to the receiving codepath. When the |
| <code class="highlighter-rouge">MemSidePort</code> gets a response, we forward the response through the |
| <code class="highlighter-rouge">SimpleMemobj</code> to the appropriate <code class="highlighter-rouge">CPUSidePort</code>.</p> |
| |
| <p>``` {.sourceCode .c++} |
| bool |
| SimpleMemobj::MemSidePort::recvTimingResp(PacketPtr pkt) |
| { |
| return owner->handleResponse(pkt); |
| }</p> |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> |
| In the `SimpleMemobj`, first, it should always be blocked when we |
| receive a response since the object is blocking. Before sending the |
| packet back to the CPU side, we need to mark that the object no longer |
| blocked. This must be done *before calling `sendTimingResp`*. Otherwise, |
| it is possible to get stuck in an infinite loop as it is possible that |
| the master port has a single callchain between receiving a response and |
| sending another request. |
| |
| After unblocking the `SimpleMemobj`, we check to see if the packet is an |
| instruction or data packet and send it back across the appropriate port. |
| Finally, since the object is now unblocked, we may need to notify the |
| CPU side ports that they can now retry their requests that failed. |
| |
| ``` {.sourceCode .c++} |
| bool |
| SimpleMemobj::handleResponse(PacketPtr pkt) |
| { |
| assert(blocked); |
| DPRINTF(SimpleMemobj, "Got response for addr %#x\n", pkt->getAddr()); |
| |
| blocked = false; |
| |
| // Simply forward to the memory port |
| if (pkt->req->isInstFetch()) { |
| instPort.sendPacket(pkt); |
| } else { |
| dataPort.sendPacket(pkt); |
| } |
| |
| instPort.trySendRetry(); |
| dataPort.trySendRetry(); |
| |
| return true; |
| } |
| </code></pre></div></div> |
| |
| <p>Similar to how we implemented a convenience function for sending packets |
| in the <code class="highlighter-rouge">MemSidePort</code>, we can implement a <code class="highlighter-rouge">sendPacket</code> function in the |
| <code class="highlighter-rouge">CPUSidePort</code> to send the responses to the CPU side. This function calls |
| <code class="highlighter-rouge">sendTimingResp</code> which will in turn call <code class="highlighter-rouge">recvTimingResp</code> on the peer |
| master port. If this call fails and the peer port is currently blocked, |
| then we store the packet to be sent later.</p> |
| |
| <p>``` {.sourceCode .c++} |
| void |
| SimpleMemobj::CPUSidePort::sendPacket(PacketPtr pkt) |
| { |
| panic_if(blockedPacket != nullptr, “Should never try to send if blocked!”);</p> |
| |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if (!sendTimingResp(pkt)) { |
| blockedPacket = pkt; |
| } } ``` |
| </code></pre></div></div> |
| |
| <p>We will send this blocked packet later when we receive a |
| <code class="highlighter-rouge">recvRespRetry</code>. This function is exactly the same as the <code class="highlighter-rouge">recvReqRetry</code> |
| above and simply tries to resend the packet, which may be blocked again.</p> |
| |
| <p>``` {.sourceCode .c++} |
| void |
| SimpleMemobj::CPUSidePort::recvRespRetry() |
| { |
| assert(blockedPacket != nullptr);</p> |
| |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PacketPtr pkt = blockedPacket; |
| blockedPacket = nullptr; |
| |
| sendPacket(pkt); } ``` |
| </code></pre></div></div> |
| |
| <p>Finally, we need to implement the extra function <code class="highlighter-rouge">trySendRetry</code> for the |
| <code class="highlighter-rouge">CPUSidePort</code>. This function is called by the <code class="highlighter-rouge">SimpleMemobj</code> whenever |
| the <code class="highlighter-rouge">SimpleMemobj</code> may be unblocked. <code class="highlighter-rouge">trySendRetry</code> checks to see if a |
| retry is needed which we marked in <code class="highlighter-rouge">recvTimingReq</code> whenever the |
| <code class="highlighter-rouge">SimpleMemobj</code> was blocked on a new request. Then, if the retry is |
| needed, this function calls <code class="highlighter-rouge">sendRetryReq</code>, which in turn calls |
| <code class="highlighter-rouge">recvReqRetry</code> on the peer master port (the CPU in this case).</p> |
| |
| <p>``` {.sourceCode .c++} |
| void |
| SimpleMemobj::CPUSidePort::trySendRetry() |
| { |
| if (needRetry && blockedPacket == nullptr) { |
| needRetry = false; |
| DPRINTF(SimpleMemobj, “Sending retry req for %d\n”, id); |
| sendRetryReq(); |
| } |
| }</p> |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> |
| You can download the implementation for the `SimpleMemobj` |
| here \<../\_static/scripts/part2/memoryobject/simple\_memobj.cc\> |
| |
| The following figure, memobj-api-figure, shows the relationships between |
| the `CPUSidePort`, `MemSidePort`, and `SimpleMemobj`. This figure shows |
| how the peer ports interact with the implementation of the |
| `SimpleMemobj`. Each bold function is one that we had to implement, and |
| the non-bold functions are the port interfaces to the peer ports. The |
| colors highlight one API path through the object (e.g., receiving a |
| request or updating the memory ranges). |
| |
| ![](../_static/figures/memobj_api.png) |
| |
| > width |
| > : 100 % |
| > |
| > alt |
| > : Interaction between SimpleMemobj and its ports |
| > |
| > Interaction between SimpleMemobj and its ports |
| |
| For this simple memory object, packets are just forwarded from the |
| CPU-side to the memory side. However, by modifying `handleRequest` and |
| `handleResponse`, we can create rich featureful objects, like a cache in |
| the next chapter \<simplecache-chapter\>. |
| |
| ### Create a config file |
| |
| This is all of the code needed to implement a simple memory object! In |
| the next chapter \<simplecache-chapter\>, we will take this framework |
| and add some caching logic to make this memory object into a simple |
| cache. However, before that, let's look at the config file to add the |
| SimpleMemobj to your system. |
| |
| This config file builds off of the simple config file in |
| simple-config-chapter. However, instead of connecting the CPU directly |
| to the memory bus, we are going to instantiate a `SimpleMemobj` and |
| place it between the CPU and the memory bus. |
| |
| ``` {.sourceCode .python} |
| import m5 |
| from m5.objects import * |
| |
| system = System() |
| system.clk_domain = SrcClockDomain() |
| system.clk_domain.clock = '1GHz' |
| system.clk_domain.voltage_domain = VoltageDomain() |
| system.mem_mode = 'timing' |
| system.mem_ranges = [AddrRange('512MB')] |
| |
| system.cpu = TimingSimpleCPU() |
| |
| system.memobj = SimpleMemobj() |
| |
| system.cpu.icache_port = system.memobj.inst_port |
| system.cpu.dcache_port = system.memobj.data_port |
| |
| system.membus = SystemXBar() |
| |
| system.memobj.mem_side = system.membus.slave |
| |
| system.cpu.createInterruptController() |
| system.cpu.interrupts[0].pio = system.membus.master |
| system.cpu.interrupts[0].int_master = system.membus.slave |
| system.cpu.interrupts[0].int_slave = system.membus.master |
| |
| system.mem_ctrl = DDR3_1600_8x8() |
| system.mem_ctrl.range = system.mem_ranges[0] |
| system.mem_ctrl.port = system.membus.master |
| |
| system.system_port = system.membus.slave |
| |
| process = Process() |
| process.cmd = ['tests/test-progs/hello/bin/x86/linux/hello'] |
| system.cpu.workload = process |
| system.cpu.createThreads() |
| |
| root = Root(full_system = False, system = system) |
| m5.instantiate() |
| |
| print "Beginning simulation!" |
| exit_event = m5.simulate() |
| print 'Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause()) |
| </code></pre></div></div> |
| |
| <p>You can download this config script |
| here <../_static/scripts/part2/memoryobject/simple_memobj.py></p> |
| |
| <p>Now, when you run this config file you get the following output.</p> |
| |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem5 Simulator System. http://gem5.org |
| gem5 is copyrighted software; use the --copyright option for details. |
| |
| gem5 compiled Jan 5 2017 13:40:18 |
| gem5 started Jan 9 2017 10:17:17 |
| gem5 executing on chinook, pid 5138 |
| command line: build/X86/gem5.opt configs/learning_gem5/part2/simple_memobj.py |
| |
| Global frequency set at 1000000000000 ticks per second |
| warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes) |
| 0: system.remote_gdb.listener: listening for remote gdb #0 on port 7000 |
| warn: CoherentXBar system.membus has no snooping ports attached! |
| warn: ClockedObject: More than one power state change request encountered within the same simulation tick |
| Beginning simulation! |
| info: Entering event queue @ 0. Starting simulation... |
| Hello world! |
| Exiting @ tick 507841000 because target called exit() |
| </code></pre></div></div> |
| |
| <p>If you run with the <code class="highlighter-rouge">SimpleMemobj</code> debug flag, you can see all of the |
| memory requests and responses from and to the CPU.</p> |
| |
| <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem5 Simulator System. http://gem5.org |
| gem5 is copyrighted software; use the --copyright option for details. |
| |
| gem5 compiled Jan 5 2017 13:40:18 |
| gem5 started Jan 9 2017 10:18:51 |
| gem5 executing on chinook, pid 5157 |
| command line: build/X86/gem5.opt --debug-flags=SimpleMemobj configs/learning_gem5/part2/simple_memobj.py |
| |
| Global frequency set at 1000000000000 ticks per second |
| Beginning simulation! |
| info: Entering event queue @ 0. Starting simulation... |
| 0: system.memobj: Got request for addr 0x190 |
| 77000: system.memobj: Got response for addr 0x190 |
| 77000: system.memobj: Got request for addr 0x190 |
| 132000: system.memobj: Got response for addr 0x190 |
| 132000: system.memobj: Got request for addr 0x190 |
| 187000: system.memobj: Got response for addr 0x190 |
| 187000: system.memobj: Got request for addr 0x94e30 |
| 250000: system.memobj: Got response for addr 0x94e30 |
| 250000: system.memobj: Got request for addr 0x190 |
| ... |
| </code></pre></div></div> |
| |
| <p>You may also want to change the CPU model to the out-of-order model |
| (<code class="highlighter-rouge">DerivO3CPU</code>). When using the out-of-order CPU you will potentially see |
| a different address stream since it allows multiple memory requests |
| outstanding at a once. When using the out-of-order CPU, there will now |
| be many stalls because the <code class="highlighter-rouge">SimpleMemobj</code> is blocking.</p> |
| |
| <br> |
| |
| <!-- RETRIVE PREVIOUS PAGE LINK --> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <!-- RETRIEVE NEXT PAGE LINK --> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <div class="navbuttons"> |
| |
| <a href="/parameters"><button type="button" class="btn btn-outline-primary">PREVIOUS</button></a> |
| |
| |
| |
| <a href="/simplecache"><button type="button" class="btn btn-outline-primary">NEXT</button></a> |
| |
| </div> |
| </div> |
| |
| </main> |
| |
| |
| <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> |
| <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> |
| |
| <script> |
| // When the user scrolls down 20px from the top of the document, show the button |
| window.onscroll = function() {scrollFunction()}; |
| |
| function scrollFunction() { |
| if (document.body.scrollTop > 100 || document.documentElement.scrollTop > 20) { |
| document.getElementById("myBtn").style.display = "block"; |
| } else { |
| document.getElementById("myBtn").style.display = "none"; |
| } |
| } |
| |
| // When the user clicks on the button, scroll to the top of the document |
| function topFunction() { |
| document.body.scrollTop = 0; |
| document.documentElement.scrollTop = 0; |
| } |
| </script> |
| |
| </body> |
| |
| |
| </html> |