blob: 98baee05183b747c39659b5803cd71820241ff1f [file] [log] [blame]
<!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/directory/">
<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="/search">Search</a>
</li>
</ul>
</div>
</nav>
<main>
<div class="sidenav-top">
<img src="/assets/img/gem5ColorLong.gif" height="80">
<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="introdution">
</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 " 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 " 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 show" 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 active" 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">
<!-- <h1>MSI Directory implementation</h1> -->
<dl>
<dt>authors</dt>
<dd>Jason Lowe-Power</dd>
</dl>
<h1 id="msi-directory-implementation">MSI Directory implementation</h1>
<p>Implementing a directory controller is very similar to the L1 cache
controller, except using a different state machine table. The state
machine fore the directory can be found in Table 8.2 in Sorin et al.
Since things are mostly similar to the L1 cache, this section mostly
just discusses a few more SLICC details and a few differences between
directory controllers and cache controllers. Let’s dive straight in and
start modifying a new file <code class="highlighter-rouge">MSI-dir.sm</code>.</p>
<p>``` {.sourceCode .c++}
machine(MachineType:Directory, “Directory protocol”)
:
DirectoryMemory * directory;
Cycles toMemLatency := 1;</p>
<p>MessageBuffer *forwardToCache, network=”To”, virtual_network=”1”,
vnet_type=”forward”;
MessageBuffer *responseToCache, network=”To”, virtual_network=”2”,
vnet_type=”response”;</p>
<p>MessageBuffer *requestFromCache, network=”From”, virtual_network=”0”,
vnet_type=”request”;</p>
<p>MessageBuffer *responseFromCache, network=”From”, virtual_network=”2”,
vnet_type=”response”;</p>
<p>MessageBuffer *responseFromMemory;</p>
<p>{
. . .
}</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
First, there are two parameter to this directory controller,
`DirectoryMemory` and a `toMemLatency`. The `DirectoryMemory` is a
little weird. It is allocated at initialization time such that it can
cover *all* of physical memory, like a complete directory *not a
directory cache*. I.e., there are pointers in the `DirectoryMemory`
object for every 64-byte block in physical memory. However, the actual
entries (as defined below) are lazily created via `getDirEntry()`. We'll
see more details about `DirectoryMemory` below.
Next, is the `toMemLatency` parameter. This will be used in the
`enqueue` function when enqueuing requests to model the directory
latency. We didn't use a parameter for this in the L1 cache, but it is
simple to make the controller latency parameterized. This parameter
defaults to 1 cycle. It is not required to set a default here. The
default is propagated to the generated SimObject description file as the
default to the SimObject parameter.
Next, we have the message buffers for the directory. Importantly, *these
need to have the same virtual network numbers* as the message buffers in
the L1 cache. These virtual network numbers are how the Ruby network
directs messages between controllers.
There is also one more special message buffer: `responseFromMemory`.
This is similar to the `mandatoryQueue`, except instead of being like a
slave port for CPUs it is like a master port. The `responseFromMemory`
buffer will deliver response sent across the the memory port, as we will
see below in the action section.
After the parameters and message buffers, we need to declare all of the
states, events, and other local structures.
``` {.sourceCode .c++}
state_declaration(State, desc="Directory states",
default="Directory_State_I") {
// Stable states.
// NOTE: These are "cache-centric" states like in Sorin et al.
// However, The access permissions are memory-centric.
I, AccessPermission:Read_Write, desc="Invalid in the caches.";
S, AccessPermission:Read_Only, desc="At least one cache has the blk";
M, AccessPermission:Invalid, desc="A cache has the block in M";
// Transient states
S_D, AccessPermission:Busy, desc="Moving to S, but need data";
// Waiting for data from memory
S_m, AccessPermission:Read_Write, desc="In S waiting for mem";
M_m, AccessPermission:Read_Write, desc="Moving to M waiting for mem";
// Waiting for write-ack from memory
MI_m, AccessPermission:Busy, desc="Moving to I waiting for ack";
SS_m, AccessPermission:Busy, desc="Moving to I waiting for ack";
}
enumeration(Event, desc="Directory events") {
// Data requests from the cache
GetS, desc="Request for read-only data from cache";
GetM, desc="Request for read-write data from cache";
// Writeback requests from the cache
PutSNotLast, desc="PutS and the block has other sharers";
PutSLast, desc="PutS and the block has no other sharers";
PutMOwner, desc="Dirty data writeback from the owner";
PutMNonOwner, desc="Dirty data writeback from non-owner";
// Cache responses
Data, desc="Response to fwd request with data";
// From Memory
MemData, desc="Data from memory";
MemAck, desc="Ack from memory that write is complete";
}
structure(Entry, desc="...", interface="AbstractEntry") {
State DirState, desc="Directory state";
NetDest Sharers, desc="Sharers for this block";
NetDest Owner, desc="Owner of this block";
}
</code></pre></div></div>
<p>In the <code class="highlighter-rouge">state_declaration</code> we define a default. For many things in SLICC
you can specify a default. However, this default must use the C++ name
(mangled SLICC name). For the state below you have to use the controller
name and the name we use for states. In this case, since the name of the
machine is “Directory” the name for “I” is “Directory”+”State” (for the
name of the structure)+”I”.</p>
<p>Note that the permissions in the directory are “memory-centric”.
Whereas, all of the states are cache centric as in Sorin et al.</p>
<p>In the <code class="highlighter-rouge">Entry</code> definition for the directory, we use a NetDest for both
the sharers and the owner. This makes sense for the sharers, since we
want a full bitvector for all L1 caches that may be sharing the block.
The reason we also use a <code class="highlighter-rouge">NetDest</code> for the owner is to simply copy the
structure into the message we send as a response as shown below.</p>
<p>In this implementation, we use a few more transient states than in Table
8.2 in Sorin et al. to deal with the fact that the memory latency in
unknown. In Sorin et al., the authors assume that the directory state
and memory data is stored together in main-memory to simplify the
protocol. Similarly, we also include new actions: the responses from
memory.</p>
<p>Next, we have the functions that need to overridden and declared. The
function <code class="highlighter-rouge">getDirectoryEntry</code> either returns the valid directory entry,
or, if it hasn’t been allocated yet, this allocates the entry.
Implementing it this way may save some host memory since this is lazily
populated.</p>
<p>``` {.sourceCode .c++}
Tick clockEdge();</p>
<p>Entry getDirectoryEntry(Addr addr), return_by_pointer = “yes” {
Entry dir_entry := static_cast(Entry, “pointer”, directory[addr]);
if (is_invalid(dir_entry)) {
// This first time we see this address allocate an entry for it.
dir_entry := static_cast(Entry, “pointer”,
directory.allocate(addr, new Entry));
}
return dir_entry;
}</p>
<p>State getState(Addr addr) {
if (directory.isPresent(addr)) {
return getDirectoryEntry(addr).DirState;
} else {
return State:I;
}
}</p>
<p>void setState(Addr addr, State state) {
if (directory.isPresent(addr)) {
if (state == State:M) {
DPRINTF(RubySlicc, “Owner %s\n”, getDirectoryEntry(addr).Owner);
assert(getDirectoryEntry(addr).Owner.count() == 1);
assert(getDirectoryEntry(addr).Sharers.count() == 0);
}
getDirectoryEntry(addr).DirState := state;
if (state == State:I) {
assert(getDirectoryEntry(addr).Owner.count() == 0);
assert(getDirectoryEntry(addr).Sharers.count() == 0);
}
}
}</p>
<p>AccessPermission getAccessPermission(Addr addr) {
if (directory.isPresent(addr)) {
Entry e := getDirectoryEntry(addr);
return Directory_State_to_permission(e.DirState);
} else {
return AccessPermission:NotPresent;
}
}
void setAccessPermission(Addr addr, State state) {
if (directory.isPresent(addr)) {
Entry e := getDirectoryEntry(addr);
e.changePermission(Directory_State_to_permission(state));
}
}</p>
<p>void functionalRead(Addr addr, Packet *pkt) {
functionalMemoryRead(pkt);
}</p>
<p>int functionalWrite(Addr addr, Packet *pkt) {
if (functionalMemoryWrite(pkt)) {
return 1;
} else {
return 0;
}</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
Next, we need to implement the ports for the cache. First we specify the
`out_port` and then the `in_port` code blocks. The only difference
between the `in_port` in the directory and in the L1 cache is that the
directory does not have a TBE or cache entry. Thus, we do not pass
either into the `trigger` function.
``` {.sourceCode .c++}
out_port(forward_out, RequestMsg, forwardToCache);
out_port(response_out, ResponseMsg, responseToCache);
in_port(memQueue_in, MemoryMsg, responseFromMemory) {
if (memQueue_in.isReady(clockEdge())) {
peek(memQueue_in, MemoryMsg) {
if (in_msg.Type == MemoryRequestType:MEMORY_READ) {
trigger(Event:MemData, in_msg.addr);
} else if (in_msg.Type == MemoryRequestType:MEMORY_WB) {
trigger(Event:MemAck, in_msg.addr);
} else {
error("Invalid message");
}
}
}
}
in_port(response_in, ResponseMsg, responseFromCache) {
if (response_in.isReady(clockEdge())) {
peek(response_in, ResponseMsg) {
if (in_msg.Type == CoherenceResponseType:Data) {
trigger(Event:Data, in_msg.addr);
} else {
error("Unexpected message type.");
}
}
}
}
in_port(request_in, RequestMsg, requestFromCache) {
if (request_in.isReady(clockEdge())) {
peek(request_in, RequestMsg) {
Entry e := getDirectoryEntry(in_msg.addr);
if (in_msg.Type == CoherenceRequestType:GetS) {
trigger(Event:GetS, in_msg.addr);
} else if (in_msg.Type == CoherenceRequestType:GetM) {
trigger(Event:GetM, in_msg.addr);
} else if (in_msg.Type == CoherenceRequestType:PutS) {
assert(is_valid(e));
// If there is only a single sharer (i.e., the requestor)
if (e.Sharers.count() == 1) {
assert(e.Sharers.isElement(in_msg.Requestor));
trigger(Event:PutSLast, in_msg.addr);
} else {
trigger(Event:PutSNotLast, in_msg.addr);
}
} else if (in_msg.Type == CoherenceRequestType:PutM) {
assert(is_valid(e));
if (e.Owner.isElement(in_msg.Requestor)) {
trigger(Event:PutMOwner, in_msg.addr);
} else {
trigger(Event:PutMNonOwner, in_msg.addr);
}
} else {
error("Unexpected message type.");
}
}
}
}
</code></pre></div></div>
<p>The next part of the state machine file is the actions. First, we define
actions for queuing memory reads and writes. For this, we will use a
special function define in the <code class="highlighter-rouge">AbstractController</code>: <code class="highlighter-rouge">queueMemoryRead</code>.
This function takes an address and converts it to a gem5 request and
packet and sends it to across the port that is connected to this
controller. We will see how to connect this port in the
configuration section &lt;MSI-config-section&gt;. Note that we need two
different actions to send data to memory for both requests and responses
since there are two different message buffers (virtual networks) that
data might arrive on.</p>
<p>``` {.sourceCode .c++}
action(sendMemRead, “r”, desc=”Send a memory read request”) {
peek(request_in, RequestMsg) {
queueMemoryRead(in_msg.Requestor, address, toMemLatency);
}
}</p>
<p>action(sendDataToMem, “w”, desc=”Write data to memory”) {
peek(request_in, RequestMsg) {
DPRINTF(RubySlicc, “Writing memory for %#x\n”, address);
DPRINTF(RubySlicc, “Writing %s\n”, in_msg.DataBlk);
queueMemoryWrite(in_msg.Requestor, address, toMemLatency,
in_msg.DataBlk);
}
}</p>
<p>action(sendRespDataToMem, “rw”, desc=”Write data to memory from resp”) {
peek(response_in, ResponseMsg) {
DPRINTF(RubySlicc, “Writing memory for %#x\n”, address);
DPRINTF(RubySlicc, “Writing %s\n”, in_msg.DataBlk);
queueMemoryWrite(in_msg.Sender, address, toMemLatency,
in_msg.DataBlk);
}
}</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
In this code, we also see the last way to add debug information to SLICC
protocols: `DPRINTF`. This is exactly the same as a `DPRINTF` in gem5,
except in SLICC only the `RubySlicc` debug flag is available.
Next, we specify actions to update the sharers and owner of a particular
block.
``` {.sourceCode .c++}
action(addReqToSharers, "aS", desc="Add requestor to sharer list") {
peek(request_in, RequestMsg) {
getDirectoryEntry(address).Sharers.add(in_msg.Requestor);
}
}
action(setOwner, "sO", desc="Set the owner") {
peek(request_in, RequestMsg) {
getDirectoryEntry(address).Owner.add(in_msg.Requestor);
}
}
action(addOwnerToSharers, "oS", desc="Add the owner to sharers") {
Entry e := getDirectoryEntry(address);
assert(e.Owner.count() == 1);
e.Sharers.addNetDest(e.Owner);
}
action(removeReqFromSharers, "rS", desc="Remove requestor from sharers") {
peek(request_in, RequestMsg) {
getDirectoryEntry(address).Sharers.remove(in_msg.Requestor);
}
}
action(clearSharers, "cS", desc="Clear the sharer list") {
getDirectoryEntry(address).Sharers.clear();
}
action(clearOwner, "cO", desc="Clear the owner") {
getDirectoryEntry(address).Owner.clear();
}
</code></pre></div></div>
<p>The next set of actions send invalidates and forward requests to caches
that the directory cannot deal with alone.</p>
<p>``` {.sourceCode .c++}
action(sendInvToSharers, “i”, desc=”Send invalidate to all sharers”) {
peek(request_in, RequestMsg) {
enqueue(forward_out, RequestMsg, 1) {
out_msg.addr := address;
out_msg.Type := CoherenceRequestType:Inv;
out_msg.Requestor := in_msg.Requestor;
out_msg.Destination := getDirectoryEntry(address).Sharers;
out_msg.MessageSize := MessageSizeType:Control;
}
}
}</p>
<p>action(sendFwdGetS, “fS”, desc=”Send forward getS to owner”) {
assert(getDirectoryEntry(address).Owner.count() == 1);
peek(request_in, RequestMsg) {
enqueue(forward_out, RequestMsg, 1) {
out_msg.addr := address;
out_msg.Type := CoherenceRequestType:GetS;
out_msg.Requestor := in_msg.Requestor;
out_msg.Destination := getDirectoryEntry(address).Owner;
out_msg.MessageSize := MessageSizeType:Control;
}
}
}</p>
<p>action(sendFwdGetM, “fM”, desc=”Send forward getM to owner”) {
assert(getDirectoryEntry(address).Owner.count() == 1);
peek(request_in, RequestMsg) {
enqueue(forward_out, RequestMsg, 1) {
out_msg.addr := address;
out_msg.Type := CoherenceRequestType:GetM;
out_msg.Requestor := in_msg.Requestor;
out_msg.Destination := getDirectoryEntry(address).Owner;
out_msg.MessageSize := MessageSizeType:Control;
}
}
}</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
Now we have responses from the directory. Here we are peeking into the
special buffer `responseFromMemory`. You can find the definition of
`MemoryMsg` in `src/mem/protocol/RubySlicc_MemControl.sm`.
``` {.sourceCode .c++}
action(sendDataToReq, "d", desc="Send data from memory to requestor. May need to send sharer number, too") {
peek(memQueue_in, MemoryMsg) {
enqueue(response_out, ResponseMsg, 1) {
out_msg.addr := address;
out_msg.Type := CoherenceResponseType:Data;
out_msg.Sender := machineID;
out_msg.Destination.add(in_msg.OriginalRequestorMachId);
out_msg.DataBlk := in_msg.DataBlk;
out_msg.MessageSize := MessageSizeType:Data;
Entry e := getDirectoryEntry(address);
// Only need to include acks if we are the owner.
if (e.Owner.isElement(in_msg.OriginalRequestorMachId)) {
out_msg.Acks := e.Sharers.count();
} else {
out_msg.Acks := 0;
}
assert(out_msg.Acks &gt;= 0);
}
}
}
action(sendPutAck, "a", desc="Send the put ack") {
peek(request_in, RequestMsg) {
enqueue(forward_out, RequestMsg, 1) {
out_msg.addr := address;
out_msg.Type := CoherenceRequestType:PutAck;
out_msg.Requestor := machineID;
out_msg.Destination.add(in_msg.Requestor);
out_msg.MessageSize := MessageSizeType:Control;
}
}
}
</code></pre></div></div>
<p>Then, we have the queue management and stall actions.</p>
<p>``` {.sourceCode .c++}
action(popResponseQueue, “pR”, desc=”Pop the response queue”) {
response_in.dequeue(clockEdge());
}</p>
<p>action(popRequestQueue, “pQ”, desc=”Pop the request queue”) {
request_in.dequeue(clockEdge());
}</p>
<p>action(popMemQueue, “pM”, desc=”Pop the memory queue”) {
memQueue_in.dequeue(clockEdge());
}</p>
<p>action(stall, “z”, desc=”Stall the incoming request”) {
// Do nothing.
}</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
Finally, we have the transition section of the state machine file. These
mostly come from Table 8.2 in Sorin et al., but there are some extra
transitions to deal with the unknown memory latency.
``` {.sourceCode .c++}
transition({I, S}, GetS, S_m) {
sendMemRead;
addReqToSharers;
popRequestQueue;
}
transition(I, {PutSNotLast, PutSLast, PutMNonOwner}) {
sendPutAck;
popRequestQueue;
}
transition(S_m, MemData, S) {
sendDataToReq;
popMemQueue;
}
transition(I, GetM, M_m) {
sendMemRead;
setOwner;
popRequestQueue;
}
transition(M_m, MemData, M) {
sendDataToReq;
clearSharers; // NOTE: This isn't *required* in some cases.
popMemQueue;
}
transition(S, GetM, M_m) {
sendMemRead;
removeReqFromSharers;
sendInvToSharers;
setOwner;
popRequestQueue;
}
transition({S, S_D, SS_m, S_m}, {PutSNotLast, PutMNonOwner}) {
removeReqFromSharers;
sendPutAck;
popRequestQueue;
}
transition(S, PutSLast, I) {
removeReqFromSharers;
sendPutAck;
popRequestQueue;
}
transition(M, GetS, S_D) {
sendFwdGetS;
addReqToSharers;
addOwnerToSharers;
clearOwner;
popRequestQueue;
}
transition(M, GetM) {
sendFwdGetM;
clearOwner;
setOwner;
popRequestQueue;
}
transition({M, M_m, MI_m}, {PutSNotLast, PutSLast, PutMNonOwner}) {
sendPutAck;
popRequestQueue;
}
transition(M, PutMOwner, MI_m) {
sendDataToMem;
clearOwner;
sendPutAck;
popRequestQueue;
}
transition(MI_m, MemAck, I) {
popMemQueue;
}
transition(S_D, {GetS, GetM}) {
stall;
}
transition(S_D, PutSLast) {
removeReqFromSharers;
sendPutAck;
popRequestQueue;
}
transition(S_D, Data, SS_m) {
sendRespDataToMem;
popResponseQueue;
}
transition(SS_m, MemAck, S) {
popMemQueue;
}
// If we get another request for a block that's waiting on memory,
// stall that request.
transition({MI_m, SS_m, S_m, M_m}, {GetS, GetM}) {
stall;
}
</code></pre></div></div>
<p>You can download the complete <code class="highlighter-rouge">MSI-dir.sm</code> file
here &lt;../../_static/scripts/part3/MSI_protocol/MSI-dir.sm&gt;.</p>
</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>
<footer class="page-footer">
<div class="container">
<div class="row">
<div class="col-12 col-sm-4">
<p><a href="/about">About</a></p>
<p><a href="/publications">Publications</a></p>
<p><a href="/contributing">Contributing</a></p>
</div><br>
<div class="col-12 col-sm-4">
<p><a href="/gettingstarted">Documentation</a></p>
<p><a href="#">Source</a></p>
</div><br>
<div class="col-12 col-sm-4">
<p><a href="/help">Help</a></p>
<p><a href="/search">Search</a></p>
<p><a href="#">Mailing Lists</a></p>
</div>
</div>
</div>
</footer>
</html>