// See LICENSE.Berkeley for license details.

package uncore.coherence

import Chisel._
import uncore.tilelink._
import uncore.constants._
import config._

/** Identifies the TLId of the inner network in a hierarchical cache controller */ 
case object InnerTLId extends Field[String]
/** Identifies the TLId of the outer network in a hierarchical cache controller */ 
case object OuterTLId extends Field[String]

/** Base class to represent coherence information in clients and managers */
abstract class CoherenceMetadata(implicit p: Parameters) extends TLBundle()(p) {
  val co = tlCoh
}

/** Stores the client-side coherence information,
  * such as permissions on the data and whether the data is dirty.
  * Its API can be used to make TileLink messages in response to
  * memory operations or [[uncore.Probe]] messages.
  */
class ClientMetadata(implicit p: Parameters) extends CoherenceMetadata()(p) {
  /** Actual state information stored in this bundle */
  val state = UInt(width = co.clientStateWidth)

  /** Metadata equality */
  def ===(rhs: ClientMetadata): Bool = this.state === rhs.state
  def =/=(rhs: ClientMetadata): Bool = !this.===(rhs)

  /** Is the block's data present in this cache */
  def isValid(dummy: Int = 0): Bool = co.isValid(this)
  /** Does this cache have permissions on this block sufficient to perform op */
  def isHit(op_code: UInt): Bool = co.isHit(op_code, this)
  /** Does this cache lack permissions on this block sufficient to perform op */ 
  def isMiss(op_code: UInt): Bool = !co.isHit(op_code, this)
  /** Does a secondary miss on the block require another Acquire message */
  def requiresAcquireOnSecondaryMiss(first_op: UInt, second_op: UInt): Bool =
    co.requiresAcquireOnSecondaryMiss(first_op, second_op, this)
  /** Does op require a Release to be made to outer memory */
  def requiresReleaseOnCacheControl(op_code: UInt): Bool =
    co.requiresReleaseOnCacheControl(op_code: UInt, this)
  /** Does an eviction require a Release to be made to outer memory */
  def requiresVoluntaryWriteback(dummy: Int = 0): Bool =
    co.requiresReleaseOnCacheControl(M_FLUSH, this)

  /** Constructs an Acquire message based on this metdata and a memory operation
    *
    * @param client_xact_id client's transaction id
    * @param addr_block address of the cache block
    * @param op_code a memory operation from [[uncore.constants.MemoryOpConstants]]
    */
  def makeAcquire(
        op_code: UInt,
        client_xact_id: UInt,
        addr_block: UInt): Acquire = {
    Acquire(
      is_builtin_type = Bool(false),
      a_type = co.getAcquireType(op_code, this),
      client_xact_id = client_xact_id,
      addr_block = addr_block,
      union = Cat(op_code, Bool(true)))(p)
  }

  /** Constructs a Release message based on this metadata on cache control op
    *
    * @param client_xact_id client's transaction id
    * @param addr_block address of the cache block
    * @param addr_beat sub-block address (which beat)
    * @param data data being written back
    */
  def makeVoluntaryRelease(
        op_code: UInt,
        client_xact_id: UInt,
        addr_block: UInt,
        addr_beat: UInt = UInt(0),
        data: UInt = UInt(0)): Release =
    Release(
      voluntary = Bool(true),
      r_type = co.getReleaseType(op_code, this),
      client_xact_id = client_xact_id,
      addr_block = addr_block,
      addr_beat = addr_beat,
      data = data)(p)

  /** Constructs a Release message based on this metadata on an eviction
    *
    * @param client_xact_id client's transaction id
    * @param addr_block address of the cache block
    * @param addr_beat sub-block address (which beat)
    * @param data data being written back
    */
  def makeVoluntaryWriteback(
        client_xact_id: UInt,
        addr_block: UInt,
        addr_beat: UInt = UInt(0),
        data: UInt = UInt(0)): Release =
    makeVoluntaryRelease(
      op_code = M_FLUSH,
      client_xact_id = client_xact_id,
      addr_block = addr_block,
      addr_beat = addr_beat,
      data = data)

  /** Constructs a Release message based on this metadata and a [[uncore.Probe]]
    *
    * @param the incoming [[uncore.Probe]]
    * @param addr_beat sub-block address (which beat)
    * @param data data being released
    */
  def makeRelease(
        prb: Probe,
        addr_beat: UInt = UInt(0),
        data: UInt = UInt(0)): Release =
    Release(
      voluntary = Bool(false),
      r_type = co.getReleaseType(prb, this),
      client_xact_id = UInt(0),
      addr_block = prb.addr_block,
      addr_beat = addr_beat,
      data = data)(p)

