From 0169113414abff79f9d3519b8de0e54f2782ba82 Mon Sep 17 00:00:00 2001 From: Jason Lowe-Power Date: Fri, 9 Mar 2018 12:01:34 -0800 Subject: learning_gem5: Add a simple Ruby protocol Adds the MSI protocol from "A Primer on Memory Consistency and Cache Coherence" by Daniel J. Sorin, Mark D. Hill, and David A. Wood. This code follows Learning gem5 Part 3. http://learning.gem5.org/book/part3/index.html This is meant to be a simple, clean, example of how to make a Ruby protocol. Currently, it only works in SE mode. The next changeset will contain the required configuration files. Change-Id: If2cc53f5e6b9c6891749f929d872671615a2b4ab Signed-off-by: Jason Lowe-Power Reviewed-on: https://gem5-review.googlesource.com/8942 --- src/learning_gem5/part3/MSI-dir.sm | 548 +++++++++++++++++++++++++++++++++++++ 1 file changed, 548 insertions(+) create mode 100644 src/learning_gem5/part3/MSI-dir.sm (limited to 'src/learning_gem5/part3/MSI-dir.sm') 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; + } + +} -- cgit v1.2.3