/*
 * 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(L2Cache, "Token protocol")
 : CacheMemory * L2cache,
   int N_tokens,
   Cycles l2_request_latency = 5,
   Cycles l2_response_latency = 5,
   bool filtering_enabled = true
{

  // L2 BANK QUEUES
  // From local bank of L2 cache TO the network

  // this L2 bank -> a local L1 || mod-directory
  MessageBuffer responseFromL2Cache, network="To", virtual_network="4", ordered="false", vnet_type="response";
  // this L2 bank -> mod-directory
  MessageBuffer GlobalRequestFromL2Cache, network="To", virtual_network="2", ordered="false", vnet_type="request";
  // this L2 bank -> a local L1
  MessageBuffer L1RequestFromL2Cache, network="To", virtual_network="1", ordered="false", vnet_type="request";


  // FROM the network to this local bank of L2 cache

  // a local L1 || mod-directory -> this L2 bank
  MessageBuffer responseToL2Cache, network="From", virtual_network="4", ordered="false", vnet_type="response";
  MessageBuffer persistentToL2Cache, network="From", virtual_network="3", ordered="true", vnet_type="persistent";
  // mod-directory -> this L2 bank
  MessageBuffer GlobalRequestToL2Cache, network="From", virtual_network="2", ordered="false", vnet_type="request";
  // a local L1 -> this L2 bank
  MessageBuffer L1RequestToL2Cache, network="From", virtual_network="1", ordered="false", vnet_type="request";

  // STATES
  state_declaration(State, desc="L2 Cache states", default="L2Cache_State_I") {
    // Base states
    NP, AccessPermission:Invalid, desc="Not Present";
    I, AccessPermission:Invalid, desc="Idle";
    S, AccessPermission:Read_Only, desc="Shared, not present in any local L1s";
    O, AccessPermission:Read_Only, desc="Owned, not present in any L1s";
    M, AccessPermission:Read_Write, desc="Modified, not present in any L1s";

    // Locked states
    I_L, AccessPermission:Busy, "I^L", desc="Invalid, Locked";
    S_L, AccessPermission:Busy, "S^L", desc="Shared, Locked";
  }

  // EVENTS
  enumeration(Event, desc="Cache events") {

    // Requests
    L1_GETS,             desc="local L1 GETS request";
    L1_GETS_Last_Token,    desc="local L1 GETS request";
    L1_GETX,             desc="local L1 GETX request";
    L1_INV,              desc="L1 no longer has tokens";
    Transient_GETX,      desc="A GetX from another processor";
    Transient_GETS,      desc="A GetS from another processor";
    Transient_GETS_Last_Token,   desc="A GetS from another processor";

    // events initiated by this L2
    L2_Replacement,     desc="L2 Replacement", format="!r";

    // events of external L2 responses

    // Responses
    Writeback_Tokens,               desc="Received a writeback from L1 with only tokens (no data)";
    Writeback_Shared_Data,               desc="Received a writeback from L1 that includes clean data";
    Writeback_All_Tokens,    desc="Received a writeback from L1";
    Writeback_Owned,                desc="Received a writeback from L1";


    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";

    // Lock/Unlock
    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";
    Own_Lock_or_Unlock,  desc="This processor now has priority";
  }

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

  structure(DirEntry, desc="...") {
    Set Sharers,            desc="Set of the internal processors that want the block in shared state";
    bool exclusive, default="false", desc="if local exclusive is likely";
  }

  structure(PerfectCacheMemory, external="yes") {
    void allocate(Address);
    void deallocate(Address);
    DirEntry lookup(Address);
    bool isTagPresent(Address);
  }

  structure(PersistentTable, external="yes") {
    void persistentRequestLock(Address, MachineID, AccessType);
    void persistentRequestUnlock(Address, MachineID);
    MachineID findSmallest(Address);
    AccessType typeOfSmallest(Address);
    void markEntries(Address);
    bool isLocked(Address);
    int countStarvingForAddress(Address);
    int countReadStarvingForAddress(Address);
  }

  PersistentTable persistentTable;
  PerfectCacheMemory localDirectory, template="<L2Cache_DirEntry>";

  void set_cache_entry(AbstractCacheEntry b);
  void unset_cache_entry();

  Entry getCacheEntry(Address address), return_by_pointer="yes" {
    Entry cache_entry := static_cast(Entry, "pointer", L2cache.lookup(address));
    return cache_entry;
  }

  DataBlock getDataBlock(Address addr), return_by_ref="yes" {
    return getCacheEntry(addr).DataBlk;
  }

  int getTokens(Entry cache_entry) {
    if (is_valid(cache_entry)) {
      return cache_entry.Tokens;
    } else {
      return 0;
    }
  }

  State getState(Entry cache_entry, Address addr) {
    if (is_valid(cache_entry)) {
      return cache_entry.CacheState;
    } else if (persistentTable.isLocked(addr) == true) {
      return State:I_L;
    } else {
      return State:NP;
    }
  }

  void setState(Entry cache_entry, Address addr, State 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));

      // Make sure we have no tokens in L
      if ((state == State:I_L) ) {
        assert(cache_entry.Tokens == 0);
      }

      // in M and E you have all the tokens
      if (state == State:M ) {
        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 ) {
        assert(cache_entry.Tokens > 0);
      }

      // You have at least half the token in O-like states
      if (state == State:O ) {
        assert(cache_entry.Tokens > (max_tokens() / 2));
      }

      cache_entry.CacheState := state;
    }
  }

  AccessPermission getAccessPermission(Address addr) {
    Entry cache_entry := getCacheEntry(addr);
    if(is_valid(cache_entry)) {
      return L2Cache_State_to_permission(cache_entry.CacheState);
    }

    return AccessPermission:NotPresent;
  }

  void setAccessPermission(Entry cache_entry, Address addr, State state) {
    if (is_valid(cache_entry)) {
      cache_entry.changePermission(L2Cache_State_to_permission(state));
    }
  }

  void removeSharer(Address addr, NodeID id) {

    if (localDirectory.isTagPresent(addr)) {
      localDirectory[addr].Sharers.remove(id);
      if (localDirectory[addr].Sharers.count() == 0) {
        localDirectory.deallocate(addr);
      }
    }
  }

  bool sharersExist(Address addr) {
    if (localDirectory.isTagPresent(addr)) {
      if (localDirectory[addr].Sharers.count() > 0) {
        return true;
      }
      else {
        return false;
      }
    }
    else {
      return false;
    }
  }

  bool exclusiveExists(Address addr) {
    if (localDirectory.isTagPresent(addr)) {
      if (localDirectory[addr].exclusive == true) {
        return true;
      }
      else {
        return false;
      }
    }
    else {
      return false;
    }
  }

  // assumes that caller will check to make sure tag is present
  Set getSharers(Address addr) {
    return localDirectory[addr].Sharers;
  }

  void setNewWriter(Address addr, NodeID id) {
    if (localDirectory.isTagPresent(addr) == false) {
      localDirectory.allocate(addr);
    }
    localDirectory[addr].Sharers.clear();
    localDirectory[addr].Sharers.add(id);
    localDirectory[addr].exclusive := true;
  }

  void addNewSharer(Address addr, NodeID id) {
    if (localDirectory.isTagPresent(addr) == false) {
      localDirectory.allocate(addr);
    }
    localDirectory[addr].Sharers.add(id);
    // localDirectory[addr].exclusive := false;
  }

  void clearExclusiveBitIfExists(Address addr) {
    if (localDirectory.isTagPresent(addr) == true) {
      localDirectory[addr].exclusive := false;
    }
  }

  // ** OUT_PORTS **
  out_port(globalRequestNetwork_out, RequestMsg, GlobalRequestFromL2Cache);
  out_port(localRequestNetwork_out, RequestMsg, L1RequestFromL2Cache);
  out_port(responseNetwork_out, ResponseMsg, responseFromL2Cache);



  // ** IN_PORTS **

  // Persistent Network
  in_port(persistentNetwork_in, PersistentMsg, persistentToL2Cache) {
    if (persistentNetwork_in.isReady()) {
      peek(persistentNetwork_in, PersistentMsg) {
        assert(in_msg.Destination.isElement(machineID));

        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");
        }

        Entry cache_entry := getCacheEntry(in_msg.Addr);
        // React to the message based on the current state of the table
        if (persistentTable.isLocked(in_msg.Addr)) {

          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);
            } else {
              trigger(Event:Persistent_GETS, in_msg.Addr, cache_entry);
            }
          } else {
            trigger(Event:Persistent_GETX, in_msg.Addr, cache_entry);
          }
        }
        else {
            trigger(Event:Own_Lock_or_Unlock, in_msg.Addr, cache_entry);
        }
      }
    }
  }


  // Request Network
  in_port(requestNetwork_in, RequestMsg, GlobalRequestToL2Cache) {
    if (requestNetwork_in.isReady()) {
      peek(requestNetwork_in, RequestMsg) {
        assert(in_msg.Destination.isElement(machineID));

        Entry cache_entry := getCacheEntry(in_msg.Addr);
        if (in_msg.Type == CoherenceRequestType:GETX) {
            trigger(Event:Transient_GETX, in_msg.Addr, cache_entry);
        } else if (in_msg.Type == CoherenceRequestType:GETS) {
          if (getTokens(cache_entry) == 1) {
            trigger(Event:Transient_GETS_Last_Token, in_msg.Addr,
                    cache_entry);
          }
          else {
            trigger(Event:Transient_GETS, in_msg.Addr, cache_entry);
          }
        } else {
          error("Unexpected message");
        }
      }
    }
  }

  in_port(L1requestNetwork_in, RequestMsg, L1RequestToL2Cache) {
    if (L1requestNetwork_in.isReady()) {
      peek(L1requestNetwork_in, RequestMsg) {
        assert(in_msg.Destination.isElement(machineID));
        Entry cache_entry := getCacheEntry(in_msg.Addr);
        if (in_msg.Type == CoherenceRequestType:GETX) {
          trigger(Event:L1_GETX, in_msg.Addr, cache_entry);
        } else if (in_msg.Type == CoherenceRequestType:GETS) {
          if (getTokens(cache_entry) == 1 ||
              getTokens(cache_entry) == (max_tokens() / 2) + 1) {
            trigger(Event:L1_GETS_Last_Token, in_msg.Addr, cache_entry);
          }
          else {
            trigger(Event:L1_GETS, in_msg.Addr, cache_entry);
          }
        } else {
          error("Unexpected message");
        }
      }
    }
  }


  // Response Network
  in_port(responseNetwork_in, ResponseMsg, responseToL2Cache) {
    if (responseNetwork_in.isReady()) {
      peek(responseNetwork_in, ResponseMsg) {
        assert(in_msg.Destination.isElement(machineID));
        Entry cache_entry := getCacheEntry(in_msg.Addr);

        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);
          } else if (in_msg.Type == CoherenceResponseType:DATA_OWNER) {
            trigger(Event:Data_Owner, in_msg.Addr, cache_entry);
          } else if (in_msg.Type == CoherenceResponseType:DATA_SHARED) {
            trigger(Event:Data_Shared, in_msg.Addr, cache_entry);
          } else if (in_msg.Type == CoherenceResponseType:WB_TOKENS ||
                     in_msg.Type == CoherenceResponseType:WB_OWNED ||
                     in_msg.Type == CoherenceResponseType:WB_SHARED_DATA) {

            if (L2cache.cacheAvail(in_msg.Addr) || is_valid(cache_entry)) {

              // either room is available or the block is already present

              if (in_msg.Type == CoherenceResponseType:WB_TOKENS) {
                assert(in_msg.Dirty == false);
                trigger(Event:Writeback_Tokens, in_msg.Addr, cache_entry);
              } else if (in_msg.Type == CoherenceResponseType:WB_SHARED_DATA) {
                assert(in_msg.Dirty == false);
                trigger(Event:Writeback_Shared_Data, in_msg.Addr, cache_entry);
              }
              else if (in_msg.Type == CoherenceResponseType:WB_OWNED) {
                //assert(in_msg.Dirty == false);
                trigger(Event:Writeback_Owned, in_msg.Addr, cache_entry);
              }
            }
            else {
                trigger(Event:L2_Replacement,
                        L2cache.cacheProbe(in_msg.Addr),
                        getCacheEntry(L2cache.cacheProbe(in_msg.Addr)));
            }
          } else if (in_msg.Type == CoherenceResponseType:INV) {
            trigger(Event:L1_INV, in_msg.Addr, cache_entry);
          } 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);
          } else if (in_msg.Type == CoherenceResponseType:DATA_OWNER ||
                     in_msg.Type == CoherenceResponseType:DATA_SHARED) {
            trigger(Event:Data_All_Tokens, in_msg.Addr, cache_entry);
          } else if (in_msg.Type == CoherenceResponseType:WB_TOKENS ||
                     in_msg.Type == CoherenceResponseType:WB_OWNED ||
                     in_msg.Type == CoherenceResponseType:WB_SHARED_DATA) {
            if (L2cache.cacheAvail(in_msg.Addr) || is_valid(cache_entry)) {

              // either room is available or the block is already present

              if (in_msg.Type == CoherenceResponseType:WB_TOKENS) {
                assert(in_msg.Dirty == false);
                assert(  (getState(cache_entry, in_msg.Addr) != State:NP)
                      && (getState(cache_entry, in_msg.Addr) != State:I) );
                trigger(Event:Writeback_All_Tokens, in_msg.Addr, cache_entry);
              } else if (in_msg.Type == CoherenceResponseType:WB_SHARED_DATA) {
                assert(in_msg.Dirty == false);
                trigger(Event:Writeback_All_Tokens, in_msg.Addr, cache_entry);
              }
              else if (in_msg.Type == CoherenceResponseType:WB_OWNED) {
                trigger(Event:Writeback_All_Tokens, in_msg.Addr, cache_entry);
              }
            }
            else {
                trigger(Event:L2_Replacement,
                        L2cache.cacheProbe(in_msg.Addr),
                        getCacheEntry(L2cache.cacheProbe(in_msg.Addr)));
            }
          } else if (in_msg.Type == CoherenceResponseType:INV) {
            trigger(Event:L1_INV, in_msg.Addr, cache_entry);
          } else {
            DPRINTF(RubySlicc, "%s\n", in_msg.Type);
            error("Unexpected message");
          }
        }
      }
    }
  }


  // ACTIONS

  action(a_broadcastLocalRequest, "a", desc="broadcast local request globally") {

    peek(L1requestNetwork_in, RequestMsg) {

     // if this is a retry or no local sharers, broadcast normally

     // if (in_msg.RetryNum > 0 || (in_msg.Type == CoherenceRequestType:GETX && exclusiveExists(in_msg.Addr) == false) || (in_msg.Type == CoherenceRequestType:GETS && sharersExist(in_msg.Addr) == false)) {
        enqueue(globalRequestNetwork_out, RequestMsg, latency=l2_request_latency) {
           out_msg.Addr := in_msg.Addr;
           out_msg.Type := in_msg.Type;
           out_msg.Requestor := in_msg.Requestor;
           out_msg.RetryNum := in_msg.RetryNum;

           //
           // If a statically shared L2 cache, then no other L2 caches can
           // store the block
           //
           //out_msg.Destination.broadcast(MachineType:L2Cache);
           //out_msg.Destination.addNetDest(getAllPertinentL2Banks(address));
           //out_msg.Destination.remove(map_L1CacheMachId_to_L2Cache(address, in_msg.Requestor));

           out_msg.Destination.add(map_Address_to_Directory(address));
           out_msg.MessageSize := MessageSizeType:Request_Control;
           out_msg.AccessMode := in_msg.AccessMode;
           out_msg.Prefetch := in_msg.Prefetch;
        } //enqueue
      // } // if

         //profile_filter_action(0);
    } // peek
  } //action


  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, latency="1") {
        out_msg.Addr := address;
        out_msg.Type := in_msg.Type;
        out_msg.Sender := machineID;
        out_msg.Destination.add(map_Address_to_Directory(address));
        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_cleanReplacement, "c", desc="Issue clean writeback") {
    assert(is_valid(cache_entry));
    if (cache_entry.Tokens > 0) {
      enqueue(responseNetwork_out, ResponseMsg, latency=l2_response_latency) {
        out_msg.Addr := address;
        out_msg.Type := CoherenceResponseType:ACK;
        out_msg.Sender := machineID;
        out_msg.Destination.add(map_Address_to_Directory(address));
        out_msg.Tokens := cache_entry.Tokens;
        out_msg.MessageSize := MessageSizeType:Writeback_Control;
      }
      cache_entry.Tokens := 0;
    }
  }

  action(cc_dirtyReplacement, "\c", desc="Issue dirty writeback") {
    assert(is_valid(cache_entry));
    enqueue(responseNetwork_out, ResponseMsg, latency=l2_response_latency) {
      out_msg.Addr := address;
      out_msg.Sender := machineID;
      out_msg.Destination.add(map_Address_to_Directory(address));
      out_msg.Tokens := cache_entry.Tokens;
      out_msg.DataBlk := cache_entry.DataBlk;
      out_msg.Dirty := cache_entry.Dirty;

      if (cache_entry.Dirty) {
        out_msg.MessageSize := MessageSizeType:Writeback_Data;
        out_msg.Type := CoherenceResponseType:DATA_OWNER;
      } else {
        out_msg.MessageSize := MessageSizeType:Writeback_Control;
        out_msg.Type := CoherenceResponseType:ACK_OWNER;
      }
    }
    cache_entry.Tokens := 0;
  }

  action(d_sendDataWithTokens, "d", desc="Send data and a token from cache to requestor") {
    peek(requestNetwork_in, RequestMsg) {
      assert(is_valid(cache_entry));
      if (cache_entry.Tokens > (N_tokens + (max_tokens() / 2))) {
        enqueue(responseNetwork_out, ResponseMsg, latency=l2_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 := false;
          out_msg.MessageSize := MessageSizeType:Response_Data;
        }
        cache_entry.Tokens := cache_entry.Tokens - N_tokens;
      }
      else {
        enqueue(responseNetwork_out, ResponseMsg, latency=l2_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 := false;
          out_msg.MessageSize := MessageSizeType:Response_Data;
        }
        cache_entry.Tokens := cache_entry.Tokens - 1;
      }
    }
  }

  action(dd_sendDataWithAllTokens, "\d", desc="Send data and all tokens from cache to requestor") {
    assert(is_valid(cache_entry));
    peek(requestNetwork_in, RequestMsg) {
      enqueue(responseNetwork_out, ResponseMsg, latency=l2_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 >= 1);
        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(e_sendAckWithCollectedTokens, "e", desc="Send ack with the tokens we've collected thus far.") {
    assert(is_valid(cache_entry));
    if (cache_entry.Tokens > 0) {
      enqueue(responseNetwork_out, ResponseMsg, latency=l2_response_latency) {
        out_msg.Addr := address;
        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.MessageSize := MessageSizeType:Response_Control;
      }
    }
    cache_entry.Tokens := 0;
  }

  action(ee_sendDataWithAllTokens, "\e", desc="Send data and all tokens from cache to starver") {
    assert(is_valid(cache_entry));
    enqueue(responseNetwork_out, ResponseMsg, latency=l2_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 >= 1);
      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_sendAckWithAllButOneTokens, "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, latency=l2_response_latency) {
        out_msg.Addr := address;
        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 - 1;
        out_msg.MessageSize := MessageSizeType:Response_Control;
      }
    }
    cache_entry.Tokens := 1;
  }

  action(ff_sendDataWithAllButOneTokens, "\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, latency=l2_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 - 1;
        out_msg.DataBlk := cache_entry.DataBlk;
        out_msg.Dirty := cache_entry.Dirty;
        out_msg.MessageSize := MessageSizeType:Response_Data;
    }
    cache_entry.Tokens := 1;
  }

  action(fa_sendDataWithAllTokens, "fa", 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, latency=l2_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;
        out_msg.DataBlk := cache_entry.DataBlk;
        out_msg.Dirty := cache_entry.Dirty;
        out_msg.MessageSize := MessageSizeType:Response_Data;
    }
    cache_entry.Tokens := 0;
  }



  action(gg_bounceResponseToStarver, "\g", desc="Redirect response to starving processor") {
    // assert(persistentTable.isLocked(address));
    peek(responseNetwork_in, ResponseMsg) {
      // FIXME, should use a 3rd vnet in some cases
      enqueue(responseNetwork_out, ResponseMsg, latency="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(gg_bounceWBSharedToStarver, "\gg", desc="Redirect response to starving processor") {
    //assert(persistentTable.isLocked(address));
    peek(responseNetwork_in, ResponseMsg) {
      // FIXME, should use a 3rd vnet in some cases
      enqueue(responseNetwork_out, ResponseMsg, latency="1") {
        out_msg.Addr := address;
        if (in_msg.Type == CoherenceResponseType:WB_SHARED_DATA) {
          out_msg.Type := CoherenceResponseType:DATA_SHARED;
        } else {
          assert(in_msg.Tokens < (max_tokens() / 2));
          out_msg.Type := CoherenceResponseType:ACK;
        }
        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(gg_bounceWBOwnedToStarver, "\ggg", desc="Redirect response to starving processor") {
    // assert(persistentTable.isLocked(address));
    peek(responseNetwork_in, ResponseMsg) {
      // FIXME, should use a 3rd vnet in some cases
      enqueue(responseNetwork_out, ResponseMsg, latency="1") {
        out_msg.Addr := address;
        out_msg.Type := CoherenceResponseType:DATA_OWNER;
        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_updateFilterFromL1HintOrWB, "h", desc="update filter from received writeback") {
    peek(responseNetwork_in, ResponseMsg) {
      removeSharer(in_msg.Addr, machineIDToNodeID(in_msg.Sender));
    }
  }

  action(j_forwardTransientRequestToLocalSharers, "j", desc="Forward external transient request to local sharers") {
    peek(requestNetwork_in, RequestMsg) {
      if (filtering_enabled == true && in_msg.RetryNum == 0 && sharersExist(in_msg.Addr) == false) {
        //profile_filter_action(1);
        DPRINTF(RubySlicc, "filtered message, Retry Num: %d\n",
                in_msg.RetryNum);
      }
      else {
        enqueue(localRequestNetwork_out, RequestMsg, latency=l2_response_latency ) {
           out_msg.Addr := in_msg.Addr;
           out_msg.Requestor := in_msg.Requestor;

           //
           // Currently assuming only one chip so all L1s are local
           //
           //out_msg.Destination := getLocalL1IDs(machineID);
           out_msg.Destination.broadcast(MachineType:L1Cache);
           out_msg.Destination.remove(in_msg.Requestor);

           out_msg.Type := in_msg.Type;
           out_msg.isLocal := false;
           out_msg.MessageSize := MessageSizeType:Broadcast_Control;
           out_msg.AccessMode := in_msg.AccessMode;
           out_msg.Prefetch := in_msg.Prefetch;
        }
        //profile_filter_action(0);
      }
    }
  }

  action(k_dataFromL2CacheToL1Requestor, "k", desc="Send data and a token from cache to L1 requestor") {
    peek(L1requestNetwork_in, RequestMsg) {
      assert(is_valid(cache_entry));
      assert(cache_entry.Tokens > 0);
      //enqueue(responseIntraChipL2Network_out, ResponseMsg, latency="L2_to_L1_RESPONSE_LATENCY") {
      enqueue(responseNetwork_out, ResponseMsg, latency=l2_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.DataBlk := cache_entry.DataBlk;
        out_msg.Dirty := false;
        out_msg.MessageSize := MessageSizeType:ResponseL2hit_Data;
        out_msg.Tokens := 1;
      }
      cache_entry.Tokens := cache_entry.Tokens - 1;
    }
  }

  action(k_dataOwnerFromL2CacheToL1Requestor, "\k", desc="Send data and a token from cache to L1 requestor") {
    peek(L1requestNetwork_in, RequestMsg) {
      assert(is_valid(cache_entry));
      assert(cache_entry.Tokens == (max_tokens() / 2) + 1);
      enqueue(responseNetwork_out, ResponseMsg, latency=l2_response_latency) {
        out_msg.Addr := address;
        out_msg.Type := CoherenceResponseType:DATA_OWNER;
        out_msg.Sender := machineID;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.DataBlk := cache_entry.DataBlk;
        out_msg.Dirty := cache_entry.Dirty;
        out_msg.MessageSize := MessageSizeType:ResponseL2hit_Data;
        out_msg.Tokens := cache_entry.Tokens;
      }
      cache_entry.Tokens := 0;
    }
  }

  action(k_dataAndAllTokensFromL2CacheToL1Requestor, "\kk", desc="Send data and a token from cache to L1 requestor") {
    peek(L1requestNetwork_in, RequestMsg) {
      assert(is_valid(cache_entry));
//      assert(cache_entry.Tokens == max_tokens());
      //enqueue(responseIntraChipL2Network_out, ResponseMsg, latency="L2_to_L1_RESPONSE_LATENCY") {
      enqueue(responseNetwork_out, ResponseMsg, latency=l2_response_latency) {
        out_msg.Addr := address;
        out_msg.Type := CoherenceResponseType:DATA_OWNER;
        out_msg.Sender := machineID;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.DataBlk := cache_entry.DataBlk;
        out_msg.Dirty := cache_entry.Dirty;
        out_msg.MessageSize := MessageSizeType:ResponseL2hit_Data;
        //out_msg.Tokens := max_tokens();
        out_msg.Tokens := cache_entry.Tokens;
      }
      cache_entry.Tokens := 0;
    }
  }

  action(l_popPersistentQueue, "l", desc="Pop persistent queue.") {
    persistentNetwork_in.dequeue();
  }

  action(m_popRequestQueue, "m", desc="Pop request queue.") {
    requestNetwork_in.dequeue();
  }

  action(n_popResponseQueue, "n", desc="Pop response queue") {
    responseNetwork_in.dequeue();
  }

  action(o_popL1RequestQueue, "o", desc="Pop L1 request queue.") {
    L1requestNetwork_in.dequeue();
  }


  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);
      cache_entry.Tokens := cache_entry.Tokens + in_msg.Tokens;

      // this should ideally be in u_writeDataToCache, but Writeback_All_Tokens
      //  may not trigger this action.
      if ( (in_msg.Type == CoherenceResponseType:DATA_OWNER || in_msg.Type == CoherenceResponseType:WB_OWNED) && in_msg.Dirty) {
        cache_entry.Dirty := true;
      }
    }
  }

  action(r_markNewSharer, "r", desc="Mark the new local sharer from local request message") {
    peek(L1requestNetwork_in, RequestMsg) {
      if (machineIDToMachineType(in_msg.Requestor) == MachineType:L1Cache) {
        if (in_msg.Type == CoherenceRequestType:GETX) {
          setNewWriter(in_msg.Addr, machineIDToNodeID(in_msg.Requestor));
        } else if (in_msg.Type == CoherenceRequestType:GETS) {
          addNewSharer(in_msg.Addr, machineIDToNodeID(in_msg.Requestor));
        }
      }
    }
  }

  action(r_clearExclusive, "\rrr", desc="clear exclusive bit") {
    clearExclusiveBitIfExists(address);
  }

  action(r_setMRU, "\rr", desc="manually set the MRU bit for cache line" ) {
    peek(L1requestNetwork_in, RequestMsg) {
      if ((machineIDToMachineType(in_msg.Requestor) == MachineType:L1Cache) &&
          (is_valid(cache_entry))) {
        L2cache.setMRU(address);
      }
    }
  }

  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, latency=l2_response_latency) {
          out_msg.Addr := address;
          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.MessageSize := MessageSizeType:Response_Control;
        }
      }
    }
    cache_entry.Tokens := 0;
  }

  action(tt_sendLocalAckWithCollectedTokens, "tt", desc="Send ack with the tokens we've collected thus far.") {
    assert(is_valid(cache_entry));
    if (cache_entry.Tokens > 0) {
      peek(L1requestNetwork_in, RequestMsg) {
        enqueue(responseNetwork_out, ResponseMsg, latency=l2_response_latency) {
          out_msg.Addr := address;
          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.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(vv_allocateL2CacheBlock, "\v", desc="Set L2 cache tag equal to tag of block B.") {
    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(uu_profileMiss, "\um", desc="Profile the demand miss") {
      ++L2cache.demand_misses;
  }

  action(uu_profileHit, "\uh", desc="Profile the demand hit") {
      ++L2cache.demand_hits;
  }

  action(w_assertIncomingDataAndCacheDataMatch, "w", desc="Assert that the incoming data and the data in the cache match") {
    peek(responseNetwork_in, ResponseMsg) {
      if (in_msg.Type != CoherenceResponseType:ACK &&
          in_msg.Type != CoherenceResponseType:WB_TOKENS) {
        assert(is_valid(cache_entry));
        assert(cache_entry.DataBlk == in_msg.DataBlk);
      }
    }
  }


  //*****************************************************
  // TRANSITIONS
  //*****************************************************

  transition({NP, I, S, O, M, I_L, S_L}, L1_INV) {

    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition({NP, I, S, O, M}, Own_Lock_or_Unlock) {
    l_popPersistentQueue;
  }


  // Transitions from NP

  transition(NP, {Transient_GETX, Transient_GETS}) {
    // forward message to local sharers
    r_clearExclusive;
    j_forwardTransientRequestToLocalSharers;
    m_popRequestQueue;
  }


  transition(NP,  {L1_GETS, L1_GETX}) {
    a_broadcastLocalRequest;
    r_markNewSharer;
    uu_profileMiss;
    o_popL1RequestQueue;
  }

  transition(NP, {Ack, Data_Shared, Data_Owner, Data_All_Tokens}) {
    bb_bounceResponse;
    n_popResponseQueue;
  }

  transition(NP, Writeback_Shared_Data, S) {
    vv_allocateL2CacheBlock;
    u_writeDataToCache;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(NP, Writeback_Tokens, I) {
    vv_allocateL2CacheBlock;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(NP, Writeback_All_Tokens, M) {
    vv_allocateL2CacheBlock;
    u_writeDataToCache;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(NP, Writeback_Owned, O) {
    vv_allocateL2CacheBlock;
    u_writeDataToCache;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }


  transition(NP,
             {Persistent_GETX, Persistent_GETS, Persistent_GETS_Last_Token},
             I_L) {
    l_popPersistentQueue;
  }

  // Transitions from Idle

  transition(I, {L1_GETS, L1_GETS_Last_Token}) {
    a_broadcastLocalRequest;
    tt_sendLocalAckWithCollectedTokens;  // send any tokens we have collected
    r_markNewSharer;
    uu_profileMiss;
    o_popL1RequestQueue;
  }

  transition(I, L1_GETX) {
    a_broadcastLocalRequest;
    tt_sendLocalAckWithCollectedTokens; // send any tokens we have collected
    r_markNewSharer;
    uu_profileMiss;
    o_popL1RequestQueue;
  }

  transition(I, L2_Replacement) {
    c_cleanReplacement; // Only needed in some cases
    rr_deallocateL2CacheBlock;
  }

  transition(I, {Transient_GETX, Transient_GETS, Transient_GETS_Last_Token}) {
    r_clearExclusive;
    t_sendAckWithCollectedTokens;
    j_forwardTransientRequestToLocalSharers;
    m_popRequestQueue;
  }

  transition(I,
             {Persistent_GETX, Persistent_GETS, Persistent_GETS_Last_Token},
             I_L) {
    e_sendAckWithCollectedTokens;
    l_popPersistentQueue;
  }


  transition(I, Ack) {
    q_updateTokensFromResponse;
    n_popResponseQueue;
  }

  transition(I, Data_Shared, S) {
    u_writeDataToCache;
    q_updateTokensFromResponse;
    n_popResponseQueue;
  }

  transition(I, Writeback_Shared_Data, S) {
    u_writeDataToCache;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(I, Writeback_Tokens) {
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(I, Data_Owner, O) {
    u_writeDataToCache;
    q_updateTokensFromResponse;
    n_popResponseQueue;
  }

  transition(I, Writeback_Owned, O) {
    u_writeDataToCache;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(I, Data_All_Tokens, M) {
    u_writeDataToCache;
    q_updateTokensFromResponse;
    n_popResponseQueue;
  }


  transition(I, Writeback_All_Tokens, M) {
    u_writeDataToCache;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  // Transitions from Shared

  transition(S, L2_Replacement, I) {
    c_cleanReplacement;
    rr_deallocateL2CacheBlock;
  }

  transition(S, Transient_GETX, I) {
    r_clearExclusive;
    t_sendAckWithCollectedTokens;
    j_forwardTransientRequestToLocalSharers;
    m_popRequestQueue;
  }

  transition(S, {Transient_GETS, Transient_GETS_Last_Token}) {
    j_forwardTransientRequestToLocalSharers;
    r_clearExclusive;
    m_popRequestQueue;
  }

  transition(S, Persistent_GETX, I_L) {
    e_sendAckWithCollectedTokens;
    l_popPersistentQueue;
  }


  transition(S, {Persistent_GETS, Persistent_GETS_Last_Token}, S_L) {
    f_sendAckWithAllButOneTokens;
    l_popPersistentQueue;
  }


  transition(S, Ack) {
    q_updateTokensFromResponse;
    n_popResponseQueue;
  }

  transition(S, Data_Shared) {
    w_assertIncomingDataAndCacheDataMatch;
    q_updateTokensFromResponse;
    n_popResponseQueue;
  }

  transition(S, Writeback_Tokens) {
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(S, Writeback_Shared_Data) {
    w_assertIncomingDataAndCacheDataMatch;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }


  transition(S, Data_Owner, O) {
    w_assertIncomingDataAndCacheDataMatch;
    q_updateTokensFromResponse;
    n_popResponseQueue;
  }

  transition(S, Writeback_Owned, O) {
    w_assertIncomingDataAndCacheDataMatch;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(S, Data_All_Tokens, M) {
    w_assertIncomingDataAndCacheDataMatch;
    q_updateTokensFromResponse;
    n_popResponseQueue;
  }

  transition(S, Writeback_All_Tokens,  M) {
    w_assertIncomingDataAndCacheDataMatch;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(S, L1_GETX, I) {
    a_broadcastLocalRequest;
    tt_sendLocalAckWithCollectedTokens;
    r_markNewSharer;
    r_setMRU;
    uu_profileMiss;
    o_popL1RequestQueue;
  }


  transition(S, L1_GETS) {
    k_dataFromL2CacheToL1Requestor;
    r_markNewSharer;
    r_setMRU;
    uu_profileHit;
    o_popL1RequestQueue;
  }

  transition(S, L1_GETS_Last_Token, I) {

    k_dataFromL2CacheToL1Requestor;
    r_markNewSharer;
    r_setMRU;
    uu_profileHit;
    o_popL1RequestQueue;
  }

  // Transitions from Owned

  transition(O, L2_Replacement, I) {
    cc_dirtyReplacement;
    rr_deallocateL2CacheBlock;
  }

  transition(O, Transient_GETX, I) {
    r_clearExclusive;
    dd_sendDataWithAllTokens;
    j_forwardTransientRequestToLocalSharers;
    m_popRequestQueue;
  }

  transition(O, Persistent_GETX, I_L) {
    ee_sendDataWithAllTokens;
    l_popPersistentQueue;
  }

  transition(O, Persistent_GETS, S_L) {
    ff_sendDataWithAllButOneTokens;
    l_popPersistentQueue;
  }

  transition(O, Persistent_GETS_Last_Token, I_L) {
    fa_sendDataWithAllTokens;
    l_popPersistentQueue;
  }

  transition(O, Transient_GETS) {
    // send multiple tokens
    r_clearExclusive;
    d_sendDataWithTokens;
    m_popRequestQueue;
  }

  transition(O, Transient_GETS_Last_Token) {
    // WAIT FOR IT TO GO PERSISTENT
    r_clearExclusive;
    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, {Writeback_Tokens, Writeback_Shared_Data}) {
    w_assertIncomingDataAndCacheDataMatch;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(O, Data_All_Tokens, M) {
    w_assertIncomingDataAndCacheDataMatch;
    q_updateTokensFromResponse;
    n_popResponseQueue;
  }

  transition(O, Writeback_All_Tokens, M) {
    w_assertIncomingDataAndCacheDataMatch;
    q_updateTokensFromResponse;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(O, L1_GETS) {
    k_dataFromL2CacheToL1Requestor;
    r_markNewSharer;
    r_setMRU;
    uu_profileHit;
    o_popL1RequestQueue;
  }

  transition(O, L1_GETS_Last_Token, I) {
    k_dataOwnerFromL2CacheToL1Requestor;
    r_markNewSharer;
    r_setMRU;
    uu_profileHit;
    o_popL1RequestQueue;
  }

  transition(O, L1_GETX, I) {
    a_broadcastLocalRequest;
    k_dataAndAllTokensFromL2CacheToL1Requestor;
    r_markNewSharer;
    r_setMRU;
    uu_profileMiss;
    o_popL1RequestQueue;
  }

  // Transitions from M

  transition(M, L2_Replacement, I) {
    cc_dirtyReplacement;
    rr_deallocateL2CacheBlock;
  }

  // MRM_DEBUG:  Give up all tokens even for GETS? ???
  transition(M, {Transient_GETX, Transient_GETS}, I) {
    r_clearExclusive;
    dd_sendDataWithAllTokens;
    m_popRequestQueue;
  }

  transition(M, {Persistent_GETS, Persistent_GETX}, I_L) {
    ee_sendDataWithAllTokens;
    l_popPersistentQueue;
  }


  transition(M, L1_GETS, O) {
    k_dataFromL2CacheToL1Requestor;
    r_markNewSharer;
    r_setMRU;
    uu_profileHit;
    o_popL1RequestQueue;
  }

  transition(M, L1_GETX, I) {
    k_dataAndAllTokensFromL2CacheToL1Requestor;
    r_markNewSharer;
    r_setMRU;
    uu_profileHit;
    o_popL1RequestQueue;
  }


  //Transitions from locked states

  transition({I_L, S_L}, Ack) {
    gg_bounceResponseToStarver;
    n_popResponseQueue;
  }

  transition({I_L, S_L}, {Data_Shared, Data_Owner, Data_All_Tokens}) {
    gg_bounceResponseToStarver;
    n_popResponseQueue;
  }

  transition({I_L, S_L}, {Writeback_Tokens, Writeback_Shared_Data}) {
    gg_bounceWBSharedToStarver;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition({I_L, S_L}, {Writeback_Owned, Writeback_All_Tokens}) {
    gg_bounceWBOwnedToStarver;
    h_updateFilterFromL1HintOrWB;
    n_popResponseQueue;
  }

  transition(S_L, L2_Replacement, I) {
    c_cleanReplacement;
    rr_deallocateL2CacheBlock;
  }

  transition(I_L, L2_Replacement, I) {
    rr_deallocateL2CacheBlock;
  }

  transition(I_L, Own_Lock_or_Unlock, I) {
    l_popPersistentQueue;
  }

  transition(S_L, Own_Lock_or_Unlock, S) {
    l_popPersistentQueue;
  }

  transition({I_L, S_L}, {Transient_GETS_Last_Token, Transient_GETS, Transient_GETX}) {
    r_clearExclusive;
    m_popRequestQueue;
  }

  transition(I_L, {L1_GETX, L1_GETS}) {
    a_broadcastLocalRequest;
    r_markNewSharer;
    uu_profileMiss;
    o_popL1RequestQueue;
  }

  transition(S_L, L1_GETX, I_L) {
    a_broadcastLocalRequest;
    tt_sendLocalAckWithCollectedTokens;
    r_markNewSharer;
    r_setMRU;
    uu_profileMiss;
    o_popL1RequestQueue;
  }

  transition(S_L, L1_GETS) {
    k_dataFromL2CacheToL1Requestor;
    r_markNewSharer;
    r_setMRU;
    uu_profileHit;
    o_popL1RequestQueue;
  }

  transition(S_L, L1_GETS_Last_Token, I_L) {
    k_dataFromL2CacheToL1Requestor;
    r_markNewSharer;
    r_setMRU;
    uu_profileHit;
    o_popL1RequestQueue;
  }

  transition(S_L, Persistent_GETX, I_L) {
    e_sendAckWithCollectedTokens;
    l_popPersistentQueue;
  }

  transition(S_L, {Persistent_GETS, Persistent_GETS_Last_Token}) {
    l_popPersistentQueue;
  }

  transition(I_L, {Persistent_GETX, Persistent_GETS}) {
    l_popPersistentQueue;
  }
}