  /** New metadata after receiving a [[uncore.Grant]]
    *
    * @param incoming the incoming [[uncore.Grant]]
    * @param pending the mem op that triggered this transaction
    */
  def onGrant(incoming: Grant, pending: UInt): ClientMetadata =
    co.clientMetadataOnGrant(incoming, pending, this)

  /** New metadata after receiving a [[uncore.Probe]]
    *
    * @param incoming the incoming [[uncore.Probe]]
    */
  def onProbe(incoming: Probe): ClientMetadata =
    co.clientMetadataOnProbe(incoming, this)

  /** New metadata after a op_code hits this block
    *
    * @param op_code a memory operation from [[uncore.constants.MemoryOpConstants]]
    */
  def onHit(op_code: UInt): ClientMetadata =
    co.clientMetadataOnHit(op_code, this)

  /** New metadata after op_code releases permissions on this block
    *
    * @param op_code a memory operation from [[uncore.constants.MemoryOpConstants]]
    */
  def onCacheControl(op_code: UInt): ClientMetadata =
    co.clientMetadataOnCacheControl(op_code, this)
}

/** Factories for ClientMetadata, including on reset */
object ClientMetadata {
  def apply(state: UInt)(implicit p: Parameters) = {
    val meta = Wire(new ClientMetadata)
    meta.state := state
    meta
  }
  def onReset(implicit p: Parameters) = ClientMetadata(UInt(0))(p) // TODO: assumes clientInvalid === 0
}

/** Stores manager-side information about the status 
  * of a cache block, including whether it has any known sharers.
  *
  * Its API can be used to create [[uncore.Probe]] and [[uncore.Grant]] messages.
  */
class ManagerMetadata(implicit p: Parameters) extends CoherenceMetadata()(p) {
  // Currently no coherence policies assume manager-side state information
  // val state = UInt(width = co.masterStateWidth) TODO: Fix 0-width wires in Chisel

  /** The directory information for this block */
  val sharers = UInt(width = co.dir.width)

  /** Metadata equality */
  def ===(rhs: ManagerMetadata): Bool = //this.state === rhs.state && TODO: Fix 0-width wires in Chisel
                                         this.sharers === rhs.sharers
  def =/=(rhs: ManagerMetadata): Bool = !this.===(rhs)

  /** Converts the directory info into an N-hot sharer bitvector (i.e. full representation) */
  def full(dummy: Int = 0): UInt = co.dir.full(this.sharers)

  /** Does this [[uncore.Acquire]] require [[uncore.Probe Probes]] to be sent */
  def requiresProbes(acq: HasAcquireType): Bool = co.requiresProbes(acq, this)
  /** Does this memory op require [[uncore.Probe Probes]] to be sent */
  def requiresProbes(op_code: UInt): Bool = co.requiresProbes(op_code, this)
  /** Does an eviction require [[uncore.Probe Probes]] to be sent */
  def requiresProbesOnVoluntaryWriteback(dummy: Int = 0): Bool =
    co.requiresProbes(M_FLUSH, this)

  /** Construct an appropriate [[uncore.ProbeToDst]] for a given [[uncore.Acquire]]
    *
    * @param dst Destination client id for this Probe
    * @param acq Acquire message triggering this Probe
    * @param addr_block address of the cache block being probed
    */
  def makeProbe(dst: UInt, acq: HasAcquireType, addr_block: UInt): ProbeToDst =
    Probe(dst, co.getProbeType(acq, this), addr_block)(p)

  /** Construct an appropriate [[uncore.ProbeToDst]] for a given [[uncore.Acquire]]
    *
    * @param dst Destination client id for this Probe
    * @param acq Acquire message triggering this Probe
    */
  def makeProbe(dst: UInt, acq: AcquireMetadata): ProbeToDst =
    Probe(dst, co.getProbeType(acq, this), acq.addr_block)(p)

  /** Construct an appropriate [[uncore.ProbeToDst]] for a given mem op
    *
    * @param dst Destination client id for this Probe
    * @param op_code memory operation triggering this Probe
    * @param addr_block address of the cache block being probed
    */
  def makeProbe(dst: UInt, op_code: UInt, addr_block: UInt): ProbeToDst =
    Probe(dst, co.getProbeType(op_code, this), addr_block)(p)

  /** Construct an appropriate [[uncore.ProbeToDst]] for an eviction
    *
    * @param dst Destination client id for this Probe
    * @param addr_block address of the cache block being probed prior to eviction
    */
  def makeProbeForVoluntaryWriteback(dst: UInt, addr_block: UInt): ProbeToDst =
    makeProbe(dst, M_FLUSH, addr_block)

