// See LICENSE.SiFive for license details.

package freechips.rocketchip.subsystem

import chisel3._
import chisel3.dontTouch
import freechips.rocketchip.config.{Field, Parameters}
import freechips.rocketchip.devices.debug.{TLDebugModule, HasPeripheryDebug}
import freechips.rocketchip.devices.tilelink._
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.interrupts._
import freechips.rocketchip.tile._
import freechips.rocketchip.tilelink._
import freechips.rocketchip.prci.{ClockGroup, ResetCrossingType, ClockGroupNode}
import freechips.rocketchip.util._
import freechips.rocketchip.rocket.{TracedInstruction}
import scala.collection.immutable.ListMap

/** A default implementation of parameterizing the connectivity of the port where the tile is the master.
  *   Optional timing buffers and/or an optional CacheCork can be inserted in the interconnect's clock domain.
  */
case class ElementMasterPortParams(
  buffers: Int = 0,
  cork: Option[Boolean] = None,
  where: TLBusWrapperLocation = SBUS
) extends ElementPortParamsLike {
  def injectNode(context: Attachable)(implicit p: Parameters): TLNode = {
    (TLBuffer.chainNode(buffers) :=* cork.map { u => TLCacheCork(unsafe = u) } .getOrElse { TLTempNode() })
  }
}

/** A default implementation of parameterizing the connectivity of the port giving access to slaves inside the tile.
  *   Optional timing buffers and/or an optional BusBlocker adapter can be inserted in the interconnect's clock domain.
  */
case class ElementSlavePortParams(
  buffers: Int = 0,
  blockerCtrlAddr: Option[BigInt] = None,
  blockerCtrlWhere: TLBusWrapperLocation = CBUS,
  where: TLBusWrapperLocation = CBUS
) extends ElementPortParamsLike {
  def injectNode(context: Attachable)(implicit p: Parameters): TLNode = {
    val controlBus = context.locateTLBusWrapper(where)
    val blockerBus = context.locateTLBusWrapper(blockerCtrlWhere)
    blockerCtrlAddr
      .map { BasicBusBlockerParams(_, blockerBus.beatBytes, controlBus.beatBytes) }
      .map { bbbp =>
        val blocker = LazyModule(new BasicBusBlocker(bbbp))
        blockerBus.coupleTo("tile_slave_port_bus_blocker") { blocker.controlNode := TLFragmenter(blockerBus) := _ }
        blocker.node :*= TLBuffer.chainNode(buffers)
      } .getOrElse { TLBuffer.chainNode(buffers) }
  }
}

/** InstantiatesTiles adds a Config-urable sequence of Elements of any type
  *   to the subsystem class into which it is mixed.
  */
trait InstantiatesElements { this: LazyModule with Attachable =>
  val location: HierarchicalLocation

  /** Record the order in which to instantiate all tiles, based on statically-assigned ids.
    *
    * Note that these ids, which are often used as the tiles' default hartid input,
    * may or may not be those actually reflected at runtime in e.g. the $mhartid CSR
    */
  val tileAttachParams: Seq[CanAttachTile] = p(TilesLocated(location)).sortBy(_.tileParams.tileId)
  val tileParams: Seq[TileParams] = tileAttachParams.map(_.tileParams)
  val tileCrossingTypes: Seq[ClockCrossingType] = tileAttachParams.map(_.crossingParams.crossingType)

  /** The actual list of instantiated tiles in this block. */
  val tile_prci_domains: ListMap[Int, TilePRCIDomain[_]] = tileAttachParams.foldLeft(ListMap[Int, TilePRCIDomain[_]]()) {
    case (instantiated, params) => instantiated + (params.tileParams.tileId -> params.instantiate(tileParams, instantiated)(p))
  }

