/*
 * Copyright (c) 2009-2012 Mark D. Hill and David A. Wood
 * Copyright (c) 2010-2012 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.
 */

machine(L1Cache, "MI Example L1 Cache")
    : Sequencer * sequencer;
      CacheMemory * cacheMemory;
      Cycles cache_response_latency := 12;
      Cycles issue_latency := 2;
      bool send_evictions;

      // NETWORK BUFFERS
      MessageBuffer * requestFromCache, network="To", virtual_network="2",
            vnet_type="request";
      MessageBuffer * responseFromCache, network="To", virtual_network="4",
            vnet_type="response";

      MessageBuffer * forwardToCache, network="From", virtual_network="3",
            vnet_type="forward";
      MessageBuffer * responseToCache, network="From", virtual_network="4",
            vnet_type="response";

      MessageBuffer * mandatoryQueue;
{
  // STATES
  state_declaration(State, desc="Cache states") {
    I, AccessPermission:Invalid, desc="Not Present/Invalid";
    II, AccessPermission:Busy, desc="Not Present/Invalid, issued PUT";
    M, AccessPermission:Read_Write, desc="Modified";
    MI, AccessPermission:Busy, desc="Modified, issued PUT";
    MII, AccessPermission:Busy, desc="Modified, issued PUTX, received nack";

    IS, AccessPermission:Busy, desc="Issued request for LOAD/IFETCH";
    IM, AccessPermission:Busy, desc="Issued request for STORE/ATOMIC";
  }

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

    Load,       desc="Load request from processor";
    Ifetch,     desc="Ifetch request from processor";
    Store,      desc="Store request from processor";

    Data,       desc="Data from network";
    Fwd_GETX,        desc="Forward from network";

    Inv,        desc="Invalidate request from dir";

    Replacement,  desc="Replace a block";
    Writeback_Ack,   desc="Ack from the directory for a writeback";
    Writeback_Nack,   desc="Nack from the directory for a writeback";
  }

  // STRUCTURE DEFINITIONS
  // CacheEntry
  structure(Entry, desc="...", interface="AbstractCacheEntry") {
    State CacheState,        desc="cache state";
    bool Dirty,              desc="Is the data dirty (different than memory)?";
    DataBlock DataBlk,       desc="Data in the block";
  }

  // TBE fields
  structure(TBE, desc="...") {
    State TBEState,          desc="Transient state";
    DataBlock DataBlk,       desc="data for the block, required for concurrent writebacks";
  }

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


  // STRUCTURES
  TBETable TBEs, template="<L1Cache_TBE>", constructor="m_number_of_TBEs";

  // PROTOTYPES
  Tick clockEdge();
  Cycles ticksToCycles(Tick t);
  void set_cache_entry(AbstractCacheEntry a);
  void unset_cache_entry();
  void set_tbe(TBE b);
  void unset_tbe();
  void profileMsgDelay(int virtualNetworkType, Cycles b);

  Entry getCacheEntry(Addr address), return_by_pointer="yes" {
    return static_cast(Entry, "pointer", cacheMemory.lookup(address));
  }

  // FUNCTIONS
  Event mandatory_request_type_to_event(RubyRequestType type) {
   if (type == RubyRequestType:LD) {
      return Event:Load;
    } else if (type == RubyRequestType:IFETCH) {
      return Event:Ifetch;
    } else if ((type == RubyRequestType:ST) || (type == RubyRequestType:ATOMIC)) {
      return Event:Store;
    } else {
      error("Invalid RubyRequestType");
    }
  }

  State getState(TBE tbe, Entry cache_entry, Addr addr) {

    if (is_valid(tbe)) {
      return tbe.TBEState;
    }
    else if (is_valid(cache_entry)) {
      return cache_entry.CacheState;
    }
    else {
      return State:I;
    }
  }

  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.CacheState := state;
    }
  }

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

    Entry cache_entry := getCacheEntry(addr);
    if(is_valid(cache_entry)) {
      return L1Cache_State_to_permission(cache_entry.CacheState);
    }

    return AccessPermission:NotPresent;
  }

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

  void functionalRead(Addr addr, Packet *pkt) {
    TBE tbe := TBEs[addr];
    if(is_valid(tbe)) {
      testAndRead(addr, tbe.DataBlk, pkt);
    } else {
      testAndRead(addr, getCacheEntry(addr).DataBlk, pkt);
    }
  }

  int functionalWrite(Addr addr, Packet *pkt) {
    int num_functional_writes := 0;

    TBE tbe := TBEs[addr];
    if(is_valid(tbe)) {
      num_functional_writes := num_functional_writes +
        testAndWrite(addr, tbe.DataBlk, pkt);
      return num_functional_writes;
    }

    num_functional_writes := num_functional_writes +
        testAndWrite(addr, getCacheEntry(addr).DataBlk, pkt);
    return num_functional_writes;
  }

  // NETWORK PORTS

  out_port(requestNetwork_out, RequestMsg, requestFromCache);
  out_port(responseNetwork_out, ResponseMsg, responseFromCache);

  in_port(forwardRequestNetwork_in, RequestMsg, forwardToCache) {
    if (forwardRequestNetwork_in.isReady(clockEdge())) {
      peek(forwardRequestNetwork_in, RequestMsg, block_on="addr") {

        Entry cache_entry := getCacheEntry(in_msg.addr);
        TBE tbe := TBEs[in_msg.addr];

        if (in_msg.Type == CoherenceRequestType:GETX) {
          trigger(Event:Fwd_GETX, in_msg.addr, cache_entry, tbe);
        }
        else if (in_msg.Type == CoherenceRequestType:WB_ACK) {
          trigger(Event:Writeback_Ack, in_msg.addr, cache_entry, tbe);
        }
        else if (in_msg.Type == CoherenceRequestType:WB_NACK) {
          trigger(Event:Writeback_Nack, in_msg.addr, cache_entry, tbe);
        }
        else if (in_msg.Type == CoherenceRequestType:INV) {
          trigger(Event:Inv, in_msg.addr, cache_entry, tbe);
        }
        else {
          error("Unexpected message");
        }
      }
    }
  }

  in_port(responseNetwork_in, ResponseMsg, responseToCache) {
    if (responseNetwork_in.isReady(clockEdge())) {
      peek(responseNetwork_in, ResponseMsg, block_on="addr") {

        Entry cache_entry := getCacheEntry(in_msg.addr);
        TBE tbe := TBEs[in_msg.addr];

        if (in_msg.Type == CoherenceResponseType:DATA) {
          trigger(Event:Data, in_msg.addr, cache_entry, tbe);
        }
        else {
          error("Unexpected message");
        }
      }
    }
  }

    // Mandatory Queue
  in_port(mandatoryQueue_in, RubyRequest, mandatoryQueue, desc="...") {
    if (mandatoryQueue_in.isReady(clockEdge())) {
      peek(mandatoryQueue_in, RubyRequest, block_on="LineAddress") {

        Entry cache_entry := getCacheEntry(in_msg.LineAddress);
        if (is_invalid(cache_entry) &&
            cacheMemory.cacheAvail(in_msg.LineAddress) == false ) {
          // make room for the block
          trigger(Event:Replacement, cacheMemory.cacheProbe(in_msg.LineAddress),
                  getCacheEntry(cacheMemory.cacheProbe(in_msg.LineAddress)),
                  TBEs[cacheMemory.cacheProbe(in_msg.LineAddress)]);
        }
        else {
          trigger(mandatory_request_type_to_event(in_msg.Type), in_msg.LineAddress,
                  cache_entry, TBEs[in_msg.LineAddress]);
        }
      }
    }
  }

  // ACTIONS

  action(a_issueRequest, "a", desc="Issue a request") {
    enqueue(requestNetwork_out, RequestMsg, issue_latency) {
    out_msg.addr := address;
      out_msg.Type := CoherenceRequestType:GETX;
      out_msg.Requestor := machineID;
      out_msg.Destination.add(map_Address_to_Directory(address));
      out_msg.MessageSize := MessageSizeType:Control;
    }
  }

  action(b_issuePUT, "b", desc="Issue a PUT request") {
    enqueue(requestNetwork_out, RequestMsg, issue_latency) {
      assert(is_valid(cache_entry));
      out_msg.addr := address;
      out_msg.Type := CoherenceRequestType:PUTX;
      out_msg.Requestor := machineID;
      out_msg.Destination.add(map_Address_to_Directory(address));
      out_msg.DataBlk := cache_entry.DataBlk;
      out_msg.MessageSize := MessageSizeType:Data;
    }
  }

  action(e_sendData, "e", desc="Send data from cache to requestor") {
    peek(forwardRequestNetwork_in, RequestMsg) {
      enqueue(responseNetwork_out, ResponseMsg, cache_response_latency) {
        assert(is_valid(cache_entry));
        out_msg.addr := address;
        out_msg.Type := CoherenceResponseType:DATA;
        out_msg.Sender := machineID;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.DataBlk := cache_entry.DataBlk;
        out_msg.MessageSize := MessageSizeType:Response_Data;
      }
    }
  }

  action(ee_sendDataFromTBE, "\e", desc="Send data from TBE to requestor") {
    peek(forwardRequestNetwork_in, RequestMsg) {
      enqueue(responseNetwork_out, ResponseMsg, cache_response_latency) {
        assert(is_valid(tbe));
        out_msg.addr := address;
        out_msg.Type := CoherenceResponseType:DATA;
        out_msg.Sender := machineID;
        out_msg.Destination.add(in_msg.Requestor);
        out_msg.DataBlk := tbe.DataBlk;
        out_msg.MessageSize := MessageSizeType:Response_Data;
      }
    }
  }

  action(i_allocateL1CacheBlock, "i", desc="Allocate a cache block") {
    if (is_valid(cache_entry)) {
    } else {
      set_cache_entry(cacheMemory.allocate(address, new Entry));
    }
  }

  action(h_deallocateL1CacheBlock, "h", desc="deallocate a cache block") {
    if (is_valid(cache_entry)) {
      cacheMemory.deallocate(address);
      unset_cache_entry();
    }
  }

  action(m_popMandatoryQueue, "m", desc="Pop the mandatory request queue") {
    mandatoryQueue_in.dequeue(clockEdge());
  }

  action(n_popResponseQueue, "n", desc="Pop the response queue") {
    Tick delay := responseNetwork_in.dequeue(clockEdge());
    profileMsgDelay(1, ticksToCycles(delay));
  }

  action(o_popForwardedRequestQueue, "o", desc="Pop the forwarded request queue") {
    Tick delay := forwardRequestNetwork_in.dequeue(clockEdge());
    profileMsgDelay(2, ticksToCycles(delay));
  }

  action(p_profileMiss, "pi", desc="Profile cache miss") {
      ++cacheMemory.demand_misses;
  }

  action(p_profileHit, "ph", desc="Profile cache miss") {
      ++cacheMemory.demand_hits;
  }

  action(r_load_hit, "r", desc="Notify sequencer the load completed.") {
    assert(is_valid(cache_entry));
    DPRINTF(RubySlicc,"%s\n", cache_entry.DataBlk);
    cacheMemory.setMRU(cache_entry);
    sequencer.readCallback(address, cache_entry.DataBlk, false);
  }

  action(rx_load_hit, "rx", desc="External load completed.") {
    peek(responseNetwork_in, ResponseMsg) {
      assert(is_valid(cache_entry));
      DPRINTF(RubySlicc,"%s\n", cache_entry.DataBlk);
      cacheMemory.setMRU(cache_entry);
      sequencer.readCallback(address, cache_entry.DataBlk, true,
                             machineIDToMachineType(in_msg.Sender));
    }
  }

  action(s_store_hit, "s", desc="Notify sequencer that store completed.") {
    assert(is_valid(cache_entry));
    DPRINTF(RubySlicc,"%s\n", cache_entry.DataBlk);
    cacheMemory.setMRU(cache_entry);
    sequencer.writeCallback(address, cache_entry.DataBlk, false);
  }

  action(sx_store_hit, "sx", desc="External store completed.") {
    peek(responseNetwork_in, ResponseMsg) {
      assert(is_valid(cache_entry));
      DPRINTF(RubySlicc,"%s\n", cache_entry.DataBlk);
      cacheMemory.setMRU(cache_entry);
      sequencer.writeCallback(address, cache_entry.DataBlk, true,
                              machineIDToMachineType(in_msg.Sender));
    }
  }

  action(u_writeDataToCache, "u", desc="Write data to the cache") {
    peek(responseNetwork_in, ResponseMsg) {
      assert(is_valid(cache_entry));
      cache_entry.DataBlk := in_msg.DataBlk;
    }
  }

  action(forward_eviction_to_cpu, "\cc", desc="sends eviction information to the processor") {
    if (send_evictions) {
      DPRINTF(RubySlicc, "Sending invalidation for %s to the CPU\n", address);
      sequencer.evictionCallback(address);
    }
  }

  action(v_allocateTBE, "v", desc="Allocate TBE") {
    TBEs.allocate(address);
    set_tbe(TBEs[address]);
  }

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

  action(x_copyDataFromCacheToTBE, "x", desc="Copy data from cache to TBE") {
    assert(is_valid(cache_entry));
    assert(is_valid(tbe));
    tbe.DataBlk := cache_entry.DataBlk;
  }

  action(z_stall, "z", desc="stall") {
    // do nothing
  }

  // TRANSITIONS

  transition({IS, IM, MI, II, MII}, {Load, Ifetch, Store, Replacement}) {
    z_stall;
  }

  transition({IS, IM}, {Fwd_GETX, Inv}) {
    z_stall;
  }

  transition(MI, Inv) {
    o_popForwardedRequestQueue;
  }

  transition(M, Store) {
    s_store_hit;
    p_profileHit;
    m_popMandatoryQueue;
  }

  transition(M, {Load, Ifetch}) {
    r_load_hit;
    p_profileHit;
    m_popMandatoryQueue;
  }

  transition(I, Inv) {
    o_popForwardedRequestQueue;
  }

  transition(I, Store, IM) {
    v_allocateTBE;
    i_allocateL1CacheBlock;
    a_issueRequest;
    p_profileMiss;
    m_popMandatoryQueue;
  }

  transition(I, {Load, Ifetch}, IS) {
    v_allocateTBE;
    i_allocateL1CacheBlock;
    a_issueRequest;
    p_profileMiss;
    m_popMandatoryQueue;
  }

  transition(IS, Data, M) {
    u_writeDataToCache;
    rx_load_hit;
    w_deallocateTBE;
    n_popResponseQueue;
  }

  transition(IM, Data, M) {
    u_writeDataToCache;
    sx_store_hit;
    w_deallocateTBE;
    n_popResponseQueue;
  }

  transition(M, Fwd_GETX, I) {
    e_sendData;
    forward_eviction_to_cpu;
    o_popForwardedRequestQueue;
  }

  transition(I, Replacement) {
     h_deallocateL1CacheBlock;
  }

  transition(M, {Replacement,Inv},  MI) {
     v_allocateTBE;
     b_issuePUT;
     x_copyDataFromCacheToTBE;
     forward_eviction_to_cpu;
     h_deallocateL1CacheBlock;
  }

  transition(MI, Writeback_Ack, I) {
    w_deallocateTBE;
    o_popForwardedRequestQueue;
  }

  transition(MI, Fwd_GETX, II) {
    ee_sendDataFromTBE;
    o_popForwardedRequestQueue;
  }

  transition(MI, Writeback_Nack, MII) {
    o_popForwardedRequestQueue;
  }

  transition(MII, Fwd_GETX, I) {
    ee_sendDataFromTBE;
    w_deallocateTBE;
    o_popForwardedRequestQueue;
  }

  transition(II, Writeback_Nack, I) {
    w_deallocateTBE;
    o_popForwardedRequestQueue;
  }
}