// See LICENSE.SiFive for license details.

package freechips.rocketchip.coreplex

import Chisel._
import freechips.rocketchip.config.{Field, Parameters}
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.tilelink._
import freechips.rocketchip.util._

case class SystemBusParams(
  beatBytes: Int,
  blockBytes: Int,
  masterBuffering: BufferParams = BufferParams.default,
  slaveBuffering: BufferParams = BufferParams.default
) extends TLBusParams

case object SystemBusParams extends Field[SystemBusParams]

class SystemBus(params: SystemBusParams)(implicit p: Parameters) extends TLBusWrapper(params, "SystemBus") {

  private val master_splitter = LazyModule(new TLSplitter)  // Allows cycle-free connection to external networks
  master_splitter.suggestName(s"${busName}_master_TLSplitter")
  inwardNode :=* master_splitter.node
  def busView = master_splitter.node.edgesIn.head

  protected def inwardSplitNode: TLInwardNode = master_splitter.node
  protected def outwardSplitNode: TLOutwardNode = master_splitter.node

  private val tile_fixer = LazyModule(new TLFIFOFixer(TLFIFOFixer.allUncacheable))
  tile_fixer.suggestName(s"${busName}_tile_TLFIFOFixer")
  master_splitter.node :=* tile_fixer.node

  private val port_fixer = LazyModule(new TLFIFOFixer(TLFIFOFixer.all))
  port_fixer.suggestName(s"${busName}_port_TLFIFOFixer")
  master_splitter.node :=* port_fixer.node

  private val pbus_fixer = LazyModule(new TLFIFOFixer(TLFIFOFixer.all))
  pbus_fixer.suggestName(s"${busName}_pbus_TLFIFOFixer")
  pbus_fixer.node :*= outwardWWNode

  def toSplitSlaves: TLOutwardNode = outwardSplitNode

  def toPeripheryBus(addBuffers: Int = 0): TLOutwardNode = {
    val (in, out) = bufferChain(addBuffers, name = Some("pbus"))
    in := pbus_fixer.node
    out
  }

  val toMemoryBus: TLOutwardNode = outwardNode

  val toSlave: TLOutwardNode = outwardBufNode

  def fromCoherentChip: TLInwardNode = inwardNode

  def fromFrontBus: TLInwardNode = master_splitter.node

  def fromSyncTiles(params: BufferParams, addBuffers: Int = 0, name: Option[String] = None): TLInwardNode = {
    val tile_buf = LazyModule(new TLBuffer(params))
    name.foreach { n => tile_buf.suggestName(s"${busName}_${n}_TLBuffer") }
    val (in, out) = bufferChain(addBuffers, name = name)

    tile_fixer.node :=* out
    in :=* tile_buf.node
    tile_buf.node
  }

  def fromRationalTiles(dir: RationalDirection, addBuffers: Int = 0, name: Option[String] = None): TLRationalInwardNode = {
    val tile_sink = LazyModule(new TLRationalCrossingSink(direction = dir))
    name.foreach { n => tile_sink.suggestName(s"${busName}_${n}_TLRationalCrossingSink") }
    val (in, out) = bufferChain(addBuffers, name = name)

    tile_fixer.node :=* out
    in :=* tile_sink.node
    tile_sink.node
  }

  def fromAsyncTiles(depth: Int, sync: Int, addBuffers: Int = 0, name: Option[String] = None): TLAsyncInwardNode = {
    val tile_sink = LazyModule(new TLAsyncCrossingSink(depth, sync))
    name.foreach { n => tile_sink.suggestName(s"${busName}_${n}_TLAsyncCrossingSink") }
    val (in, out) = bufferChain(addBuffers, name = name)

    tile_fixer.node :=* out
    in :=* tile_sink.node
    tile_sink.node
  }

  def fromSyncPorts(params: BufferParams =  BufferParams.default, name: Option[String] = None): TLInwardNode = {
    val buffer = LazyModule(new TLBuffer(params))
    name.foreach { n => buffer.suggestName(s"${busName}_${n}_TLBuffer") }
    port_fixer.node :=* buffer.node
    buffer.node
  }

  def fromSyncFIFOMaster(params: BufferParams =  BufferParams.default, name: Option[String] = None): TLInwardNode = {
    fromSyncPorts(params, name)
  }

  def fromAsyncPorts(depth: Int = 8, sync: Int = 3, name : Option[String] = None): TLAsyncInwardNode = {
    val sink = LazyModule(new TLAsyncCrossingSink(depth, sync))
    name.foreach { n => sink.suggestName(s"${busName}_${n}_TLAsyncCrossingSink") }
    port_fixer.node :=* sink.node
    sink.node
  }

  def fromAsyncFIFOMaster(depth: Int = 8, sync: Int = 3, name: Option[String] = None): TLAsyncInwardNode = fromAsyncPorts(depth, sync, name)

  def fromRationalPorts(dir: RationalDirection, name: Option[String] = None): TLRationalInwardNode = {
    val sink = LazyModule(new TLRationalCrossingSink(dir))
    name.foreach{ n => sink.suggestName(s"${busName}_${n}_TLRationalCrossingSink") }
    port_fixer.node :=* sink.node
    sink.node
  }

  def fromRationalFIFOMaster(dir: RationalDirection, name: Option[String] = None): TLRationalInwardNode = fromRationalPorts(dir, name)
}

/** Provides buses that serve as attachment points,
  * for use in traits that connect individual devices or external ports.
  */
trait HasSystemBus extends HasInterruptBus {
  private val sbusParams = p(SystemBusParams)
  val sbusBeatBytes = sbusParams.beatBytes

  val sbus = new SystemBus(sbusParams)

  def sharedMemoryTLEdge: TLEdge = sbus.busView
}
