| /* | |
| * Copyright (c) 2020 ARM Limited | |
| * All rights reserved | |
| * | |
| * The license below extends only to copyright in the software and shall | |
| * not be construed as granting a license to any other intellectual | |
| * property including but not limited to intellectual property relating | |
| * to a hardware implementation of the functionality of the software | |
| * licensed hereunder. You may use the software subject to the license | |
| * terms below provided that you ensure that this notice is replicated | |
| * unmodified and in its entirety in all distributions of the software, | |
| * modified or unmodified, in source code or in binary form. | |
| * | |
| * Copyright (c) 1999-2013 Mark D. Hill and David A. Wood | |
| * 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. | |
| */ | |
| machine(MachineType:L2Cache, "MESI Directory L2 Cache CMP") | |
| : CacheMemory * L2cache; | |
| Cycles l2_request_latency := 2; | |
| Cycles l2_response_latency := 2; | |
| Cycles to_l1_latency := 1; | |
| // Message Queues | |
| // From local bank of L2 cache TO the network | |
| MessageBuffer * DirRequestFromL2Cache, network="To", virtual_network="0", | |
| vnet_type="request"; // this L2 bank -> Memory | |
| MessageBuffer * L1RequestFromL2Cache, network="To", virtual_network="2", | |
| vnet_type="request"; // this L2 bank -> a local L1 | |
| MessageBuffer * responseFromL2Cache, network="To", virtual_network="1", | |
| vnet_type="response"; // this L2 bank -> a local L1 || Memory | |
| // FROM the network to this local bank of L2 cache | |
| MessageBuffer * unblockToL2Cache, network="From", virtual_network="2", | |
| vnet_type="unblock"; // a local L1 || Memory -> this L2 bank | |
| MessageBuffer * L1RequestToL2Cache, network="From", virtual_network="0", | |
| vnet_type="request"; // a local L1 -> this L2 bank | |
| MessageBuffer * responseToL2Cache, network="From", virtual_network="1", | |
| vnet_type="response"; // a local L1 || Memory -> this L2 bank | |
| { | |
| // STATES | |
| state_declaration(State, desc="L2 Cache states", default="L2Cache_State_NP") { | |
| // Base states | |
| NP, AccessPermission:Invalid, desc="Not present in either cache"; | |
| SS, AccessPermission:Read_Only, desc="L2 cache entry Shared, also present in one or more L1s"; | |
| M, AccessPermission:Read_Write, desc="L2 cache entry Modified, not present in any L1s", format="!b"; | |
| MT, AccessPermission:Maybe_Stale, desc="L2 cache entry Modified in a local L1, assume L2 copy stale", format="!b"; | |
| // L2 replacement | |
| M_I, AccessPermission:Busy, desc="L2 cache replacing, have all acks, sent dirty data to memory, waiting for ACK from memory"; | |
| MT_I, AccessPermission:Busy, desc="L2 cache replacing, getting data from exclusive"; | |
| MCT_I, AccessPermission:Busy, desc="L2 cache replacing, clean in L2, getting data or ack from exclusive"; | |
| I_I, AccessPermission:Busy, desc="L2 replacing clean data, need to inv sharers and then drop data"; | |
| S_I, AccessPermission:Busy, desc="L2 replacing dirty data, collecting acks from L1s"; | |
| // Transient States for fetching data from memory | |
| ISS, AccessPermission:Busy, desc="L2 idle, got single L1_GETS, issued memory fetch, have not seen response yet"; | |
| IS, AccessPermission:Busy, desc="L2 idle, got L1_GET_INSTR or multiple L1_GETS, issued memory fetch, have not seen response yet"; | |
| IM, AccessPermission:Busy, desc="L2 idle, got L1_GETX, issued memory fetch, have not seen response(s) yet"; | |
| // Blocking states | |
| SS_MB, AccessPermission:Busy, desc="Blocked for L1_GETX from SS"; | |
| MT_MB, AccessPermission:Busy, desc="Blocked for L1_GETX from MT"; | |
| MT_IIB, AccessPermission:Busy, desc="Blocked for L1_GETS from MT, waiting for unblock and data"; | |
| MT_IB, AccessPermission:Busy, desc="Blocked for L1_GETS from MT, got unblock, waiting for data"; | |
| MT_SB, AccessPermission:Busy, desc="Blocked for L1_GETS from MT, got data, waiting for unblock"; | |
| } | |
| // EVENTS | |
| enumeration(Event, desc="L2 Cache events") { | |
| // L2 events | |
| // events initiated by the local L1s | |
| L1_GET_INSTR, desc="a L1I GET INSTR request for a block maped to us"; | |
| L1_GETS, desc="a L1D GETS request for a block maped to us"; | |
| L1_GETX, desc="a L1D GETX request for a block maped to us"; | |
| L1_UPGRADE, desc="a L1D GETX request for a block maped to us"; | |
| L1_PUTX, desc="L1 replacing data"; | |
| L1_PUTX_old, desc="L1 replacing data, but no longer sharer"; | |
| // events initiated by this L2 | |
| L2_Replacement, desc="L2 Replacement", format="!r"; | |
| L2_Replacement_clean, desc="L2 Replacement, but data is clean", format="!r"; | |
| // events from memory controller | |
| Mem_Data, desc="data from memory", format="!r"; | |
| Mem_Ack, desc="ack from memory", format="!r"; | |
| // M->S data writeback | |
| WB_Data, desc="data from L1"; | |
| WB_Data_clean, desc="clean data from L1"; | |
| Ack, desc="writeback ack"; | |
| Ack_all, desc="writeback ack"; | |
| Unblock, desc="Unblock from L1 requestor"; | |
| Exclusive_Unblock, desc="Unblock from L1 requestor"; | |
| MEM_Inv, desc="Invalidation from directory"; | |
| } | |
| // TYPES | |
| // CacheEntry | |
| structure(Entry, desc="...", interface="AbstractCacheEntry") { | |
| State CacheState, desc="cache state"; | |
| NetDest Sharers, desc="tracks the L1 shares on-chip"; | |
| MachineID Exclusive, desc="Exclusive holder of block"; | |
| DataBlock DataBlk, desc="data for the block"; | |
| bool Dirty, default="false", desc="data is dirty"; | |
| } | |
| // TBE fields | |
| structure(TBE, desc="...") { | |
| Addr addr, desc="Physical address for this TBE"; | |
| State TBEState, desc="Transient state"; | |
| DataBlock DataBlk, desc="Buffer for the data block"; | |
| bool Dirty, default="false", desc="Data is Dirty"; | |
| NetDest L1_GetS_IDs, desc="Set of the internal processors that want the block in shared state"; | |
| MachineID L1_GetX_ID, desc="ID of the L1 cache to forward the block to once we get a response"; | |
| int pendingAcks, desc="number of pending acks for invalidates during writeback"; | |
| } | |
| structure(TBETable, external="yes") { | |
| TBE lookup(Addr); | |
| void allocate(Addr); | |
| void deallocate(Addr); | |
| bool isPresent(Addr); | |
| } | |
| TBETable TBEs, template="<L2Cache_TBE>", constructor="m_number_of_TBEs"; | |
| Tick clockEdge(); | |
| Tick cyclesToTicks(Cycles c); | |
| Cycles ticksToCycles(Tick t); | |
| void set_cache_entry(AbstractCacheEntry a); | |
| void unset_cache_entry(); | |
| void set_tbe(TBE a); | |
| void unset_tbe(); | |
| void wakeUpBuffers(Addr a); | |
| void profileMsgDelay(int virtualNetworkType, Cycles c); | |
| MachineID mapAddressToMachine(Addr addr, MachineType mtype); | |
| // inclusive cache, returns L2 entries only | |
| Entry getCacheEntry(Addr addr), return_by_pointer="yes" { | |
| return static_cast(Entry, "pointer", L2cache[addr]); | |
| } | |
| bool isSharer(Addr addr, MachineID requestor, Entry cache_entry) { | |
| if (is_valid(cache_entry)) { | |
| return cache_entry.Sharers.isElement(requestor); | |
| } else { | |
| return false; | |
| } | |
| } | |
| void addSharer(Addr addr, MachineID requestor, Entry cache_entry) { | |
| assert(is_valid(cache_entry)); | |
| DPRINTF(RubySlicc, "machineID: %s, requestor: %s, address: %#x\n", | |
| machineID, requestor, addr); | |
| cache_entry.Sharers.add(requestor); | |
| } | |
| State getState(TBE tbe, Entry cache_entry, Addr addr) { | |
| if(is_valid(tbe)) { | |
| return tbe.TBEState; | |
| } else if (is_valid(cache_entry)) { | |
| return cache_entry.CacheState; | |
| } | |
| return State:NP; | |
| } | |
| void setState(TBE tbe, Entry cache_entry, Addr addr, State state) { | |
| // MUST CHANGE | |
| if (is_valid(tbe)) { | |
| tbe.TBEState := state; | |
| } | |
| if (is_valid(cache_entry)) { | |
| cache_entry.CacheState := state; | |
| } | |
| } | |
| AccessPermission getAccessPermission(Addr addr) { | |
| TBE tbe := TBEs[addr]; | |
| if(is_valid(tbe)) { | |
| DPRINTF(RubySlicc, "%s\n", L2Cache_State_to_permission(tbe.TBEState)); | |
| return L2Cache_State_to_permission(tbe.TBEState); | |
| } | |
| Entry cache_entry := getCacheEntry(addr); | |
| if(is_valid(cache_entry)) { | |
| DPRINTF(RubySlicc, "%s\n", L2Cache_State_to_permission(cache_entry.CacheState)); | |
| return L2Cache_State_to_permission(cache_entry.CacheState); | |
| } | |
| DPRINTF(RubySlicc, "%s\n", AccessPermission:NotPresent); | |
| return AccessPermission:NotPresent; | |
| } | |
| void functionalRead(Addr addr, Packet *pkt) { | |
| TBE tbe := TBEs[addr]; | |
| if(is_valid(tbe)) { | |
| testAndRead(addr, tbe.DataBlk, pkt); | |
| } else { | |
| testAndRead(addr, getCacheEntry(addr).DataBlk, pkt); | |
| } | |
| } | |
| int functionalWrite(Addr addr, Packet *pkt) { | |
| int num_functional_writes := 0; | |
| TBE tbe := TBEs[addr]; | |
| if(is_valid(tbe)) { | |
| num_functional_writes := num_functional_writes + | |
| testAndWrite(addr, tbe.DataBlk, pkt); | |
| return num_functional_writes; | |
| } | |
| num_functional_writes := num_functional_writes + | |
| testAndWrite(addr, getCacheEntry(addr).DataBlk, pkt); | |
| return num_functional_writes; | |
| } | |
| void setAccessPermission(Entry cache_entry, Addr addr, State state) { | |
| if (is_valid(cache_entry)) { | |
| cache_entry.changePermission(L2Cache_State_to_permission(state)); | |
| } | |
| } | |
| Event L1Cache_request_type_to_event(CoherenceRequestType type, Addr addr, | |
| MachineID requestor, Entry cache_entry) { | |
| if(type == CoherenceRequestType:GETS) { | |
| return Event:L1_GETS; | |
| } else if(type == CoherenceRequestType:GET_INSTR) { | |
| return Event:L1_GET_INSTR; | |
| } else if (type == CoherenceRequestType:GETX) { | |
| return Event:L1_GETX; | |
| } else if (type == CoherenceRequestType:UPGRADE) { | |
| if ( is_valid(cache_entry) && cache_entry.Sharers.isElement(requestor) ) { | |
| return Event:L1_UPGRADE; | |
| } else { | |
| return Event:L1_GETX; | |
| } | |
| } else if (type == CoherenceRequestType:PUTX) { | |
| if (isSharer(addr, requestor, cache_entry)) { | |
| return Event:L1_PUTX; | |
| } else { | |
| return Event:L1_PUTX_old; | |
| } | |
| } else { | |
| DPRINTF(RubySlicc, "address: %#x, Request Type: %s\n", addr, type); | |
| error("Invalid L1 forwarded request type"); | |
| } | |
| } | |
| int getPendingAcks(TBE tbe) { | |
| return tbe.pendingAcks; | |
| } | |
| bool isDirty(Entry cache_entry) { | |
| assert(is_valid(cache_entry)); | |
| return cache_entry.Dirty; | |
| } | |
| // ** OUT_PORTS ** | |
| out_port(L1RequestL2Network_out, RequestMsg, L1RequestFromL2Cache); | |
| out_port(DirRequestL2Network_out, RequestMsg, DirRequestFromL2Cache); | |
| out_port(responseL2Network_out, ResponseMsg, responseFromL2Cache); | |
| in_port(L1unblockNetwork_in, ResponseMsg, unblockToL2Cache, rank = 2) { | |
| if(L1unblockNetwork_in.isReady(clockEdge())) { | |
| peek(L1unblockNetwork_in, ResponseMsg) { | |
| Entry cache_entry := getCacheEntry(in_msg.addr); | |
| TBE tbe := TBEs[in_msg.addr]; | |
| DPRINTF(RubySlicc, "Addr: %#x State: %s Sender: %s Type: %s Dest: %s\n", | |
| in_msg.addr, getState(tbe, cache_entry, in_msg.addr), | |
| in_msg.Sender, in_msg.Type, in_msg.Destination); | |
| assert(in_msg.Destination.isElement(machineID)); | |
| if (in_msg.Type == CoherenceResponseType:EXCLUSIVE_UNBLOCK) { | |
| trigger(Event:Exclusive_Unblock, in_msg.addr, cache_entry, tbe); | |
| } else if (in_msg.Type == CoherenceResponseType:UNBLOCK) { | |
| trigger(Event:Unblock, in_msg.addr, cache_entry, tbe); | |
| } else { | |
| error("unknown unblock message"); | |
| } | |
| } | |
| } | |
| } | |
| // Response L2 Network - response msg to this particular L2 bank | |
| in_port(responseL2Network_in, ResponseMsg, responseToL2Cache, rank = 1) { | |
| if (responseL2Network_in.isReady(clockEdge())) { | |
| peek(responseL2Network_in, ResponseMsg) { | |
| // test wether it's from a local L1 or an off chip source | |
| assert(in_msg.Destination.isElement(machineID)); | |
| Entry cache_entry := getCacheEntry(in_msg.addr); | |
| TBE tbe := TBEs[in_msg.addr]; | |
| if(machineIDToMachineType(in_msg.Sender) == MachineType:L1Cache) { | |
| if(in_msg.Type == CoherenceResponseType:DATA) { | |
| if (in_msg.Dirty) { | |
| trigger(Event:WB_Data, in_msg.addr, cache_entry, tbe); | |
| } else { | |
| trigger(Event:WB_Data_clean, in_msg.addr, cache_entry, tbe); | |
| } | |
| } else if (in_msg.Type == CoherenceResponseType:ACK) { | |
| if ((getPendingAcks(tbe) - in_msg.AckCount) == 0) { | |
| trigger(Event:Ack_all, in_msg.addr, cache_entry, tbe); | |
| } else { | |
| trigger(Event:Ack, in_msg.addr, cache_entry, tbe); | |
| } | |
| } else { | |
| error("unknown message type"); | |
| } | |
| } else { // external message | |
| if(in_msg.Type == CoherenceResponseType:MEMORY_DATA) { | |
| trigger(Event:Mem_Data, in_msg.addr, cache_entry, tbe); | |
| } else if(in_msg.Type == CoherenceResponseType:MEMORY_ACK) { | |
| trigger(Event:Mem_Ack, in_msg.addr, cache_entry, tbe); | |
| } else if(in_msg.Type == CoherenceResponseType:INV) { | |
| trigger(Event:MEM_Inv, in_msg.addr, cache_entry, tbe); | |
| } else { | |
| error("unknown message type"); | |
| } | |
| } | |
| } | |
| } // if not ready, do nothing | |
| } | |
| // L1 Request | |
| in_port(L1RequestL2Network_in, RequestMsg, L1RequestToL2Cache, rank = 0) { | |
| if(L1RequestL2Network_in.isReady(clockEdge())) { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| Entry cache_entry := getCacheEntry(in_msg.addr); | |
| TBE tbe := TBEs[in_msg.addr]; | |
| DPRINTF(RubySlicc, "Addr: %#x State: %s Req: %s Type: %s Dest: %s\n", | |
| in_msg.addr, getState(tbe, cache_entry, in_msg.addr), | |
| in_msg.Requestor, in_msg.Type, in_msg.Destination); | |
| assert(machineIDToMachineType(in_msg.Requestor) == MachineType:L1Cache); | |
| assert(in_msg.Destination.isElement(machineID)); | |
| if (is_valid(cache_entry)) { | |
| // The L2 contains the block, so proceeded with handling the request | |
| trigger(L1Cache_request_type_to_event(in_msg.Type, in_msg.addr, | |
| in_msg.Requestor, cache_entry), | |
| in_msg.addr, cache_entry, tbe); | |
| } else { | |
| if (L2cache.cacheAvail(in_msg.addr)) { | |
| // L2 does't have the line, but we have space for it in the L2 | |
| trigger(L1Cache_request_type_to_event(in_msg.Type, in_msg.addr, | |
| in_msg.Requestor, cache_entry), | |
| in_msg.addr, cache_entry, tbe); | |
| } else { | |
| // No room in the L2, so we need to make room before handling the request | |
| Addr victim := L2cache.cacheProbe(in_msg.addr); | |
| Entry L2cache_entry := getCacheEntry(victim); | |
| if (isDirty(L2cache_entry)) { | |
| trigger(Event:L2_Replacement, victim, L2cache_entry, TBEs[victim]); | |
| } else { | |
| trigger(Event:L2_Replacement_clean, | |
| victim, L2cache_entry, TBEs[victim]); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // ACTIONS | |
| action(a_issueFetchToMemory, "a", desc="fetch data from memory") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| enqueue(DirRequestL2Network_out, RequestMsg, l2_request_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceRequestType:GETS; | |
| out_msg.Requestor := machineID; | |
| out_msg.Destination.add(mapAddressToMachine(address, MachineType:Directory)); | |
| out_msg.MessageSize := MessageSizeType:Control; | |
| out_msg.AccessMode := in_msg.AccessMode; | |
| out_msg.Prefetch := in_msg.Prefetch; | |
| } | |
| } | |
| } | |
| action(b_forwardRequestToExclusive, "b", desc="Forward request to the exclusive L1") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| enqueue(L1RequestL2Network_out, RequestMsg, to_l1_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := in_msg.Type; | |
| out_msg.Requestor := in_msg.Requestor; | |
| out_msg.Destination.add(cache_entry.Exclusive); | |
| out_msg.MessageSize := MessageSizeType:Request_Control; | |
| } | |
| } | |
| } | |
| action(c_exclusiveReplacement, "c", desc="Send data to memory") { | |
| enqueue(responseL2Network_out, ResponseMsg, l2_response_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:MEMORY_DATA; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(mapAddressToMachine(address, MachineType:Directory)); | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.Dirty := cache_entry.Dirty; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| } | |
| action(c_exclusiveCleanReplacement, "cc", desc="Send ack to memory for clean replacement") { | |
| enqueue(responseL2Network_out, ResponseMsg, l2_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:ACK; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(mapAddressToMachine(address, MachineType:Directory)); | |
| out_msg.MessageSize := MessageSizeType:Response_Control; | |
| } | |
| } | |
| action(ct_exclusiveReplacementFromTBE, "ct", desc="Send data to memory") { | |
| enqueue(responseL2Network_out, ResponseMsg, l2_response_latency) { | |
| assert(is_valid(tbe)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:MEMORY_DATA; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(mapAddressToMachine(address, MachineType:Directory)); | |
| out_msg.DataBlk := tbe.DataBlk; | |
| out_msg.Dirty := tbe.Dirty; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| } | |
| action(d_sendDataToRequestor, "d", desc="Send data from cache to reqeustor") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| enqueue(responseL2Network_out, ResponseMsg, l2_response_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| out_msg.AckCount := 0 - cache_entry.Sharers.count(); | |
| if (cache_entry.Sharers.isElement(in_msg.Requestor)) { | |
| out_msg.AckCount := out_msg.AckCount + 1; | |
| } | |
| } | |
| } | |
| } | |
| action(dd_sendExclusiveDataToRequestor, "dd", desc="Send data from cache to reqeustor") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| enqueue(responseL2Network_out, ResponseMsg, l2_response_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA_EXCLUSIVE; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| out_msg.AckCount := 0 - cache_entry.Sharers.count(); | |
| if (cache_entry.Sharers.isElement(in_msg.Requestor)) { | |
| out_msg.AckCount := out_msg.AckCount + 1; | |
| } | |
| } | |
| } | |
| } | |
| action(ds_sendSharedDataToRequestor, "ds", desc="Send data from cache to reqeustor") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| enqueue(responseL2Network_out, ResponseMsg, l2_response_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| out_msg.AckCount := 0; | |
| } | |
| } | |
| } | |
| action(e_sendDataToGetSRequestors, "e", desc="Send data from cache to all GetS IDs") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.L1_GetS_IDs.count() > 0); | |
| enqueue(responseL2Network_out, ResponseMsg, to_l1_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination := tbe.L1_GetS_IDs; // internal nodes | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| } | |
| action(ex_sendExclusiveDataToGetSRequestors, "ex", desc="Send data from cache to all GetS IDs") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.L1_GetS_IDs.count() == 1); | |
| enqueue(responseL2Network_out, ResponseMsg, to_l1_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA_EXCLUSIVE; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination := tbe.L1_GetS_IDs; // internal nodes | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| } | |
| action(ee_sendDataToGetXRequestor, "ee", desc="Send data from cache to GetX ID") { | |
| enqueue(responseL2Network_out, ResponseMsg, to_l1_latency) { | |
| assert(is_valid(tbe)); | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(tbe.L1_GetX_ID); | |
| DPRINTF(RubySlicc, "%s\n", out_msg.Destination); | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| DPRINTF(RubySlicc, "Address: %#x, Destination: %s, DataBlock: %s\n", | |
| out_msg.addr, out_msg.Destination, out_msg.DataBlk); | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| } | |
| action(f_sendInvToSharers, "f", desc="invalidate sharers for L2 replacement") { | |
| enqueue(L1RequestL2Network_out, RequestMsg, to_l1_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceRequestType:INV; | |
| out_msg.Requestor := machineID; | |
| out_msg.Destination := cache_entry.Sharers; | |
| out_msg.MessageSize := MessageSizeType:Request_Control; | |
| } | |
| } | |
| action(fw_sendFwdInvToSharers, "fw", desc="invalidate sharers for request") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| enqueue(L1RequestL2Network_out, RequestMsg, to_l1_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceRequestType:INV; | |
| out_msg.Requestor := in_msg.Requestor; | |
| out_msg.Destination := cache_entry.Sharers; | |
| out_msg.MessageSize := MessageSizeType:Request_Control; | |
| } | |
| } | |
| } | |
| action(fwm_sendFwdInvToSharersMinusRequestor, "fwm", desc="invalidate sharers for request, requestor is sharer") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| enqueue(L1RequestL2Network_out, RequestMsg, to_l1_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceRequestType:INV; | |
| out_msg.Requestor := in_msg.Requestor; | |
| out_msg.Destination := cache_entry.Sharers; | |
| out_msg.Destination.remove(in_msg.Requestor); | |
| out_msg.MessageSize := MessageSizeType:Request_Control; | |
| } | |
| } | |
| } | |
| // OTHER ACTIONS | |
| action(i_allocateTBE, "i", desc="Allocate TBE for request") { | |
| check_allocate(TBEs); | |
| assert(is_valid(cache_entry)); | |
| TBEs.allocate(address); | |
| set_tbe(TBEs[address]); | |
| tbe.L1_GetS_IDs.clear(); | |
| tbe.DataBlk := cache_entry.DataBlk; | |
| tbe.Dirty := cache_entry.Dirty; | |
| tbe.pendingAcks := cache_entry.Sharers.count(); | |
| } | |
| action(s_deallocateTBE, "s", desc="Deallocate external TBE") { | |
| TBEs.deallocate(address); | |
| unset_tbe(); | |
| } | |
| action(jj_popL1RequestQueue, "\j", desc="Pop incoming L1 request queue") { | |
| Tick delay := L1RequestL2Network_in.dequeue(clockEdge()); | |
| profileMsgDelay(0, ticksToCycles(delay)); | |
| } | |
| action(k_popUnblockQueue, "k", desc="Pop incoming unblock queue") { | |
| Tick delay := L1unblockNetwork_in.dequeue(clockEdge()); | |
| profileMsgDelay(0, ticksToCycles(delay)); | |
| } | |
| action(o_popIncomingResponseQueue, "o", desc="Pop Incoming Response queue") { | |
| Tick delay := responseL2Network_in.dequeue(clockEdge()); | |
| profileMsgDelay(1, ticksToCycles(delay)); | |
| } | |
| action(m_writeDataToCache, "m", desc="Write data from response queue to cache") { | |
| peek(responseL2Network_in, ResponseMsg) { | |
| assert(is_valid(cache_entry)); | |
| cache_entry.DataBlk := in_msg.DataBlk; | |
| if (in_msg.Dirty) { | |
| cache_entry.Dirty := in_msg.Dirty; | |
| } | |
| } | |
| } | |
| action(mr_writeDataToCacheFromRequest, "mr", desc="Write data from response queue to cache") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| assert(is_valid(cache_entry)); | |
| if (in_msg.Dirty) { | |
| cache_entry.DataBlk := in_msg.DataBlk; | |
| cache_entry.Dirty := in_msg.Dirty; | |
| } | |
| } | |
| } | |
| action(q_updateAck, "q", desc="update pending ack count") { | |
| peek(responseL2Network_in, ResponseMsg) { | |
| assert(is_valid(tbe)); | |
| tbe.pendingAcks := tbe.pendingAcks - in_msg.AckCount; | |
| APPEND_TRANSITION_COMMENT(in_msg.AckCount); | |
| APPEND_TRANSITION_COMMENT(" p: "); | |
| APPEND_TRANSITION_COMMENT(tbe.pendingAcks); | |
| } | |
| } | |
| action(qq_writeDataToTBE, "\qq", desc="Write data from response queue to TBE") { | |
| peek(responseL2Network_in, ResponseMsg) { | |
| assert(is_valid(tbe)); | |
| tbe.DataBlk := in_msg.DataBlk; | |
| tbe.Dirty := in_msg.Dirty; | |
| } | |
| } | |
| action(ss_recordGetSL1ID, "\s", desc="Record L1 GetS for load response") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| assert(is_valid(tbe)); | |
| tbe.L1_GetS_IDs.add(in_msg.Requestor); | |
| } | |
| } | |
| action(xx_recordGetXL1ID, "\x", desc="Record L1 GetX for store response") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| assert(is_valid(tbe)); | |
| tbe.L1_GetX_ID := in_msg.Requestor; | |
| } | |
| } | |
| action(set_setMRU, "\set", desc="set the MRU entry") { | |
| L2cache.setMRU(address); | |
| } | |
| action(qq_allocateL2CacheBlock, "\q", desc="Set L2 cache tag equal to tag of block B.") { | |
| if (is_invalid(cache_entry)) { | |
| set_cache_entry(L2cache.allocate(address, new Entry)); | |
| } | |
| } | |
| action(rr_deallocateL2CacheBlock, "\r", desc="Deallocate L2 cache block. Sets the cache to not present, allowing a replacement in parallel with a fetch.") { | |
| L2cache.deallocate(address); | |
| unset_cache_entry(); | |
| } | |
| action(t_sendWBAck, "t", desc="Send writeback ACK") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| enqueue(responseL2Network_out, ResponseMsg, to_l1_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:WB_ACK; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| out_msg.MessageSize := MessageSizeType:Response_Control; | |
| } | |
| } | |
| } | |
| action(ts_sendInvAckToUpgrader, "ts", desc="Send ACK to upgrader") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| enqueue(responseL2Network_out, ResponseMsg, to_l1_latency) { | |
| assert(is_valid(cache_entry)); | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:ACK; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| out_msg.MessageSize := MessageSizeType:Response_Control; | |
| // upgrader doesn't get ack from itself, hence the + 1 | |
| out_msg.AckCount := 0 - cache_entry.Sharers.count() + 1; | |
| } | |
| } | |
| } | |
| action(uu_profileMiss, "\um", desc="Profile the demand miss") { | |
| L2cache.profileDemandMiss(); | |
| } | |
| action(uu_profileHit, "\uh", desc="Profile the demand hit") { | |
| L2cache.profileDemandHit(); | |
| } | |
| action(nn_addSharer, "\n", desc="Add L1 sharer to list") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| assert(is_valid(cache_entry)); | |
| addSharer(address, in_msg.Requestor, cache_entry); | |
| APPEND_TRANSITION_COMMENT( cache_entry.Sharers ); | |
| } | |
| } | |
| action(nnu_addSharerFromUnblock, "\nu", desc="Add L1 sharer to list") { | |
| peek(L1unblockNetwork_in, ResponseMsg) { | |
| assert(is_valid(cache_entry)); | |
| addSharer(address, in_msg.Sender, cache_entry); | |
| } | |
| } | |
| action(kk_removeRequestSharer, "\k", desc="Remove L1 Request sharer from list") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| assert(is_valid(cache_entry)); | |
| cache_entry.Sharers.remove(in_msg.Requestor); | |
| } | |
| } | |
| action(ll_clearSharers, "\l", desc="Remove all L1 sharers from list") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| assert(is_valid(cache_entry)); | |
| cache_entry.Sharers.clear(); | |
| } | |
| } | |
| action(mm_markExclusive, "\m", desc="set the exclusive owner") { | |
| peek(L1RequestL2Network_in, RequestMsg) { | |
| assert(is_valid(cache_entry)); | |
| cache_entry.Sharers.clear(); | |
| cache_entry.Exclusive := in_msg.Requestor; | |
| addSharer(address, in_msg.Requestor, cache_entry); | |
| } | |
| } | |
| action(mmu_markExclusiveFromUnblock, "\mu", desc="set the exclusive owner") { | |
| peek(L1unblockNetwork_in, ResponseMsg) { | |
| assert(is_valid(cache_entry)); | |
| cache_entry.Sharers.clear(); | |
| cache_entry.Exclusive := in_msg.Sender; | |
| addSharer(address, in_msg.Sender, cache_entry); | |
| } | |
| } | |
| action(zz_stallAndWaitL1RequestQueue, "zz", desc="recycle L1 request queue") { | |
| stall_and_wait(L1RequestL2Network_in, address); | |
| } | |
| action(zn_recycleResponseNetwork, "zn", desc="recycle memory request") { | |
| responseL2Network_in.recycle(clockEdge(), cyclesToTicks(recycle_latency)); | |
| } | |
| action(kd_wakeUpDependents, "kd", desc="wake-up dependents") { | |
| wakeUpBuffers(address); | |
| } | |
| //***************************************************** | |
| // TRANSITIONS | |
| //***************************************************** | |
| //=============================================== | |
| // BASE STATE - I | |
| // Transitions from I (Idle) | |
| transition({NP, IS, ISS, IM, SS, M, M_I, I_I, S_I, MT_IB, MT_SB}, L1_PUTX) { | |
| t_sendWBAck; | |
| jj_popL1RequestQueue; | |
| } | |
| transition({NP, SS, M, MT, M_I, I_I, S_I, IS, ISS, IM, MT_IB, MT_SB}, L1_PUTX_old) { | |
| t_sendWBAck; | |
| jj_popL1RequestQueue; | |
| } | |
| transition({IM, IS, ISS, SS_MB, MT_MB, MT_IIB, MT_IB, MT_SB}, {L2_Replacement, L2_Replacement_clean}) { | |
| zz_stallAndWaitL1RequestQueue; | |
| } | |
| transition({IM, IS, ISS, SS_MB, MT_MB, MT_IIB, MT_IB, MT_SB}, MEM_Inv) { | |
| zn_recycleResponseNetwork; | |
| } | |
| transition({I_I, S_I, M_I, MT_I, MCT_I, NP}, MEM_Inv) { | |
| o_popIncomingResponseQueue; | |
| } | |
| transition({SS_MB, MT_MB, MT_IIB, MT_IB, MT_SB}, {L1_GETS, L1_GET_INSTR, L1_GETX, L1_UPGRADE}) { | |
| zz_stallAndWaitL1RequestQueue; | |
| } | |
| transition(NP, L1_GETS, ISS) { | |
| qq_allocateL2CacheBlock; | |
| ll_clearSharers; | |
| nn_addSharer; | |
| i_allocateTBE; | |
| ss_recordGetSL1ID; | |
| a_issueFetchToMemory; | |
| uu_profileMiss; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(NP, L1_GET_INSTR, IS) { | |
| qq_allocateL2CacheBlock; | |
| ll_clearSharers; | |
| nn_addSharer; | |
| i_allocateTBE; | |
| ss_recordGetSL1ID; | |
| a_issueFetchToMemory; | |
| uu_profileMiss; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(NP, L1_GETX, IM) { | |
| qq_allocateL2CacheBlock; | |
| ll_clearSharers; | |
| // nn_addSharer; | |
| i_allocateTBE; | |
| xx_recordGetXL1ID; | |
| a_issueFetchToMemory; | |
| uu_profileMiss; | |
| jj_popL1RequestQueue; | |
| } | |
| // transitions from IS/IM | |
| transition(ISS, Mem_Data, MT_MB) { | |
| m_writeDataToCache; | |
| ex_sendExclusiveDataToGetSRequestors; | |
| s_deallocateTBE; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition(IS, Mem_Data, SS) { | |
| m_writeDataToCache; | |
| e_sendDataToGetSRequestors; | |
| s_deallocateTBE; | |
| o_popIncomingResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(IM, Mem_Data, MT_MB) { | |
| m_writeDataToCache; | |
| ee_sendDataToGetXRequestor; | |
| s_deallocateTBE; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition({IS, ISS}, {L1_GETS, L1_GET_INSTR}, IS) { | |
| nn_addSharer; | |
| ss_recordGetSL1ID; | |
| uu_profileMiss; | |
| jj_popL1RequestQueue; | |
| } | |
| transition({IS, ISS}, L1_GETX) { | |
| zz_stallAndWaitL1RequestQueue; | |
| } | |
| transition(IM, {L1_GETX, L1_GETS, L1_GET_INSTR}) { | |
| zz_stallAndWaitL1RequestQueue; | |
| } | |
| // transitions from SS | |
| transition(SS, {L1_GETS, L1_GET_INSTR}) { | |
| ds_sendSharedDataToRequestor; | |
| nn_addSharer; | |
| set_setMRU; | |
| uu_profileHit; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(SS, L1_GETX, SS_MB) { | |
| d_sendDataToRequestor; | |
| // fw_sendFwdInvToSharers; | |
| fwm_sendFwdInvToSharersMinusRequestor; | |
| set_setMRU; | |
| uu_profileHit; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(SS, L1_UPGRADE, SS_MB) { | |
| fwm_sendFwdInvToSharersMinusRequestor; | |
| ts_sendInvAckToUpgrader; | |
| set_setMRU; | |
| uu_profileHit; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(SS, L2_Replacement_clean, I_I) { | |
| i_allocateTBE; | |
| f_sendInvToSharers; | |
| rr_deallocateL2CacheBlock; | |
| } | |
| transition(SS, {L2_Replacement, MEM_Inv}, S_I) { | |
| i_allocateTBE; | |
| f_sendInvToSharers; | |
| rr_deallocateL2CacheBlock; | |
| } | |
| transition(M, L1_GETX, MT_MB) { | |
| d_sendDataToRequestor; | |
| set_setMRU; | |
| uu_profileHit; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(M, L1_GET_INSTR, SS) { | |
| d_sendDataToRequestor; | |
| nn_addSharer; | |
| set_setMRU; | |
| uu_profileHit; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(M, L1_GETS, MT_MB) { | |
| dd_sendExclusiveDataToRequestor; | |
| set_setMRU; | |
| uu_profileHit; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(M, {L2_Replacement, MEM_Inv}, M_I) { | |
| i_allocateTBE; | |
| c_exclusiveReplacement; | |
| rr_deallocateL2CacheBlock; | |
| } | |
| transition(M, L2_Replacement_clean, M_I) { | |
| i_allocateTBE; | |
| c_exclusiveCleanReplacement; | |
| rr_deallocateL2CacheBlock; | |
| } | |
| // transitions from MT | |
| transition(MT, L1_GETX, MT_MB) { | |
| b_forwardRequestToExclusive; | |
| uu_profileMiss; | |
| set_setMRU; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(MT, {L1_GETS, L1_GET_INSTR}, MT_IIB) { | |
| b_forwardRequestToExclusive; | |
| uu_profileMiss; | |
| set_setMRU; | |
| jj_popL1RequestQueue; | |
| } | |
| transition(MT, {L2_Replacement, MEM_Inv}, MT_I) { | |
| i_allocateTBE; | |
| f_sendInvToSharers; | |
| rr_deallocateL2CacheBlock; | |
| } | |
| transition(MT, L2_Replacement_clean, MCT_I) { | |
| i_allocateTBE; | |
| f_sendInvToSharers; | |
| rr_deallocateL2CacheBlock; | |
| } | |
| transition(MT, L1_PUTX, M) { | |
| ll_clearSharers; | |
| mr_writeDataToCacheFromRequest; | |
| t_sendWBAck; | |
| jj_popL1RequestQueue; | |
| } | |
| transition({SS_MB,MT_MB}, Exclusive_Unblock, MT) { | |
| // update actual directory | |
| mmu_markExclusiveFromUnblock; | |
| k_popUnblockQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(MT_IIB, {L1_PUTX, L1_PUTX_old}){ | |
| zz_stallAndWaitL1RequestQueue; | |
| } | |
| transition(MT_IIB, Unblock, MT_IB) { | |
| nnu_addSharerFromUnblock; | |
| k_popUnblockQueue; | |
| } | |
| transition(MT_IIB, {WB_Data, WB_Data_clean}, MT_SB) { | |
| m_writeDataToCache; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition(MT_IB, {WB_Data, WB_Data_clean}, SS) { | |
| m_writeDataToCache; | |
| o_popIncomingResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(MT_SB, Unblock, SS) { | |
| nnu_addSharerFromUnblock; | |
| k_popUnblockQueue; | |
| kd_wakeUpDependents; | |
| } | |
| // writeback states | |
| transition({I_I, S_I, MT_I, MCT_I, M_I}, {L1_GETX, L1_UPGRADE, L1_GETS, L1_GET_INSTR}) { | |
| zz_stallAndWaitL1RequestQueue; | |
| } | |
| transition(I_I, Ack) { | |
| q_updateAck; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition(I_I, Ack_all, M_I) { | |
| c_exclusiveCleanReplacement; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition({MT_I, MCT_I}, WB_Data, M_I) { | |
| qq_writeDataToTBE; | |
| ct_exclusiveReplacementFromTBE; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition(MCT_I, {WB_Data_clean, Ack_all}, M_I) { | |
| c_exclusiveCleanReplacement; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition(MCT_I, {L1_PUTX, L1_PUTX_old}){ | |
| zz_stallAndWaitL1RequestQueue; | |
| } | |
| // L1 never changed Dirty data | |
| transition(MT_I, {WB_Data_clean, Ack_all}, M_I) { | |
| ct_exclusiveReplacementFromTBE; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition(MT_I, {L1_PUTX, L1_PUTX_old}){ | |
| zz_stallAndWaitL1RequestQueue; | |
| } | |
| // possible race between unblock and immediate replacement | |
| transition({MT_MB,SS_MB}, {L1_PUTX, L1_PUTX_old}) { | |
| zz_stallAndWaitL1RequestQueue; | |
| } | |
| transition(S_I, Ack) { | |
| q_updateAck; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition(S_I, Ack_all, M_I) { | |
| ct_exclusiveReplacementFromTBE; | |
| o_popIncomingResponseQueue; | |
| } | |
| transition(M_I, Mem_Ack, NP) { | |
| s_deallocateTBE; | |
| o_popIncomingResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| } |