package xiangshan.backend.fu.util

import chisel3._
import chisel3.util._
import xiangshan.{XSBundle, HasXSParameter}
import org.chipsalliance.cde.config.Parameters
import utils.ConsecutiveOnes

trait SdtrigExt extends HasXSParameter{
  implicit val p: Parameters
  class TDataRegs extends XSBundle {
    val tdata1 = UInt(XLEN.W)
    val tdata2 = UInt(XLEN.W)
  }
  object TDataRegs extends TDataRegs {
    def apply() = new TDataRegs
  }

  class Tdata1Bundle extends XSBundle {
    val type_ = TrigTypeEnum()    // [XLEN-1: XLEN-4]
    val dmode = Bool()            // [XLEN-5]
    val data  = new Tdata1Data    // [XLEN-6, 0]
    require(this.getWidth == XLEN)
    def getTriggerAction : TrigActionEnum = {
      this.data.asTypeOf(new MControlData).action
    }
    def getTriggerChain: Bool = {
      this.data.asTypeOf(new MControlData).chain
    }
    def getTiming: Bool = {
      this.data.asTypeOf(new MControlData).timing
    }
  }
  object Tdata1Bundle extends Tdata1Bundle {
    def apply(): Tdata1Bundle = new Tdata1Bundle
    def Read(rdata: UInt) : UInt = rdata
    def Write(wdata: UInt, tdata1: UInt, chainable: Bool, debug_mode: Bool) : UInt = {
      val tdata1_old = WireInit(tdata1.asTypeOf(new Tdata1Bundle))
      val tdata1_new = Wire(new Tdata1Bundle)
      val wdata_new = WireInit(wdata.asTypeOf(new Tdata1Bundle))
      tdata1_new.type_ := wdata_new.type_.legalize
      tdata1_new.dmode := wdata_new.dmode && debug_mode // dmode only support write in debug mode
      when (wdata_new.type_.asUInt === TrigTypeEnum.MCONTROL) {
        tdata1_new.data.value := MControlData.Write(wdata_new.asUInt, tdata1_old.data.asUInt, chainable)
      }.otherwise {
        tdata1_new.data.value := 0.U
      }
      tdata1_new.asUInt
    }
    def default : UInt = {
      (TrigTypeEnum.disabled.litValue << (XLEN - 4)).U
    }
  }

  class TrigTypeEnum extends Bundle {
    val value         = UInt(4.W)
    def NONE          = 0.U
    def LEGACY        = 1.U
    def MCONTROL      = 2.U
    def ICOUNT        = 3.U
    def ITRIGGER      = 4.U
    def ETRIGGER      = 5.U
    def MCONTROL6     = 6.U
    def TMEXTTRIGGER  = 7.U
    def disabled      = 15.U
    /**
     * XS supports part of trigger type of Sdtrig extension
     * @param data trigger type checked
     * @return true.B, If XS support this trigger type
     */
    def isLegal : Bool = {
      this.asUInt === this.MCONTROL
    }
    def legalize : TrigTypeEnum = {
      Mux(this.isLegal, this.asUInt, this.disabled).asTypeOf(new TrigTypeEnum)
    }
  }
  object TrigTypeEnum extends TrigTypeEnum {
    def apply()   = new TrigTypeEnum
  }

  protected class Tdata1Data extends XSBundle {
    val value = UInt((XLEN-5).W)
  }

  class MControlData extends Tdata1Data {
    override val value = null
    val maskmax   = UInt(6.W)                           // [XLEN-6: XLEN-11]
    val zero1     = if (XLEN==64) UInt(30.W) else null  // [XLEN-12: 23]
    val sizehi    = if (XLEN==64) UInt(2.W) else null   // [22:21]
    val hit       = Bool()                              // [20]
    val select    = Bool()                              // [19]
    val timing    = Bool()                              // [18]
    val sizelo    = UInt(2.W)                           // [17:16]
    val action    = TrigActionEnum()                    // [15:12]
    val chain     = Bool()                              // [11]
    val match_    = TrigMatchEnum()                     // [10:7]
    val m         = Bool()                              // [6]
    val zero2     = Bool()                              // [5]
    val s         = Bool()                              // [4]
    val u         = Bool()                              // [3]
    val execute   = Bool()                              // [2]
    val store     = Bool()                              // [1]
    val load      = Bool()                              // [0]
    require(this.getWidth == (new Tdata1Data).getWidth)

