package xiangshan.backend.trace

import chisel3._
import chisel3.util._
import org.chipsalliance.cde.config.Parameters
import utils.NamedUInt
import xiangshan.HasXSParameter
import xiangshan.frontend.{BrType, FtqPtr, PreDecodeInfo}

class TraceCSR(implicit val p: Parameters) extends Bundle with HasXSParameter {
  val cause = UInt(CauseWidth.W)
  val tval  = UInt(TvalWidth.W)
  val lastPriv    = Priv()
  val currentPriv = Priv()
}

class TracePipe(iretireWidth: Int)(implicit val p: Parameters) extends Bundle with HasXSParameter {
  val itype     = Itype()
  val iretire   = UInt(iretireWidth.W)
  val ilastsize = Ilastsize()
}

class TraceBlock(hasIaddr: Boolean, iretireWidth: Int)(implicit val p: Parameters) extends Bundle with HasXSParameter {
  val iaddr     = if (hasIaddr)   Some(UInt(IaddrWidth.W))                else None
  val ftqIdx    = if (!hasIaddr)  Some(new FtqPtr)                        else None
  val ftqOffset = if (!hasIaddr)  Some(UInt(log2Up(PredictWidth).W))      else None
  val tracePipe = new TracePipe(iretireWidth)
}

class TraceBundle(hasIaddr: Boolean, blockSize: Int, iretireWidth: Int)(implicit val p: Parameters) extends Bundle with HasXSParameter {
  val blocks = Vec(blockSize, ValidIO(new TraceBlock(hasIaddr, iretireWidth)))
}

class FromEncoder extends Bundle {
  val enable = Bool()
  val stall  = Bool()
}

class TraceCoreInterface(hasOffset: Boolean = false)(implicit val p: Parameters) extends Bundle with HasXSParameter {
  val fromEncoder = Input(new Bundle {
    val enable = Bool()
    val stall  = Bool()
  })
  val toEncoder = Output(new Bundle {
    val priv   = Priv()
    val trap   = new Bundle{
      val cause = UInt(CauseWidth.W)
      val tval  = UInt(TvalWidth.W)
    }
    val groups = Vec(TraceGroupNum, ValidIO(new Bundle{
      val iaddr     = UInt(IaddrWidth.W)
      val ftqOffset = if (hasOffset)  Some(UInt(log2Up(PredictWidth).W)) else None
      val itype     = UInt(ItypeWidth.W)
      val iretire   = UInt(IretireWidthCompressed.W)
      val ilastsize = UInt(IlastsizeWidth.W)
    }))
  })
}

object Itype extends NamedUInt(4) {
  def None                 = 0.U
  def Exception            = 1.U    //rob
  def Interrupt            = 2.U    //rob
  def ExpIntReturn         = 3.U    //rename
  def NonTaken             = 4.U    //commit
  def Taken                = 5.U    //commit
  def UninferableJump      = 6.U    //It's reserved when width of itype is 4.
  def reserved             = 7.U    //reserved
  def UninferableCall      = 8.U    //rename
  def InferableCall        = 9.U    //rename
  def UninferableTailCall  = 10.U   //rename
  def InferableTailCall    = 11.U   //rename
  def CoRoutineSwap        = 12.U   //rename
  def FunctionReturn       = 13.U   //rename
  def OtherUninferableJump = 14.U   //rename
  def OtherInferableJump   = 15.U   //rename

  // Assuming the branchType is NonTaken here, it will be correctly modified after writeBack.
  def Branch = NonTaken

  def jumpTypeGen(brType: UInt, rd: OpRegType, rs: OpRegType): UInt = {

    val isEqualRdRs = rd === rs
    val isJal       = brType === BrType.jal
    val isJalr      = brType === BrType.jalr
    val isBranch    = brType === BrType.branch

    // push to RAS when rd is link, pop from RAS when rs is link
    def isUninferableCall      = isJalr && rd.isLink && (!rs.isLink || rs.isLink && isEqualRdRs)  //8   push
    def isInferableCall        = isJal && rd.isLink                                               //9   push
    def isUninferableTailCall  = isJalr && rd.isX0 && !rs.isLink                                  //10  no op
    def isInferableTailCall    = isJal && rd.isX0                                                 //11  no op
    def isCoRoutineSwap        = isJalr && rd.isLink && rs.isLink && !isEqualRdRs                 //12  pop then push
    def isFunctionReturn       = isJalr && !rd.isLink && rs.isLink                                //13  pop
    def isOtherUninferableJump = isJalr && !rd.isLink && !rd.isX0 && !rs.isLink                   //14  no op
    def isOtherInferableJump   = isJal && !rd.isLink && !rd.isX0                                  //15  no op

    val jumpType = Mux1H(
      Seq(
        isBranch,
        isUninferableCall,
        isInferableCall,
        isUninferableTailCall,
        isInferableTailCall,
        isCoRoutineSwap,
        isFunctionReturn,
        isOtherUninferableJump,
        isOtherInferableJump,
      ),
      Seq(
        Branch,
        UninferableCall,
        InferableCall,
        UninferableTailCall,
        InferableTailCall,
        CoRoutineSwap,
        FunctionReturn,
        OtherUninferableJump,
        OtherInferableJump,
      )
    )

    Mux(isBranch || isJal || isJalr, jumpType, 0.U)
  }

  def isTrap(itype: UInt) = Seq(Exception, Interrupt).map(_ === itype).reduce(_ || _)

  def isTrapOrXret(itype: UInt) = Seq(Exception, Interrupt, ExpIntReturn).map(_ === itype).reduce(_ || _)

  def isNotNone(itype: UInt) = itype =/= None

  def isBranchType(itype: UInt) = itype === Branch

  // supportSijump
  def isUninferable(itype: UInt) = Seq(UninferableCall, UninferableTailCall, CoRoutineSwap,
    UninferableTailCall, OtherUninferableJump).map(_ === itype).reduce(_ || _)
}

object Ilastsize extends NamedUInt(1) {
  def HalfWord = 0.U
  def Word     = 1.U
}

object Priv extends NamedUInt(3) {
  def HU = 0.U
  def HS = 1.U
  def M  = 3.U
  def D  = 4.U
  def VU = 5.U
  def VS = 6.U
}

class OpRegType extends Bundle {
  val value = UInt(6.W)
  def isX0   = this.value === 0.U
  def isX1   = this.value === 1.U
  def isX5   = this.value === 5.U
  def isLink = Seq(isX1, isX5).reduce(_ || _)
}