  val clusterAttachParams: Seq[CanAttachCluster] = p(ClustersLocated(location)).sortBy(_.clusterParams.clusterId)
  val clusterParams: Seq[ClusterParams] = clusterAttachParams.map(_.clusterParams)
  val clusterCrossingTypes: Seq[ClockCrossingType] = clusterAttachParams.map(_.crossingParams.crossingType)
  val cluster_prci_domains: ListMap[Int, ClusterPRCIDomain] = clusterAttachParams.foldLeft(ListMap[Int, ClusterPRCIDomain]()) {
    case (instantiated, params) => instantiated + (params.clusterParams.clusterId -> params.instantiate(clusterParams, instantiated)(p))
  }

  val element_prci_domains: Seq[ElementPRCIDomain[_]] = tile_prci_domains.values.toSeq ++ cluster_prci_domains.values.toSeq

  val leafTiles: ListMap[Int, BaseTile] = tile_prci_domains.mapValues(_.element.asInstanceOf[BaseTile]).to(ListMap)
  val totalTiles: ListMap[Int, BaseTile] = (leafTiles ++ cluster_prci_domains.values.map(_.element.totalTiles).flatten)

  // Helper functions for accessing certain parameters that are popular to refer to in subsystem code
  def nLeafTiles: Int = leafTiles.size
  def nTotalTiles: Int = totalTiles.size
  def leafTileIdList: Seq[Int] = leafTiles.keys.toSeq.sorted
  def totalTileIdList: Seq[Int] = totalTiles.keys.toSeq.sorted
  def localIntCounts: ListMap[Int, Int] = totalTiles.mapValues(_.tileParams.core.nLocalInterrupts).to(ListMap)

  require(totalTileIdList.distinct.size == totalTiles.size, s"Every tile must be statically assigned a unique id, but got:\n${totalTileIdList}")
}

/** HasTiles instantiates and also connects a Config-urable sequence of tiles of any type to subsystem interconnect resources. */
trait HasElements extends DefaultElementContextType
{ this: LazyModule with Attachable with InstantiatesElements =>
  implicit val p: Parameters

  // connect all the tiles to interconnect attachment points made available in this subsystem context
  tileAttachParams.foreach { params =>
    params.connect(tile_prci_domains(params.tileParams.tileId).asInstanceOf[TilePRCIDomain[params.TileType]], this.asInstanceOf[params.TileContextType])
  }
  clusterAttachParams.foreach { params =>
    params.connect(cluster_prci_domains(params.clusterParams.clusterId).asInstanceOf[ClusterPRCIDomain], this.asInstanceOf[params.ClusterContextType])
  }
}

/** Provides some Chisel connectivity to certain tile IOs
  * This trait is intended for the root subsystem
  */
trait HasElementsRootContextModuleImp extends LazyRawModuleImp {
  val outer: InstantiatesElements with HasElements with HasElementsRootContext with HasTileInputConstants

  val reset_vector = outer.tileResetVectorIONodes.zipWithIndex.map { case (n, i) => n.makeIO(s"reset_vector_$i") }
  val tile_hartids = outer.tileHartIdIONodes.zipWithIndex.map { case (n, i) => n.makeIO(s"tile_hartids_$i") }

  val meip = if (outer.meipIONode.isDefined) Some(IO(Input(Vec(outer.meipIONode.get.out.size, Bool())))) else None
  meip.foreach { m =>
    m.zipWithIndex.foreach{ case (pin, i) =>
      (outer.meipIONode.get.out(i)._1)(0) := pin
    }
  }
  val seip = if (outer.seipIONode.isDefined) Some(IO(Input(Vec(outer.seipIONode.get.out.size, Bool())))) else None
  seip.foreach { s =>
    s.zipWithIndex.foreach{ case (pin, i) =>
      (outer.seipIONode.get.out(i)._1)(0) := pin
    }
  }
  val nmi = outer.nmiIONodes.map { case (i, node) =>
    node.makeIO(s"nmi_$i")
  }
}

/** Most tile types require only these traits in order for their standardized connect functions to apply.
  *
  *    BaseTiles subtypes with different needs can extend this trait to provide themselves with
  *    additional external connection points.
  */