    def isFetchTrigger: Bool = this.execute
    def isMemAccTrigger: Bool = this.store || this.load
  }
  object MControlData {
    def Read(rdata: UInt) : UInt = rdata
    def Write(wdata: UInt, tdata1data: UInt, chainable: Bool) : UInt = {
      val mcontrol_old = WireInit(tdata1data.asTypeOf(new MControlData))
      val tdata1_new = WireInit(wdata.asTypeOf(new Tdata1Bundle))
      val mcontrol_new = WireInit(tdata1_new.data.asTypeOf(new MControlData))
      val wdata_new = WireInit(wdata.asTypeOf(new MControlData))
      mcontrol_new.maskmax  := 0.U
      mcontrol_new.zero1    := 0.U
      mcontrol_new.sizehi   := 0.U
      mcontrol_new.hit      := false.B
      mcontrol_new.select   := wdata_new.execute && wdata_new.select // not support rdata/wdata trigger
      mcontrol_new.timing   := false.B // only support trigger fires before its execution
      mcontrol_new.sizelo   := 0.U
      mcontrol_new.action   := wdata_new.action.legalize(tdata1_new.dmode)
      mcontrol_new.chain    := chainable && wdata_new.chain
      mcontrol_new.match_   := wdata_new.match_.legalize
      mcontrol_new.zero2    := 0.U
      mcontrol_new.asUInt
    }
  }

  class TrigActionEnum extends Bundle {
    val value       = UInt(4.W)
    def BKPT_EXCPT  = 0.U // raise breakpoint exception
    def DEBUG_MODE  = 1.U // enter debug mode
    def TRACE_ON    = 2.U
    def TRACE_OFF   = 3.U
    def TRACE_NOTIFY= 4.U
    def default     = this.BKPT_EXCPT
    /**
     * XS supports part of trigger action type of Sdtrig extension
     * @param data action checked
     * @return true.B, If XS support such trigger action type
     */
    def isLegal(dmode: Bool) : Bool = {
      this.asUInt === this.BKPT_EXCPT || this.asUInt === this.DEBUG_MODE && dmode
    }
    def legalize(dmode: Bool) : TrigActionEnum = {
      Mux(this.isLegal(dmode), this.asUInt, this.default).asTypeOf(new TrigActionEnum)
    }
  }
  object TrigActionEnum extends TrigActionEnum {
    def apply() = new TrigActionEnum
  }

  class TrigMatchEnum extends Bundle {
    val value     = UInt(4.W)
    private def not_bit = 8.U
    def EQ        = 0.U
    def NAPOT     = 1.U
    def GE        = 2.U
    def LT        = 3.U
    def MASK_LO   = 4.U
    def MASK_HI   = 5.U
    def NE        = EQ      | this.not_bit // not eq
    def NNAPOT    = NAPOT   | this.not_bit // not napot
    def NMASK_LO  = MASK_LO | this.not_bit // not mask low
    def NMASK_HI  = MASK_HI | this.not_bit // not mask high
    def default   = this.EQ
    def isRVSpecLegal : Bool = {
      this.asUInt === this.EQ || this.asUInt === this.NAPOT ||
        this.asUInt === this.GE || this.asUInt === this.LT ||
        this.asUInt === this.MASK_LO || this.asUInt === this.MASK_HI ||
        this.asUInt === this.NE || this.asUInt === this.NNAPOT ||
        this.asUInt === this.NMASK_LO || this.asUInt === this.NMASK_HI
    }

    /**
     * XS supports part of trigger match type of Sdtrig extension
     * @param data match type checked
     * @return true.B, If XS support such trigger match type
     */
    def isLegal : Bool = {
      this.asUInt === this.EQ || this.asUInt === this.GE || this.asUInt === this.LT
    }
    def legalize : TrigMatchEnum = {
      Mux(this.isLegal, this.asUInt, this.default).asTypeOf(new TrigMatchEnum)
    }
  }
  object TrigMatchEnum extends TrigMatchEnum {
    def apply() = new TrigMatchEnum
  }

  /**
   * Check if triggers can fire
   * @param triggerNum
   * @param canFireVec
   * @param hitVec
   * @param timingVec
   * @param chainVec
   */
  def TriggerCheckCanFire(triggerNum: Int, canFireVec: Vec[Bool], hitVec: Vec[Bool], timingVec: Vec[Bool], chainVec: Vec[Bool]): Unit = {
    val trigger2ChainVec = WireInit(VecInit(Seq.fill(triggerNum)(false.B)))
    val trigger2TimingSameVec = WireInit(VecInit(Seq.fill(triggerNum)(true.B)))
    val trigger2TimingOkVec = WireInit(VecInit(Seq.fill(triggerNum)(true.B)))
    val trigger2ChainOkVec = WireInit(VecInit(Seq.fill(triggerNum)(true.B)))
    for (i <- 1 until triggerNum) { // the 0th trigger always chain ok
      trigger2ChainOkVec(i) := chainVec(i - 1) && hitVec(i - 1) || !chainVec(i - 1)
    }

    for (i <- 1 until triggerNum) { // the 0th trigger always timing same, not chain, timing ok
      trigger2TimingSameVec(i) := timingVec(i - 1) === timingVec(i)
      trigger2ChainVec(i) := chainVec(i - 1) && !chainVec(i)
      trigger2TimingOkVec(i) := trigger2ChainVec(i) && trigger2TimingSameVec(i) || !chainVec(i - 1)
    }
    canFireVec.zipWithIndex.foreach {
      case (canFire, i) => canFire := trigger2ChainOkVec(i) && trigger2TimingOkVec(i) && hitVec(i) && !chainVec(i)
    }
  }

