summaryrefslogtreecommitdiff
path: root/src/learning_gem5/part3/MSI-dir.sm
diff options
context:
space:
mode:
Diffstat (limited to 'src/learning_gem5/part3/MSI-dir.sm')
-rw-r--r--src/learning_gem5/part3/MSI-dir.sm548
1 files changed, 548 insertions, 0 deletions
diff --git a/src/learning_gem5/part3/MSI-dir.sm b/src/learning_gem5/part3/MSI-dir.sm
new file mode 100644
index 000000000..7bd7aae83
--- /dev/null
+++ b/src/learning_gem5/part3/MSI-dir.sm
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 2017 Jason Lowe-Power
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * This file contains the directory controller of a simple example MSI protocol
+ *
+ * In Ruby the directory controller both contains the directory coherence state
+ * but also functions as the memory controller in many ways. There are states
+ * in the directory that are both memory-centric and cache-centric. Be careful!
+ *
+ * The protocol in this file is based off of the MSI protocol found in
+ * A Primer on Memory Consistency and Cache Coherence
+ * Daniel J. Sorin, Mark D. Hill, and David A. Wood
+ * Synthesis Lectures on Computer Architecture 2011 6:3, 141-149
+ *
+ * Table 8.2 contains the transitions and actions found in this file and
+ * section 8.2.4 explains the protocol in detail.
+ *
+ * See Learning gem5 Part 3: Ruby for more details.
+ *
+ * Authors: Jason Lowe-Power
+ */
+
+machine(MachineType:Directory, "Directory protocol")
+ :
+ // This "DirectoryMemory" is a little weird. It is initially allocated
+ // so that it *can* cover all of memory (i.e., there are pointers for
+ // every 64-byte block in memory). However, the entries are lazily
+ // created in getDirEntry()
+ DirectoryMemory * directory;
+ // You can put any parameters you want here. They will be exported as
+ // normal SimObject parameters (like in the SimObject description file)
+ // and you can set these parameters at runtime via the python config
+ // file. If there is no default here (like directory), it is mandatory
+ // to set the parameter in the python config. Otherwise, it uses the
+ // default value set here.
+ Cycles toMemLatency := 1;
+
+ // Forwarding requests from the directory *to* the caches.
+ MessageBuffer *forwardToCache, network="To", virtual_network="1",
+ vnet_type="forward";
+ // Response from the directory *to* the cache.
+ MessageBuffer *responseToCache, network="To", virtual_network="2",
+ vnet_type="response";
+
+ // Requests *from* the cache to the directory
+ MessageBuffer *requestFromCache, network="From", virtual_network="0",
+ vnet_type="request";
+
+ // Responses *from* the cache to the directory
+ MessageBuffer *responseFromCache, network="From", virtual_network="2",
+ vnet_type="response";
+
+ // Special buffer for memory responses. Kind of like the mandatory queue
+ MessageBuffer *responseFromMemory;
+
+{
+ // 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.
+ state_declaration(State, desc="Directory states",
+ default="Directory_State_I") {
+ // Stable states.
+ // NOTE: Thise 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 S 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";
+ }
+
+ // NOTE: We use a netdest for the sharers and the owner so we can simply
+ // copy the structure into the message we send as a response.
+ structure(Entry, desc="...", interface="AbstractEntry") {
+ State DirState, desc="Directory state";
+ NetDest Sharers, desc="Sharers for this block";
+ NetDest Owner, desc="Owner of this block";
+ }
+
+ Tick clockEdge();
+
+ // This either returns the valid directory entry, or, if it hasn't been
+ // allocated yet, this allocates the entry. This may save some host memory
+ // since this is lazily populated.
+ 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;
+ }
+
+ /*************************************************************************/
+ // Functions that we need to define/override to use our specific structures
+ // in this implementation.
+ // NOTE: we don't have TBE in this machine, so we don't need to pass it
+ // to these overridden functions.
+
+ State getState(Addr addr) {
+ if (directory.isPresent(addr)) {
+ return getDirectoryEntry(addr).DirState;
+ } else {
+ return State:I;
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+
+ // This is really the access permissions of memory.
+ // TODO: I don't understand this at the directory.
+ 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));
+ }
+ }
+
+ void functionalRead(Addr addr, Packet *pkt) {
+ functionalMemoryRead(pkt);
+ }
+
+ // This returns the number of writes. So, if we write then return 1
+ int functionalWrite(Addr addr, Packet *pkt) {
+ if (functionalMemoryWrite(pkt)) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+
+ /*************************************************************************/
+ // Network ports
+
+ 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 entry := getDirectoryEntry(in_msg.addr);
+ if (in_msg.Type == CoherenceRequestType:GetS) {
+ // NOTE: Since we don't have a TBE in this machine, there
+ // is no need to pass a TBE into trigger. Also, for the
+ // directory there is no cache entry.
+ 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(entry));
+ // If there is only a single sharer (i.e., the requestor)
+ if (entry.Sharers.count() == 1) {
+ assert(entry.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(entry));
+ if (entry.Owner.isElement(in_msg.Requestor)) {
+ trigger(Event:PutMOwner, in_msg.addr);
+ } else {
+ trigger(Event:PutMNonOwner, in_msg.addr);
+ }
+ } else {
+ error("Unexpected message type.");
+ }
+ }
+ }
+ }
+
+
+
+ /*************************************************************************/
+ // Actions
+
+ // Memory actions.
+
+ action(sendMemRead, "r", desc="Send a memory read request") {
+ peek(request_in, RequestMsg) {
+ // Special function from AbstractController that will send a new
+ // packet out of the "Ruby" black box to the memory side. At some
+ // point the response will be on the memory queue.
+ // Like enqeue, this takes a latency for the request.
+ queueMemoryRead(in_msg.Requestor, address, toMemLatency);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ // Sharer/owner actions
+
+ 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();
+ }
+
+ // Invalidates and forwards
+
+ 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;
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ // Responses to requests
+
+ // This also needs to send along the number of sharers!!!!
+ 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 >= 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;
+ }
+ }
+ }
+
+ // Queue management
+
+ action(popResponseQueue, "pR", desc="Pop the response queue") {
+ response_in.dequeue(clockEdge());
+ }
+
+ action(popRequestQueue, "pQ", desc="Pop the request queue") {
+ request_in.dequeue(clockEdge());
+ }
+
+ action(popMemQueue, "pM", desc="Pop the memory queue") {
+ memQueue_in.dequeue(clockEdge());
+ }
+
+ // Stalling actions
+ action(stall, "z", desc="Stall the incoming request") {
+ // Do nothing.
+ }
+
+
+ /*************************************************************************/
+ // transitions
+
+ 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;
+ }
+
+}