  /** Construct an appropriate [[uncore.GrantToDst]] to acknowledge an [[uncore.Release]]
    *
    * @param rel Release message being acknowledged by this Grant
    */
  def makeGrant(rel: ReleaseMetadata with HasClientId): GrantToDst =
    Grant(
      dst = rel.client_id,
      is_builtin_type = Bool(true),
      g_type = Grant.voluntaryAckType,
      client_xact_id = rel.client_xact_id,
      manager_xact_id = UInt(0))(p)

  /** Construct an appropriate [[uncore.GrantToDst]] to respond to an [[uncore.Acquire]]
    *
    * May contain single or multiple beats of data, or just be a permissions upgrade.
    *
    * @param acq Acquire message being responded to by this Grant
    * @param manager_xact_id manager's transaction id
    * @param addr_beat beat id of the data
    * @param data data being refilled to the original requestor
    */
  def makeGrant(
        acq: AcquireMetadata with HasClientId,
        manager_xact_id: UInt, 
        addr_beat: UInt = UInt(0),
        data: UInt = UInt(0)): GrantToDst =
    Grant(
      dst = acq.client_id,
      is_builtin_type = acq.isBuiltInType(),
      g_type = co.getGrantType(acq, this),
      client_xact_id = acq.client_xact_id,
      manager_xact_id = manager_xact_id,
      addr_beat = addr_beat,
      data = data)(p)

  /** Construct an [[uncore.GrantToDst]] to respond to an [[uncore.Acquire]] with some overrides
    *
    * Used to respond to secondary misses merged into this transaction.
    * May contain single or multiple beats of data.
    *
    * @param sec Secondary miss info
    * @param manager_xact_id manager's transaction id
    * @param data data being refilled to the original requestor
    */
  def makeGrant(
        sec: SecondaryMissInfo,
        manager_xact_id: UInt,
        data: UInt): GrantToDst = {
    Grant(
      dst = sec.client_id,
      is_builtin_type = sec.isBuiltInType(),
      g_type = co.getGrantType(sec, this),
      client_xact_id = sec.client_xact_id,
      manager_xact_id = manager_xact_id,
      addr_beat = sec.addr_beat,
      data = data)(p)
  }
    
  /** New metadata after receiving a [[uncore.ReleaseFromSrc]]
    *
    * @param incoming the incoming [[uncore.ReleaseFromSrc]]
    */
  def onRelease(incoming: ReleaseMetadata with HasClientId): ManagerMetadata =
    co.managerMetadataOnRelease(incoming, incoming.client_id, this)

  /** New metadata after sending a [[uncore.GrantToDst]]
    *
    * @param outgoing the outgoing [[uncore.GrantToDst]]
    */
  def onGrant(outgoing: GrantMetadata with HasClientId): ManagerMetadata =
    co.managerMetadataOnGrant(outgoing, outgoing.client_id, this)
}

/** Factories for ManagerMetadata, including on reset */
object ManagerMetadata {
  def apply(sharers: UInt, state: UInt = UInt(width = 0))(implicit p: Parameters) = {
    val meta = Wire(new ManagerMetadata)
    //meta.state := state TODO: Fix 0-width wires in Chisel 
    meta.sharers := sharers
    meta
  }
  def apply(implicit p: Parameters) = {
    val meta = Wire(new ManagerMetadata)
    //meta.state := UInt(width = 0) TODO: Fix 0-width wires in Chisel 
    meta.sharers := meta.co.dir.flush
    meta
  }
  def onReset(implicit p: Parameters) = ManagerMetadata(p)
}

/** HierarchicalMetadata is used in a cache in a multi-level memory hierarchy
  * that is a manager with respect to some inner caches and a client with
  * respect to some outer cache.
  *
  * This class makes use of two different sets of TileLink parameters, which are
  * applied by contextually mapping [[uncore.TLId]] to one of 
  * [[uncore.InnerTLId]] or [[uncore.OuterTLId]].
  */ 
class HierarchicalMetadata(implicit p: Parameters) extends CoherenceMetadata()(p) {
  val inner: ManagerMetadata = new ManagerMetadata()(p.alterPartial({case TLId => p(InnerTLId)}))
  val outer: ClientMetadata = new ClientMetadata()(p.alterPartial({case TLId => p(OuterTLId)}))
  def ===(rhs: HierarchicalMetadata): Bool = 
    this.inner === rhs.inner && this.outer === rhs.outer
  def =/=(rhs: HierarchicalMetadata): Bool = !this.===(rhs)
}

/** Factories for HierarchicalMetadata, including on reset */
object HierarchicalMetadata {
  def apply(inner: ManagerMetadata, outer: ClientMetadata)
           (implicit p: Parameters): HierarchicalMetadata = {
    val m = Wire(new HierarchicalMetadata)
    m.inner := inner
    m.outer := outer
    m
  }
  def onReset(implicit p: Parameters): HierarchicalMetadata = 
    apply(ManagerMetadata.onReset, ClientMetadata.onReset)
}
