| /* | |
| * 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; | |
| } | |
| } |