/*
 * Copyright (c) 2012-2015 Advanced Micro Devices, Inc.
 * All rights reserved.
 *
 * For use for simulation and test purposes only
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. Neither the name of the copyright holder 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 HOLDER 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.
 *
 * Author: Jason Power
 */

machine(MachineType:RegionDir, "Region Directory for AMD_Base-like protocol")
: CacheMemory *cacheMemory; // stores only region addresses. Must set block size same as below
  NodeID cpuRegionBufferNum;
  NodeID gpuRegionBufferNum;
  int blocksPerRegion := 64; // 4k regions
  Cycles toDirLatency := 10;    // Latency to fwd requests and send invs to directory
  bool always_migrate := "False";
  bool sym_migrate := "False";
  bool asym_migrate := "False";
  bool noTCCdir := "False";
  int TCC_select_num_bits := 1;

  // To the directory
  MessageBuffer * requestToDir, network="To", virtual_network="5", vnet_type="request";

  // To the region buffers
  MessageBuffer * notifyToRBuffer, network="To", virtual_network="7", vnet_type="request";
  MessageBuffer * probeToRBuffer, network="To", virtual_network="8", vnet_type="request";

  // From the region buffers
  MessageBuffer * responseFromRBuffer, network="From", virtual_network="2", vnet_type="response";
  MessageBuffer * requestFromRegBuf, network="From", virtual_network="0", vnet_type="request";

  MessageBuffer * triggerQueue;
{

  // States
  state_declaration(State, desc="Region states", default="RegionDir_State_NP") {
    NP, AccessPermission:Invalid,       desc="Not present in region directory";
    P,  AccessPermission:Invalid,       desc="Region is private to owner";
    S,  AccessPermission:Invalid,       desc="Region is shared between CPU and GPU";

    P_NP,  AccessPermission:Invalid,    desc="Evicting the region";
    NP_P,  AccessPermission:Invalid,    desc="Must wait for ack from R-buf";
    NP_S,  AccessPermission:Invalid,    desc="Must wait for ack from R-buf";
    P_P,   AccessPermission:Invalid,    desc="Waiting for ack from R-buf";
    S_S,   AccessPermission:Invalid,    desc="Waiting for ack from R-buf";
    P_S,   AccessPermission:Invalid,    desc="Downgrading the region";
    S_P,   AccessPermission:Invalid,    desc="Upgrading the region";
    P_AS,  AccessPermission:Invalid,    desc="Sent invalidates, waiting for acks";
    S_AP,  AccessPermission:Invalid,    desc="Sent invalidates, waiting for acks";
    P_AP,  AccessPermission:Invalid,    desc="Sent invalidates, waiting for acks";

    SP_NP_W, AccessPermission:Invalid,   desc="Last sharer writing back, waiting for ack";
    S_W,   AccessPermission:Invalid,   desc="Sharer writing back, waiting for ack";

    P_AP_W, AccessPermission:Invalid,   desc="Fwded request to dir, waiting for ack";
    P_AS_W, AccessPermission:Invalid,   desc="Fwded request to dir, waiting for ack";
    S_AP_W, AccessPermission:Invalid,   desc="Fwded request to dir, waiting for ack";
  }

  enumeration(Event, desc="Region directory events") {
    SendInv,        desc="Send inv message to any machine that has a region buffer";
    SendUpgrade,    desc="Send upgrade message to any machine that has a region buffer";
    SendDowngrade,  desc="Send downgrade message to any machine that has a region buffer";

    Evict,          desc="Evict this region";

    UpgradeRequest, desc="Request from r-buf for an upgrade";
    SharedRequest,  desc="Request from r-buf for read";
    PrivateRequest, desc="Request from r-buf for write";

    InvAckCore,     desc="Ack from region buffer to order the invalidate";
    InvAckCoreNoShare,     desc="Ack from region buffer to order the invalidate, and it does not have the region";
    CPUPrivateAck,  desc="Ack from region buffer to order private notification";

    LastAck,      desc="Done eviciting all the blocks";

    StaleCleanWbRequest, desc="stale clean writeback reqeust";
    StaleCleanWbRequestNoShare, desc="stale clean wb req from a cache which should be removed from sharers";
    CleanWbRequest, desc="clean writeback reqeust, multiple sharers";
    CleanWbRequest_LastSharer, desc="clean writeback reqeust, last sharer";
    WritebackAck,   desc="Writeback Ack from region buffer";
    DirReadyAck,   desc="Directory is ready, waiting Ack from region buffer";

    TriggerInv,   desc="trigger invalidate message";
    TriggerDowngrade, desc="trigger downgrade message";
  }

  enumeration(RequestType, desc="To communicate stats from transitions to recordStats") {
    DataArrayRead,    desc="Read the data array";
    DataArrayWrite,   desc="Write the data array";
    TagArrayRead,     desc="Read the data array";
    TagArrayWrite,    desc="Write the data array";
  }

  structure(BoolVec, external="yes") {
    bool at(int);
    void resize(int);
    void clear();
  }

  structure(Entry, desc="Region entry", interface="AbstractCacheEntry") {
    Addr addr,        desc="Base address of this region";
    NetDest Sharers,        desc="Set of machines that are sharing, but not owners";
    State RegionState,      desc="Region state";
    DataBlock DataBlk,      desc="Data for the block (always empty in region dir)";
    MachineID Owner,        desc="Machine which owns all blocks in this region";
    Cycles ProbeStart,        desc="Time when the first probe request was issued";
    bool LastWriten, default="false", desc="The last time someone accessed this region, it wrote it";
    bool LastWritenByCpu, default="false", desc="The last time the CPU accessed this region, it wrote it";
    bool LastWritenByGpu, default="false", desc="The last time the GPU accessed this region, it wrote it";
  }

  structure(TBE, desc="...") {
    State TBEState,         desc="Transient state";
    MachineID Owner,        desc="Machine which owns all blocks in this region";
    NetDest Sharers,        desc="Set of machines to send evicts";
    int NumValidBlocks,     desc="Number of blocks valid so we don't have to count a BoolVec";
    bool AllAcksReceived,   desc="Got all necessary acks from dir";
    CoherenceRequestType MsgType, desc="Msg type for the evicts could be inv or dwngrd";
    Cycles ProbeRequestTime, default="Cycles(0)", desc="Start of probe request";
    Cycles InitialRequestTime, default="Cycles(0)", desc="To forward back on out msg";
    Addr DemandAddress, desc="Demand address from original request";
    uint64_t probe_id,        desc="probe id for lifetime profiling";
  }

  structure(TBETable, external="yes") {
    TBE lookup(Addr);
    void allocate(Addr);
    void deallocate(Addr);
    bool isPresent(Addr);
  }

  // Stores only region addresses
  TBETable TBEs, template="<RegionDir_TBE>", constructor="m_number_of_TBEs";
  int TCC_select_low_bit, default="RubySystem::getBlockSizeBits()";

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

  int blockBits,  default="RubySystem::getBlockSizeBits()";
  int blockBytes, default="RubySystem::getBlockSizeBytes()";
  int regionBits, default="log2(m_blocksPerRegion)";

  // Functions

  MachineID getCoreMachine(MachineID rBuf, Addr address) {
    if (machineIDToNodeID(rBuf) == cpuRegionBufferNum) {
      return createMachineID(MachineType:CorePair, intToID(0));
    } else if (machineIDToNodeID(rBuf) == gpuRegionBufferNum) {
      if (noTCCdir) {
        return mapAddressToRange(address,MachineType:TCC,
                                    TCC_select_low_bit, TCC_select_num_bits);
      } else {
        return createMachineID(MachineType:TCCdir, intToID(0));
      }
    } else {
      error("Unexpected region buffer number");
    }
  }

  bool isCpuMachine(MachineID rBuf) {
    if (machineIDToNodeID(rBuf) == cpuRegionBufferNum) {
      return true;
    } else if (machineIDToNodeID(rBuf) == gpuRegionBufferNum) {
      return false;
    } else {
      error("Unexpected region buffer number");
    }
  }

  bool symMigrate(Entry cache_entry) {
      return cache_entry.LastWriten;
  }

  bool asymMigrate(Entry cache_entry, MachineID requestor) {
      if (isCpuMachine(requestor)) {
          return cache_entry.LastWritenByCpu;
      } else {
          return cache_entry.LastWritenByGpu;
      }
  }

  int getRegionOffset(Addr addr) {
    if (blocksPerRegion > 1) {
      Addr offset := bitSelect(addr, blockBits, regionBits+blockBits-1);
      int ret := addressToInt(offset);
      assert(ret < blocksPerRegion);
      return ret;
    } else {
      return 0;
    }
  }

  Addr getRegionBase(Addr addr) {
    return maskLowOrderBits(addr, blockBits+regionBits);
  }

  Addr getNextBlock(Addr addr) {
    Addr a := addr;
    makeNextStrideAddress(a, 1);
    return a;
  }

  bool presentOrAvail(Addr addr) {
    DPRINTF(RubySlicc, "Present? %s, avail? %s\n", cacheMemory.isTagPresent(getRegionBase(addr)), cacheMemory.cacheAvail(getRegionBase(addr)));
    return cacheMemory.isTagPresent(getRegionBase(addr)) || cacheMemory.cacheAvail(getRegionBase(addr));
  }

  // Returns a region entry!
  Entry getCacheEntry(Addr addr), return_by_pointer="yes" {
    return static_cast(Entry, "pointer", cacheMemory.lookup(getRegionBase(addr)));
  }

  TBE getTBE(Addr addr), return_by_pointer="yes" {
    return TBEs.lookup(getRegionBase(addr));
  }

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

  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.RegionState;
    }
    return State:NP;
  }

  void setState(TBE tbe, Entry cache_entry, Addr addr, State state) {
    if (is_valid(tbe)) {
        tbe.TBEState := state;
    }
    if (is_valid(cache_entry)) {
        cache_entry.RegionState := state;
    }
  }

  AccessPermission getAccessPermission(Addr addr) {
    TBE tbe := getTBE(addr);
    if(is_valid(tbe)) {
      return RegionDir_State_to_permission(tbe.TBEState);
    }
    Entry cache_entry := getCacheEntry(addr);
    if(is_valid(cache_entry)) {
      return RegionDir_State_to_permission(cache_entry.RegionState);
    }
    return AccessPermission:NotPresent;
  }

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

  void functionalRead(Addr addr, Packet *pkt) {
    functionalMemoryRead(pkt);
  }

  int functionalWrite(Addr addr, Packet *pkt) {
    if (functionalMemoryWrite(pkt)) {
      return 1;
    } else {
      return 0;
    }
  }

  void recordRequestType(RequestType request_type, Addr addr) {
    if (request_type == RequestType:DataArrayRead) {
      cacheMemory.recordRequestType(CacheRequestType:DataArrayRead, addr);
    } else if (request_type == RequestType:DataArrayWrite) {
      cacheMemory.recordRequestType(CacheRequestType:DataArrayWrite, addr);
    } else if (request_type == RequestType:TagArrayRead) {
      cacheMemory.recordRequestType(CacheRequestType:TagArrayRead, addr);
    } else if (request_type == RequestType:TagArrayWrite) {
      cacheMemory.recordRequestType(CacheRequestType:TagArrayWrite, addr);
    }
  }

  bool checkResourceAvailable(RequestType request_type, Addr addr) {
    if (request_type == RequestType:DataArrayRead) {
      return cacheMemory.checkResourceAvailable(CacheResourceType:DataArray, addr);
    } else if (request_type == RequestType:DataArrayWrite) {
      return cacheMemory.checkResourceAvailable(CacheResourceType:DataArray, addr);
    } else if (request_type == RequestType:TagArrayRead) {
      return cacheMemory.checkResourceAvailable(CacheResourceType:TagArray, addr);
    } else if (request_type == RequestType:TagArrayWrite) {
      return cacheMemory.checkResourceAvailable(CacheResourceType:TagArray, addr);
    } else {
      error("Invalid RequestType type in checkResourceAvailable");
      return true;
    }
  }

  out_port(triggerQueue_out, TriggerMsg, triggerQueue);

  out_port(requestNetwork_out, CPURequestMsg, requestToDir);
  out_port(notifyNetwork_out, CPURequestMsg, notifyToRBuffer);
  out_port(probeNetwork_out, NBProbeRequestMsg, probeToRBuffer);

  in_port(triggerQueue_in, TriggerMsg, triggerQueue, rank=2) {
    if (triggerQueue_in.isReady(clockEdge())) {
      peek(triggerQueue_in, TriggerMsg) {
        assert(in_msg.addr == getRegionBase(in_msg.addr));
        Entry cache_entry := getCacheEntry(in_msg.addr);
        TBE tbe := getTBE(in_msg.addr);
        DPRINTF(RubySlicc, "trigger msg: %s (%s)\n", in_msg, getRegionBase(in_msg.addr));
        if (in_msg.Type == TriggerType:AcksComplete) {
          assert(is_valid(tbe));
          trigger(Event:LastAck, in_msg.addr, cache_entry, tbe);
        } else if (in_msg.Type == TriggerType:InvRegion) {
          assert(is_valid(tbe));
          trigger(Event:TriggerInv, in_msg.addr, cache_entry, tbe);
        } else if (in_msg.Type == TriggerType:DowngradeRegion) {
          assert(is_valid(tbe));
          trigger(Event:TriggerDowngrade, in_msg.addr, cache_entry, tbe);
        } else {
          error("Unknown trigger message");
        }
      }
    }
  }

  in_port(responseNetwork_in, ResponseMsg, responseFromRBuffer, rank=1) {
    if (responseNetwork_in.isReady(clockEdge())) {
      peek(responseNetwork_in, ResponseMsg) {
        TBE tbe := getTBE(in_msg.addr);
        Entry cache_entry := getCacheEntry(in_msg.addr);
        if (in_msg.Type == CoherenceResponseType:CPUPrbResp) {
          assert(in_msg.addr == getRegionBase(in_msg.addr));
          assert(is_valid(tbe));
          if (in_msg.NotCached) {
            trigger(Event:InvAckCoreNoShare, in_msg.addr, cache_entry, tbe);
          } else {
            trigger(Event:InvAckCore, in_msg.addr, cache_entry, tbe);
          }
        } else if (in_msg.Type == CoherenceResponseType:PrivateAck) {
          assert(in_msg.addr == getRegionBase(in_msg.addr));
          assert(is_valid(cache_entry));
          //Fix Me...add back in: assert(cache_entry.Sharers.isElement(in_msg.Sender));
          trigger(Event:CPUPrivateAck, in_msg.addr, cache_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:RegionWbAck) {
            //Fix Me...add back in: assert(cache_entry.Sharers.isElement(in_msg.Sender) == false);
          assert(in_msg.addr == getRegionBase(in_msg.addr));
          trigger(Event:WritebackAck, in_msg.addr, cache_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:DirReadyAck) {
            assert(is_valid(tbe));
            trigger(Event:DirReadyAck, getRegionBase(in_msg.addr), cache_entry, tbe);
        } else {
          error("Invalid response type");
        }
      }
    }
  }

  // In from cores
  // NOTE: We get the cache / TBE entry based on the region address,
  //       but pass the block address to the actions
  in_port(requestNetwork_in, CPURequestMsg, requestFromRegBuf, rank=0) {
    if (requestNetwork_in.isReady(clockEdge())) {
      peek(requestNetwork_in, CPURequestMsg) {
        //assert(in_msg.addr == getRegionBase(in_msg.addr));
        Addr address := getRegionBase(in_msg.addr);
        DPRINTF(RubySlicc, "Got %s, base %s\n", in_msg.addr, address);
        if (presentOrAvail(address)) {
          TBE tbe := getTBE(address);
          Entry cache_entry := getCacheEntry(address);
          if (in_msg.Type == CoherenceRequestType:PrivateRequest) {
            if (is_valid(cache_entry) && (cache_entry.Owner != in_msg.Requestor ||
                getState(tbe, cache_entry, address) == State:S)) {
              trigger(Event:SendInv, address, cache_entry, tbe);
            } else {
              trigger(Event:PrivateRequest, address, cache_entry, tbe);
            }
          } else if (in_msg.Type == CoherenceRequestType:SharedRequest) {
            if (is_invalid(cache_entry)) {
              // If no one has ever requested this region give private permissions
              trigger(Event:PrivateRequest, address, cache_entry, tbe);
            } else {
                if (always_migrate ||
                    (sym_migrate && symMigrate(cache_entry)) ||
                    (asym_migrate && asymMigrate(cache_entry, in_msg.Requestor))) {
                    if (cache_entry.Sharers.count() == 1 &&
                        cache_entry.Sharers.isElement(in_msg.Requestor)) {
                        trigger(Event:UpgradeRequest, address, cache_entry, tbe);
                    } else {
                        trigger(Event:SendInv, address, cache_entry, tbe);
                    }
                } else { // don't migrate
                    if(cache_entry.Sharers.isElement(in_msg.Requestor) ||
                       getState(tbe, cache_entry, address) == State:S) {
                        trigger(Event:SharedRequest, address, cache_entry, tbe);
                    } else {
                        trigger(Event:SendDowngrade, address, cache_entry, tbe);
                    }
                }
            }
          } else if (in_msg.Type == CoherenceRequestType:UpgradeRequest) {
            if (is_invalid(cache_entry)) {
              trigger(Event:PrivateRequest, address, cache_entry, tbe);
            } else if (cache_entry.Sharers.count() == 1 && cache_entry.Sharers.isElement(in_msg.Requestor)) {
              trigger(Event:UpgradeRequest, address, cache_entry, tbe);
            } else {
              trigger(Event:SendUpgrade, address, cache_entry, tbe);
            }
          } else if (in_msg.Type == CoherenceRequestType:CleanWbRequest) {
            if (is_invalid(cache_entry) || cache_entry.Sharers.isElement(in_msg.Requestor) == false) {
              trigger(Event:StaleCleanWbRequest, address, cache_entry, tbe);
            } else {
                DPRINTF(RubySlicc, "wb address %s(%s) owner %s sharers %s requestor %s %d %d\n", in_msg.addr, getRegionBase(in_msg.addr), cache_entry.Owner, cache_entry.Sharers, in_msg.Requestor, cache_entry.Sharers.isElement(in_msg.Requestor), cache_entry.Sharers.count());
                if (cache_entry.Sharers.isElement(in_msg.Requestor) && cache_entry.Sharers.count() == 1) {
                    DPRINTF(RubySlicc, "last wb\n");
                    trigger(Event:CleanWbRequest_LastSharer, address, cache_entry, tbe);
                } else {
                    DPRINTF(RubySlicc, "clean wb\n");
                    trigger(Event:CleanWbRequest, address, cache_entry, tbe);
                }
            }
          } else {
            error("unknown region dir request type");
          }
        } else {
          Addr victim := cacheMemory.cacheProbe(getRegionBase(in_msg.addr));
          TBE victim_tbe := getTBE(victim);
          Entry victim_entry := getCacheEntry(victim);
          DPRINTF(RubySlicc, "Evicting address %s for new region at address %s(%s)\n", victim, in_msg.addr, getRegionBase(in_msg.addr));
          assert(is_valid(victim_entry));
          trigger(Event:Evict, victim, victim_entry, victim_tbe);
        }
      }
    }
  }

  // Actions

  action(f_fwdReqToDir, "f", desc="Forward CPU request to directory") {
    peek(requestNetwork_in, CPURequestMsg) {
      enqueue(requestNetwork_out, CPURequestMsg, toDirLatency) {
        out_msg.addr := in_msg.addr;  // This is the block address. "address" is the region address
        out_msg.Type := in_msg.OriginalType;
        out_msg.DataBlk := in_msg.DataBlk;
        out_msg.Dirty := in_msg.Dirty;
        out_msg.Requestor := getCoreMachine(in_msg.Requestor,address);
        out_msg.WTRequestor := in_msg.WTRequestor;
        out_msg.Destination.add(mapAddressToMachine(in_msg.addr, MachineType:Directory));
        out_msg.Shared := in_msg.Shared;
        out_msg.MessageSize := in_msg.MessageSize;
        out_msg.Private := in_msg.Private;
        out_msg.NoAckNeeded := true;
        out_msg.InitialRequestTime := in_msg.InitialRequestTime;
        out_msg.ProbeRequestStartTime := curCycle();
        out_msg.DemandRequest := true;
        if (is_valid(cache_entry) && getState(tbe, cache_entry, address) != State:S) {
            out_msg.Acks := cache_entry.Sharers.count();
        } else {
            out_msg.Acks := 0;
        }
      }
    }
  }

  action(f_fwdReqToDirShared, "fs", desc="Forward CPU request to directory (shared)") {
    peek(requestNetwork_in, CPURequestMsg) {
      enqueue(requestNetwork_out, CPURequestMsg, toDirLatency) {
        out_msg.addr := in_msg.addr;  // This is the block address. "address" is the region address
        out_msg.Type := in_msg.OriginalType;
        out_msg.DataBlk := in_msg.DataBlk;
        out_msg.Dirty := in_msg.Dirty;
        out_msg.Requestor := getCoreMachine(in_msg.Requestor,address);
        out_msg.WTRequestor := in_msg.WTRequestor;
        out_msg.Destination.add(mapAddressToMachine(in_msg.addr, MachineType:Directory));
        out_msg.Shared := in_msg.Shared;
        out_msg.MessageSize := in_msg.MessageSize;
        out_msg.Private := in_msg.Private;
        out_msg.NoAckNeeded := true;
        out_msg.InitialRequestTime := in_msg.InitialRequestTime;
        out_msg.ProbeRequestStartTime := curCycle();
        out_msg.DemandRequest := true;
        out_msg.ForceShared := true;
        if (is_valid(cache_entry) && getState(tbe, cache_entry, address) != State:S) {
            out_msg.Acks := cache_entry.Sharers.count();
        } else {
            out_msg.Acks := 0;
        }
      }
    }
  }

  action(f_fwdReqToDirWithAck, "fa", desc="Forward CPU request to directory with ack request") {
    peek(requestNetwork_in, CPURequestMsg) {
      enqueue(requestNetwork_out, CPURequestMsg, toDirLatency) {
        out_msg.addr := in_msg.addr; // This is the block address. "address" is the region address
        out_msg.Type := in_msg.OriginalType;
        out_msg.DataBlk := in_msg.DataBlk;
        out_msg.Dirty := in_msg.Dirty;
        out_msg.Requestor := getCoreMachine(in_msg.Requestor,address);
        out_msg.WTRequestor := in_msg.WTRequestor;
        out_msg.Destination.add(mapAddressToMachine(in_msg.addr, MachineType:Directory));
        out_msg.Shared := in_msg.Shared;
        out_msg.MessageSize := in_msg.MessageSize;
        out_msg.Private := in_msg.Private;
        out_msg.NoAckNeeded := false;
        out_msg.InitialRequestTime := in_msg.InitialRequestTime;
        out_msg.ProbeRequestStartTime := curCycle();
        out_msg.DemandRequest := true;
        if (is_valid(cache_entry)) {
            out_msg.Acks := cache_entry.Sharers.count();
            // Don't need an ack from the requestor!
            if (cache_entry.Sharers.isElement(in_msg.Requestor)) {
                out_msg.Acks := out_msg.Acks - 1;
            }
        } else {
            out_msg.Acks := 0;
        }
      }
    }
  }

  action(f_fwdReqToDirWithAckShared, "fas", desc="Forward CPU request to directory with ack request") {
    peek(requestNetwork_in, CPURequestMsg) {
      enqueue(requestNetwork_out, CPURequestMsg, toDirLatency) {
        out_msg.addr := in_msg.addr; // This is the block address. "address" is the region address
        out_msg.Type := in_msg.OriginalType;
        out_msg.DataBlk := in_msg.DataBlk;
        out_msg.Dirty := in_msg.Dirty;
        out_msg.Requestor := getCoreMachine(in_msg.Requestor,address);
        out_msg.WTRequestor := in_msg.WTRequestor;
        out_msg.Destination.add(mapAddressToMachine(in_msg.addr, MachineType:Directory));
        out_msg.Shared := in_msg.Shared;
        out_msg.MessageSize := in_msg.MessageSize;
        out_msg.Private := in_msg.Private;
        out_msg.NoAckNeeded := false;
        out_msg.InitialRequestTime := in_msg.InitialRequestTime;
        out_msg.ProbeRequestStartTime := curCycle();
        out_msg.DemandRequest := true;
        out_msg.ForceShared := true;
        if (is_valid(cache_entry)) {
            out_msg.Acks := cache_entry.Sharers.count();
            // Don't need an ack from the requestor!
            if (cache_entry.Sharers.isElement(in_msg.Requestor)) {
                out_msg.Acks := out_msg.Acks - 1;
            }
        } else {
            out_msg.Acks := 0;
        }
      }
    }
  }

  action(a_allocateRegionEntry, "a", desc="Allocate a new entry") {
    set_cache_entry(cacheMemory.allocate(getRegionBase(address), new Entry));
    peek(requestNetwork_in, CPURequestMsg) {
      APPEND_TRANSITION_COMMENT(in_msg.Requestor);
    }
  }

  action(d_deallocateRegionEntry, "d", desc="Deallocate region entry") {
    cacheMemory.deallocate(getRegionBase(address));
    unset_cache_entry();
  }

  action(ra_receiveAck, "ra", desc="Mark TBE entry as received this ack") {
    //assert(tbe.ValidBlocks.at(getRegionOffset(address)));
    DPRINTF(RubySlicc, "received ack for %s reg: %s\n", address, getRegionBase(address));
    tbe.NumValidBlocks := tbe.NumValidBlocks - 1;
    assert(tbe.NumValidBlocks >= 0);
    if (tbe.NumValidBlocks == 0) {
      tbe.AllAcksReceived := true;
      enqueue(triggerQueue_out, TriggerMsg, 1) {
        out_msg.Type := TriggerType:AcksComplete;
        out_msg.addr := address;
      }
    }
    APPEND_TRANSITION_COMMENT(getRegionBase(address));
    APPEND_TRANSITION_COMMENT(" Acks left receive ");
    APPEND_TRANSITION_COMMENT(tbe.NumValidBlocks);
  }

  action(ca_checkAcks, "ca", desc="Check to see if we need more acks") {
    if (tbe.NumValidBlocks == 0) {
      tbe.AllAcksReceived := true;
      enqueue(triggerQueue_out, TriggerMsg, 1) {
        out_msg.Type := TriggerType:AcksComplete;
        out_msg.addr := address;
      }
    }
  }

  action(ti_triggerInv, "ti", desc="") {
      enqueue(triggerQueue_out, TriggerMsg, 1) {
          out_msg.Type := TriggerType:InvRegion;
          out_msg.addr := address;
      }
  }

  action(td_triggerDowngrade, "td", desc="") {
      enqueue(triggerQueue_out, TriggerMsg, 1) {
        out_msg.Type := TriggerType:DowngradeRegion;
        out_msg.addr := address;
      }
  }

  action(t_allocateTBE, "t", desc="allocate TBE Entry") {
    check_allocate(TBEs);
    TBEs.allocate(getRegionBase(address));
    set_tbe(getTBE(address));
    if (is_valid(cache_entry)) {
      tbe.Owner := cache_entry.Owner;
      tbe.Sharers := cache_entry.Sharers;
      tbe.AllAcksReceived := true; // assume no acks are required
    }
    tbe.ProbeRequestTime := curCycle();
    peek(requestNetwork_in, CPURequestMsg) {
      tbe.InitialRequestTime := in_msg.InitialRequestTime;
      tbe.DemandAddress := in_msg.addr;
    }
    APPEND_TRANSITION_COMMENT(getRegionBase(address));
    APPEND_TRANSITION_COMMENT(" Acks left ");
    APPEND_TRANSITION_COMMENT(tbe.NumValidBlocks);
    APPEND_TRANSITION_COMMENT(" Owner, ");
    APPEND_TRANSITION_COMMENT(tbe.Owner);
    APPEND_TRANSITION_COMMENT(" sharers, ");
    APPEND_TRANSITION_COMMENT(tbe.Sharers);
  }

  action(ss_setSharers, "ss", desc="Add requestor to sharers") {
    peek(requestNetwork_in, CPURequestMsg) {
        cache_entry.Sharers.add(in_msg.Requestor);
        APPEND_TRANSITION_COMMENT(cache_entry.Sharers);
    }
  }

  action(rs_removeSharer, "rs", desc="Remove requestor to sharers") {
    peek(requestNetwork_in, CPURequestMsg) {
        cache_entry.Sharers.remove(in_msg.Requestor);
        APPEND_TRANSITION_COMMENT(" removing ");
        APPEND_TRANSITION_COMMENT(in_msg.Requestor);
        APPEND_TRANSITION_COMMENT(" sharers ");
        APPEND_TRANSITION_COMMENT(cache_entry.Sharers);
    }
  }

  action(rsr_removeSharerResponse, "rsr", desc="Remove requestor to sharers") {
    peek(responseNetwork_in, ResponseMsg) {
        cache_entry.Sharers.remove(in_msg.Sender);
        APPEND_TRANSITION_COMMENT(cache_entry.Sharers);
    }
  }

  action(cs_clearSharers, "cs", desc="Add requestor to sharers") {
    cache_entry.Sharers.clear();
  }

  action(so_setOwner, "so", desc="Set the owner to the requestor") {
    peek(requestNetwork_in, CPURequestMsg) {
      cache_entry.Owner := in_msg.Requestor;
      APPEND_TRANSITION_COMMENT(" Owner now: ");
      APPEND_TRANSITION_COMMENT(cache_entry.Owner);
    }
  }

  action(rr_removeRequestorFromTBE, "rr", desc="Remove requestor from TBE sharers") {
    peek(requestNetwork_in, CPURequestMsg) {
      tbe.Sharers.remove(in_msg.Requestor);
    }
  }

  action(ur_updateDirtyStatusOnRequest, "ur", desc="Update dirty status on demand request") {
      peek(requestNetwork_in, CPURequestMsg) {
          if (is_valid(cache_entry)) {
              if ((in_msg.Type == CoherenceRequestType:SharedRequest) &&
                  (cache_entry.Sharers.isElement(in_msg.Requestor) == false)) {
                  cache_entry.LastWriten := false;
                  if (isCpuMachine(in_msg.Requestor)) {
                      cache_entry.LastWritenByCpu := false;
                  } else {
                      cache_entry.LastWritenByGpu := false;
                  }
              } else if ((in_msg.Type == CoherenceRequestType:PrivateRequest) ||
                         (in_msg.Type == CoherenceRequestType:UpgradeRequest)) {
                  cache_entry.LastWriten := true;
                  if (isCpuMachine(in_msg.Requestor)) {
                      cache_entry.LastWritenByCpu := true;
                  } else {
                      cache_entry.LastWritenByGpu := true;
                  }
              }
          }
      }
  }

  action(ud_updateDirtyStatusWithWb, "ud", desc="Update dirty status on writeback") {
      peek(requestNetwork_in, CPURequestMsg) {
          if (is_valid(cache_entry) && in_msg.Dirty) {
              cache_entry.LastWriten := true;
              if (isCpuMachine(in_msg.Requestor)) {
                  cache_entry.LastWritenByCpu := true;
              } else {
                  cache_entry.LastWritenByGpu := true;
              }
          }
      }
  }

  action(sns_setNumAcksSharers, "sns", desc="Set number of acks to one per shared region buffer") {
    assert(is_valid(tbe));
    assert(is_valid(cache_entry));
    tbe.NumValidBlocks := tbe.Sharers.count();
  }

  action(sno_setNumAcksOne, "sno", desc="Set number of acks to one per shared region buffer") {
    assert(is_valid(tbe));
    assert(is_valid(cache_entry));
    tbe.NumValidBlocks := 1;
  }

  action(dt_deallocateTBE, "dt", desc="deallocate TBE Entry") {
    TBEs.deallocate(getRegionBase(address));
    APPEND_TRANSITION_COMMENT(" reg: ");
    APPEND_TRANSITION_COMMENT(getRegionBase(address));
    unset_tbe();
  }

  action(wb_sendWbNotice, "wb", desc="Send notice to cache that writeback is acknowledged") {
    peek(requestNetwork_in, CPURequestMsg) {
      enqueue(notifyNetwork_out, CPURequestMsg, 1) {
        out_msg.addr := getRegionBase(address);
        out_msg.Type := CoherenceRequestType:WbNotify;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.Requestor := machineID;
        out_msg.MessageSize := MessageSizeType:Request_Control;
        out_msg.InitialRequestTime := in_msg.InitialRequestTime;
      }
    }
  }

  action(wbn_sendWbNoticeNoAck, "wbn", desc="Send notice to cache that writeback is acknowledged (no ack needed)") {
    peek(requestNetwork_in, CPURequestMsg) {
      enqueue(notifyNetwork_out, CPURequestMsg, 1) {
        out_msg.addr := getRegionBase(address);
        out_msg.Type := CoherenceRequestType:WbNotify;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.Requestor := machineID;
        out_msg.MessageSize := MessageSizeType:Request_Control;
        out_msg.InitialRequestTime := in_msg.InitialRequestTime;
        out_msg.NoAckNeeded := true;
      }
    }
  }

  action(b_sendPrivateNotice, "b", desc="Send notice to private cache that it has private access") {
    peek(requestNetwork_in, CPURequestMsg) {
      enqueue(notifyNetwork_out, CPURequestMsg, 1) {
        out_msg.addr := getRegionBase(address);
        out_msg.Type := CoherenceRequestType:PrivateNotify;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.Requestor := machineID;
        out_msg.MessageSize := MessageSizeType:Request_Control;
        out_msg.InitialRequestTime := in_msg.InitialRequestTime;
      }
    }
  }

  action(bs_sendSharedNotice, "bs", desc="Send notice to private cache that it has private access") {
    peek(requestNetwork_in, CPURequestMsg) {
      enqueue(notifyNetwork_out, CPURequestMsg, 1) {
        out_msg.addr := getRegionBase(address);
        out_msg.Type := CoherenceRequestType:SharedNotify;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.Requestor := machineID;
        out_msg.MessageSize := MessageSizeType:Request_Control;
        out_msg.InitialRequestTime := in_msg.InitialRequestTime;
      }
    }
  }

  action(c_sendSharedNoticeToOrigReq, "c", desc="Send notice to private cache that it has shared access") {
    assert(is_valid(tbe));
    enqueue(notifyNetwork_out, CPURequestMsg, 1) {
      out_msg.addr := getRegionBase(address);
      out_msg.Type := CoherenceRequestType:SharedNotify;
      out_msg.Destination.add(tbe.Owner);
      out_msg.Requestor := machineID;
      out_msg.MessageSize := MessageSizeType:Request_Control;
      out_msg.ProbeRequestStartTime := tbe.ProbeRequestTime;
      out_msg.InitialRequestTime := tbe.InitialRequestTime;
      APPEND_TRANSITION_COMMENT("dest: ");
      APPEND_TRANSITION_COMMENT(out_msg.Destination);
    }
  }

  action(sp_sendPrivateNoticeToOrigReq, "sp", desc="Send notice to private cache that it has private access") {
    assert(is_valid(tbe));
    enqueue(notifyNetwork_out, CPURequestMsg, 1) {
      out_msg.addr := getRegionBase(address);
      out_msg.Type := CoherenceRequestType:PrivateNotify;
      out_msg.Destination.add(tbe.Owner);
      out_msg.Requestor := machineID;
      out_msg.MessageSize := MessageSizeType:Request_Control;
      out_msg.ProbeRequestStartTime := tbe.ProbeRequestTime;
      out_msg.InitialRequestTime := tbe.InitialRequestTime;
      APPEND_TRANSITION_COMMENT("dest: ");
      APPEND_TRANSITION_COMMENT(out_msg.Destination);
    }
  }

  action(i_RegionInvNotify, "i", desc="Send notice to private cache that it no longer has private access") {
      enqueue(probeNetwork_out, NBProbeRequestMsg, 1) {
          out_msg.addr := address;
          out_msg.DemandAddress := tbe.DemandAddress;
          //out_msg.Requestor := tbe.Requestor;
          out_msg.Requestor := machineID;
          out_msg.Type := ProbeRequestType:PrbInv;
          //Fix me: assert(tbe.Sharers.count() > 0);
          out_msg.DemandRequest := true;
          out_msg.Destination := tbe.Sharers;
          out_msg.MessageSize := MessageSizeType:Request_Control;
          APPEND_TRANSITION_COMMENT("dest: ");
          APPEND_TRANSITION_COMMENT(out_msg.Destination);
      }
  }

  action(i0_RegionInvNotifyDemand0, "i0", desc="Send notice to private cache that it no longer has private access") {
      enqueue(probeNetwork_out, NBProbeRequestMsg, 1) {
          out_msg.addr := address;
          // Demand address should default to 0 -> out_msg.DemandAddress := 0;
          out_msg.Requestor := machineID;
          out_msg.Type := ProbeRequestType:PrbInv;
          out_msg.Destination := tbe.Sharers;
          out_msg.MessageSize := MessageSizeType:Request_Control;
          APPEND_TRANSITION_COMMENT("dest: ");
          APPEND_TRANSITION_COMMENT(out_msg.Destination);
      }
  }

  action(rd_RegionDowngrade, "rd", desc="Send notice to private cache that it only has shared access") {
        enqueue(probeNetwork_out, NBProbeRequestMsg, 1) {
            out_msg.addr := address;
            out_msg.DemandAddress := tbe.DemandAddress;
            out_msg.Requestor := machineID;
            out_msg.Type := ProbeRequestType:PrbDowngrade;
            out_msg.DemandRequest := true;
            out_msg.Destination := tbe.Sharers;
            out_msg.MessageSize := MessageSizeType:Request_Control;
            APPEND_TRANSITION_COMMENT("dest: ");
            APPEND_TRANSITION_COMMENT(out_msg.Destination);
        }
  }

  action(p_popRequestQueue, "p", desc="Pop the request queue") {
    requestNetwork_in.dequeue(clockEdge());
  }

  action(pt_popTriggerQueue, "pt", desc="Pop the trigger queue") {
    triggerQueue_in.dequeue(clockEdge());
  }

  action(pr_popResponseQueue, "pr", desc="Pop the response queue") {
    responseNetwork_in.dequeue(clockEdge());
  }

  action(s_stallAndWaitRequest, "s", desc="Stall and wait on the region address") {
    Addr regAddr := getRegionBase(address);
    stall_and_wait(requestNetwork_in, regAddr);
  }

  action(w_wakeUpRegionDependents, "w", desc="Wake up any requests waiting for this region") {
    wakeUpBuffers(getRegionBase(address));
  }

  action(wa_wakeUpAllDependents, "wa", desc="Wake up any requests waiting for this region") {
    wakeUpAllBuffers();
  }

  action(zz_recycleRequestQueue, "\z", desc="...") {
    requestNetwork_in.recycle(clockEdge(), cyclesToTicks(recycle_latency));
  }

  action(z_stall, "z", desc="stall request queue") {
    // fake state
  }

  action(mru_setMRU, "mru", desc="set MRU") {
    cacheMemory.setMRU(address);
  }

 // Transistions

  transition({NP_P, P_P, NP_S, S_S, S_P, P_S, P_NP, S_AP, P_AS, P_AP, SP_NP_W, S_W, P_AP_W, P_AS_W, S_AP_W}, {PrivateRequest, SharedRequest, UpgradeRequest, SendInv, SendUpgrade, SendDowngrade, CleanWbRequest, CleanWbRequest_LastSharer, StaleCleanWbRequest}) {
    s_stallAndWaitRequest
  }

  transition({NP_P, P_P, NP_S, S_S, S_P, S_W, P_S, P_NP, S_AP, P_AS, P_AP, P_AP_W, P_AS_W, S_AP_W}, Evict) {
    zz_recycleRequestQueue;
  }

  transition(NP, {PrivateRequest, SendUpgrade}, NP_P) {TagArrayRead, TagArrayWrite} {
    a_allocateRegionEntry;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDir;
    b_sendPrivateNotice;
    so_setOwner;
    ss_setSharers;
    t_allocateTBE;
    p_popRequestQueue;
  }

  transition(P, {PrivateRequest, UpgradeRequest}, P_P) {TagArrayRead} {
    mru_setMRU;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDir;
    b_sendPrivateNotice;
    t_allocateTBE;
    p_popRequestQueue;
  }

  transition({NP_P, P_P}, CPUPrivateAck, P) {
    dt_deallocateTBE;
    w_wakeUpRegionDependents;
    pr_popResponseQueue;
  }

  transition({NP, P, S}, StaleCleanWbRequest) {TagArrayRead, TagArrayWrite} {
      wbn_sendWbNoticeNoAck;
      ud_updateDirtyStatusWithWb;
      p_popRequestQueue;
  }

  transition(NP, SharedRequest, NP_S) {TagArrayRead, TagArrayWrite} {
    a_allocateRegionEntry;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDirShared;
    bs_sendSharedNotice;
    so_setOwner;
    ss_setSharers;
    t_allocateTBE;
    p_popRequestQueue;
  }

  // Could probably do this in parallel with other shared requests
  transition(S, SharedRequest, S_S) {TagArrayRead, TagArrayWrite} {
    mru_setMRU;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDirShared;
    bs_sendSharedNotice;
    ss_setSharers;
    t_allocateTBE;
    p_popRequestQueue;
  }

  transition({P, S}, CleanWbRequest_LastSharer, SP_NP_W) {TagArrayRead, TagArrayWrite} {
    ud_updateDirtyStatusWithWb;
    wb_sendWbNotice;
    rs_removeSharer;
    t_allocateTBE;
    d_deallocateRegionEntry;
    p_popRequestQueue;
  }

  transition(S, CleanWbRequest, S_W) {TagArrayRead, TagArrayWrite} {
    ud_updateDirtyStatusWithWb;
    wb_sendWbNotice;
    rs_removeSharer;
    t_allocateTBE;
    p_popRequestQueue;
  }

  transition(SP_NP_W, WritebackAck, NP) {
    dt_deallocateTBE;
    w_wakeUpRegionDependents;
    pr_popResponseQueue;
  }

  transition(S_W, WritebackAck, S) {
    dt_deallocateTBE;
    w_wakeUpRegionDependents;
    pr_popResponseQueue;
  }

  transition({NP_S, S_S}, CPUPrivateAck, S) {
    dt_deallocateTBE;
    w_wakeUpRegionDependents;
    pr_popResponseQueue;
  }

  transition(S, UpgradeRequest, S_P) {TagArrayRead, TagArrayWrite} {
    mru_setMRU;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDir;
    b_sendPrivateNotice;
    so_setOwner;
    t_allocateTBE;
    p_popRequestQueue;
  }

  transition(S_P, CPUPrivateAck, P) {
    dt_deallocateTBE;
    w_wakeUpRegionDependents;
    pr_popResponseQueue;
  }

  transition(P, SendInv, P_AP_W) {TagArrayRead, TagArrayWrite} {
    mru_setMRU;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDirWithAck;
    so_setOwner;
    t_allocateTBE;
    rr_removeRequestorFromTBE;
    sns_setNumAcksSharers;
    cs_clearSharers;
    ss_setSharers;
    //i_RegionInvNotify;
    p_popRequestQueue;
  }

  transition({P_AP_W, S_AP_W}, DirReadyAck) {
      ti_triggerInv;
      pr_popResponseQueue;
  }

  transition(P_AS_W, DirReadyAck) {
      td_triggerDowngrade;
      pr_popResponseQueue;
  }

  transition(P_AS_W, TriggerDowngrade, P_AS) {
      rd_RegionDowngrade;
      pt_popTriggerQueue;
  }

  transition(P_AP_W, TriggerInv, P_AP) {
      i_RegionInvNotify;
      pt_popTriggerQueue;
  }

  transition(S_AP_W, TriggerInv, S_AP) {
      i_RegionInvNotify;
      pt_popTriggerQueue;
  }

  transition(P, SendUpgrade, P_AP_W) {TagArrayRead, TagArrayWrite} {
    mru_setMRU;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDirWithAck;
    so_setOwner;
    t_allocateTBE;
    rr_removeRequestorFromTBE;
    sns_setNumAcksSharers;
    cs_clearSharers;
    ss_setSharers;
    p_popRequestQueue;
  }

  transition(P, Evict, P_NP) {TagArrayRead, TagArrayWrite} {
    t_allocateTBE;
    sns_setNumAcksSharers;
    i0_RegionInvNotifyDemand0;
    d_deallocateRegionEntry;
  }

  transition(S, SendInv, P_AP_W) {TagArrayRead, TagArrayWrite} {
    mru_setMRU;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDirWithAck;
    so_setOwner;
    t_allocateTBE;
    rr_removeRequestorFromTBE;
    sns_setNumAcksSharers;
    cs_clearSharers;
    ss_setSharers;
    p_popRequestQueue;
  }

  transition(S, Evict, P_NP) {TagArrayRead, TagArrayWrite} {
    t_allocateTBE;
    sns_setNumAcksSharers;
    i0_RegionInvNotifyDemand0;
    d_deallocateRegionEntry;
  }

  transition(P_NP, LastAck, NP) {
    dt_deallocateTBE;
    wa_wakeUpAllDependents;
    pt_popTriggerQueue;
  }

  transition(S, SendUpgrade, S_AP_W) {TagArrayRead, TagArrayWrite} {
    mru_setMRU;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDirWithAck;
    so_setOwner;
    t_allocateTBE;
    rr_removeRequestorFromTBE;
    sns_setNumAcksSharers;
    cs_clearSharers;
    ss_setSharers;
    p_popRequestQueue;
  }

  transition(S_AP, LastAck, S_P) {
    sp_sendPrivateNoticeToOrigReq;
    pt_popTriggerQueue;
  }

  transition(P_AP, LastAck, P_P) {
    sp_sendPrivateNoticeToOrigReq;
    pt_popTriggerQueue;
  }

  transition(P, SendDowngrade, P_AS_W) {TagArrayRead, TagArrayWrite} {
    mru_setMRU;
    ur_updateDirtyStatusOnRequest;
    f_fwdReqToDirWithAckShared;
    so_setOwner;
    t_allocateTBE;
    sns_setNumAcksSharers;
    ss_setSharers; //why do we set the sharers before sending the downgrade?  Are we sending a downgrade to the requestor?
    p_popRequestQueue;
  }

  transition(P_AS, LastAck, P_S) {
    c_sendSharedNoticeToOrigReq;
    pt_popTriggerQueue;
  }

  transition(P_S, CPUPrivateAck, S) {
    dt_deallocateTBE;
    w_wakeUpRegionDependents;
    pr_popResponseQueue;
  }

  transition({P_NP, P_AS, S_AP, P_AP}, InvAckCore) {} {
    ra_receiveAck;
    pr_popResponseQueue;
  }

  transition({P_NP, S_AP, P_AP}, InvAckCoreNoShare) {} {
    ra_receiveAck;
    pr_popResponseQueue;
  }

  transition(P_AS, InvAckCoreNoShare) {} {
    ra_receiveAck;
    rsr_removeSharerResponse;
    pr_popResponseQueue;
  }

}