/*
 * Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
 * Copyright (c) 2009 Advanced Micro Devices, Inc.
 * 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.
 *
 * AMD's contributions to the MOESI hammer protocol do not constitute an 
 * endorsement of its similarity to any AMD products.
 *
 * Authors: Milo Martin
 *          Brad Beckmann
 */

machine(Directory, "AMD Hammer-like protocol") 
: DirectoryMemory * directory,
  CacheMemory * probeFilter,
  MemoryControl * memBuffer,
  Cycles memory_controller_latency = 2,
  bool probe_filter_enabled = false,
  bool full_bit_dir_enabled = false
{

  MessageBuffer forwardFromDir, network="To", virtual_network="3", ordered="false", vnet_type="forward";
  MessageBuffer responseFromDir, network="To", virtual_network="4", ordered="false", vnet_type="response";
  //
  // For a finite buffered network, note that the DMA response network only 
  // works at this relatively lower numbered (lower priority) virtual network
  // because the trigger queue decouples cache responses from DMA responses.
  //
  MessageBuffer dmaResponseFromDir, network="To", virtual_network="1", ordered="true", vnet_type="response";

  MessageBuffer unblockToDir, network="From", virtual_network="5", ordered="false", vnet_type="unblock";
  MessageBuffer responseToDir, network="From", virtual_network="4", ordered="false", vnet_type="response";
  MessageBuffer requestToDir, network="From", virtual_network="2", ordered="false", vnet_type="request", recycle_latency="1";
  MessageBuffer dmaRequestToDir, network="From", virtual_network="0", ordered="true", vnet_type="request";

  // STATES
  state_declaration(State, desc="Directory states", default="Directory_State_E") {
    // Base states
    NX, AccessPermission:Maybe_Stale, desc="Not Owner, probe filter entry exists, block in O at Owner";
    NO, AccessPermission:Maybe_Stale, desc="Not Owner, probe filter entry exists, block in E/M at Owner";
    S, AccessPermission:Read_Only, desc="Data clean, probe filter entry exists pointing to the current owner";
    O, AccessPermission:Read_Only, desc="Data clean, probe filter entry exists";
    E, AccessPermission:Read_Write, desc="Exclusive Owner, no probe filter entry";

    O_R, AccessPermission:Read_Only, desc="Was data Owner, replacing probe filter entry";
    S_R, AccessPermission:Read_Only, desc="Was Not Owner or Sharer, replacing probe filter entry";
    NO_R, AccessPermission:Busy, desc="Was Not Owner or Sharer, replacing probe filter entry";

    NO_B, AccessPermission:Busy, "NO^B", desc="Not Owner, Blocked";
    NO_B_X, AccessPermission:Busy, "NO^B", desc="Not Owner, Blocked, next queued request GETX";
    NO_B_S, AccessPermission:Busy, "NO^B", desc="Not Owner, Blocked, next queued request GETS";
    NO_B_S_W, AccessPermission:Busy, "NO^B", desc="Not Owner, Blocked, forwarded merged GETS, waiting for responses";
    O_B, AccessPermission:Busy, "O^B", desc="Owner, Blocked";
    NO_B_W, AccessPermission:Busy, desc="Not Owner, Blocked, waiting for Dram";
    O_B_W, AccessPermission:Busy, desc="Owner, Blocked, waiting for Dram";
    NO_W, AccessPermission:Busy, desc="Not Owner, waiting for Dram";
    O_W, AccessPermission:Busy, desc="Owner, waiting for Dram";
    NO_DW_B_W, AccessPermission:Busy, desc="Not Owner, Dma Write waiting for Dram and cache responses";
    NO_DR_B_W, AccessPermission:Busy, desc="Not Owner, Dma Read waiting for Dram and cache responses";
    NO_DR_B_D, AccessPermission:Busy, desc="Not Owner, Dma Read waiting for cache responses including dirty data";
    NO_DR_B, AccessPermission:Busy, desc="Not Owner, Dma Read waiting for cache responses";
    NO_DW_W, AccessPermission:Busy, desc="Not Owner, Dma Write waiting for Dram";
    O_DR_B_W, AccessPermission:Busy, desc="Owner, Dma Read waiting for Dram and cache responses";
    O_DR_B, AccessPermission:Busy, desc="Owner, Dma Read waiting for cache responses";
    WB, AccessPermission:Busy, desc="Blocked on a writeback";
    WB_O_W, AccessPermission:Busy, desc="Blocked on memory write, will go to O";
    WB_E_W, AccessPermission:Busy, desc="Blocked on memory write, will go to E";

    NO_F,  AccessPermission:Busy, desc="Blocked on a flush";
    NO_F_W, AccessPermission:Busy, desc="Not Owner, Blocked, waiting for Dram";
  }

  // Events
  enumeration(Event, desc="Directory events") {
    GETX,                      desc="A GETX arrives";
    GETS,                      desc="A GETS arrives";
    PUT,                       desc="A PUT arrives"; 
    Unblock,                   desc="An unblock message arrives";
    UnblockS,                   desc="An unblock message arrives";
    UnblockM,                   desc="An unblock message arrives";
    Writeback_Clean,           desc="The final part of a PutX (no data)";
    Writeback_Dirty,           desc="The final part of a PutX (data)";
    Writeback_Exclusive_Clean, desc="The final part of a PutX (no data, exclusive)";
    Writeback_Exclusive_Dirty, desc="The final part of a PutX (data, exclusive)";

    // Probe filter
    Pf_Replacement,            desc="probe filter replacement";

    // DMA requests
    DMA_READ, desc="A DMA Read memory request";
    DMA_WRITE, desc="A DMA Write memory request";

    // Memory Controller
    Memory_Data, desc="Fetched data from memory arrives";
    Memory_Ack, desc="Writeback Ack from memory arrives";

    // Cache responses required to handle DMA
    Ack,             desc="Received an ack message";
    Shared_Ack,      desc="Received an ack message, responder has a shared copy";
    Shared_Data,     desc="Received a data message, responder has a shared copy";
    Data,            desc="Received a data message, responder had a owner or exclusive copy, they gave it to us";
    Exclusive_Data,  desc="Received a data message, responder had an exclusive copy, they gave it to us";

    // Triggers
    All_acks_and_shared_data,     desc="Received shared data and message acks";
    All_acks_and_owner_data,     desc="Received shared data and message acks";
    All_acks_and_data_no_sharers, desc="Received all acks and no other processor has a shared copy";
    All_Unblocks, desc="Received all unblocks for a merged gets request";
    GETF,  desc="A GETF arrives";
    PUTF,  desc="A PUTF arrives";
  }

  // TYPES

  // DirectoryEntry
  structure(Entry, desc="...", interface="AbstractEntry") {
    State DirectoryState,          desc="Directory state";
    DataBlock DataBlk,             desc="data for the block";
  }

  // ProbeFilterEntry
  structure(PfEntry, desc="...", interface="AbstractCacheEntry") {
    State PfState,                 desc="Directory state";
    MachineID Owner,               desc="Owner node";
    DataBlock DataBlk,             desc="data for the block";
    Set Sharers,                   desc="sharing vector for full bit directory";
  }

  // TBE entries for DMA requests
  structure(TBE, desc="TBE entries for outstanding DMA requests") {
    Address PhysicalAddress, desc="physical address";
    State TBEState,        desc="Transient State";
    CoherenceResponseType ResponseType, desc="The type for the subsequent response message";
    int Acks, default="0", desc="The number of acks that the waiting response represents";
    int SilentAcks, default="0", desc="The number of silent acks associated with this transaction";
    DataBlock DmaDataBlk,  desc="DMA Data to be written.  Partial blocks need to merged with system memory";
    DataBlock DataBlk,     desc="The current view of system memory";
    int Len,               desc="...";
    MachineID DmaRequestor, desc="DMA requestor";
    NetDest GetSRequestors, desc="GETS merged requestors";
    int NumPendingMsgs,    desc="Number of pending acks/messages";
    bool CacheDirty, default="false", desc="Indicates whether a cache has responded with dirty data";
    bool Sharers, default="false", desc="Indicates whether a cache has indicated it is currently a sharer";
    bool Owned, default="false", desc="Indicates whether a cache has indicated it is currently a sharer";
  }

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

  void set_cache_entry(AbstractCacheEntry b);
  void unset_cache_entry();
  void set_tbe(TBE a);
  void unset_tbe();
  void wakeUpBuffers(Address a);
  Cycles curCycle();

  // ** OBJECTS **

  Set fwd_set;

  TBETable TBEs, template="<Directory_TBE>", constructor="m_number_of_TBEs";

  Entry getDirectoryEntry(Address addr), return_by_pointer="yes" {
    Entry dir_entry := static_cast(Entry, "pointer", directory[addr]);

    if (is_valid(dir_entry)) {
      return dir_entry;
    }

    dir_entry :=  static_cast(Entry, "pointer",
                              directory.allocate(addr, new Entry));
    return dir_entry;
  }

  DataBlock getDataBlock(Address addr), return_by_ref="yes" {
    Entry dir_entry := getDirectoryEntry(addr);
    if(is_valid(dir_entry)) {
        return dir_entry.DataBlk;
    }

    TBE tbe := TBEs[addr];
    if(is_valid(tbe)) {
      return tbe.DataBlk;
    }

    error("Data block missing!");
  }

  PfEntry getProbeFilterEntry(Address addr), return_by_pointer="yes" {
    if (probe_filter_enabled || full_bit_dir_enabled) {
      PfEntry pfEntry := static_cast(PfEntry, "pointer", probeFilter.lookup(addr));
      return pfEntry;
    }
    return OOD;
  }

  State getState(TBE tbe, PfEntry pf_entry, Address addr) {
    if (is_valid(tbe)) {
      return tbe.TBEState;
    } else {
      if (probe_filter_enabled || full_bit_dir_enabled) {
        if (is_valid(pf_entry)) {
          assert(pf_entry.PfState == getDirectoryEntry(addr).DirectoryState);
        }
      }
      return getDirectoryEntry(addr).DirectoryState;
    }
  }

  void setState(TBE tbe, PfEntry pf_entry, Address addr, State state) {
    if (is_valid(tbe)) {
      tbe.TBEState := state;
    }
    if (probe_filter_enabled || full_bit_dir_enabled) {
      if (is_valid(pf_entry)) {
        pf_entry.PfState := state;
      }
      if (state == State:NX || state == State:NO || state == State:S || state == State:O) {
        assert(is_valid(pf_entry));
      }
      if (state == State:E) {
        assert(is_valid(pf_entry) == false);
      }
    }
    if (state == State:E || state == State:NX || state == State:NO || state == State:S || 
        state == State:O) {
      assert(is_valid(tbe) == false);
    }
    getDirectoryEntry(addr).DirectoryState := state;
  }

  AccessPermission getAccessPermission(Address addr) {
    TBE tbe := TBEs[addr];
    if(is_valid(tbe)) {
      return Directory_State_to_permission(tbe.TBEState);
    }

    if(directory.isPresent(addr)) {
      return Directory_State_to_permission(getDirectoryEntry(addr).DirectoryState);
    }

    return AccessPermission:NotPresent;
  }

  void setAccessPermission(PfEntry pf_entry, Address addr, State state) {
    getDirectoryEntry(addr).changePermission(Directory_State_to_permission(state));
  }

  Event cache_request_to_event(CoherenceRequestType type) {
    if (type == CoherenceRequestType:GETS) {
      return Event:GETS;
    } else if (type == CoherenceRequestType:GETX) {
      return Event:GETX;
    } else if (type == CoherenceRequestType:GETF) {
      return Event:GETF;
    } else {
      error("Invalid CoherenceRequestType");
    }
  }

  MessageBuffer triggerQueue, ordered="true";

  // ** OUT_PORTS **
  out_port(requestQueue_out, ResponseMsg, requestToDir); // For recycling requests
  out_port(forwardNetwork_out, RequestMsg, forwardFromDir);
  out_port(responseNetwork_out, ResponseMsg, responseFromDir);
  out_port(dmaResponseNetwork_out, DMAResponseMsg, dmaResponseFromDir);
  out_port(triggerQueue_out, TriggerMsg, triggerQueue);
  
  //
  // Memory buffer for memory controller to DIMM communication
  //
  out_port(memQueue_out, MemoryMsg, memBuffer);

  // ** IN_PORTS **
  
  // Trigger Queue
  in_port(triggerQueue_in, TriggerMsg, triggerQueue, rank=5) {
    if (triggerQueue_in.isReady()) {
      peek(triggerQueue_in, TriggerMsg) {
        PfEntry pf_entry := getProbeFilterEntry(in_msg.Addr);
        TBE tbe := TBEs[in_msg.Addr];
        if (in_msg.Type == TriggerType:ALL_ACKS) {
          trigger(Event:All_acks_and_owner_data, in_msg.Addr,
                  pf_entry, tbe);
        } else if (in_msg.Type == TriggerType:ALL_ACKS_OWNER_EXISTS) {
          trigger(Event:All_acks_and_shared_data, in_msg.Addr,
                  pf_entry, tbe);
        } else if (in_msg.Type == TriggerType:ALL_ACKS_NO_SHARERS) {
          trigger(Event:All_acks_and_data_no_sharers, in_msg.Addr,
                  pf_entry, tbe);
        } else if (in_msg.Type == TriggerType:ALL_UNBLOCKS) {
          trigger(Event:All_Unblocks, in_msg.Addr,
                  pf_entry, tbe);
        } else {
          error("Unexpected message");
        }
      }
    }
  }

  in_port(unblockNetwork_in, ResponseMsg, unblockToDir, rank=4) {
    if (unblockNetwork_in.isReady()) {
      peek(unblockNetwork_in, ResponseMsg) {
        PfEntry pf_entry := getProbeFilterEntry(in_msg.Addr);
        TBE tbe := TBEs[in_msg.Addr];
        if (in_msg.Type == CoherenceResponseType:UNBLOCK) {
          trigger(Event:Unblock, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:UNBLOCKS) {
          trigger(Event:UnblockS, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:UNBLOCKM) {
          trigger(Event:UnblockM, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:WB_CLEAN) {
          trigger(Event:Writeback_Clean, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:WB_DIRTY) {
          trigger(Event:Writeback_Dirty, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:WB_EXCLUSIVE_CLEAN) {
          trigger(Event:Writeback_Exclusive_Clean, in_msg.Addr,
                  pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:WB_EXCLUSIVE_DIRTY) {
          trigger(Event:Writeback_Exclusive_Dirty, in_msg.Addr,
                  pf_entry, tbe);
        } else {
          error("Invalid message");
        }
      }
    }
  }

  // Response Network
  in_port(responseToDir_in, ResponseMsg, responseToDir, rank=3) {
    if (responseToDir_in.isReady()) {
      peek(responseToDir_in, ResponseMsg) {
        PfEntry pf_entry := getProbeFilterEntry(in_msg.Addr);
        TBE tbe := TBEs[in_msg.Addr];
        if (in_msg.Type == CoherenceResponseType:ACK) {
          trigger(Event:Ack, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:ACK_SHARED) {
          trigger(Event:Shared_Ack, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:DATA_SHARED) {
          trigger(Event:Shared_Data, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:DATA) {
          trigger(Event:Data, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceResponseType:DATA_EXCLUSIVE) {
          trigger(Event:Exclusive_Data, in_msg.Addr, pf_entry, tbe);
        } else {
          error("Unexpected message");
        }
      }
    }
  }

  // off-chip memory request/response is done
  in_port(memQueue_in, MemoryMsg, memBuffer, rank=2) {
    if (memQueue_in.isReady()) {
      peek(memQueue_in, MemoryMsg) {
        PfEntry pf_entry := getProbeFilterEntry(in_msg.Addr);
        TBE tbe := TBEs[in_msg.Addr];
        if (in_msg.Type == MemoryRequestType:MEMORY_READ) {
          trigger(Event:Memory_Data, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == MemoryRequestType:MEMORY_WB) {
          trigger(Event:Memory_Ack, in_msg.Addr, pf_entry, tbe);
        } else {
          DPRINTF(RubySlicc, "%d\n", in_msg.Type);
          error("Invalid message");
        }
      }
    }
  }

  in_port(requestQueue_in, RequestMsg, requestToDir, rank=1) {
    if (requestQueue_in.isReady()) {
      peek(requestQueue_in, RequestMsg) {
        PfEntry pf_entry := getProbeFilterEntry(in_msg.Addr);
        TBE tbe := TBEs[in_msg.Addr];
        if (in_msg.Type == CoherenceRequestType:PUT) {
          trigger(Event:PUT, in_msg.Addr, pf_entry, tbe);
        } else if (in_msg.Type == CoherenceRequestType:PUTF) {
          trigger(Event:PUTF, in_msg.Addr, pf_entry, tbe);
        } else {
          if (probe_filter_enabled || full_bit_dir_enabled) {
            if (is_valid(pf_entry)) {
              trigger(cache_request_to_event(in_msg.Type), in_msg.Addr,
                      pf_entry, tbe);
            } else {
              if (probeFilter.cacheAvail(in_msg.Addr)) {
                trigger(cache_request_to_event(in_msg.Type), in_msg.Addr,
                        pf_entry, tbe);
              } else {
                trigger(Event:Pf_Replacement,
                        probeFilter.cacheProbe(in_msg.Addr),
                        getProbeFilterEntry(probeFilter.cacheProbe(in_msg.Addr)),
                        TBEs[probeFilter.cacheProbe(in_msg.Addr)]);
              }
            }
          } else {
            trigger(cache_request_to_event(in_msg.Type), in_msg.Addr,
                    pf_entry, tbe);
          }
        }
      }
    }
  }

  in_port(dmaRequestQueue_in, DMARequestMsg, dmaRequestToDir, rank=0) {
    if (dmaRequestQueue_in.isReady()) {
      peek(dmaRequestQueue_in, DMARequestMsg) {
        PfEntry pf_entry := getProbeFilterEntry(in_msg.LineAddress);
        TBE tbe := TBEs[in_msg.LineAddress];
        if (in_msg.Type == DMARequestType:READ) {
          trigger(Event:DMA_READ, in_msg.LineAddress, pf_entry, tbe);
        } else if (in_msg.Type == DMARequestType:WRITE) {
          trigger(Event:DMA_WRITE, in_msg.LineAddress, pf_entry, tbe);
        } else {
          error("Invalid message");
        }
      }
    }
  }

  // Actions
  
  action(r_setMRU, "\rr", desc="manually set the MRU bit for pf entry" ) {
    if (probe_filter_enabled || full_bit_dir_enabled) {
      assert(is_valid(cache_entry)); 
      probeFilter.setMRU(address);
    }
  }

  action(auno_assertUnblockerNotOwner, "auno", desc="assert unblocker not owner") {
    if (probe_filter_enabled || full_bit_dir_enabled) {
      assert(is_valid(cache_entry)); 
      peek(unblockNetwork_in, ResponseMsg) {
        assert(cache_entry.Owner != in_msg.Sender);
        if (full_bit_dir_enabled) {
          assert(cache_entry.Sharers.isElement(machineIDToNodeID(in_msg.Sender)) == false);
        }
      }
    }
  }

  action(uo_updateOwnerIfPf, "uo", desc="update owner") {
    if (probe_filter_enabled || full_bit_dir_enabled) {
      assert(is_valid(cache_entry)); 
      peek(unblockNetwork_in, ResponseMsg) {
        cache_entry.Owner := in_msg.Sender;
        if (full_bit_dir_enabled) {
          cache_entry.Sharers.clear();
          cache_entry.Sharers.add(machineIDToNodeID(in_msg.Sender));
          APPEND_TRANSITION_COMMENT(cache_entry.Sharers);
          DPRINTF(RubySlicc, "Sharers = %d\n", cache_entry.Sharers);
        }
      }
    }
  }

  action(us_updateSharerIfFBD, "us", desc="update sharer if full-bit directory") {
   if (full_bit_dir_enabled) {
      assert(probeFilter.isTagPresent(address));
      peek(unblockNetwork_in, ResponseMsg) {
        cache_entry.Sharers.add(machineIDToNodeID(in_msg.Sender));
      }
    }
  }

  action(a_sendWriteBackAck, "a", desc="Send writeback ack to requestor") {
    peek(requestQueue_in, RequestMsg) {
      enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
        out_msg.Addr := address;
        out_msg.Type := CoherenceRequestType:WB_ACK;
        out_msg.Requestor := in_msg.Requestor;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.MessageSize := MessageSizeType:Writeback_Control;
      }
    }
  }

  action(oc_sendBlockAck, "oc", desc="Send block ack to the owner") {
    peek(requestQueue_in, RequestMsg) {
        if (((probe_filter_enabled || full_bit_dir_enabled) && (in_msg.Requestor == cache_entry.Owner)) || machineCount(MachineType:L1Cache) == 1) {
        enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
          out_msg.Addr := address;
          out_msg.Type := CoherenceRequestType:BLOCK_ACK;
          out_msg.Requestor := in_msg.Requestor;
          out_msg.Destination.add(in_msg.Requestor);
          out_msg.MessageSize := MessageSizeType:Writeback_Control;
        }
      }
    }
  }

  action(b_sendWriteBackNack, "b", desc="Send writeback nack to requestor") {
    peek(requestQueue_in, RequestMsg) {
      enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
        out_msg.Addr := address;
        out_msg.Type := CoherenceRequestType:WB_NACK;
        out_msg.Requestor := in_msg.Requestor;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.MessageSize := MessageSizeType:Writeback_Control;
      }
    }
  }

  action(pfa_probeFilterAllocate, "pfa", desc="Allocate ProbeFilterEntry") {
    if (probe_filter_enabled || full_bit_dir_enabled) {
      peek(requestQueue_in, RequestMsg) {
        set_cache_entry(probeFilter.allocate(address, new PfEntry));
        cache_entry.Owner := in_msg.Requestor;
        cache_entry.Sharers.setSize(machineCount(MachineType:L1Cache));
      }
    }
  }

  action(pfd_probeFilterDeallocate, "pfd", desc="Deallocate ProbeFilterEntry") {
    if (probe_filter_enabled || full_bit_dir_enabled) {
      probeFilter.deallocate(address);
      unset_cache_entry();
    }
  }

  action(ppfd_possibleProbeFilterDeallocate, "ppfd", desc="Deallocate ProbeFilterEntry") {
    if ((probe_filter_enabled || full_bit_dir_enabled) && is_valid(cache_entry)) {
      probeFilter.deallocate(address);
      unset_cache_entry();
    }
  }

  action(v_allocateTBE, "v", desc="Allocate TBE") {
    check_allocate(TBEs);
    peek(requestQueue_in, RequestMsg) {
      TBEs.allocate(address);
      set_tbe(TBEs[address]);
      tbe.PhysicalAddress := address;
      tbe.ResponseType := CoherenceResponseType:NULL;
    }
  }

  action(vd_allocateDmaRequestInTBE, "vd", desc="Record Data in TBE") {
    check_allocate(TBEs);
    peek(dmaRequestQueue_in, DMARequestMsg) {
      TBEs.allocate(address);
      set_tbe(TBEs[address]);
      tbe.DmaDataBlk := in_msg.DataBlk;
      tbe.PhysicalAddress := in_msg.PhysicalAddress;
      tbe.Len := in_msg.Len;
      tbe.DmaRequestor := in_msg.Requestor;
      tbe.ResponseType := CoherenceResponseType:DATA_EXCLUSIVE;
      //
      // One ack for each last-level cache
      //
      tbe.NumPendingMsgs := machineCount(MachineType:L1Cache);
      //
      // Assume initially that the caches store a clean copy and that memory
      // will provide the data
      //
      tbe.CacheDirty := false;
    }
  }

  action(pa_setPendingMsgsToAll, "pa", desc="set pending msgs to all") {
    assert(is_valid(tbe));
    if (full_bit_dir_enabled) {
      assert(is_valid(cache_entry));
      tbe.NumPendingMsgs := cache_entry.Sharers.count();
    } else {
      tbe.NumPendingMsgs := machineCount(MachineType:L1Cache);
    }
  }

  action(po_setPendingMsgsToOne, "po", desc="set pending msgs to one") {
    assert(is_valid(tbe));
    tbe.NumPendingMsgs := 1;
  }

  action(w_deallocateTBE, "w", desc="Deallocate TBE") {
    TBEs.deallocate(address);
    unset_tbe();
  }

  action(sa_setAcksToOne, "sa", desc="Forwarded request, set the ack amount to one") {
    assert(is_valid(tbe));
    peek(requestQueue_in, RequestMsg) {
      if (full_bit_dir_enabled) {
        assert(is_valid(cache_entry));
        //
        // If we are using the full-bit directory and no sharers exists beyond
        // the requestor, then we must set the ack number to all, not one
        //
        fwd_set := cache_entry.Sharers;
        fwd_set.remove(machineIDToNodeID(in_msg.Requestor));
        if (fwd_set.count() > 0) {
          tbe.Acks := 1;
          tbe.SilentAcks := machineCount(MachineType:L1Cache) - fwd_set.count();
          tbe.SilentAcks := tbe.SilentAcks - 1;
        } else {
          tbe.Acks := machineCount(MachineType:L1Cache);
          tbe.SilentAcks := 0;
        }
      } else {
        tbe.Acks := 1;
      }
    }
  } 

  action(saa_setAcksToAllIfPF, "saa", desc="Non-forwarded request, set the ack amount to all") {
    assert(is_valid(tbe));
    if (probe_filter_enabled || full_bit_dir_enabled) {
      tbe.Acks := machineCount(MachineType:L1Cache);
      tbe.SilentAcks := 0;
    } else {
      tbe.Acks := 1;
    }
  }   

  action(m_decrementNumberOfMessages, "m", desc="Decrement the number of messages for which we're waiting") {
    peek(responseToDir_in, ResponseMsg) {
      assert(is_valid(tbe));
      assert(in_msg.Acks > 0);
      DPRINTF(RubySlicc, "%d\n", tbe.NumPendingMsgs);
      //
      // Note that cache data responses will have an ack count of 2.  However, 
      // directory DMA requests must wait for acks from all LLC caches, so 
      // only decrement by 1.
      //
      if ((in_msg.Type == CoherenceResponseType:DATA_SHARED) ||
          (in_msg.Type == CoherenceResponseType:DATA) ||
          (in_msg.Type == CoherenceResponseType:DATA_EXCLUSIVE)) {
        tbe.NumPendingMsgs := tbe.NumPendingMsgs - 1;
      } else {
        tbe.NumPendingMsgs := tbe.NumPendingMsgs - in_msg.Acks;
      }
      DPRINTF(RubySlicc, "%d\n", tbe.NumPendingMsgs);
    }
  }

  action(mu_decrementNumberOfUnblocks, "mu", desc="Decrement the number of messages for which we're waiting") {
    peek(unblockNetwork_in, ResponseMsg) {
      assert(is_valid(tbe));
      assert(in_msg.Type == CoherenceResponseType:UNBLOCKS);
      DPRINTF(RubySlicc, "%d\n", tbe.NumPendingMsgs);
      tbe.NumPendingMsgs := tbe.NumPendingMsgs - 1;
      DPRINTF(RubySlicc, "%d\n", tbe.NumPendingMsgs);
    }
  }

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

  action(o_checkForCompletion, "o", desc="Check if we have received all the messages required for completion") {
    assert(is_valid(tbe));
    if (tbe.NumPendingMsgs == 0) {
      enqueue(triggerQueue_out, TriggerMsg) {
        out_msg.Addr := address;
        if (tbe.Sharers) {
          if (tbe.Owned) {
            out_msg.Type := TriggerType:ALL_ACKS_OWNER_EXISTS;
          } else {
            out_msg.Type := TriggerType:ALL_ACKS;
          }
        } else {
          out_msg.Type := TriggerType:ALL_ACKS_NO_SHARERS;
        }
      }
    }
  }

  action(os_checkForMergedGetSCompletion, "os", desc="Check for merged GETS completion") {
    assert(is_valid(tbe));
    if (tbe.NumPendingMsgs == 0) {
      enqueue(triggerQueue_out, TriggerMsg) {
        out_msg.Addr := address;
        out_msg.Type := TriggerType:ALL_UNBLOCKS;
      }
    }
  }

  action(sp_setPendingMsgsToMergedSharers, "sp", desc="Set pending messages to waiting sharers") {
    assert(is_valid(tbe));
    tbe.NumPendingMsgs := tbe.GetSRequestors.count();
  }

  action(spa_setPendingAcksToZeroIfPF, "spa", desc="if probe filter, no need to wait for acks") {
    if (probe_filter_enabled || full_bit_dir_enabled) {
      assert(is_valid(tbe));
      tbe.NumPendingMsgs := 0;
    }
  }

  action(sc_signalCompletionIfPF, "sc", desc="indicate that we should skip waiting for cpu acks") {
    assert(is_valid(tbe));
    if (tbe.NumPendingMsgs == 0) {
      assert(probe_filter_enabled || full_bit_dir_enabled);
      enqueue(triggerQueue_out, TriggerMsg) {
        out_msg.Addr := address;
        out_msg.Type := TriggerType:ALL_ACKS_NO_SHARERS;
      }
    }
  }

  action(d_sendData, "d", desc="Send data to requestor") {
    peek(memQueue_in, MemoryMsg) {
      enqueue(responseNetwork_out, ResponseMsg, 1) {
        assert(is_valid(tbe));
        out_msg.Addr := address;
        out_msg.Type := tbe.ResponseType;
        out_msg.Sender := machineID;
        out_msg.Destination.add(in_msg.OriginalRequestorMachId);
        out_msg.DataBlk := in_msg.DataBlk;
        DPRINTF(RubySlicc, "%s\n", out_msg.DataBlk);
        out_msg.Dirty := false; // By definition, the block is now clean
        out_msg.Acks := tbe.Acks;
        out_msg.SilentAcks := tbe.SilentAcks;
        DPRINTF(RubySlicc, "%d\n", out_msg.Acks);
        assert(out_msg.Acks > 0);
        out_msg.MessageSize := MessageSizeType:Response_Data;
      }
    }
  }

  action(dr_sendDmaData, "dr", desc="Send Data to DMA controller from memory") {
    peek(memQueue_in, MemoryMsg) {
      enqueue(dmaResponseNetwork_out, DMAResponseMsg, 1) {
        assert(is_valid(tbe));
        out_msg.PhysicalAddress := address;
        out_msg.LineAddress := address;
        out_msg.Type := DMAResponseType:DATA;
        //
        // we send the entire data block and rely on the dma controller to 
        // split it up if need be
        //
        out_msg.DataBlk := in_msg.DataBlk;
        out_msg.Destination.add(tbe.DmaRequestor);
        out_msg.MessageSize := MessageSizeType:Response_Data;
      }
    }
  }

  action(dt_sendDmaDataFromTbe, "dt", desc="Send Data to DMA controller from tbe") {
    peek(triggerQueue_in, TriggerMsg) {
      enqueue(dmaResponseNetwork_out, DMAResponseMsg, 1) {
        assert(is_valid(tbe));
        out_msg.PhysicalAddress := address;
        out_msg.LineAddress := address;
        out_msg.Type := DMAResponseType:DATA;
        //
        // we send the entire data block and rely on the dma controller to 
        // split it up if need be
        //
        out_msg.DataBlk := tbe.DataBlk;
        out_msg.Destination.add(tbe.DmaRequestor);
        out_msg.MessageSize := MessageSizeType:Response_Data;
      }
    }
  }

  action(da_sendDmaAck, "da", desc="Send Ack to DMA controller") {
    enqueue(dmaResponseNetwork_out, DMAResponseMsg, 1) {
      assert(is_valid(tbe));
      out_msg.PhysicalAddress := address;
      out_msg.LineAddress := address;
      out_msg.Type := DMAResponseType:ACK;
      out_msg.Destination.add(tbe.DmaRequestor); 
      out_msg.MessageSize := MessageSizeType:Writeback_Control;
    }
  }

  action(rx_recordExclusiveInTBE, "rx", desc="Record Exclusive in TBE") {
    peek(requestQueue_in, RequestMsg) {
      assert(is_valid(tbe));
      tbe.ResponseType := CoherenceResponseType:DATA_EXCLUSIVE;
    }
  }

  action(r_recordDataInTBE, "rt", desc="Record Data in TBE") {
    peek(requestQueue_in, RequestMsg) {
      assert(is_valid(tbe));
      if (full_bit_dir_enabled) {
        fwd_set := cache_entry.Sharers;
        fwd_set.remove(machineIDToNodeID(in_msg.Requestor));
        if (fwd_set.count() > 0) {
          tbe.ResponseType := CoherenceResponseType:DATA;
        } else {
          tbe.ResponseType := CoherenceResponseType:DATA_EXCLUSIVE;
        }
      } else {
        tbe.ResponseType := CoherenceResponseType:DATA;
      }
    }
  }

  action(rs_recordGetSRequestor, "rs", desc="Record GETS requestor in TBE") {
    peek(requestQueue_in, RequestMsg) {
      assert(is_valid(tbe));
      tbe.GetSRequestors.add(in_msg.Requestor);
    }
  }

  action(r_setSharerBit, "r", desc="We saw other sharers") {
    assert(is_valid(tbe));
    tbe.Sharers := true;
  }

  action(so_setOwnerBit, "so", desc="We saw other sharers") {
    assert(is_valid(tbe));
    tbe.Sharers := true;
    tbe.Owned := true;
  }

  action(qf_queueMemoryFetchRequest, "qf", desc="Queue off-chip fetch request") {
    peek(requestQueue_in, RequestMsg) {
      enqueue(memQueue_out, MemoryMsg, 1) {
        out_msg.Addr := address;
        out_msg.Type := MemoryRequestType:MEMORY_READ;
        out_msg.Sender := machineID;
        out_msg.OriginalRequestorMachId := in_msg.Requestor;
        out_msg.MessageSize := in_msg.MessageSize;
        out_msg.DataBlk := getDirectoryEntry(address).DataBlk;
        DPRINTF(RubySlicc, "%s\n", out_msg);
      }
    }
  }

  action(qd_queueMemoryRequestFromDmaRead, "qd", desc="Queue off-chip fetch request") {
    peek(dmaRequestQueue_in, DMARequestMsg) {
      enqueue(memQueue_out, MemoryMsg, 1) {
        out_msg.Addr := address;
        out_msg.Type := MemoryRequestType:MEMORY_READ;
        out_msg.Sender := machineID;
        out_msg.OriginalRequestorMachId := in_msg.Requestor;
        out_msg.MessageSize := in_msg.MessageSize;
        out_msg.DataBlk := getDirectoryEntry(address).DataBlk;
        DPRINTF(RubySlicc, "%s\n", out_msg);
      }
    }
  }

  action(fn_forwardRequestIfNecessary, "fn", desc="Forward requests if necessary") {
    assert(is_valid(tbe));
    if ((machineCount(MachineType:L1Cache) > 1) && (tbe.Acks <= 1)) {
      if (full_bit_dir_enabled) {
        assert(is_valid(cache_entry));
        peek(requestQueue_in, RequestMsg) {
          fwd_set := cache_entry.Sharers;
          fwd_set.remove(machineIDToNodeID(in_msg.Requestor));
          if (fwd_set.count() > 0) {
            enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
              out_msg.Addr := address;
              out_msg.Type := in_msg.Type;
              out_msg.Requestor := in_msg.Requestor;
              out_msg.Destination.setNetDest(MachineType:L1Cache, fwd_set);
              out_msg.MessageSize := MessageSizeType:Multicast_Control;
              out_msg.InitialRequestTime := in_msg.InitialRequestTime;
              out_msg.ForwardRequestTime := curCycle();
              assert(tbe.SilentAcks > 0);
              out_msg.SilentAcks := tbe.SilentAcks;
            }
          }
        }
      } else {
        peek(requestQueue_in, RequestMsg) {
          enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
            out_msg.Addr := address;
            out_msg.Type := in_msg.Type;
            out_msg.Requestor := in_msg.Requestor;
            out_msg.Destination.broadcast(MachineType:L1Cache); // Send to all L1 caches
            out_msg.Destination.remove(in_msg.Requestor); // Don't include the original requestor
            out_msg.MessageSize := MessageSizeType:Broadcast_Control;
            out_msg.InitialRequestTime := in_msg.InitialRequestTime;
            out_msg.ForwardRequestTime := curCycle();
          }
        }
      }
    }
  }

  action(ia_invalidateAllRequest, "ia", desc="invalidate all copies") {
    if (machineCount(MachineType:L1Cache) > 1) {
      if (full_bit_dir_enabled) {
        assert(cache_entry.Sharers.count() > 0);
        peek(requestQueue_in, RequestMsg) {
          enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
            out_msg.Addr := address;
            out_msg.Type := CoherenceRequestType:INV;
            out_msg.Requestor := machineID;
            out_msg.Destination.setNetDest(MachineType:L1Cache, cache_entry.Sharers);
            out_msg.MessageSize := MessageSizeType:Multicast_Control;
          }
        }
      } else {
        enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
          out_msg.Addr := address;
          out_msg.Type := CoherenceRequestType:INV;
          out_msg.Requestor := machineID;
          out_msg.Destination.broadcast(MachineType:L1Cache); // Send to all L1 caches
          out_msg.MessageSize := MessageSizeType:Broadcast_Control;
        }
      }
    }
  }

  action(io_invalidateOwnerRequest, "io", desc="invalidate all copies") {
    if (machineCount(MachineType:L1Cache) > 1) {
      enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
        assert(is_valid(cache_entry)); 
        out_msg.Addr := address;
        out_msg.Type := CoherenceRequestType:INV;
        out_msg.Requestor := machineID;
        out_msg.Destination.add(cache_entry.Owner);
        out_msg.MessageSize := MessageSizeType:Request_Control;
        out_msg.DirectedProbe := true;
      }
    }
  }

  action(fb_forwardRequestBcast, "fb", desc="Forward requests to all nodes") {
    if (machineCount(MachineType:L1Cache) > 1) {
      peek(requestQueue_in, RequestMsg) {
        if (full_bit_dir_enabled) {
          fwd_set := cache_entry.Sharers;
          fwd_set.remove(machineIDToNodeID(in_msg.Requestor));
          if (fwd_set.count() > 0) {
            enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
                  out_msg.Addr := address;
                  out_msg.Type := in_msg.Type;
                  out_msg.Requestor := in_msg.Requestor;
                  out_msg.Destination.setNetDest(MachineType:L1Cache, fwd_set);
                  out_msg.MessageSize := MessageSizeType:Multicast_Control;
                  out_msg.InitialRequestTime := in_msg.InitialRequestTime;
                  out_msg.ForwardRequestTime := curCycle();
                  out_msg.SilentAcks := machineCount(MachineType:L1Cache) - fwd_set.count();
                  out_msg.SilentAcks := out_msg.SilentAcks - 1;
              }
          }
        } else {
            enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
                out_msg.Addr := address;
                out_msg.Type := in_msg.Type;
                out_msg.Requestor := in_msg.Requestor;
                out_msg.Destination.broadcast(MachineType:L1Cache); // Send to all L1 caches
                out_msg.Destination.remove(in_msg.Requestor); // Don't include the original requestor
                out_msg.MessageSize := MessageSizeType:Broadcast_Control;
                out_msg.InitialRequestTime := in_msg.InitialRequestTime;
                out_msg.ForwardRequestTime := curCycle();
            }
        }
      }
    } else {
      peek(requestQueue_in, RequestMsg) {
          enqueue(responseNetwork_out, ResponseMsg, 1) {
            out_msg.Addr := address;
            out_msg.Type := CoherenceResponseType:ACK;
            out_msg.Sender := machineID;
            out_msg.Destination.add(in_msg.Requestor);
            out_msg.Dirty := false; // By definition, the block is now clean
            out_msg.Acks := 0;
            out_msg.SilentAcks := 0;
            DPRINTF(RubySlicc, "%d\n", out_msg.Acks);
            out_msg.MessageSize := MessageSizeType:Response_Control;
          }
      }
    }
  }

  action(fr_forwardMergeReadRequestsToOwner, "frr", desc="Forward coalesced read request to owner") {
    assert(machineCount(MachineType:L1Cache) > 1);
    //
    // Fixme! The unblock network should not stall on the forward network.  Add a trigger queue to
    // decouple the two.
    //
    peek(unblockNetwork_in, ResponseMsg) {
      enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
        assert(is_valid(tbe));
        out_msg.Addr := address;
        out_msg.Type := CoherenceRequestType:MERGED_GETS;
        out_msg.MergedRequestors := tbe.GetSRequestors;
        if (in_msg.Type == CoherenceResponseType:UNBLOCKS) {
          out_msg.Destination.add(in_msg.CurOwner);
        } else {
          out_msg.Destination.add(in_msg.Sender);
        }
        out_msg.MessageSize := MessageSizeType:Request_Control;
        out_msg.InitialRequestTime := zero_time();
        out_msg.ForwardRequestTime := curCycle();
      }      
    }
  }

  action(fc_forwardRequestConditionalOwner, "fc", desc="Forward request to one or more nodes") {
    assert(machineCount(MachineType:L1Cache) > 1);
    if (probe_filter_enabled || full_bit_dir_enabled) {
      peek(requestQueue_in, RequestMsg) {
        enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
          assert(is_valid(cache_entry)); 
          out_msg.Addr := address;
          out_msg.Type := in_msg.Type;
          out_msg.Requestor := in_msg.Requestor;
          out_msg.Destination.add(cache_entry.Owner);
          out_msg.MessageSize := MessageSizeType:Request_Control;
          out_msg.DirectedProbe := true;
          out_msg.InitialRequestTime := in_msg.InitialRequestTime;
          out_msg.ForwardRequestTime := curCycle();
        }
      }      
    } else {
      peek(requestQueue_in, RequestMsg) {
        enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
          out_msg.Addr := address;
          out_msg.Type := in_msg.Type;
          out_msg.Requestor := in_msg.Requestor;
          out_msg.Destination.broadcast(MachineType:L1Cache); // Send to all L1 caches
          out_msg.Destination.remove(in_msg.Requestor); // Don't include the original requestor
          out_msg.MessageSize := MessageSizeType:Broadcast_Control;
          out_msg.InitialRequestTime := in_msg.InitialRequestTime;
          out_msg.ForwardRequestTime := curCycle();
        }
      }
    }
  }

  action(nofc_forwardRequestConditionalOwner, "nofc", desc="Forward request to one or more nodes if the requestor is not the owner") {
   if (machineCount(MachineType:L1Cache) > 1) {

     if (probe_filter_enabled || full_bit_dir_enabled) {
       peek(requestQueue_in, RequestMsg) {
        if (in_msg.Requestor != cache_entry.Owner) {
          enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
            assert(is_valid(cache_entry));
            out_msg.Addr := address;
            out_msg.Type := in_msg.Type;
            out_msg.Requestor := in_msg.Requestor;
            out_msg.Destination.add(cache_entry.Owner);
            out_msg.MessageSize := MessageSizeType:Request_Control;
            out_msg.DirectedProbe := true;
            out_msg.InitialRequestTime := in_msg.InitialRequestTime;
            out_msg.ForwardRequestTime := curCycle();
          }
        }
       }
     } else {
      peek(requestQueue_in, RequestMsg) {
        enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
          out_msg.Addr := address;
          out_msg.Type := in_msg.Type;
          out_msg.Requestor := in_msg.Requestor;
          out_msg.Destination.broadcast(MachineType:L1Cache); // Send to all L1 caches
          out_msg.Destination.remove(in_msg.Requestor); // Don't include the original requestor
          out_msg.MessageSize := MessageSizeType:Broadcast_Control;
          out_msg.InitialRequestTime := in_msg.InitialRequestTime;
          out_msg.ForwardRequestTime := curCycle();
        }
      }
     }
   }
  }

  action(f_forwardWriteFromDma, "fw", desc="Forward requests") {
    assert(is_valid(tbe));
    if (tbe.NumPendingMsgs > 0) {
      peek(dmaRequestQueue_in, DMARequestMsg) {
        enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
          out_msg.Addr := address;
          out_msg.Type := CoherenceRequestType:GETX;
          //
          // Send to all L1 caches, since the requestor is the memory controller
          // itself
          //
          out_msg.Requestor := machineID;
          out_msg.Destination.broadcast(MachineType:L1Cache); 
          out_msg.MessageSize := MessageSizeType:Broadcast_Control;
        }
      }
    }
  }

  action(f_forwardReadFromDma, "fr", desc="Forward requests") {
    assert(is_valid(tbe));
    if (tbe.NumPendingMsgs > 0) {
      peek(dmaRequestQueue_in, DMARequestMsg) {
        enqueue(forwardNetwork_out, RequestMsg, memory_controller_latency) {
          out_msg.Addr := address;
          out_msg.Type := CoherenceRequestType:GETS;
          //
          // Send to all L1 caches, since the requestor is the memory controller
          // itself
          //
          out_msg.Requestor := machineID;
          out_msg.Destination.broadcast(MachineType:L1Cache); 
          out_msg.MessageSize := MessageSizeType:Broadcast_Control;
        }
      }
    }
  }

  action(i_popIncomingRequestQueue, "i", desc="Pop incoming request queue") {
    requestQueue_in.dequeue();
  }

  action(j_popIncomingUnblockQueue, "j", desc="Pop incoming unblock queue") {
    peek(unblockNetwork_in, ResponseMsg) {
        APPEND_TRANSITION_COMMENT(in_msg.Sender);
    } 
    unblockNetwork_in.dequeue();
  }

  action(k_wakeUpDependents, "k", desc="wake-up dependents") {
    wakeUpBuffers(address);
  }

  action(l_popMemQueue, "q", desc="Pop off-chip request queue") {
    memQueue_in.dequeue();
  }

  action(g_popTriggerQueue, "g", desc="Pop trigger queue") {
    triggerQueue_in.dequeue();
  }

  action(p_popDmaRequestQueue, "pd", desc="pop dma request queue") {
    dmaRequestQueue_in.dequeue();
  }

  action(zd_stallAndWaitDMARequest, "zd", desc="Stall and wait the dma request queue") {
    peek(dmaRequestQueue_in, DMARequestMsg) {
        APPEND_TRANSITION_COMMENT(in_msg.Requestor);
    } 
    stall_and_wait(dmaRequestQueue_in, address);
  }

  action(r_recordMemoryData, "rd", desc="record data from memory to TBE") {
    peek(memQueue_in, MemoryMsg) {
      assert(is_valid(tbe));
      if (tbe.CacheDirty == false) {
        tbe.DataBlk := in_msg.DataBlk;
      }
    }
  }

  action(r_recordCacheData, "rc", desc="record data from cache response to TBE") {
    peek(responseToDir_in, ResponseMsg) {
      assert(is_valid(tbe));
      tbe.CacheDirty := true;
      tbe.DataBlk := in_msg.DataBlk;
    }
  }

  action(wr_writeResponseDataToMemory, "wr", desc="Write response data to memory") {
    peek(responseToDir_in, ResponseMsg) {
      getDirectoryEntry(address).DataBlk := in_msg.DataBlk;
      DPRINTF(RubySlicc, "Address: %s, Data Block: %s\n",
              in_msg.Addr, in_msg.DataBlk);
    }
  }

  action(l_writeDataToMemory, "l", desc="Write PUTX/PUTO data to memory") {
    peek(memQueue_in, MemoryMsg) {
      getDirectoryEntry(address).DataBlk := in_msg.DataBlk;
      DPRINTF(RubySlicc, "Address: %s, Data Block: %s\n",
              in_msg.Addr, in_msg.DataBlk);
    }
  }

  action(dwt_writeDmaDataFromTBE, "dwt", desc="DMA Write data to memory from TBE") {
    DPRINTF(RubySlicc, "%s\n", getDirectoryEntry(address).DataBlk);
    assert(is_valid(tbe));
    getDirectoryEntry(address).DataBlk := tbe.DataBlk;
    DPRINTF(RubySlicc, "%s\n", getDirectoryEntry(address).DataBlk);
    getDirectoryEntry(address).DataBlk.copyPartial(tbe.DmaDataBlk, addressOffset(tbe.PhysicalAddress), tbe.Len);
    DPRINTF(RubySlicc, "%s\n", getDirectoryEntry(address).DataBlk);
  }

  action(wdt_writeDataFromTBE, "wdt", desc="DMA Write data to memory from TBE") {
    assert(is_valid(tbe));
    DPRINTF(RubySlicc, "%s\n", getDirectoryEntry(address).DataBlk);
    getDirectoryEntry(address).DataBlk := tbe.DataBlk;
    DPRINTF(RubySlicc, "%s\n", getDirectoryEntry(address).DataBlk);
  }

  action(a_assertCacheData, "ac", desc="Assert that a cache provided the data") {
    assert(is_valid(tbe));
    assert(tbe.CacheDirty);
  }

  action(ano_assertNotOwner, "ano", desc="Assert that request is not current owner") {
    if (probe_filter_enabled || full_bit_dir_enabled) {
      peek(requestQueue_in, RequestMsg) {
        assert(is_valid(cache_entry)); 
        assert(cache_entry.Owner != in_msg.Requestor);
      }
    }
  }

  action(ans_assertNotSharer, "ans", desc="Assert that request is not a current sharer") {
    if (full_bit_dir_enabled) {
      peek(requestQueue_in, RequestMsg) {
        assert(cache_entry.Sharers.isElement(machineIDToNodeID(in_msg.Requestor)) == false);
      }
    }
  }

  action(rs_removeSharer, "s", desc="remove current sharer") {
    if (full_bit_dir_enabled) {
      peek(unblockNetwork_in, ResponseMsg) {
        assert(cache_entry.Sharers.isElement(machineIDToNodeID(in_msg.Sender)));
        cache_entry.Sharers.remove(machineIDToNodeID(in_msg.Sender));
      }
    }
  }

  action(cs_clearSharers, "cs", desc="clear current sharers") {
    if (full_bit_dir_enabled) {
      peek(requestQueue_in, RequestMsg) {
        cache_entry.Sharers.clear();
        cache_entry.Sharers.add(machineIDToNodeID(in_msg.Requestor));
      }
    }
  }

  action(l_queueMemoryWBRequest, "lq", desc="Write PUTX data to memory") {
    peek(unblockNetwork_in, ResponseMsg) {
      enqueue(memQueue_out, MemoryMsg, 1) {
        assert(in_msg.Dirty);
        assert(in_msg.MessageSize == MessageSizeType:Writeback_Data);
        out_msg.Addr := address;
        out_msg.Type := MemoryRequestType:MEMORY_WB;
        out_msg.DataBlk := in_msg.DataBlk;
        DPRINTF(RubySlicc, "%s\n", out_msg);
      }
    }
  }

  action(ld_queueMemoryDmaWrite, "ld", desc="Write DMA data to memory") {
    enqueue(memQueue_out, MemoryMsg, 1) {
      assert(is_valid(tbe));
      out_msg.Addr := address;
      out_msg.Type := MemoryRequestType:MEMORY_WB;
      // first, initialize the data blk to the current version of system memory
      out_msg.DataBlk := tbe.DataBlk;
      // then add the dma write data
      out_msg.DataBlk.copyPartial(tbe.DmaDataBlk, addressOffset(tbe.PhysicalAddress), tbe.Len);
      DPRINTF(RubySlicc, "%s\n", out_msg);
    }
  }

  action(ll_checkIncomingWriteback, "\l", desc="Check PUTX/PUTO response message") {
    peek(unblockNetwork_in, ResponseMsg) {
      assert(in_msg.Dirty == false);
      assert(in_msg.MessageSize == MessageSizeType:Writeback_Control);
      DPRINTF(RubySlicc, "%s\n", in_msg.DataBlk);
      DPRINTF(RubySlicc, "%s\n", getDirectoryEntry(address).DataBlk);

      // NOTE: The following check would not be valid in a real
      // implementation.  We include the data in the "dataless"
      // message so we can assert the clean data matches the datablock
      // in memory
      assert(getDirectoryEntry(address).DataBlk == in_msg.DataBlk);
    }
  }

  action(z_stallAndWaitRequest, "z", desc="Recycle the request queue") {
    peek(requestQueue_in, RequestMsg) {
        APPEND_TRANSITION_COMMENT(in_msg.Requestor);
    } 
    stall_and_wait(requestQueue_in, address);
  }

  // TRANSITIONS

  // Transitions out of E state
  transition(E, GETX, NO_B_W) {
    pfa_probeFilterAllocate;
    v_allocateTBE;
    rx_recordExclusiveInTBE;
    saa_setAcksToAllIfPF;
    qf_queueMemoryFetchRequest;
    fn_forwardRequestIfNecessary;
    i_popIncomingRequestQueue;
  }

  transition(E, GETF, NO_F_W) {
    pfa_probeFilterAllocate;
    v_allocateTBE;
    rx_recordExclusiveInTBE;
    saa_setAcksToAllIfPF;
    qf_queueMemoryFetchRequest;
    fn_forwardRequestIfNecessary;
    i_popIncomingRequestQueue;
  }

  transition(E, GETS, NO_B_W) {
    pfa_probeFilterAllocate;
    v_allocateTBE;
    rx_recordExclusiveInTBE;
    saa_setAcksToAllIfPF;
    qf_queueMemoryFetchRequest;
    fn_forwardRequestIfNecessary;
    i_popIncomingRequestQueue;
  }

  transition(E, DMA_READ, NO_DR_B_W) {
    vd_allocateDmaRequestInTBE;
    qd_queueMemoryRequestFromDmaRead;
    spa_setPendingAcksToZeroIfPF;
    f_forwardReadFromDma;
    p_popDmaRequestQueue;
  }

  transition(E, DMA_WRITE, NO_DW_B_W) {
    vd_allocateDmaRequestInTBE;
    spa_setPendingAcksToZeroIfPF;
    sc_signalCompletionIfPF;
    f_forwardWriteFromDma;
    p_popDmaRequestQueue;
  }

  // Transitions out of O state
  transition(O, GETX, NO_B_W) {
    r_setMRU;
    v_allocateTBE;
    r_recordDataInTBE;
    sa_setAcksToOne;
    qf_queueMemoryFetchRequest;
    fb_forwardRequestBcast;
    cs_clearSharers;
    i_popIncomingRequestQueue;
  }

  transition(O, GETF, NO_F_W) {
    r_setMRU;
    v_allocateTBE;
    r_recordDataInTBE;
    sa_setAcksToOne;
    qf_queueMemoryFetchRequest;
    fb_forwardRequestBcast;
    cs_clearSharers;
    i_popIncomingRequestQueue;
  }

  // This transition is dumb, if a shared copy exists on-chip, then that should
  // provide data, not slow off-chip dram.  The problem is that the current
  // caches don't provide data in S state
  transition(O, GETS, O_B_W) {
    r_setMRU;
    v_allocateTBE;
    r_recordDataInTBE;
    saa_setAcksToAllIfPF;
    qf_queueMemoryFetchRequest;
    fn_forwardRequestIfNecessary;
    i_popIncomingRequestQueue;
  }

  transition(O, DMA_READ, O_DR_B_W) {
    vd_allocateDmaRequestInTBE;
    spa_setPendingAcksToZeroIfPF;
    qd_queueMemoryRequestFromDmaRead;
    f_forwardReadFromDma;
    p_popDmaRequestQueue;
  }

  transition(O, Pf_Replacement, O_R) {
    v_allocateTBE;
    pa_setPendingMsgsToAll;
    ia_invalidateAllRequest;
    pfd_probeFilterDeallocate;
  }

  transition(S, Pf_Replacement, S_R) {
    v_allocateTBE;
    pa_setPendingMsgsToAll;
    ia_invalidateAllRequest;
    pfd_probeFilterDeallocate;
  }

  transition(NO, Pf_Replacement, NO_R) {
    v_allocateTBE;
    po_setPendingMsgsToOne;
    io_invalidateOwnerRequest;
    pfd_probeFilterDeallocate;
  }

  transition(NX, Pf_Replacement, NO_R) {
    v_allocateTBE;
    pa_setPendingMsgsToAll;
    ia_invalidateAllRequest;
    pfd_probeFilterDeallocate;
  }

  transition({O, S, NO, NX}, DMA_WRITE, NO_DW_B_W) {
    vd_allocateDmaRequestInTBE;
    f_forwardWriteFromDma;
    p_popDmaRequestQueue;
  }

  // Transitions out of NO state
  transition(NX, GETX, NO_B) {
    r_setMRU;
    fb_forwardRequestBcast;
    cs_clearSharers;
    i_popIncomingRequestQueue;
  }

  transition(NX, GETF, NO_F) {
    r_setMRU;
    fb_forwardRequestBcast;
    cs_clearSharers;
    i_popIncomingRequestQueue;
  }

  // Transitions out of NO state
  transition(NO, GETX, NO_B) {
    r_setMRU;
    ano_assertNotOwner;
    fc_forwardRequestConditionalOwner;
    cs_clearSharers;
    i_popIncomingRequestQueue;
  }

  transition(NO, GETF, NO_F) {
    r_setMRU;
    //ano_assertNotOwner;
    nofc_forwardRequestConditionalOwner; //forward request if the requester is not the owner
    cs_clearSharers;
    oc_sendBlockAck; // send ack if the owner
    i_popIncomingRequestQueue;
  }

  transition(S, GETX, NO_B) {
    r_setMRU;
    fb_forwardRequestBcast;
    cs_clearSharers;
    i_popIncomingRequestQueue;
  }

  transition(S, GETF, NO_F) {
    r_setMRU;
    fb_forwardRequestBcast;
    cs_clearSharers;
    i_popIncomingRequestQueue;
  }

  transition(S, GETS, NO_B) {
    r_setMRU;
    ano_assertNotOwner;
    fb_forwardRequestBcast;
    i_popIncomingRequestQueue;
  }

  transition(NO, GETS, NO_B) {
    r_setMRU;
    ano_assertNotOwner;
    ans_assertNotSharer;
    fc_forwardRequestConditionalOwner;
    i_popIncomingRequestQueue;
  }

  transition(NX, GETS, NO_B) {
    r_setMRU;
    ano_assertNotOwner;
    fc_forwardRequestConditionalOwner;
    i_popIncomingRequestQueue;
  }

  transition({NO, NX, S}, PUT, WB) {
    //
    // note that the PUT requestor may not be the current owner if an invalidate
    // raced with PUT
    //    
    a_sendWriteBackAck;
    i_popIncomingRequestQueue;
  }

  transition({NO, NX, S}, DMA_READ, NO_DR_B_D) {
    vd_allocateDmaRequestInTBE;
    f_forwardReadFromDma;
    p_popDmaRequestQueue;
  }

  // Nack PUT requests when races cause us to believe we own the data
  transition({O, E}, PUT) {
    b_sendWriteBackNack;
    i_popIncomingRequestQueue;
  }

  // Blocked transient states
  transition({NO_B_X, O_B, NO_DR_B_W, NO_DW_B_W, NO_B_W, NO_DR_B_D, 
              NO_DR_B, O_DR_B, O_B_W, O_DR_B_W, NO_DW_W, NO_B_S_W,
              NO_W, O_W, WB, WB_E_W, WB_O_W, O_R, S_R, NO_R, NO_F_W}, 
             {GETS, GETX, GETF, PUT, Pf_Replacement}) {
    z_stallAndWaitRequest;
  }

  transition(NO_F, {GETS, GETX, GETF, PUT, Pf_Replacement}){
    z_stallAndWaitRequest;
  }

  transition(NO_B, {GETX, GETF}, NO_B_X) {
    z_stallAndWaitRequest;
  }

  transition(NO_B, {PUT, Pf_Replacement}) {
    z_stallAndWaitRequest;
  }

  transition(NO_B_S, {GETX, GETF, PUT, Pf_Replacement}) {
    z_stallAndWaitRequest;
  }

  transition({NO_B_X, NO_B, NO_B_S, O_B, NO_DR_B_W, NO_DW_B_W, NO_B_W, NO_DR_B_D, 
              NO_DR_B, O_DR_B, O_B_W, O_DR_B_W, NO_DW_W, NO_B_S_W,
              NO_W, O_W, WB, WB_E_W, WB_O_W, O_R, S_R, NO_R, NO_F_W}, 
             {DMA_READ, DMA_WRITE}) {
    zd_stallAndWaitDMARequest;
  }

  // merge GETS into one response
  transition(NO_B, GETS, NO_B_S) {
    v_allocateTBE;
    rs_recordGetSRequestor;
    i_popIncomingRequestQueue;
  }

  transition(NO_B_S, GETS) {
    rs_recordGetSRequestor;
    i_popIncomingRequestQueue;
  }

  // unblock responses
  transition({NO_B, NO_B_X}, UnblockS, NX) {
    us_updateSharerIfFBD;
    k_wakeUpDependents;
    j_popIncomingUnblockQueue;
  }

  transition({NO_B, NO_B_X}, UnblockM, NO) {
    uo_updateOwnerIfPf;
    us_updateSharerIfFBD;
    k_wakeUpDependents;
    j_popIncomingUnblockQueue;
  }

  transition(NO_B_S, UnblockS, NO_B_S_W) {
    us_updateSharerIfFBD;
    fr_forwardMergeReadRequestsToOwner;
    sp_setPendingMsgsToMergedSharers;
    j_popIncomingUnblockQueue;
  }

  transition(NO_B_S, UnblockM, NO_B_S_W) {
    uo_updateOwnerIfPf;
    fr_forwardMergeReadRequestsToOwner;
    sp_setPendingMsgsToMergedSharers;
    j_popIncomingUnblockQueue;
  }

  transition(NO_B_S_W, UnblockS) {
    us_updateSharerIfFBD;
    mu_decrementNumberOfUnblocks;
    os_checkForMergedGetSCompletion;
    j_popIncomingUnblockQueue;
  }

  transition(NO_B_S_W, All_Unblocks, NX) {
    w_deallocateTBE;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition(O_B, UnblockS, O) {
    us_updateSharerIfFBD;
    k_wakeUpDependents;
    j_popIncomingUnblockQueue;
  }

  transition(O_B, UnblockM, NO) {
    us_updateSharerIfFBD;
    uo_updateOwnerIfPf;
    k_wakeUpDependents;
    j_popIncomingUnblockQueue;
  }

  transition(NO_B_W, Memory_Data, NO_B) {
    d_sendData;
    w_deallocateTBE;
    l_popMemQueue;
  }

  transition(NO_F_W, Memory_Data, NO_F) {
    d_sendData;
    w_deallocateTBE;
    l_popMemQueue;
  }

  transition(NO_DR_B_W, Memory_Data, NO_DR_B) {
    r_recordMemoryData;
    o_checkForCompletion;
    l_popMemQueue;
  }

  transition(O_DR_B_W, Memory_Data, O_DR_B) {
    r_recordMemoryData;
    dr_sendDmaData;
    o_checkForCompletion;
    l_popMemQueue;
  }

  transition({NO_DR_B, O_DR_B, NO_DR_B_D, NO_DW_B_W}, Ack) {
    m_decrementNumberOfMessages;
    o_checkForCompletion;
    n_popResponseQueue;
  }

  transition({O_R, S_R, NO_R}, Ack) {
    m_decrementNumberOfMessages;
    o_checkForCompletion;
    n_popResponseQueue;
  }

  transition(S_R, Data) {
    wr_writeResponseDataToMemory;
    m_decrementNumberOfMessages;
    o_checkForCompletion;
    n_popResponseQueue;
  }

  transition(NO_R, {Data, Exclusive_Data}) {
    wr_writeResponseDataToMemory;
    m_decrementNumberOfMessages;
    o_checkForCompletion;
    n_popResponseQueue;
  }

  transition({O_R, S_R, NO_R}, All_acks_and_data_no_sharers, E) {
    w_deallocateTBE;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition({NO_DR_B_W, O_DR_B_W}, Ack) {
    m_decrementNumberOfMessages;
    n_popResponseQueue;
  }

  transition(NO_DR_B_W, Shared_Ack) {
    m_decrementNumberOfMessages;
    r_setSharerBit;
    n_popResponseQueue;
  }

  transition(O_DR_B, Shared_Ack) {
    m_decrementNumberOfMessages;
    r_setSharerBit;
    o_checkForCompletion;
    n_popResponseQueue;
  }

  transition(O_DR_B_W, Shared_Ack) {
    m_decrementNumberOfMessages;
    r_setSharerBit;
    n_popResponseQueue;
  }

  transition({NO_DR_B, NO_DR_B_D}, Shared_Ack) {
    m_decrementNumberOfMessages;
    r_setSharerBit;
    o_checkForCompletion;
    n_popResponseQueue;
  }

  transition(NO_DR_B_W, Shared_Data) {
    r_recordCacheData;
    m_decrementNumberOfMessages;
    so_setOwnerBit;
    o_checkForCompletion;
    n_popResponseQueue;
  }

  transition({NO_DR_B, NO_DR_B_D}, Shared_Data) {
    r_recordCacheData;
    m_decrementNumberOfMessages;
    so_setOwnerBit;
    o_checkForCompletion;
    n_popResponseQueue;
  }

  transition(NO_DR_B_W, {Exclusive_Data, Data}) {
    r_recordCacheData;
    m_decrementNumberOfMessages;
    n_popResponseQueue;
  }

  transition({NO_DR_B, NO_DR_B_D, NO_DW_B_W}, {Exclusive_Data, Data}) {
    r_recordCacheData;
    m_decrementNumberOfMessages;
    o_checkForCompletion;
    n_popResponseQueue;
  }

  transition(NO_DR_B, All_acks_and_owner_data, O) {
    //
    // Note that the DMA consistency model allows us to send the DMA device
    // a response as soon as we receive valid data and prior to receiving
    // all acks.  However, to simplify the protocol we wait for all acks.
    //
    dt_sendDmaDataFromTbe;
    wdt_writeDataFromTBE;
    w_deallocateTBE;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition(NO_DR_B, All_acks_and_shared_data, S) {
    //
    // Note that the DMA consistency model allows us to send the DMA device
    // a response as soon as we receive valid data and prior to receiving
    // all acks.  However, to simplify the protocol we wait for all acks.
    //
    dt_sendDmaDataFromTbe;
    wdt_writeDataFromTBE;
    w_deallocateTBE;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition(NO_DR_B_D, All_acks_and_owner_data, O) {
    //
    // Note that the DMA consistency model allows us to send the DMA device
    // a response as soon as we receive valid data and prior to receiving
    // all acks.  However, to simplify the protocol we wait for all acks.
    //
    dt_sendDmaDataFromTbe;
    wdt_writeDataFromTBE;
    w_deallocateTBE;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition(NO_DR_B_D, All_acks_and_shared_data, S) {
    //
    // Note that the DMA consistency model allows us to send the DMA device
    // a response as soon as we receive valid data and prior to receiving
    // all acks.  However, to simplify the protocol we wait for all acks.
    //
    dt_sendDmaDataFromTbe;
    wdt_writeDataFromTBE;
    w_deallocateTBE;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition(O_DR_B, All_acks_and_owner_data, O) {
    wdt_writeDataFromTBE;
    w_deallocateTBE;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition(O_DR_B, All_acks_and_data_no_sharers, E) {
    wdt_writeDataFromTBE;
    w_deallocateTBE;
    pfd_probeFilterDeallocate;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition(NO_DR_B, All_acks_and_data_no_sharers, E) {
    //
    // Note that the DMA consistency model allows us to send the DMA device
    // a response as soon as we receive valid data and prior to receiving
    // all acks.  However, to simplify the protocol we wait for all acks.
    //
    dt_sendDmaDataFromTbe;
    wdt_writeDataFromTBE;
    w_deallocateTBE;
    ppfd_possibleProbeFilterDeallocate;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition(NO_DR_B_D, All_acks_and_data_no_sharers, E) {
    a_assertCacheData;
    //
    // Note that the DMA consistency model allows us to send the DMA device
    // a response as soon as we receive valid data and prior to receiving
    // all acks.  However, to simplify the protocol we wait for all acks.
    //
    dt_sendDmaDataFromTbe;
    wdt_writeDataFromTBE;
    w_deallocateTBE;
    ppfd_possibleProbeFilterDeallocate;
    k_wakeUpDependents;
    g_popTriggerQueue;
  }

  transition(NO_DW_B_W, All_acks_and_data_no_sharers, NO_DW_W) {
    dwt_writeDmaDataFromTBE;
    ld_queueMemoryDmaWrite;
    g_popTriggerQueue;
  }

  transition(NO_DW_W, Memory_Ack, E) {
    da_sendDmaAck;
    w_deallocateTBE;
    ppfd_possibleProbeFilterDeallocate;
    k_wakeUpDependents;
    l_popMemQueue;
  }

  transition(O_B_W, Memory_Data, O_B) {
    d_sendData;
    w_deallocateTBE;
    l_popMemQueue;
  }

  transition(NO_B_W, UnblockM, NO_W) {
    uo_updateOwnerIfPf;
    j_popIncomingUnblockQueue;
  }

  transition(NO_B_W, UnblockS, NO_W) {
    us_updateSharerIfFBD;
    j_popIncomingUnblockQueue;
  }

  transition(O_B_W, UnblockS, O_W) {
    us_updateSharerIfFBD;
    j_popIncomingUnblockQueue;
  }

  transition(NO_W, Memory_Data, NO) {
    w_deallocateTBE;
    k_wakeUpDependents;
    l_popMemQueue;
  }

  transition(O_W, Memory_Data, O) {
    w_deallocateTBE;
    k_wakeUpDependents;
    l_popMemQueue;
  }

  // WB State Transistions
  transition(WB, Writeback_Dirty, WB_O_W) {
    rs_removeSharer;
    l_queueMemoryWBRequest;
    j_popIncomingUnblockQueue;
  }

  transition(WB, Writeback_Exclusive_Dirty, WB_E_W) {
    rs_removeSharer;
    l_queueMemoryWBRequest;
    j_popIncomingUnblockQueue;
  }

  transition(WB_E_W, Memory_Ack, E) {
    l_writeDataToMemory;
    pfd_probeFilterDeallocate;
    k_wakeUpDependents;
    l_popMemQueue;
  }

  transition(WB_O_W, Memory_Ack, O) {
    l_writeDataToMemory;
    k_wakeUpDependents;
    l_popMemQueue;
  }

  transition(WB, Writeback_Clean, O) {
    ll_checkIncomingWriteback;
    rs_removeSharer;
    k_wakeUpDependents;
    j_popIncomingUnblockQueue;
  }

  transition(WB, Writeback_Exclusive_Clean, E) {
    ll_checkIncomingWriteback;
    rs_removeSharer;
    pfd_probeFilterDeallocate;
    k_wakeUpDependents;
    j_popIncomingUnblockQueue;
  }

  transition(WB, Unblock, NX) {
    auno_assertUnblockerNotOwner;
    k_wakeUpDependents;
    j_popIncomingUnblockQueue;
  }

  transition(NO_F, PUTF, WB) {
    a_sendWriteBackAck;
    i_popIncomingRequestQueue;
  }

  //possible race between GETF and UnblockM -- not sure needed any more?
  transition(NO_F, UnblockM) {
    us_updateSharerIfFBD;
    uo_updateOwnerIfPf;
    j_popIncomingUnblockQueue;
  }
}
