| /* | |
| * 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. | |
| */ | |
| /* | |
| * $Id: MOESI_CMP_token-L1cache.sm 1.22 05/01/19 15:55:39-06:00 beckmann@s0-28.cs.wisc.edu $ | |
| * | |
| */ | |
| machine(MachineType:L1Cache, "Token protocol") | |
| : Sequencer * sequencer; | |
| CacheMemory * L1Icache; | |
| CacheMemory * L1Dcache; | |
| int l2_select_num_bits; | |
| int N_tokens; | |
| Cycles l1_request_latency := 2; | |
| Cycles l1_response_latency := 2; | |
| int retry_threshold := 1; | |
| Cycles fixed_timeout_latency := 100; | |
| Cycles reissue_wakeup_latency := 10; | |
| Cycles use_timeout_latency := 50; | |
| bool dynamic_timeout_enabled := "True"; | |
| bool no_mig_atomic := "True"; | |
| bool send_evictions; | |
| // Message Queues | |
| // From this node's L1 cache TO the network | |
| // a local L1 -> this L2 bank | |
| MessageBuffer * responseFromL1Cache, network="To", virtual_network="4", | |
| vnet_type="response"; | |
| MessageBuffer * persistentFromL1Cache, network="To", virtual_network="3", | |
| vnet_type="persistent"; | |
| // a local L1 -> this L2 bank, currently ordered with directory forwarded requests | |
| MessageBuffer * requestFromL1Cache, network="To", virtual_network="1", | |
| vnet_type="request"; | |
| // To this node's L1 cache FROM the network | |
| // a L2 bank -> this L1 | |
| MessageBuffer * responseToL1Cache, network="From", virtual_network="4", | |
| vnet_type="response"; | |
| MessageBuffer * persistentToL1Cache, network="From", virtual_network="3", | |
| vnet_type="persistent"; | |
| // a L2 bank -> this L1 | |
| MessageBuffer * requestToL1Cache, network="From", virtual_network="1", | |
| vnet_type="request"; | |
| MessageBuffer * mandatoryQueue; | |
| { | |
| // STATES | |
| state_declaration(State, desc="Cache states", default="L1Cache_State_I") { | |
| // Base states | |
| NP, AccessPermission:Invalid, "NP", desc="Not Present"; | |
| I, AccessPermission:Invalid, "I", desc="Idle"; | |
| S, AccessPermission:Read_Only, "S", desc="Shared"; | |
| O, AccessPermission:Read_Only, "O", desc="Owned"; | |
| M, AccessPermission:Read_Only, "M", desc="Modified (dirty)"; | |
| MM, AccessPermission:Read_Write, "MM", desc="Modified (dirty and locally modified)"; | |
| M_W, AccessPermission:Read_Only, "M^W", desc="Modified (dirty), waiting"; | |
| MM_W, AccessPermission:Read_Write, "MM^W", desc="Modified (dirty and locally modified), waiting"; | |
| // Transient States | |
| IM, AccessPermission:Busy, "IM", desc="Issued GetX"; | |
| SM, AccessPermission:Read_Only, "SM", desc="Issued GetX, we still have an old copy of the line"; | |
| OM, AccessPermission:Read_Only, "OM", desc="Issued GetX, received data"; | |
| IS, AccessPermission:Busy, "IS", desc="Issued GetS"; | |
| // Locked states | |
| I_L, AccessPermission:Busy, "I^L", desc="Invalid, Locked"; | |
| S_L, AccessPermission:Busy, "S^L", desc="Shared, Locked"; | |
| IM_L, AccessPermission:Busy, "IM^L", desc="Invalid, Locked, trying to go to Modified"; | |
| SM_L, AccessPermission:Busy, "SM^L", desc="Shared, Locked, trying to go to Modified"; | |
| IS_L, AccessPermission:Busy, "IS^L", desc="Invalid, Locked, trying to go to Shared"; | |
| } | |
| // EVENTS | |
| enumeration(Event, desc="Cache events") { | |
| Load, desc="Load request from the processor"; | |
| Ifetch, desc="I-fetch request from the processor"; | |
| Store, desc="Store request from the processor"; | |
| Atomic, desc="Atomic request from the processor"; | |
| L1_Replacement, desc="L1 Replacement"; | |
| // Responses | |
| Data_Shared, desc="Received a data message, we are now a sharer"; | |
| Data_Owner, desc="Received a data message, we are now the owner"; | |
| Data_All_Tokens, desc="Received a data message, we are now the owner, we now have all the tokens"; | |
| Ack, desc="Received an ack message"; | |
| Ack_All_Tokens, desc="Received an ack message, we now have all the tokens"; | |
| // Requests | |
| Transient_GETX, desc="A GetX from another processor"; | |
| Transient_Local_GETX, desc="A GetX from another processor"; | |
| Transient_GETS, desc="A GetS from another processor"; | |
| Transient_Local_GETS, desc="A GetS from another processor"; | |
| Transient_GETS_Last_Token, desc="A GetS from another processor"; | |
| Transient_Local_GETS_Last_Token, desc="A GetS from another processor"; | |
| // Lock/Unlock for distributed | |
| Persistent_GETX, desc="Another processor has priority to read/write"; | |
| Persistent_GETS, desc="Another processor has priority to read"; | |
| Persistent_GETS_Last_Token, desc="Another processor has priority to read, no more tokens"; | |
| Own_Lock_or_Unlock, desc="This processor now has priority"; | |
| // Triggers | |
| Request_Timeout, desc="Timeout"; | |
| Use_TimeoutStarverX, desc="Timeout"; | |
| Use_TimeoutStarverS, desc="Timeout"; | |
| Use_TimeoutNoStarvers, desc="Timeout"; | |
| Use_TimeoutNoStarvers_NoMig, desc="Timeout Don't Migrate"; | |
| } | |
| // TYPES | |
| // CacheEntry | |
| structure(Entry, desc="...", interface="AbstractCacheEntry") { | |
| State CacheState, desc="cache state"; | |
| bool Dirty, desc="Is the data dirty (different than memory)?"; | |
| int Tokens, desc="The number of tokens we're holding for the line"; | |
| DataBlock DataBlk, desc="data for the block"; | |
| } | |
| // TBE fields | |
| structure(TBE, desc="...") { | |
| Addr addr, desc="Physical address for this TBE"; | |
| State TBEState, desc="Transient state"; | |
| int IssueCount, default="0", desc="The number of times we've issued a request for this line."; | |
| Addr PC, desc="Program counter of request"; | |
| bool WentPersistent, default="false", desc="Request went persistent"; | |
| bool ExternalResponse, default="false", desc="Response came from an external controller"; | |
| bool IsAtomic, default="false", desc="Request was an atomic request"; | |
| AccessType TypeOfAccess, desc="Type of request (used for profiling)"; | |
| Cycles IssueTime, desc="Time the request was issued"; | |
| RubyAccessMode AccessMode, desc="user/supervisor access type"; | |
| PrefetchBit Prefetch, desc="Is this a prefetch request"; | |
| } | |
| structure(TBETable, external="yes") { | |
| TBE lookup(Addr); | |
| void allocate(Addr); | |
| void deallocate(Addr); | |
| bool isPresent(Addr); | |
| } | |
| structure(PersistentTable, external="yes") { | |
| void persistentRequestLock(Addr, MachineID, AccessType); | |
| void persistentRequestUnlock(Addr, MachineID); | |
| bool okToIssueStarving(Addr, MachineID); | |
| MachineID findSmallest(Addr); | |
| AccessType typeOfSmallest(Addr); | |
| void markEntries(Addr); | |
| bool isLocked(Addr); | |
| int countStarvingForAddress(Addr); | |
| int countReadStarvingForAddress(Addr); | |
| } | |
| Tick clockEdge(); | |
| Tick cyclesToTicks(Cycles c); | |
| void set_cache_entry(AbstractCacheEntry b); | |
| void unset_cache_entry(); | |
| void set_tbe(TBE b); | |
| void unset_tbe(); | |
| void wakeUpAllBuffers(); | |
| void wakeUpBuffers(Addr a); | |
| Cycles curCycle(); | |
| MachineID mapAddressToMachine(Addr addr, MachineType mtype); | |
| TBETable L1_TBEs, template="<L1Cache_TBE>", constructor="m_number_of_TBEs"; | |
| bool starving, default="false"; | |
| int l2_select_low_bit, default="RubySystem::getBlockSizeBits()"; | |
| PersistentTable persistentTable; | |
| TimerTable useTimerTable; | |
| TimerTable reissueTimerTable; | |
| int outstandingRequests, default="0"; | |
| int outstandingPersistentRequests, default="0"; | |
| // Constant that provides hysteresis for calculated the estimated average | |
| int averageLatencyHysteresis, default="(8)"; | |
| Cycles averageLatencyCounter, | |
| default="(Cycles(500) << (*m_averageLatencyHysteresis_ptr))"; | |
| Cycles averageLatencyEstimate() { | |
| DPRINTF(RubySlicc, "%d\n", | |
| (averageLatencyCounter >> averageLatencyHysteresis)); | |
| return averageLatencyCounter >> averageLatencyHysteresis; | |
| } | |
| void updateAverageLatencyEstimate(Cycles latency) { | |
| DPRINTF(RubySlicc, "%d\n", latency); | |
| // By subtracting the current average and then adding the most | |
| // recent sample, we calculate an estimate of the recent average. | |
| // If we simply used a running sum and divided by the total number | |
| // of entries, the estimate of the average would adapt very slowly | |
| // after the execution has run for a long time. | |
| // averageLatencyCounter := averageLatencyCounter - averageLatencyEstimate() + latency; | |
| averageLatencyCounter := averageLatencyCounter - averageLatencyEstimate() + latency; | |
| } | |
| Entry getCacheEntry(Addr addr), return_by_pointer="yes" { | |
| Entry L1Dcache_entry := static_cast(Entry, "pointer", L1Dcache.lookup(addr)); | |
| if(is_valid(L1Dcache_entry)) { | |
| return L1Dcache_entry; | |
| } | |
| Entry L1Icache_entry := static_cast(Entry, "pointer", L1Icache.lookup(addr)); | |
| return L1Icache_entry; | |
| } | |
| void functionalRead(Addr addr, Packet *pkt) { | |
| testAndRead(addr, getCacheEntry(addr).DataBlk, pkt); | |
| } | |
| int functionalWrite(Addr addr, Packet *pkt) { | |
| int num_functional_writes := 0; | |
| num_functional_writes := num_functional_writes + | |
| testAndWrite(addr, getCacheEntry(addr).DataBlk, pkt); | |
| return num_functional_writes; | |
| } | |
| Entry getL1DCacheEntry(Addr addr), return_by_pointer="yes" { | |
| Entry L1Dcache_entry := static_cast(Entry, "pointer", L1Dcache.lookup(addr)); | |
| return L1Dcache_entry; | |
| } | |
| Entry getL1ICacheEntry(Addr addr), return_by_pointer="yes" { | |
| Entry L1Icache_entry := static_cast(Entry, "pointer", L1Icache.lookup(addr)); | |
| return L1Icache_entry; | |
| } | |
| int getTokens(Entry cache_entry) { | |
| if (is_valid(cache_entry)) { | |
| return cache_entry.Tokens; | |
| } | |
| return 0; | |
| } | |
| 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; | |
| } else { | |
| if (persistentTable.isLocked(addr) && (persistentTable.findSmallest(addr) != machineID)) { | |
| // Not in cache, in persistent table, but this processor isn't highest priority | |
| return State:I_L; | |
| } else { | |
| return State:NP; | |
| } | |
| } | |
| } | |
| void setState(TBE tbe, Entry cache_entry, Addr addr, State state) { | |
| assert((L1Dcache.isTagPresent(addr) && L1Icache.isTagPresent(addr)) == false); | |
| if (is_valid(tbe)) { | |
| assert(state != State:I); | |
| assert(state != State:S); | |
| assert(state != State:O); | |
| assert(state != State:MM); | |
| assert(state != State:M); | |
| tbe.TBEState := state; | |
| } | |
| if (is_valid(cache_entry)) { | |
| // Make sure the token count is in range | |
| assert(cache_entry.Tokens >= 0); | |
| assert(cache_entry.Tokens <= max_tokens()); | |
| assert(cache_entry.Tokens != (max_tokens() / 2)); | |
| if ((state == State:I_L) || | |
| (state == State:IM_L) || | |
| (state == State:IS_L)) { | |
| // Make sure we have no tokens in the "Invalid, locked" states | |
| assert(cache_entry.Tokens == 0); | |
| // Make sure the line is locked | |
| // assert(persistentTable.isLocked(addr)); | |
| // But we shouldn't have highest priority for it | |
| // assert(persistentTable.findSmallest(addr) != id); | |
| } else if ((state == State:S_L) || | |
| (state == State:SM_L)) { | |
| assert(cache_entry.Tokens >= 1); | |
| assert(cache_entry.Tokens < (max_tokens() / 2)); | |
| // Make sure the line is locked... | |
| // assert(persistentTable.isLocked(addr)); | |
| // ...But we shouldn't have highest priority for it... | |
| // assert(persistentTable.findSmallest(addr) != id); | |
| // ...And it must be a GETS request | |
| // assert(persistentTable.typeOfSmallest(addr) == AccessType:Read); | |
| } else { | |
| // If there is an entry in the persistent table of this block, | |
| // this processor needs to have an entry in the table for this | |
| // block, and that entry better be the smallest (highest | |
| // priority). Otherwise, the state should have been one of | |
| // locked states | |
| //if (persistentTable.isLocked(addr)) { | |
| // assert(persistentTable.findSmallest(addr) == id); | |
| //} | |
| } | |
| // in M and E you have all the tokens | |
| if (state == State:MM || state == State:M || state == State:MM_W || state == State:M_W) { | |
| assert(cache_entry.Tokens == max_tokens()); | |
| } | |
| // in NP you have no tokens | |
| if (state == State:NP) { | |
| assert(cache_entry.Tokens == 0); | |
| } | |
| // You have at least one token in S-like states | |
| if (state == State:S || state == State:SM) { | |
| assert(cache_entry.Tokens > 0); | |
| } | |
| // You have at least half the token in O-like states | |
| if (state == State:O && state == State:OM) { | |
| assert(cache_entry.Tokens > (max_tokens() / 2)); | |
| } | |
| cache_entry.CacheState := state; | |
| } | |
| } | |
| AccessPermission getAccessPermission(Addr addr) { | |
| TBE tbe := L1_TBEs[addr]; | |
| if(is_valid(tbe)) { | |
| return L1Cache_State_to_permission(tbe.TBEState); | |
| } | |
| Entry cache_entry := getCacheEntry(addr); | |
| if(is_valid(cache_entry)) { | |
| return L1Cache_State_to_permission(cache_entry.CacheState); | |
| } | |
| return AccessPermission:NotPresent; | |
| } | |
| void setAccessPermission(Entry cache_entry, Addr addr, State state) { | |
| if (is_valid(cache_entry)) { | |
| cache_entry.changePermission(L1Cache_State_to_permission(state)); | |
| } | |
| } | |
| Event mandatory_request_type_to_event(RubyRequestType type) { | |
| if (type == RubyRequestType:LD) { | |
| return Event:Load; | |
| } else if (type == RubyRequestType:IFETCH) { | |
| return Event:Ifetch; | |
| } else if (type == RubyRequestType:ST) { | |
| return Event:Store; | |
| } else if (type == RubyRequestType:ATOMIC) { | |
| if (no_mig_atomic) { | |
| return Event:Atomic; | |
| } else { | |
| return Event:Store; | |
| } | |
| } else { | |
| error("Invalid RubyRequestType"); | |
| } | |
| } | |
| AccessType cache_request_type_to_access_type(RubyRequestType type) { | |
| if ((type == RubyRequestType:LD) || (type == RubyRequestType:IFETCH)) { | |
| return AccessType:Read; | |
| } else if ((type == RubyRequestType:ST) || (type == RubyRequestType:ATOMIC)) { | |
| return AccessType:Write; | |
| } else { | |
| error("Invalid RubyRequestType"); | |
| } | |
| } | |
| // NOTE: direct local hits should not call this function | |
| bool isExternalHit(Addr addr, MachineID sender) { | |
| if (machineIDToMachineType(sender) == MachineType:L1Cache) { | |
| return true; | |
| } else if (machineIDToMachineType(sender) == MachineType:L2Cache) { | |
| if (sender == mapAddressToRange(addr, MachineType:L2Cache, | |
| l2_select_low_bit, l2_select_num_bits, intToID(0))) { | |
| return false; | |
| } else { | |
| return true; | |
| } | |
| } | |
| return true; | |
| } | |
| bool okToIssueStarving(Addr addr, MachineID machineID) { | |
| return persistentTable.okToIssueStarving(addr, machineID); | |
| } | |
| void markPersistentEntries(Addr addr) { | |
| persistentTable.markEntries(addr); | |
| } | |
| void setExternalResponse(TBE tbe) { | |
| assert(is_valid(tbe)); | |
| tbe.ExternalResponse := true; | |
| } | |
| bool IsAtomic(TBE tbe) { | |
| assert(is_valid(tbe)); | |
| return tbe.IsAtomic; | |
| } | |
| // ** OUT_PORTS ** | |
| out_port(persistentNetwork_out, PersistentMsg, persistentFromL1Cache); | |
| out_port(requestNetwork_out, RequestMsg, requestFromL1Cache); | |
| out_port(responseNetwork_out, ResponseMsg, responseFromL1Cache); | |
| out_port(requestRecycle_out, RequestMsg, requestToL1Cache); | |
| // ** IN_PORTS ** | |
| // Use Timer | |
| in_port(useTimerTable_in, Addr, useTimerTable, rank=5) { | |
| if (useTimerTable_in.isReady(clockEdge())) { | |
| Addr readyAddress := useTimerTable.nextAddress(); | |
| TBE tbe := L1_TBEs.lookup(readyAddress); | |
| if (persistentTable.isLocked(readyAddress) && | |
| (persistentTable.findSmallest(readyAddress) != machineID)) { | |
| if (persistentTable.typeOfSmallest(readyAddress) == AccessType:Write) { | |
| trigger(Event:Use_TimeoutStarverX, readyAddress, | |
| getCacheEntry(readyAddress), tbe); | |
| } else { | |
| trigger(Event:Use_TimeoutStarverS, readyAddress, | |
| getCacheEntry(readyAddress), tbe); | |
| } | |
| } else { | |
| if (no_mig_atomic && IsAtomic(tbe)) { | |
| trigger(Event:Use_TimeoutNoStarvers_NoMig, readyAddress, | |
| getCacheEntry(readyAddress), tbe); | |
| } else { | |
| trigger(Event:Use_TimeoutNoStarvers, readyAddress, | |
| getCacheEntry(readyAddress), tbe); | |
| } | |
| } | |
| } | |
| } | |
| // Reissue Timer | |
| in_port(reissueTimerTable_in, Addr, reissueTimerTable, rank=4) { | |
| Tick current_time := clockEdge(); | |
| if (reissueTimerTable_in.isReady(current_time)) { | |
| Addr addr := reissueTimerTable.nextAddress(); | |
| trigger(Event:Request_Timeout, addr, getCacheEntry(addr), | |
| L1_TBEs.lookup(addr)); | |
| } | |
| } | |
| // Persistent Network | |
| in_port(persistentNetwork_in, PersistentMsg, persistentToL1Cache, rank=3) { | |
| if (persistentNetwork_in.isReady(clockEdge())) { | |
| peek(persistentNetwork_in, PersistentMsg, block_on="addr") { | |
| assert(in_msg.Destination.isElement(machineID)); | |
| // Apply the lockdown or unlockdown message to the table | |
| if (in_msg.Type == PersistentRequestType:GETX_PERSISTENT) { | |
| persistentTable.persistentRequestLock(in_msg.addr, in_msg.Requestor, AccessType:Write); | |
| } else if (in_msg.Type == PersistentRequestType:GETS_PERSISTENT) { | |
| persistentTable.persistentRequestLock(in_msg.addr, in_msg.Requestor, AccessType:Read); | |
| } else if (in_msg.Type == PersistentRequestType:DEACTIVATE_PERSISTENT) { | |
| persistentTable.persistentRequestUnlock(in_msg.addr, in_msg.Requestor); | |
| } else { | |
| error("Unexpected message"); | |
| } | |
| // React to the message based on the current state of the table | |
| Entry cache_entry := getCacheEntry(in_msg.addr); | |
| TBE tbe := L1_TBEs[in_msg.addr]; | |
| if (persistentTable.isLocked(in_msg.addr)) { | |
| if (persistentTable.findSmallest(in_msg.addr) == machineID) { | |
| // Our Own Lock - this processor is highest priority | |
| trigger(Event:Own_Lock_or_Unlock, in_msg.addr, | |
| cache_entry, tbe); | |
| } else { | |
| if (persistentTable.typeOfSmallest(in_msg.addr) == AccessType:Read) { | |
| if (getTokens(cache_entry) == 1 || | |
| getTokens(cache_entry) == (max_tokens() / 2) + 1) { | |
| trigger(Event:Persistent_GETS_Last_Token, in_msg.addr, | |
| cache_entry, tbe); | |
| } else { | |
| trigger(Event:Persistent_GETS, in_msg.addr, | |
| cache_entry, tbe); | |
| } | |
| } else { | |
| trigger(Event:Persistent_GETX, in_msg.addr, | |
| cache_entry, tbe); | |
| } | |
| } | |
| } else { | |
| // Unlock case - no entries in the table | |
| trigger(Event:Own_Lock_or_Unlock, in_msg.addr, | |
| cache_entry, tbe); | |
| } | |
| } | |
| } | |
| } | |
| // Response Network | |
| in_port(responseNetwork_in, ResponseMsg, responseToL1Cache, rank=2) { | |
| if (responseNetwork_in.isReady(clockEdge())) { | |
| peek(responseNetwork_in, ResponseMsg, block_on="addr") { | |
| assert(in_msg.Destination.isElement(machineID)); | |
| Entry cache_entry := getCacheEntry(in_msg.addr); | |
| TBE tbe := L1_TBEs[in_msg.addr]; | |
| // Mark TBE flag if response received off-chip. Use this to update average latency estimate | |
| if ( machineIDToMachineType(in_msg.Sender) == MachineType:L2Cache ) { | |
| if (in_msg.Sender == mapAddressToRange(in_msg.addr, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))) { | |
| // came from an off-chip L2 cache | |
| if (is_valid(tbe)) { | |
| // L1_TBEs[in_msg.addr].ExternalResponse := true; | |
| // profile_offchipL2_response(in_msg.addr); | |
| } | |
| } | |
| else { | |
| // profile_onchipL2_response(in_msg.addr ); | |
| } | |
| } else if ( machineIDToMachineType(in_msg.Sender) == MachineType:Directory ) { | |
| if (is_valid(tbe)) { | |
| setExternalResponse(tbe); | |
| // profile_memory_response( in_msg.addr); | |
| } | |
| } else if ( machineIDToMachineType(in_msg.Sender) == MachineType:L1Cache) { | |
| //if (isLocalProcessor(machineID, in_msg.Sender) == false) { | |
| //if (is_valid(tbe)) { | |
| // tbe.ExternalResponse := true; | |
| // profile_offchipL1_response(in_msg.addr ); | |
| //} | |
| //} | |
| //else { | |
| // profile_onchipL1_response(in_msg.addr ); | |
| //} | |
| } else { | |
| error("unexpected SenderMachine"); | |
| } | |
| if (getTokens(cache_entry) + in_msg.Tokens != max_tokens()) { | |
| if (in_msg.Type == CoherenceResponseType:ACK) { | |
| assert(in_msg.Tokens < (max_tokens() / 2)); | |
| trigger(Event:Ack, in_msg.addr, cache_entry, tbe); | |
| } else if (in_msg.Type == CoherenceResponseType:DATA_OWNER) { | |
| trigger(Event:Data_Owner, in_msg.addr, cache_entry, tbe); | |
| } else if (in_msg.Type == CoherenceResponseType:DATA_SHARED) { | |
| assert(in_msg.Tokens < (max_tokens() / 2)); | |
| trigger(Event:Data_Shared, in_msg.addr, cache_entry, tbe); | |
| } else { | |
| error("Unexpected message"); | |
| } | |
| } else { | |
| if (in_msg.Type == CoherenceResponseType:ACK) { | |
| assert(in_msg.Tokens < (max_tokens() / 2)); | |
| trigger(Event:Ack_All_Tokens, in_msg.addr, cache_entry, tbe); | |
| } else if (in_msg.Type == CoherenceResponseType:DATA_OWNER || in_msg.Type == CoherenceResponseType:DATA_SHARED) { | |
| trigger(Event:Data_All_Tokens, in_msg.addr, cache_entry, tbe); | |
| } else { | |
| error("Unexpected message"); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Request Network | |
| in_port(requestNetwork_in, RequestMsg, requestToL1Cache) { | |
| if (requestNetwork_in.isReady(clockEdge())) { | |
| peek(requestNetwork_in, RequestMsg, block_on="addr") { | |
| assert(in_msg.Destination.isElement(machineID)); | |
| Entry cache_entry := getCacheEntry(in_msg.addr); | |
| TBE tbe := L1_TBEs[in_msg.addr]; | |
| if (in_msg.Type == CoherenceRequestType:GETX) { | |
| if (in_msg.isLocal) { | |
| trigger(Event:Transient_Local_GETX, in_msg.addr, | |
| cache_entry, tbe); | |
| } | |
| else { | |
| trigger(Event:Transient_GETX, in_msg.addr, | |
| cache_entry, tbe); | |
| } | |
| } else if (in_msg.Type == CoherenceRequestType:GETS) { | |
| if (getTokens(cache_entry) == 1 || | |
| getTokens(cache_entry) == (max_tokens() / 2) + 1) { | |
| if (in_msg.isLocal) { | |
| trigger(Event:Transient_Local_GETS_Last_Token, in_msg.addr, | |
| cache_entry, tbe); | |
| } | |
| else { | |
| trigger(Event:Transient_GETS_Last_Token, in_msg.addr, | |
| cache_entry, tbe); | |
| } | |
| } | |
| else { | |
| if (in_msg.isLocal) { | |
| trigger(Event:Transient_Local_GETS, in_msg.addr, | |
| cache_entry, tbe); | |
| } | |
| else { | |
| trigger(Event:Transient_GETS, in_msg.addr, | |
| cache_entry, tbe); | |
| } | |
| } | |
| } else { | |
| error("Unexpected message"); | |
| } | |
| } | |
| } | |
| } | |
| // Mandatory Queue | |
| in_port(mandatoryQueue_in, RubyRequest, mandatoryQueue, desc="...", rank=0) { | |
| if (mandatoryQueue_in.isReady(clockEdge())) { | |
| peek(mandatoryQueue_in, RubyRequest, block_on="LineAddress") { | |
| // Check for data access to blocks in I-cache and ifetchs to blocks in D-cache | |
| TBE tbe := L1_TBEs[in_msg.LineAddress]; | |
| if (in_msg.Type == RubyRequestType:IFETCH) { | |
| // ** INSTRUCTION ACCESS *** | |
| Entry L1Icache_entry := getL1ICacheEntry(in_msg.LineAddress); | |
| if (is_valid(L1Icache_entry)) { | |
| // The tag matches for the L1, so the L1 fetches the line. | |
| // We know it can't be in the L2 due to exclusion. | |
| trigger(mandatory_request_type_to_event(in_msg.Type), | |
| in_msg.LineAddress, L1Icache_entry, tbe); | |
| } else { | |
| // Check to see if it is in the OTHER L1 | |
| Entry L1Dcache_entry := getL1DCacheEntry(in_msg.LineAddress); | |
| if (is_valid(L1Dcache_entry)) { | |
| // The block is in the wrong L1, try to write it to the L2 | |
| trigger(Event:L1_Replacement, in_msg.LineAddress, | |
| L1Dcache_entry, tbe); | |
| } | |
| if (L1Icache.cacheAvail(in_msg.LineAddress)) { | |
| // L1 does't have the line, but we have space for it in the L1 | |
| trigger(mandatory_request_type_to_event(in_msg.Type), | |
| in_msg.LineAddress, L1Icache_entry, tbe); | |
| } else { | |
| // No room in the L1, so we need to make room | |
| Addr victim := L1Icache.cacheProbe(in_msg.LineAddress); | |
| trigger(Event:L1_Replacement, | |
| victim, getL1ICacheEntry(victim), L1_TBEs[victim]); | |
| } | |
| } | |
| } else { | |
| // *** DATA ACCESS *** | |
| Entry L1Dcache_entry := getL1DCacheEntry(in_msg.LineAddress); | |
| if (is_valid(L1Dcache_entry)) { | |
| // The tag matches for the L1, so the L1 fetches the line. | |
| // We know it can't be in the L2 due to exclusion. | |
| trigger(mandatory_request_type_to_event(in_msg.Type), | |
| in_msg.LineAddress, L1Dcache_entry, tbe); | |
| } else { | |
| // Check to see if it is in the OTHER L1 | |
| Entry L1Icache_entry := getL1ICacheEntry(in_msg.LineAddress); | |
| if (is_valid(L1Icache_entry)) { | |
| // The block is in the wrong L1, try to write it to the L2 | |
| trigger(Event:L1_Replacement, in_msg.LineAddress, | |
| L1Icache_entry, tbe); | |
| } | |
| if (L1Dcache.cacheAvail(in_msg.LineAddress)) { | |
| // L1 does't have the line, but we have space for it in the L1 | |
| trigger(mandatory_request_type_to_event(in_msg.Type), | |
| in_msg.LineAddress, L1Dcache_entry, tbe); | |
| } else { | |
| // No room in the L1, so we need to make room | |
| Addr victim := L1Dcache.cacheProbe(in_msg.LineAddress); | |
| trigger(Event:L1_Replacement, | |
| victim, getL1DCacheEntry(victim), L1_TBEs[victim]); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // ACTIONS | |
| action(a_issueReadRequest, "a", desc="Issue GETS") { | |
| assert(is_valid(tbe)); | |
| if (tbe.IssueCount == 0) { | |
| // Update outstanding requests | |
| //profile_outstanding_request(outstandingRequests); | |
| outstandingRequests := outstandingRequests + 1; | |
| } | |
| if (tbe.IssueCount >= retry_threshold) { | |
| // Issue a persistent request if possible | |
| if (okToIssueStarving(address, machineID) && (starving == false)) { | |
| enqueue(persistentNetwork_out, PersistentMsg, l1_request_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := PersistentRequestType:GETS_PERSISTENT; | |
| out_msg.Requestor := machineID; | |
| out_msg.Destination.broadcast(MachineType:L1Cache); | |
| // | |
| // Currently the configuration system limits the system to only one | |
| // chip. Therefore, if we assume one shared L2 cache, then only one | |
| // pertinent L2 cache exist. | |
| // | |
| //out_msg.Destination.addNetDest(getAllPertinentL2Banks(address)); | |
| out_msg.Destination.add(mapAddressToRange(address, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))); | |
| out_msg.Destination.add(mapAddressToMachine(address, MachineType:Directory)); | |
| out_msg.MessageSize := MessageSizeType:Persistent_Control; | |
| out_msg.Prefetch := tbe.Prefetch; | |
| out_msg.AccessMode := tbe.AccessMode; | |
| } | |
| markPersistentEntries(address); | |
| starving := true; | |
| if (tbe.IssueCount == 0) { | |
| //profile_persistent_prediction(address, tbe.TypeOfAccess); | |
| } | |
| // Update outstanding requests | |
| //profile_outstanding_persistent_request(outstandingPersistentRequests); | |
| outstandingPersistentRequests := outstandingPersistentRequests + 1; | |
| // Increment IssueCount | |
| tbe.IssueCount := tbe.IssueCount + 1; | |
| tbe.WentPersistent := true; | |
| // Do not schedule a wakeup, a persistent requests will always complete | |
| } | |
| else { | |
| // We'd like to issue a persistent request, but are not allowed | |
| // to issue a P.R. right now. This, we do not increment the | |
| // IssueCount. | |
| // Set a wakeup timer | |
| reissueTimerTable.set( | |
| address, clockEdge() + cyclesToTicks(reissue_wakeup_latency)); | |
| } | |
| } else { | |
| // Make a normal request | |
| enqueue(requestNetwork_out, RequestMsg, l1_request_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceRequestType:GETS; | |
| out_msg.Requestor := machineID; | |
| out_msg.Destination.add(mapAddressToRange(address, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))); | |
| out_msg.RetryNum := tbe.IssueCount; | |
| if (tbe.IssueCount == 0) { | |
| out_msg.MessageSize := MessageSizeType:Request_Control; | |
| } else { | |
| out_msg.MessageSize := MessageSizeType:Reissue_Control; | |
| } | |
| out_msg.Prefetch := tbe.Prefetch; | |
| out_msg.AccessMode := tbe.AccessMode; | |
| } | |
| // send to other local L1s, with local bit set | |
| enqueue(requestNetwork_out, RequestMsg, l1_request_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceRequestType:GETS; | |
| out_msg.Requestor := machineID; | |
| // | |
| // Since only one chip, assuming all L1 caches are local | |
| // | |
| //out_msg.Destination := getOtherLocalL1IDs(machineID); | |
| out_msg.Destination.broadcast(MachineType:L1Cache); | |
| out_msg.Destination.remove(machineID); | |
| out_msg.RetryNum := tbe.IssueCount; | |
| out_msg.isLocal := true; | |
| if (tbe.IssueCount == 0) { | |
| out_msg.MessageSize := MessageSizeType:Broadcast_Control; | |
| } else { | |
| out_msg.MessageSize := MessageSizeType:Broadcast_Control; | |
| } | |
| out_msg.Prefetch := tbe.Prefetch; | |
| out_msg.AccessMode := tbe.AccessMode; | |
| } | |
| // Increment IssueCount | |
| tbe.IssueCount := tbe.IssueCount + 1; | |
| // Set a wakeup timer | |
| if (dynamic_timeout_enabled) { | |
| reissueTimerTable.set( | |
| address, clockEdge() + cyclesToTicks(averageLatencyEstimate())); | |
| } else { | |
| reissueTimerTable.set( | |
| address, clockEdge() + cyclesToTicks(fixed_timeout_latency)); | |
| } | |
| } | |
| } | |
| action(b_issueWriteRequest, "b", desc="Issue GETX") { | |
| assert(is_valid(tbe)); | |
| if (tbe.IssueCount == 0) { | |
| // Update outstanding requests | |
| //profile_outstanding_request(outstandingRequests); | |
| outstandingRequests := outstandingRequests + 1; | |
| } | |
| if (tbe.IssueCount >= retry_threshold) { | |
| // Issue a persistent request if possible | |
| if ( okToIssueStarving(address, machineID) && (starving == false)) { | |
| enqueue(persistentNetwork_out, PersistentMsg, l1_request_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := PersistentRequestType:GETX_PERSISTENT; | |
| out_msg.Requestor := machineID; | |
| out_msg.Destination.broadcast(MachineType:L1Cache); | |
| // | |
| // Currently the configuration system limits the system to only one | |
| // chip. Therefore, if we assume one shared L2 cache, then only one | |
| // pertinent L2 cache exist. | |
| // | |
| //out_msg.Destination.addNetDest(getAllPertinentL2Banks(address)); | |
| out_msg.Destination.add(mapAddressToRange(address, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))); | |
| out_msg.Destination.add(mapAddressToMachine(address, MachineType:Directory)); | |
| out_msg.MessageSize := MessageSizeType:Persistent_Control; | |
| out_msg.Prefetch := tbe.Prefetch; | |
| out_msg.AccessMode := tbe.AccessMode; | |
| } | |
| markPersistentEntries(address); | |
| starving := true; | |
| // Update outstanding requests | |
| //profile_outstanding_persistent_request(outstandingPersistentRequests); | |
| outstandingPersistentRequests := outstandingPersistentRequests + 1; | |
| if (tbe.IssueCount == 0) { | |
| //profile_persistent_prediction(address, tbe.TypeOfAccess); | |
| } | |
| // Increment IssueCount | |
| tbe.IssueCount := tbe.IssueCount + 1; | |
| tbe.WentPersistent := true; | |
| // Do not schedule a wakeup, a persistent requests will always complete | |
| } | |
| else { | |
| // We'd like to issue a persistent request, but are not allowed | |
| // to issue a P.R. right now. This, we do not increment the | |
| // IssueCount. | |
| // Set a wakeup timer | |
| reissueTimerTable.set( | |
| address, clockEdge() + cyclesToTicks(reissue_wakeup_latency)); | |
| } | |
| } else { | |
| // Make a normal request | |
| enqueue(requestNetwork_out, RequestMsg, l1_request_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceRequestType:GETX; | |
| out_msg.Requestor := machineID; | |
| out_msg.Destination.add(mapAddressToRange(address, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))); | |
| out_msg.RetryNum := tbe.IssueCount; | |
| if (tbe.IssueCount == 0) { | |
| out_msg.MessageSize := MessageSizeType:Request_Control; | |
| } else { | |
| out_msg.MessageSize := MessageSizeType:Reissue_Control; | |
| } | |
| out_msg.Prefetch := tbe.Prefetch; | |
| out_msg.AccessMode := tbe.AccessMode; | |
| } | |
| // send to other local L1s too | |
| enqueue(requestNetwork_out, RequestMsg, l1_request_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceRequestType:GETX; | |
| out_msg.Requestor := machineID; | |
| out_msg.isLocal := true; | |
| // | |
| // Since only one chip, assuming all L1 caches are local | |
| // | |
| //out_msg.Destination := getOtherLocalL1IDs(machineID); | |
| out_msg.Destination.broadcast(MachineType:L1Cache); | |
| out_msg.Destination.remove(machineID); | |
| out_msg.RetryNum := tbe.IssueCount; | |
| if (tbe.IssueCount == 0) { | |
| out_msg.MessageSize := MessageSizeType:Broadcast_Control; | |
| } else { | |
| out_msg.MessageSize := MessageSizeType:Broadcast_Control; | |
| } | |
| out_msg.Prefetch := tbe.Prefetch; | |
| out_msg.AccessMode := tbe.AccessMode; | |
| } | |
| // Increment IssueCount | |
| tbe.IssueCount := tbe.IssueCount + 1; | |
| DPRINTF(RubySlicc, "incremented issue count to %d\n", | |
| tbe.IssueCount); | |
| // Set a wakeup timer | |
| if (dynamic_timeout_enabled) { | |
| reissueTimerTable.set( | |
| address, clockEdge() + cyclesToTicks(averageLatencyEstimate())); | |
| } else { | |
| reissueTimerTable.set( | |
| address, clockEdge() + cyclesToTicks(fixed_timeout_latency)); | |
| } | |
| } | |
| } | |
| action(bb_bounceResponse, "\b", desc="Bounce tokens and data to memory") { | |
| peek(responseNetwork_in, ResponseMsg) { | |
| // FIXME, should use a 3rd vnet | |
| enqueue(responseNetwork_out, ResponseMsg, 1) { | |
| out_msg.addr := address; | |
| out_msg.Type := in_msg.Type; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(mapAddressToMachine(address, MachineType:Directory)); | |
| out_msg.Tokens := in_msg.Tokens; | |
| out_msg.MessageSize := in_msg.MessageSize; | |
| out_msg.DataBlk := in_msg.DataBlk; | |
| out_msg.Dirty := in_msg.Dirty; | |
| } | |
| } | |
| } | |
| action(c_ownedReplacement, "c", desc="Issue writeback") { | |
| assert(is_valid(cache_entry)); | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(mapAddressToRange(address, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))); | |
| out_msg.Tokens := cache_entry.Tokens; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.Dirty := cache_entry.Dirty; | |
| out_msg.Type := CoherenceResponseType:WB_OWNED; | |
| // always send the data? | |
| out_msg.MessageSize := MessageSizeType:Writeback_Data; | |
| } | |
| cache_entry.Tokens := 0; | |
| } | |
| action(cc_sharedReplacement, "\c", desc="Issue shared writeback") { | |
| // don't send writeback if replacing block with no tokens | |
| assert(is_valid(cache_entry)); | |
| assert (cache_entry.Tokens > 0); | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(mapAddressToRange(address, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))); | |
| out_msg.Tokens := cache_entry.Tokens; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| // assert(cache_entry.Dirty == false); | |
| out_msg.Dirty := false; | |
| out_msg.MessageSize := MessageSizeType:Writeback_Data; | |
| out_msg.Type := CoherenceResponseType:WB_SHARED_DATA; | |
| } | |
| cache_entry.Tokens := 0; | |
| } | |
| action(tr_tokenReplacement, "tr", desc="Issue token writeback") { | |
| assert(is_valid(cache_entry)); | |
| if (cache_entry.Tokens > 0) { | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(mapAddressToRange(address, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))); | |
| out_msg.Tokens := cache_entry.Tokens; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| // assert(cache_entry.Dirty == false); | |
| out_msg.Dirty := false; | |
| // always send the data? | |
| out_msg.MessageSize := MessageSizeType:Writeback_Control; | |
| out_msg.Type := CoherenceResponseType:WB_TOKENS; | |
| } | |
| } | |
| cache_entry.Tokens := 0; | |
| } | |
| action(d_sendDataWithToken, "d", desc="Send data and a token from cache to requestor") { | |
| assert(is_valid(cache_entry)); | |
| peek(requestNetwork_in, RequestMsg) { | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA_SHARED; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| out_msg.Tokens := 1; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| // out_msg.Dirty := cache_entry.Dirty; | |
| out_msg.Dirty := false; | |
| if (in_msg.isLocal) { | |
| out_msg.MessageSize := MessageSizeType:ResponseLocal_Data; | |
| } else { | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| } | |
| } | |
| cache_entry.Tokens := cache_entry.Tokens - 1; | |
| assert(cache_entry.Tokens >= 1); | |
| } | |
| action(d_sendDataWithNTokenIfAvail, "\dd", desc="Send data and a token from cache to requestor") { | |
| assert(is_valid(cache_entry)); | |
| peek(requestNetwork_in, RequestMsg) { | |
| if (cache_entry.Tokens > (N_tokens + (max_tokens() / 2))) { | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA_SHARED; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| out_msg.Tokens := N_tokens; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| // out_msg.Dirty := cache_entry.Dirty; | |
| out_msg.Dirty := false; | |
| if (in_msg.isLocal) { | |
| out_msg.MessageSize := MessageSizeType:ResponseLocal_Data; | |
| } else { | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| } | |
| cache_entry.Tokens := cache_entry.Tokens - N_tokens; | |
| } | |
| else if (cache_entry.Tokens > 1) { | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA_SHARED; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| out_msg.Tokens := 1; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| // out_msg.Dirty := cache_entry.Dirty; | |
| out_msg.Dirty := false; | |
| if (in_msg.isLocal) { | |
| out_msg.MessageSize := MessageSizeType:ResponseLocal_Data; | |
| } else { | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| } | |
| cache_entry.Tokens := cache_entry.Tokens - 1; | |
| } | |
| } | |
| // assert(cache_entry.Tokens >= 1); | |
| } | |
| action(dd_sendDataWithAllTokens, "\d", desc="Send data and all tokens from cache to requestor") { | |
| peek(requestNetwork_in, RequestMsg) { | |
| assert(is_valid(cache_entry)); | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA_OWNER; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| assert(cache_entry.Tokens > (max_tokens() / 2)); | |
| out_msg.Tokens := cache_entry.Tokens; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.Dirty := cache_entry.Dirty; | |
| if (in_msg.isLocal) { | |
| out_msg.MessageSize := MessageSizeType:ResponseLocal_Data; | |
| } else { | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| } | |
| } | |
| cache_entry.Tokens := 0; | |
| } | |
| action(e_sendAckWithCollectedTokens, "e", desc="Send ack with the tokens we've collected thus far.") { | |
| // assert(persistentTable.findSmallest(address) != id); // Make sure we never bounce tokens to ourself | |
| assert(is_valid(cache_entry)); | |
| if (cache_entry.Tokens > 0) { | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| if (cache_entry.Tokens > (max_tokens() / 2)) { | |
| out_msg.Type := CoherenceResponseType:DATA_OWNER; | |
| } else { | |
| out_msg.Type := CoherenceResponseType:ACK; | |
| } | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(persistentTable.findSmallest(address)); | |
| assert(cache_entry.Tokens >= 1); | |
| out_msg.Tokens := cache_entry.Tokens; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.MessageSize := MessageSizeType:Response_Control; | |
| } | |
| } | |
| cache_entry.Tokens := 0; | |
| } | |
| action(ee_sendDataWithAllTokens, "\e", desc="Send data and all tokens from cache to starver") { | |
| //assert(persistentTable.findSmallest(address) != id); // Make sure we never bounce tokens to ourself | |
| assert(is_valid(cache_entry)); | |
| assert(cache_entry.Tokens > 0); | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA_OWNER; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(persistentTable.findSmallest(address)); | |
| assert(cache_entry.Tokens > (max_tokens() / 2)); | |
| out_msg.Tokens := cache_entry.Tokens; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.Dirty := cache_entry.Dirty; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| cache_entry.Tokens := 0; | |
| } | |
| action(f_sendAckWithAllButNorOneTokens, "f", desc="Send ack with all our tokens but one to starver.") { | |
| //assert(persistentTable.findSmallest(address) != id); // Make sure we never bounce tokens to ourself | |
| assert(is_valid(cache_entry)); | |
| assert(cache_entry.Tokens > 0); | |
| if (cache_entry.Tokens > 1) { | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| if (cache_entry.Tokens > (max_tokens() / 2)) { | |
| out_msg.Type := CoherenceResponseType:DATA_OWNER; | |
| } else { | |
| out_msg.Type := CoherenceResponseType:ACK; | |
| } | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(persistentTable.findSmallest(address)); | |
| assert(cache_entry.Tokens >= 1); | |
| if (cache_entry.Tokens > N_tokens) { | |
| out_msg.Tokens := cache_entry.Tokens - N_tokens; | |
| } else { | |
| out_msg.Tokens := cache_entry.Tokens - 1; | |
| } | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.MessageSize := MessageSizeType:Response_Control; | |
| } | |
| } | |
| if (cache_entry.Tokens > N_tokens) { | |
| cache_entry.Tokens := N_tokens; | |
| } else { | |
| cache_entry.Tokens := 1; | |
| } | |
| } | |
| action(ff_sendDataWithAllButNorOneTokens, "\f", desc="Send data and out tokens but one to starver") { | |
| //assert(persistentTable.findSmallest(address) != id); // Make sure we never bounce tokens to ourself | |
| assert(is_valid(cache_entry)); | |
| assert(cache_entry.Tokens > ((max_tokens() / 2) + 1)); | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA_OWNER; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(persistentTable.findSmallest(address)); | |
| if (cache_entry.Tokens > (N_tokens + (max_tokens() / 2))) { | |
| out_msg.Tokens := cache_entry.Tokens - N_tokens; | |
| } else { | |
| out_msg.Tokens := cache_entry.Tokens - 1; | |
| } | |
| assert(out_msg.Tokens > (max_tokens() / 2)); | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.Dirty := cache_entry.Dirty; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| if (cache_entry.Tokens > (N_tokens + (max_tokens() / 2))) { | |
| cache_entry.Tokens := N_tokens; | |
| } else { | |
| cache_entry.Tokens := 1; | |
| } | |
| } | |
| action(fo_sendDataWithOwnerToken, "fo", desc="Send data and owner tokens") { | |
| assert(is_valid(cache_entry)); | |
| assert(cache_entry.Tokens == ((max_tokens() / 2) + 1)); | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:DATA_OWNER; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(persistentTable.findSmallest(address)); | |
| out_msg.Tokens := cache_entry.Tokens; | |
| assert(out_msg.Tokens > (max_tokens() / 2)); | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.Dirty := cache_entry.Dirty; | |
| out_msg.MessageSize := MessageSizeType:Response_Data; | |
| } | |
| cache_entry.Tokens := 0; | |
| } | |
| action(g_bounceResponseToStarver, "g", desc="Redirect response to starving processor") { | |
| // assert(persistentTable.isLocked(address)); | |
| peek(responseNetwork_in, ResponseMsg) { | |
| // assert(persistentTable.findSmallest(address) != id); // Make sure we never bounce tokens to ourself | |
| // FIXME, should use a 3rd vnet in some cases | |
| enqueue(responseNetwork_out, ResponseMsg, 1) { | |
| out_msg.addr := address; | |
| out_msg.Type := in_msg.Type; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(persistentTable.findSmallest(address)); | |
| out_msg.Tokens := in_msg.Tokens; | |
| out_msg.DataBlk := in_msg.DataBlk; | |
| out_msg.Dirty := in_msg.Dirty; | |
| out_msg.MessageSize := in_msg.MessageSize; | |
| } | |
| } | |
| } | |
| action(h_load_hit, "hd", desc="Notify sequencer the load completed.") { | |
| assert(is_valid(cache_entry)); | |
| DPRINTF(RubySlicc, "Address: %#x, Data Block: %s\n", | |
| address, cache_entry.DataBlk); | |
| L1Dcache.setMRU(cache_entry); | |
| sequencer.readCallback(address, cache_entry.DataBlk, false, | |
| MachineType:L1Cache); | |
| } | |
| action(h_ifetch_hit, "hi", desc="Notify sequencer the load completed.") { | |
| assert(is_valid(cache_entry)); | |
| DPRINTF(RubySlicc, "Address: %#x, Data Block: %s\n", | |
| address, cache_entry.DataBlk); | |
| L1Icache.setMRU(cache_entry); | |
| sequencer.readCallback(address, cache_entry.DataBlk, false, | |
| MachineType:L1Cache); | |
| } | |
| action(x_external_load_hit, "x", desc="Notify sequencer the load completed.") { | |
| assert(is_valid(cache_entry)); | |
| DPRINTF(RubySlicc, "Address: %#x, Data Block: %s\n", | |
| address, cache_entry.DataBlk); | |
| peek(responseNetwork_in, ResponseMsg) { | |
| L1Icache.setMRU(address); | |
| L1Dcache.setMRU(address); | |
| sequencer.readCallback(address, cache_entry.DataBlk, | |
| isExternalHit(address, in_msg.Sender), | |
| machineIDToMachineType(in_msg.Sender)); | |
| } | |
| } | |
| action(hh_store_hit, "\h", desc="Notify sequencer that store completed.") { | |
| assert(is_valid(cache_entry)); | |
| DPRINTF(RubySlicc, "Address: %#x, Data Block: %s\n", | |
| address, cache_entry.DataBlk); | |
| L1Dcache.setMRU(cache_entry); | |
| sequencer.writeCallback(address, cache_entry.DataBlk, false, | |
| MachineType:L1Cache); | |
| cache_entry.Dirty := true; | |
| DPRINTF(RubySlicc, "%s\n", cache_entry.DataBlk); | |
| } | |
| action(xx_external_store_hit, "\x", desc="Notify sequencer that store completed.") { | |
| assert(is_valid(cache_entry)); | |
| DPRINTF(RubySlicc, "Address: %#x, Data Block: %s\n", | |
| address, cache_entry.DataBlk); | |
| peek(responseNetwork_in, ResponseMsg) { | |
| L1Icache.setMRU(address); | |
| L1Dcache.setMRU(address); | |
| sequencer.writeCallback(address, cache_entry.DataBlk, | |
| isExternalHit(address, in_msg.Sender), | |
| machineIDToMachineType(in_msg.Sender)); | |
| } | |
| cache_entry.Dirty := true; | |
| DPRINTF(RubySlicc, "%s\n", cache_entry.DataBlk); | |
| } | |
| action(i_allocateTBE, "i", desc="Allocate TBE") { | |
| check_allocate(L1_TBEs); | |
| L1_TBEs.allocate(address); | |
| set_tbe(L1_TBEs[address]); | |
| tbe.IssueCount := 0; | |
| peek(mandatoryQueue_in, RubyRequest) { | |
| tbe.PC := in_msg.ProgramCounter; | |
| tbe.TypeOfAccess := cache_request_type_to_access_type(in_msg.Type); | |
| if (in_msg.Type == RubyRequestType:ATOMIC) { | |
| tbe.IsAtomic := true; | |
| } | |
| tbe.Prefetch := in_msg.Prefetch; | |
| tbe.AccessMode := in_msg.AccessMode; | |
| } | |
| tbe.IssueTime := curCycle(); | |
| } | |
| action(ta_traceStalledAddress, "ta", desc="Trace Stalled Address") { | |
| peek(mandatoryQueue_in, RubyRequest) { | |
| APPEND_TRANSITION_COMMENT(in_msg.LineAddress); | |
| } | |
| } | |
| action(j_unsetReissueTimer, "j", desc="Unset reissue timer.") { | |
| if (reissueTimerTable.isSet(address)) { | |
| reissueTimerTable.unset(address); | |
| } | |
| } | |
| action(jj_unsetUseTimer, "\j", desc="Unset use timer.") { | |
| useTimerTable.unset(address); | |
| } | |
| action(k_popMandatoryQueue, "k", desc="Pop mandatory queue.") { | |
| mandatoryQueue_in.dequeue(clockEdge()); | |
| } | |
| action(l_popPersistentQueue, "l", desc="Pop persistent queue.") { | |
| persistentNetwork_in.dequeue(clockEdge()); | |
| } | |
| action(m_popRequestQueue, "m", desc="Pop request queue.") { | |
| requestNetwork_in.dequeue(clockEdge()); | |
| } | |
| action(n_popResponseQueue, "n", desc="Pop response queue") { | |
| responseNetwork_in.dequeue(clockEdge()); | |
| } | |
| action(o_scheduleUseTimeout, "o", desc="Schedule a use timeout.") { | |
| useTimerTable.set( | |
| address, clockEdge() + cyclesToTicks(use_timeout_latency)); | |
| } | |
| action(p_informL2AboutTokenLoss, "p", desc="Inform L2 about loss of all tokens") { | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := CoherenceResponseType:INV; | |
| out_msg.Tokens := 0; | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(mapAddressToRange(address, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))); | |
| out_msg.MessageSize := MessageSizeType:Response_Control; | |
| } | |
| } | |
| action(q_updateTokensFromResponse, "q", desc="Update the token count based on the incoming response message") { | |
| peek(responseNetwork_in, ResponseMsg) { | |
| assert(is_valid(cache_entry)); | |
| assert(in_msg.Tokens != 0); | |
| DPRINTF(RubySlicc, "L1 received tokens for address: %#x, tokens: %d\n", | |
| in_msg.addr, in_msg.Tokens); | |
| cache_entry.Tokens := cache_entry.Tokens + in_msg.Tokens; | |
| DPRINTF(RubySlicc, "%d\n", cache_entry.Tokens); | |
| if (cache_entry.Dirty == false && in_msg.Dirty) { | |
| cache_entry.Dirty := true; | |
| } | |
| } | |
| } | |
| action(s_deallocateTBE, "s", desc="Deallocate TBE") { | |
| assert(is_valid(tbe)); | |
| if (tbe.WentPersistent) { | |
| // assert(starving); | |
| outstandingRequests := outstandingRequests - 1; | |
| enqueue(persistentNetwork_out, PersistentMsg, l1_request_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := PersistentRequestType:DEACTIVATE_PERSISTENT; | |
| out_msg.Requestor := machineID; | |
| out_msg.Destination.broadcast(MachineType:L1Cache); | |
| // | |
| // Currently the configuration system limits the system to only one | |
| // chip. Therefore, if we assume one shared L2 cache, then only one | |
| // pertinent L2 cache exist. | |
| // | |
| //out_msg.Destination.addNetDest(getAllPertinentL2Banks(address)); | |
| out_msg.Destination.add(mapAddressToRange(address, | |
| MachineType:L2Cache, l2_select_low_bit, | |
| l2_select_num_bits, intToID(0))); | |
| out_msg.Destination.add(mapAddressToMachine(address, MachineType:Directory)); | |
| out_msg.MessageSize := MessageSizeType:Persistent_Control; | |
| } | |
| starving := false; | |
| } | |
| // Update average latency | |
| if (tbe.IssueCount <= 1) { | |
| if (tbe.ExternalResponse) { | |
| updateAverageLatencyEstimate(curCycle() - tbe.IssueTime); | |
| } | |
| } | |
| // Profile | |
| //if (tbe.WentPersistent) { | |
| // profile_token_retry(address, tbe.TypeOfAccess, 2); | |
| //} | |
| //else { | |
| // profile_token_retry(address, tbe.TypeOfAccess, 1); | |
| //} | |
| //profile_token_retry(address, tbe.TypeOfAccess, tbe.IssueCount); | |
| L1_TBEs.deallocate(address); | |
| unset_tbe(); | |
| } | |
| action(t_sendAckWithCollectedTokens, "t", desc="Send ack with the tokens we've collected thus far.") { | |
| assert(is_valid(cache_entry)); | |
| if (cache_entry.Tokens > 0) { | |
| peek(requestNetwork_in, RequestMsg) { | |
| enqueue(responseNetwork_out, ResponseMsg, l1_response_latency) { | |
| out_msg.addr := address; | |
| if (cache_entry.Tokens > (max_tokens() / 2)) { | |
| out_msg.Type := CoherenceResponseType:DATA_OWNER; | |
| } else { | |
| out_msg.Type := CoherenceResponseType:ACK; | |
| } | |
| out_msg.Sender := machineID; | |
| out_msg.Destination.add(in_msg.Requestor); | |
| assert(cache_entry.Tokens >= 1); | |
| out_msg.Tokens := cache_entry.Tokens; | |
| out_msg.DataBlk := cache_entry.DataBlk; | |
| out_msg.MessageSize := MessageSizeType:Response_Control; | |
| } | |
| } | |
| } | |
| cache_entry.Tokens := 0; | |
| } | |
| action(u_writeDataToCache, "u", desc="Write data to cache") { | |
| peek(responseNetwork_in, ResponseMsg) { | |
| assert(is_valid(cache_entry)); | |
| cache_entry.DataBlk := in_msg.DataBlk; | |
| if (cache_entry.Dirty == false && in_msg.Dirty) { | |
| cache_entry.Dirty := in_msg.Dirty; | |
| } | |
| } | |
| } | |
| action(gg_deallocateL1CacheBlock, "\g", desc="Deallocate cache block. Sets the cache to invalid, allowing a replacement in parallel with a fetch.") { | |
| assert(getTokens(cache_entry) == 0); | |
| if (L1Dcache.isTagPresent(address)) { | |
| L1Dcache.deallocate(address); | |
| } else { | |
| L1Icache.deallocate(address); | |
| } | |
| unset_cache_entry(); | |
| } | |
| action(ii_allocateL1DCacheBlock, "\i", desc="Set L1 D-cache tag equal to tag of block B.") { | |
| if (is_valid(cache_entry)) { | |
| } else { | |
| set_cache_entry(L1Dcache.allocate(address, new Entry)); | |
| } | |
| } | |
| action(pp_allocateL1ICacheBlock, "\p", desc="Set L1 I-cache tag equal to tag of block B.") { | |
| if (is_valid(cache_entry)) { | |
| } else { | |
| set_cache_entry(L1Icache.allocate(address, new Entry)); | |
| } | |
| } | |
| action(forward_eviction_to_cpu, "\cc", desc="sends eviction information to the processor") { | |
| if (send_evictions) { | |
| DPRINTF(RubySlicc, "Sending invalidation for %#x to the CPU\n", address); | |
| sequencer.evictionCallback(address); | |
| } | |
| } | |
| action(uu_profileInstMiss, "\uim", desc="Profile the demand miss") { | |
| ++L1Icache.demand_misses; | |
| } | |
| action(uu_profileInstHit, "\uih", desc="Profile the demand hit") { | |
| ++L1Icache.demand_hits; | |
| } | |
| action(uu_profileDataMiss, "\udm", desc="Profile the demand miss") { | |
| ++L1Dcache.demand_misses; | |
| } | |
| action(uu_profileDataHit, "\udh", desc="Profile the demand hit") { | |
| ++L1Dcache.demand_hits; | |
| } | |
| action(w_assertIncomingDataAndCacheDataMatch, "w", desc="Assert that the incoming data and the data in the cache match") { | |
| peek(responseNetwork_in, ResponseMsg) { | |
| assert(is_valid(cache_entry)); | |
| assert(cache_entry.DataBlk == in_msg.DataBlk); | |
| } | |
| } | |
| action(zz_stallAndWaitMandatoryQueue, "\z", desc="Send the head of the mandatory queue to the back of the queue.") { | |
| peek(mandatoryQueue_in, RubyRequest) { | |
| APPEND_TRANSITION_COMMENT(in_msg.LineAddress); | |
| } | |
| stall_and_wait(mandatoryQueue_in, address); | |
| } | |
| action(kd_wakeUpDependents, "kd", desc="wake-up dependents") { | |
| wakeUpBuffers(address); | |
| } | |
| action(ka_wakeUpAllDependents, "ka", desc="wake-up all dependents") { | |
| wakeUpAllBuffers(); | |
| } | |
| //***************************************************** | |
| // TRANSITIONS | |
| //***************************************************** | |
| // Transitions for Load/Store/L2_Replacement from transient states | |
| transition({IM, SM, OM, IS, IM_L, IS_L, I_L, S_L, SM_L, M_W, MM_W}, L1_Replacement) { | |
| ta_traceStalledAddress; | |
| zz_stallAndWaitMandatoryQueue; | |
| } | |
| transition({IM, SM, OM, IS, IM_L, IS_L, SM_L}, {Store, Atomic}) { | |
| zz_stallAndWaitMandatoryQueue; | |
| } | |
| transition({IM, IS, IM_L, IS_L}, {Load, Ifetch}) { | |
| zz_stallAndWaitMandatoryQueue; | |
| } | |
| // Lockdowns | |
| transition({NP, I, S, O, M, MM, M_W, MM_W, IM, SM, OM, IS}, Own_Lock_or_Unlock) { | |
| l_popPersistentQueue; | |
| } | |
| // Transitions from NP | |
| transition(NP, Load, IS) { | |
| ii_allocateL1DCacheBlock; | |
| i_allocateTBE; | |
| a_issueReadRequest; | |
| uu_profileDataMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(NP, Ifetch, IS) { | |
| pp_allocateL1ICacheBlock; | |
| i_allocateTBE; | |
| a_issueReadRequest; | |
| uu_profileInstMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(NP, {Store, Atomic}, IM) { | |
| ii_allocateL1DCacheBlock; | |
| i_allocateTBE; | |
| b_issueWriteRequest; | |
| uu_profileDataMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(NP, {Ack, Data_Shared, Data_Owner, Data_All_Tokens}) { | |
| bb_bounceResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(NP, {Transient_GETX, Transient_Local_GETX, Transient_GETS, Transient_Local_GETS}) { | |
| m_popRequestQueue; | |
| } | |
| transition(NP, {Persistent_GETX, Persistent_GETS, Persistent_GETS_Last_Token}, I_L) { | |
| l_popPersistentQueue; | |
| } | |
| // Transitions from Idle | |
| transition(I, Load, IS) { | |
| i_allocateTBE; | |
| a_issueReadRequest; | |
| uu_profileDataMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(I, Ifetch, IS) { | |
| i_allocateTBE; | |
| a_issueReadRequest; | |
| uu_profileInstMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(I, {Store, Atomic}, IM) { | |
| i_allocateTBE; | |
| b_issueWriteRequest; | |
| uu_profileDataMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(I, L1_Replacement) { | |
| ta_traceStalledAddress; | |
| tr_tokenReplacement; | |
| gg_deallocateL1CacheBlock; | |
| ka_wakeUpAllDependents; | |
| } | |
| transition(I, {Transient_GETX, Transient_Local_GETX}) { | |
| t_sendAckWithCollectedTokens; | |
| m_popRequestQueue; | |
| } | |
| transition(I, {Transient_GETS, Transient_GETS_Last_Token, Transient_Local_GETS_Last_Token, Transient_Local_GETS}) { | |
| m_popRequestQueue; | |
| } | |
| transition(I, {Persistent_GETX, Persistent_GETS, Persistent_GETS_Last_Token}, I_L) { | |
| e_sendAckWithCollectedTokens; | |
| l_popPersistentQueue; | |
| } | |
| transition(I_L, {Persistent_GETX, Persistent_GETS, Persistent_GETS_Last_Token}) { | |
| l_popPersistentQueue; | |
| } | |
| transition(I, Ack) { | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(I, Data_Shared, S) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(I, Data_Owner, O) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(I, Data_All_Tokens, M) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| // Transitions from Shared | |
| transition({S, SM, S_L, SM_L}, Load) { | |
| h_load_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition({S, SM, S_L, SM_L}, Ifetch) { | |
| h_ifetch_hit; | |
| uu_profileInstHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(S, {Store, Atomic}, SM) { | |
| i_allocateTBE; | |
| b_issueWriteRequest; | |
| uu_profileDataMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(S, L1_Replacement, I) { | |
| ta_traceStalledAddress; | |
| cc_sharedReplacement; // Only needed in some cases | |
| forward_eviction_to_cpu; | |
| gg_deallocateL1CacheBlock; | |
| ka_wakeUpAllDependents; | |
| } | |
| transition(S, {Transient_GETX, Transient_Local_GETX}, I) { | |
| t_sendAckWithCollectedTokens; | |
| p_informL2AboutTokenLoss; | |
| forward_eviction_to_cpu | |
| m_popRequestQueue; | |
| } | |
| // only owner responds to non-local requests | |
| transition(S, Transient_GETS) { | |
| m_popRequestQueue; | |
| } | |
| transition(S, Transient_Local_GETS) { | |
| d_sendDataWithToken; | |
| m_popRequestQueue; | |
| } | |
| transition(S, {Transient_GETS_Last_Token, Transient_Local_GETS_Last_Token}) { | |
| m_popRequestQueue; | |
| } | |
| transition({S, S_L}, Persistent_GETX, I_L) { | |
| e_sendAckWithCollectedTokens; | |
| p_informL2AboutTokenLoss; | |
| forward_eviction_to_cpu | |
| l_popPersistentQueue; | |
| } | |
| transition(S, {Persistent_GETS, Persistent_GETS_Last_Token}, S_L) { | |
| f_sendAckWithAllButNorOneTokens; | |
| l_popPersistentQueue; | |
| } | |
| transition(S_L, {Persistent_GETS, Persistent_GETS_Last_Token}) { | |
| l_popPersistentQueue; | |
| } | |
| transition(S, Ack) { | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(S, Data_Shared) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(S, Data_Owner, O) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(S, Data_All_Tokens, M) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| // Transitions from Owned | |
| transition({O, OM}, Ifetch) { | |
| h_ifetch_hit; | |
| uu_profileInstHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition({O, OM}, Load) { | |
| h_load_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(O, {Store, Atomic}, OM) { | |
| i_allocateTBE; | |
| b_issueWriteRequest; | |
| uu_profileDataMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(O, L1_Replacement, I) { | |
| ta_traceStalledAddress; | |
| c_ownedReplacement; | |
| forward_eviction_to_cpu | |
| gg_deallocateL1CacheBlock; | |
| ka_wakeUpAllDependents; | |
| } | |
| transition(O, {Transient_GETX, Transient_Local_GETX}, I) { | |
| dd_sendDataWithAllTokens; | |
| p_informL2AboutTokenLoss; | |
| forward_eviction_to_cpu | |
| m_popRequestQueue; | |
| } | |
| transition(O, Persistent_GETX, I_L) { | |
| ee_sendDataWithAllTokens; | |
| p_informL2AboutTokenLoss; | |
| forward_eviction_to_cpu | |
| l_popPersistentQueue; | |
| } | |
| transition(O, Persistent_GETS, S_L) { | |
| ff_sendDataWithAllButNorOneTokens; | |
| l_popPersistentQueue; | |
| } | |
| transition(O, Persistent_GETS_Last_Token, I_L) { | |
| fo_sendDataWithOwnerToken; | |
| forward_eviction_to_cpu | |
| l_popPersistentQueue; | |
| } | |
| transition(O, Transient_GETS) { | |
| d_sendDataWithToken; | |
| m_popRequestQueue; | |
| } | |
| transition(O, Transient_Local_GETS) { | |
| d_sendDataWithToken; | |
| m_popRequestQueue; | |
| } | |
| // ran out of tokens, wait for it to go persistent | |
| transition(O, {Transient_GETS_Last_Token, Transient_Local_GETS_Last_Token}) { | |
| m_popRequestQueue; | |
| } | |
| transition(O, Ack) { | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(O, Ack_All_Tokens, M) { | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(O, Data_Shared) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(O, Data_All_Tokens, M) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| // Transitions from Modified | |
| transition({MM, MM_W}, Ifetch) { | |
| h_ifetch_hit; | |
| uu_profileInstHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition({MM, MM_W}, Load) { | |
| h_load_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition({MM_W}, {Store, Atomic}) { | |
| hh_store_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(MM, Store) { | |
| hh_store_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(MM, Atomic, M) { | |
| hh_store_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(MM, L1_Replacement, I) { | |
| ta_traceStalledAddress; | |
| c_ownedReplacement; | |
| forward_eviction_to_cpu | |
| gg_deallocateL1CacheBlock; | |
| ka_wakeUpAllDependents; | |
| } | |
| transition(MM, {Transient_GETX, Transient_Local_GETX, Transient_GETS, Transient_Local_GETS}, I) { | |
| dd_sendDataWithAllTokens; | |
| p_informL2AboutTokenLoss; | |
| forward_eviction_to_cpu | |
| m_popRequestQueue; | |
| } | |
| transition({MM_W}, {Transient_GETX, Transient_Local_GETX, Transient_GETS, Transient_Local_GETS}) { // Ignore the request | |
| m_popRequestQueue; | |
| } | |
| // Implement the migratory sharing optimization, even for persistent requests | |
| transition(MM, {Persistent_GETX, Persistent_GETS}, I_L) { | |
| ee_sendDataWithAllTokens; | |
| p_informL2AboutTokenLoss; | |
| forward_eviction_to_cpu | |
| l_popPersistentQueue; | |
| } | |
| // ignore persistent requests in lockout period | |
| transition(MM_W, {Persistent_GETX, Persistent_GETS}) { | |
| l_popPersistentQueue; | |
| } | |
| transition(MM_W, Use_TimeoutNoStarvers, MM) { | |
| s_deallocateTBE; | |
| jj_unsetUseTimer; | |
| kd_wakeUpDependents; | |
| } | |
| transition(MM_W, Use_TimeoutNoStarvers_NoMig, M) { | |
| s_deallocateTBE; | |
| jj_unsetUseTimer; | |
| kd_wakeUpDependents; | |
| } | |
| // Transitions from Dirty Exclusive | |
| transition({M, M_W}, Ifetch) { | |
| h_ifetch_hit; | |
| uu_profileInstHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition({M, M_W}, Load) { | |
| h_load_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(M, Store, MM) { | |
| hh_store_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(M, Atomic) { | |
| hh_store_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(M_W, Store, MM_W) { | |
| hh_store_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(M_W, Atomic) { | |
| hh_store_hit; | |
| uu_profileDataHit; | |
| k_popMandatoryQueue; | |
| } | |
| transition(M, L1_Replacement, I) { | |
| ta_traceStalledAddress; | |
| c_ownedReplacement; | |
| forward_eviction_to_cpu | |
| gg_deallocateL1CacheBlock; | |
| ka_wakeUpAllDependents; | |
| } | |
| transition(M, {Transient_GETX, Transient_Local_GETX}, I) { | |
| dd_sendDataWithAllTokens; | |
| p_informL2AboutTokenLoss; | |
| forward_eviction_to_cpu | |
| m_popRequestQueue; | |
| } | |
| transition(M, Transient_Local_GETS, O) { | |
| d_sendDataWithToken; | |
| m_popRequestQueue; | |
| } | |
| transition(M, Transient_GETS, O) { | |
| d_sendDataWithNTokenIfAvail; | |
| m_popRequestQueue; | |
| } | |
| transition(M_W, {Transient_GETX, Transient_Local_GETX, Transient_GETS, Transient_Local_GETS}) { // Ignore the request | |
| m_popRequestQueue; | |
| } | |
| transition(M, Persistent_GETX, I_L) { | |
| ee_sendDataWithAllTokens; | |
| p_informL2AboutTokenLoss; | |
| forward_eviction_to_cpu | |
| l_popPersistentQueue; | |
| } | |
| transition(M, Persistent_GETS, S_L) { | |
| ff_sendDataWithAllButNorOneTokens; | |
| l_popPersistentQueue; | |
| } | |
| // ignore persistent requests in lockout period | |
| transition(M_W, {Persistent_GETX, Persistent_GETS}) { | |
| l_popPersistentQueue; | |
| } | |
| transition(M_W, Use_TimeoutStarverS, S_L) { | |
| s_deallocateTBE; | |
| ff_sendDataWithAllButNorOneTokens; | |
| jj_unsetUseTimer; | |
| } | |
| // someone unlocked during timeout | |
| transition(M_W, {Use_TimeoutNoStarvers, Use_TimeoutNoStarvers_NoMig}, M) { | |
| s_deallocateTBE; | |
| jj_unsetUseTimer; | |
| kd_wakeUpDependents; | |
| } | |
| transition(M_W, Use_TimeoutStarverX, I_L) { | |
| s_deallocateTBE; | |
| ee_sendDataWithAllTokens; | |
| forward_eviction_to_cpu; | |
| p_informL2AboutTokenLoss; | |
| jj_unsetUseTimer; | |
| } | |
| // migratory | |
| transition(MM_W, {Use_TimeoutStarverX, Use_TimeoutStarverS}, I_L) { | |
| s_deallocateTBE; | |
| ee_sendDataWithAllTokens; | |
| forward_eviction_to_cpu; | |
| p_informL2AboutTokenLoss; | |
| jj_unsetUseTimer; | |
| } | |
| // Transient_GETX and Transient_GETS in transient states | |
| transition(OM, {Transient_GETX, Transient_Local_GETX, Transient_GETS, Transient_GETS_Last_Token, Transient_Local_GETS_Last_Token, Transient_Local_GETS}) { | |
| m_popRequestQueue; // Even if we have the data, we can pretend we don't have it yet. | |
| } | |
| transition(IS, {Transient_GETX, Transient_Local_GETX}) { | |
| t_sendAckWithCollectedTokens; | |
| m_popRequestQueue; | |
| } | |
| transition(IS, {Transient_GETS, Transient_GETS_Last_Token, Transient_Local_GETS_Last_Token, Transient_Local_GETS}) { | |
| m_popRequestQueue; | |
| } | |
| transition(IS, {Persistent_GETX, Persistent_GETS, Persistent_GETS_Last_Token}, IS_L) { | |
| e_sendAckWithCollectedTokens; | |
| l_popPersistentQueue; | |
| } | |
| transition(IS_L, {Persistent_GETX, Persistent_GETS}) { | |
| l_popPersistentQueue; | |
| } | |
| transition(IM, {Persistent_GETX, Persistent_GETS, Persistent_GETS_Last_Token}, IM_L) { | |
| e_sendAckWithCollectedTokens; | |
| l_popPersistentQueue; | |
| } | |
| transition(IM_L, {Persistent_GETX, Persistent_GETS}) { | |
| l_popPersistentQueue; | |
| } | |
| transition({SM, SM_L}, Persistent_GETX, IM_L) { | |
| e_sendAckWithCollectedTokens; | |
| forward_eviction_to_cpu | |
| l_popPersistentQueue; | |
| } | |
| transition(SM, {Persistent_GETS, Persistent_GETS_Last_Token}, SM_L) { | |
| f_sendAckWithAllButNorOneTokens; | |
| l_popPersistentQueue; | |
| } | |
| transition(SM_L, {Persistent_GETS, Persistent_GETS_Last_Token}) { | |
| l_popPersistentQueue; | |
| } | |
| transition(OM, Persistent_GETX, IM_L) { | |
| ee_sendDataWithAllTokens; | |
| forward_eviction_to_cpu | |
| l_popPersistentQueue; | |
| } | |
| transition(OM, Persistent_GETS, SM_L) { | |
| ff_sendDataWithAllButNorOneTokens; | |
| l_popPersistentQueue; | |
| } | |
| transition(OM, Persistent_GETS_Last_Token, IM_L) { | |
| fo_sendDataWithOwnerToken; | |
| l_popPersistentQueue; | |
| } | |
| // Transitions from IM/SM | |
| transition({IM, SM}, Ack) { | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(IM, Data_Shared, SM) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(IM, Data_Owner, OM) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(IM, Data_All_Tokens, MM_W) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| xx_external_store_hit; | |
| o_scheduleUseTimeout; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(SM, Data_Shared) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(SM, Data_Owner, OM) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(SM, Data_All_Tokens, MM_W) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| xx_external_store_hit; | |
| o_scheduleUseTimeout; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition({IM, SM}, {Transient_GETX, Transient_Local_GETX}, IM) { // We don't have the data yet, but we might have collected some tokens. We give them up here to avoid livelock | |
| t_sendAckWithCollectedTokens; | |
| forward_eviction_to_cpu; | |
| m_popRequestQueue; | |
| } | |
| transition({IM, SM}, {Transient_GETS, Transient_GETS_Last_Token, Transient_Local_GETS_Last_Token, Transient_Local_GETS}) { | |
| m_popRequestQueue; | |
| } | |
| transition({IM, SM}, Request_Timeout) { | |
| j_unsetReissueTimer; | |
| b_issueWriteRequest; | |
| } | |
| // Transitions from OM | |
| transition(OM, Ack) { | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(OM, Ack_All_Tokens, MM_W) { | |
| q_updateTokensFromResponse; | |
| xx_external_store_hit; | |
| o_scheduleUseTimeout; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(OM, Data_Shared) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(OM, Data_All_Tokens, MM_W) { | |
| w_assertIncomingDataAndCacheDataMatch; | |
| q_updateTokensFromResponse; | |
| xx_external_store_hit; | |
| o_scheduleUseTimeout; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(OM, Request_Timeout) { | |
| j_unsetReissueTimer; | |
| b_issueWriteRequest; | |
| } | |
| // Transitions from IS | |
| transition(IS, Ack) { | |
| q_updateTokensFromResponse; | |
| n_popResponseQueue; | |
| } | |
| transition(IS, Data_Shared, S) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| x_external_load_hit; | |
| s_deallocateTBE; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(IS, Data_Owner, O) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| x_external_load_hit; | |
| s_deallocateTBE; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(IS, Data_All_Tokens, M_W) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| x_external_load_hit; | |
| o_scheduleUseTimeout; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(IS, Request_Timeout) { | |
| j_unsetReissueTimer; | |
| a_issueReadRequest; | |
| } | |
| // Transitions from I_L | |
| transition(I_L, Load, IS_L) { | |
| ii_allocateL1DCacheBlock; | |
| i_allocateTBE; | |
| a_issueReadRequest; | |
| uu_profileDataMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(I_L, Ifetch, IS_L) { | |
| pp_allocateL1ICacheBlock; | |
| i_allocateTBE; | |
| a_issueReadRequest; | |
| uu_profileInstMiss; | |
| k_popMandatoryQueue; | |
| } | |
| transition(I_L, {Store, Atomic}, IM_L) { | |
| ii_allocateL1DCacheBlock; | |
| i_allocateTBE; | |
| b_issueWriteRequest; | |
| uu_profileDataMiss; | |
| k_popMandatoryQueue; | |
| } | |
| // Transitions from S_L | |
| transition(S_L, {Store, Atomic}, SM_L) { | |
| i_allocateTBE; | |
| b_issueWriteRequest; | |
| uu_profileDataMiss; | |
| k_popMandatoryQueue; | |
| } | |
| // Other transitions from *_L states | |
| transition({I_L, IM_L, IS_L, S_L, SM_L}, {Transient_GETS, Transient_GETS_Last_Token, Transient_Local_GETS_Last_Token, Transient_Local_GETS, Transient_GETX, Transient_Local_GETX}) { | |
| m_popRequestQueue; | |
| } | |
| transition({I_L, IM_L, IS_L, S_L, SM_L}, Ack) { | |
| g_bounceResponseToStarver; | |
| n_popResponseQueue; | |
| } | |
| transition({I_L, IM_L, S_L, SM_L}, {Data_Shared, Data_Owner}) { | |
| g_bounceResponseToStarver; | |
| n_popResponseQueue; | |
| } | |
| transition({I_L, S_L}, Data_All_Tokens) { | |
| g_bounceResponseToStarver; | |
| n_popResponseQueue; | |
| } | |
| transition(IS_L, Request_Timeout) { | |
| j_unsetReissueTimer; | |
| a_issueReadRequest; | |
| } | |
| transition({IM_L, SM_L}, Request_Timeout) { | |
| j_unsetReissueTimer; | |
| b_issueWriteRequest; | |
| } | |
| // Opportunisticly Complete the memory operation in the following | |
| // cases. Note: these transitions could just use | |
| // g_bounceResponseToStarver, but if we have the data and tokens, we | |
| // might as well complete the memory request while we have the | |
| // chance (and then immediately forward on the data) | |
| transition(IM_L, Data_All_Tokens, MM_W) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| xx_external_store_hit; | |
| j_unsetReissueTimer; | |
| o_scheduleUseTimeout; | |
| n_popResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(SM_L, Data_All_Tokens, S_L) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| xx_external_store_hit; | |
| ff_sendDataWithAllButNorOneTokens; | |
| s_deallocateTBE; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| } | |
| transition(IS_L, Data_Shared, I_L) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| x_external_load_hit; | |
| s_deallocateTBE; | |
| e_sendAckWithCollectedTokens; | |
| p_informL2AboutTokenLoss; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| } | |
| transition(IS_L, Data_Owner, I_L) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| x_external_load_hit; | |
| ee_sendDataWithAllTokens; | |
| s_deallocateTBE; | |
| p_informL2AboutTokenLoss; | |
| j_unsetReissueTimer; | |
| n_popResponseQueue; | |
| } | |
| transition(IS_L, Data_All_Tokens, M_W) { | |
| u_writeDataToCache; | |
| q_updateTokensFromResponse; | |
| x_external_load_hit; | |
| j_unsetReissueTimer; | |
| o_scheduleUseTimeout; | |
| n_popResponseQueue; | |
| kd_wakeUpDependents; | |
| } | |
| // Own_Lock_or_Unlock | |
| transition(I_L, Own_Lock_or_Unlock, I) { | |
| l_popPersistentQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(S_L, Own_Lock_or_Unlock, S) { | |
| l_popPersistentQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(IM_L, Own_Lock_or_Unlock, IM) { | |
| l_popPersistentQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(IS_L, Own_Lock_or_Unlock, IS) { | |
| l_popPersistentQueue; | |
| kd_wakeUpDependents; | |
| } | |
| transition(SM_L, Own_Lock_or_Unlock, SM) { | |
| l_popPersistentQueue; | |
| kd_wakeUpDependents; | |
| } | |
| } |