trait DefaultElementContextType
    extends Attachable
    with HasTileNotificationSinks
{ this: LazyModule with Attachable =>
  val msipNodes: ListMap[Int, IntNode]
  val meipNodes: ListMap[Int, IntNode]
  val seipNodes: ListMap[Int, IntNode]
  val tileToPlicNodes: ListMap[Int, IntNode]
  val debugNodes: ListMap[Int, IntSyncNode]
  val nmiNodes: ListMap[Int, BundleBridgeNode[NMI]]
  val tileHartIdNodes: ListMap[Int, BundleBridgeNode[UInt]]
  val tileResetVectorNodes: ListMap[Int, BundleBridgeNode[UInt]]
  val traceCoreNodes: ListMap[Int, BundleBridgeNode[TraceCoreInterface]]
  val traceNodes: ListMap[Int, BundleBridgeNode[Vec[TracedInstruction]]]
}

/** This trait provides the tile attachment context for the root (outermost) subsystem */
trait HasElementsRootContext
{ this: HasElements
    with HasTileNotificationSinks
    with InstantiatesElements =>

  val clintOpt: Option[CLINT]
  val plicOpt: Option[TLPLIC]
  val debugOpt: Option[TLDebugModule]

  val msipNodes: ListMap[Int, IntNode] = (0 until nTotalTiles).map { i =>
    (i, IntEphemeralNode() := clintOpt.map(_.intnode).getOrElse(NullIntSource(sources = CLINTConsts.ints)))
  }.to(ListMap)

  val meipIONode = Option.when(plicOpt.isEmpty)(IntNexusNode(
    sourceFn = { _ => IntSourcePortParameters(Seq(IntSourceParameters(1))) },
    sinkFn   = { _ => IntSinkPortParameters(Seq(IntSinkParameters())) },
    outputRequiresInput = false,
    inputRequiresOutput = false))
  val meipNodes: ListMap[Int, IntNode] = (0 until nTotalTiles).map { i =>
    (i, IntEphemeralNode() := plicOpt.map(_.intnode).getOrElse(meipIONode.get))
  }.to(ListMap)

  val seipIONode = Option.when(plicOpt.isEmpty)(IntNexusNode(
    sourceFn = { _ => IntSourcePortParameters(Seq(IntSourceParameters(1))) },
    sinkFn   = { _ => IntSinkPortParameters(Seq(IntSinkParameters())) },
    outputRequiresInput = false,
    inputRequiresOutput = false))
  val seipNodes: ListMap[Int, IntNode] = totalTiles.filter { case (_, t) => t.tileParams.core.hasSupervisorMode }
    .mapValues( _ => IntEphemeralNode() := plicOpt.map(_.intnode).getOrElse(seipIONode.get)).to(ListMap)

  val tileToPlicNodes: ListMap[Int, IntNode] = (0 until nTotalTiles).map { i =>
    plicOpt.map(o => (i, o.intnode :=* IntEphemeralNode()))
  }.flatten.to(ListMap)

  val debugNodes: ListMap[Int, IntSyncNode] = (0 until nTotalTiles).map { i =>
    (i, IntSyncIdentityNode())
  }.to(ListMap)

  debugNodes.foreach { case (hartid, node) =>
    node := debugOpt.map(_.intnode).getOrElse(IntSyncCrossingSource() := NullIntSource())
  }

  val nmiHarts = totalTiles.filter { case (_, t) => t.tileParams.core.useNMI }.keys
  val nmiIONodes = nmiHarts.map { i => (i, BundleBridgeSource[NMI]()) }.to(ListMap)
  val nmiNodes: ListMap[Int, BundleBridgeNode[NMI]] = nmiIONodes.map { case (i, n) =>
    (i, BundleBridgeEphemeralNode[NMI]() := n)
  }.to(ListMap)

  val traceCoreNodes: ListMap[Int, BundleBridgeSink[TraceCoreInterface]] = (0 until nTotalTiles).map { i => (i, BundleBridgeSink[TraceCoreInterface]()) }.to(ListMap)
  val traceNodes: ListMap[Int, BundleBridgeSink[Vec[TracedInstruction]]] = (0 until nTotalTiles).map { i => (i, BundleBridgeSink[Vec[TracedInstruction]]()) }.to(ListMap)
}
