package xiangshan.backend.datapath

import org.chipsalliance.cde.config.Parameters
import chisel3._
import chisel3.util.{Arbiter, DecoupledIO, RRArbiter, Valid, PopCount}
import utils.SeqUtils.{MixedVec3, Seq3}
import utils.{OptionWrapper, SeqUtils}
import xiangshan.backend.BackendParams
import xiangshan.backend.datapath.DataConfig._
import xiangshan.backend.datapath.RdConfig._
import xiangshan.backend.regfile.PregParams
import utility._

case class RFRdArbParams(
  inRdCfgs: Seq3[RdConfig],
  pregParams: PregParams,
) {
  require(inRdCfgs.nonEmpty)

  def genInputBundle: MixedVec3[DecoupledIO[RFArbiterBundle]] = {
    val pregWidth = pregParams.addrWidth
    SeqUtils.mapToMixedVec3(this.inRdCfgs, (rd: RdConfig) => DecoupledIO(new RFArbiterBundle(rd, pregWidth)))
  }

  def portMax: Int = inRdCfgs.flatten.flatten.map(_.port).max
}

class RFArbiterBundle(var rdCfg: Option[RdConfig], pregWidth: Int) extends Bundle {
  val addr = UInt(pregWidth.W)

  def this(rdCfg_ : RdConfig, pregWidth_ : Int) = this(Some(rdCfg_), pregWidth_)

  def this(pregWidth_ :Int) = this(None, pregWidth_)
}

class RFReadArbiterIO(params: RFRdArbParams)(implicit p: Parameters) extends Bundle {
  private val pregWidth = params.pregParams.addrWidth
  val in = Flipped(params.genInputBundle)
  val out = Vec(params.portMax + 1, Valid(new RFArbiterBundle(pregWidth)))
}

abstract class RFReadArbiterBase(val params: RFRdArbParams)(implicit p: Parameters) extends Module {
  protected def portRange: Range

  val io = IO(new RFReadArbiterIO(params))
  dontTouch(io)

  protected val pregParams = params.pregParams
  protected val pregWidth = pregParams.addrWidth

  protected val inGroup: Map[Int, Seq[DecoupledIO[RFArbiterBundle]]] = io.in
    .flatten.flatten
    .groupBy(_.bits.rdCfg.get.port)
    .map(x => (x._1, x._2.sortBy(_.bits.rdCfg.get.priority).toSeq))
  protected val arbiters: Seq[Option[WBArbiter[RFArbiterBundle]]] = portRange.map { portIdx =>
    Option.when(
      inGroup.isDefinedAt(portIdx))(
      Module(new WBArbiter(
        new RFArbiterBundle(pregWidth),
        inGroup(portIdx).size
      ))
    )
  }

  arbiters.zipWithIndex.foreach { case (arbiter, portIdx) =>
    if (arbiter.nonEmpty) {
      arbiter.get.io.in.zip(inGroup(portIdx)).foreach { case (arbiterIn, ioIn) =>
        arbiterIn <> ioIn
      }
    }
  }

  if (params.pregParams.dataCfg.isInstanceOf[IntData]) {
    val arbitersIn = arbiters.filter(_.nonEmpty).map(_.get.io.in)
    val hasConflict = arbitersIn.map { case a =>
      PopCount(a.map(_.valid)) > 1.U
    }
    for (i <- hasConflict.indices) {
      XSPerfAccumulate(s"IntRFReadPort_${i}_Conflict", PopCount(hasConflict(i)))
    }
    val hasRead0 = arbitersIn.map { case a =>
      a.map(x => x.valid && x.bits.addr === 0.U).reduce(_ || _)
    }
    val hasSameAddr = arbitersIn.map { case a =>
      if (a.size == 2) a(0).valid && a(1).valid && a(0).bits.addr === a(1).bits.addr
      else false.B
    }
    val hasRead0Conflict = hasConflict.zip(hasRead0).map(x => x._1 && x._2)
    val hasReadSameAddrConflict = hasConflict.zip(hasSameAddr).map(x => x._1 && x._2)
    XSPerfAccumulate("IntRFRead0_conflict_count", PopCount(hasRead0Conflict))
    XSPerfAccumulate("IntRFReadSameAddr_conflict_count", PopCount(hasReadSameAddrConflict))
  }

  // connection of NoRD
  io.in.map(_.map(_.map(x =>
    if (x.bits.rdCfg.get.isInstanceOf[NoRD]) {
      x.ready := true.B
    }
  )))

  for (portIdx <- io.out.indices) {
    val arb = arbiters(portIdx)
    val out = io.out(portIdx)
    if (arb.nonEmpty) {
      val arbOut = arb.get.io.out
      arbOut.ready := true.B
      out.valid := arbOut.valid
      out.bits := arbOut.bits
    } else {
      out := 0.U.asTypeOf(out)
    }
  }
}

class IntRFReadArbiter(
  backendParams: BackendParams
)(implicit
  p: Parameters
) extends RFReadArbiterBase(RFRdArbParams(backendParams.getRdCfgs[IntRD], backendParams.intPregParams)) {
  override protected def portRange: Range = 0 to backendParams.getRdPortIndices(IntData()).max
}

class FpRFReadArbiter(
  backendParams: BackendParams
)(implicit
  p: Parameters
) extends RFReadArbiterBase(RFRdArbParams(backendParams.getRdCfgs[FpRD], backendParams.fpPregParams)) {
  override protected def portRange: Range = 0 to backendParams.getRdPortIndices(FpData()).max
}

class VfRFReadArbiter(
  backendParams: BackendParams
)(implicit
  p: Parameters
) extends RFReadArbiterBase(RFRdArbParams(backendParams.getRdCfgs[VfRD], backendParams.vfPregParams)) {
  override protected def portRange: Range = 0 to backendParams.getRdPortIndices(VecData()).max
}

class V0RFReadArbiter(
  backendParams: BackendParams
)(implicit
  p: Parameters
) extends RFReadArbiterBase(RFRdArbParams(backendParams.getRdCfgs[V0RD], backendParams.v0PregParams)) {
  override protected def portRange: Range = 0 to backendParams.getRdPortIndices(V0Data()).max
}

class VlRFReadArbiter(
  backendParams: BackendParams
)(implicit
  p: Parameters
) extends RFReadArbiterBase(RFRdArbParams(backendParams.getRdCfgs[VlRD], backendParams.vlPregParams)) {
  override protected def portRange: Range = 0 to backendParams.getRdPortIndices(VlData()).max
}