  /**
   * Check if chain vector is legal
   * @param chainVec
   * @param chainLen
   * @return true.B if the max length of chain don't exceed the permitted length
   */
  def TriggerCheckChainLegal(chainVec: Seq[Bool], chainLen: Int): Bool = {
    !ConsecutiveOnes(chainVec, chainLen)
  }

  /**
   * Compare data with trigger data
   * @param data data compared
   * @param tdata data from trigger
   * @param matchType trigger match type in UInt
   * @param enable if the trigger is enabled
   * @return true.B if data meet the trigger match condition
   */
  def TriggerCmp(data: UInt, tdata: UInt, matchType: UInt, enable: Bool): Bool = {
    val eq = data(VAddrBits - 1, 0) === tdata(VAddrBits - 1, 0)
    val ge = data(VAddrBits - 1, 0) >= tdata(VAddrBits - 1, 0)
    val lt = data(VAddrBits - 1, 0) < tdata(VAddrBits - 1, 0)
    val res = MuxLookup(matchType, false.B)(Seq(
      TrigMatchEnum.EQ -> eq,
      TrigMatchEnum.GE -> ge,
      TrigMatchEnum.LT -> lt
    ))
    res && enable
  }

  /**
   * author @Guokai Chen
   * compare between Consecutive pc and tdada2
   */
  def TriggerCmpConsecutive(pcVec: Vec[UInt], tdata: UInt, matchType: UInt, enable: Bool) : Vec[Bool] = {
    // opt: only compare two possible high bits: orig and orig+1
    val pcVecWidth = pcVec.length
    // also take care of that triggerMatch is less
    val highPos = log2Up(pcVecWidth) + 1

    val lowPCVec = Wire(Vec(pcVecWidth, UInt(highPos.W)))
    lowPCVec.zipWithIndex.map{case (h, i) => h := pcVec(i)(highPos - 1, 0)}
    val highPC = pcVec(0)(VAddrBits - 1, highPos)
    val highPC1 = pcVec(0)(VAddrBits - 1, highPos) + 1.U

    val lowTdata = tdata(highPos - 1, 0)
    val highTdata = tdata(VAddrBits - 1, highPos)

    val highPCEqual = highPC === highTdata
    val highPC1Equal = highPC1 === highTdata
    val highPCGreater = highPC > highTdata
    val highPC1Greater = highPC1 > highTdata
    val highPCLess = highPC < highTdata
    val highPC1Less = highPC1 < highTdata

    val carry = Wire(Vec(pcVecWidth, Bool()))
    carry.zipWithIndex.map{case (c, i) => c := pcVec(i)(highPos) =/= pcVec(0)(highPos)}

    val lowPCEqual = Wire(Vec(pcVecWidth, Bool()))
    val lowPCGreater = Wire(Vec(pcVecWidth, Bool()))
    val lowPCLess = Wire(Vec(pcVecWidth, Bool()))

    lowPCEqual.zipWithIndex.map{case (l, i) => l := lowPCVec(i) === lowTdata }
    lowPCGreater.zipWithIndex.map{case (l, i) => l :=  lowPCVec(i) >= lowTdata }
    lowPCLess.zipWithIndex.map{case (l, i) => l :=  lowPCVec(i) < lowTdata }

    val overallEqual = Wire(Vec(pcVecWidth, Bool()))
    val overallGreater = Wire(Vec(pcVecWidth, Bool()))
    val overallLess = Wire(Vec(pcVecWidth, Bool()))

    overallEqual.zipWithIndex.map{case (o, i) => o :=
      !carry(i) && highPCEqual  && lowPCEqual(i) ||
        carry(i) && highPC1Equal && lowPCEqual(i)
    }

    // [left : greater 1. nonCarry 2. carry
    overallGreater.zipWithIndex.map { case (o, i) => o :=
      !carry(i) && (highPCGreater || highPCEqual && (lowPCEqual(i) || lowPCGreater(i))) ||
       carry(i) && (highPC1Greater || highPC1Equal && (lowPCEqual(i) || lowPCGreater(i)))
    }

    // right) : less 1. nonCarry 2. carry
    overallLess.zipWithIndex.map { case (o, i) => o :=
      !carry(i) && (highPCLess || highPCEqual && lowPCLess(i)) ||
       carry(i) && (highPC1Less || highPC1Equal && lowPCLess(i))
    }

    val ret = Wire(Vec(pcVecWidth, Bool()))
    ret.zipWithIndex.map{case (r, i) => r := MuxLookup(matchType, false.B)(
      Seq(
        TrigMatchEnum.EQ -> overallEqual(i),
        TrigMatchEnum.GE -> overallGreater(i),
        TrigMatchEnum.LT -> overallLess(i))) && enable}
    ret
  }
}
