/***************************************************************************************
* Copyright (c) 2020-2021 Institute of Computing Technology, Chinese Academy of Sciences
* Copyright (c) 2020-2021 Peng Cheng Laboratory
*
* XiangShan is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

package xiangshan.backend.fu.util

import chisel3._
import chisel3.util._
import freechips.rocketchip.rocket.CSRs

trait HasCSRConst {
  // Supervisor Custom Read/Write
  val Sbpctl        = 0x5C0
  val Spfctl        = 0x5C1
  val Slvpredctl    = 0x5C2
  val Smblockctl    = 0x5C3
  val Srnctl        = 0x5C4
  /** 0x5C5-0x5E5 for cache instruction register*/
  val Scachebase    = 0x5C5

  // Machine level PMA TODO: remove this
  val PmacfgBase    = 0x7C0
  val PmaaddrBase   = 0x7C8 // 64 entry at most

  // Machine level Bitmap Check(Custom Read/Write)
  val Mbmc = 0xBC2

  def privEcall  = 0x000.U
  def privEbreak = 0x001.U
  def privMNret  = 0x702.U
  def privMret   = 0x302.U
  def privSret   = 0x102.U
  def privUret   = 0x002.U
  def privDret   = 0x7b2.U

  def ModeM     = 0x3.U
  def ModeH     = 0x2.U
  def ModeS     = 0x1.U
  def ModeU     = 0x0.U

  def IRQ_USIP  = 0
  def IRQ_SSIP  = 1
  def IRQ_VSSIP = 2
  def IRQ_MSIP  = 3

  def IRQ_UTIP  = 4
  def IRQ_STIP  = 5
  def IRQ_VSTIP = 6
  def IRQ_MTIP  = 7

  def IRQ_UEIP  = 8
  def IRQ_SEIP  = 9
  def IRQ_VSEIP = 10
  def IRQ_MEIP  = 11

  def IRQ_SGEIP = 12

  val Hgatp_Mode_len = 4
  val Hgatp_Vmid_len = 16
  val Hgatp_Addr_len = 44

  val Satp_Mode_len = 4
  val Satp_Asid_len = 16
  val Satp_Addr_len = 44
  def satp_part_wmask(max_length: Int, length: Int) : UInt = {
    require(length > 0 && length <= max_length)
    ((1L << length) - 1).U(max_length.W)
  }

  val IntPriority = Seq(
    IRQ_DEBUG,
    IRQ_MEIP, IRQ_MSIP, IRQ_MTIP,
    IRQ_SEIP, IRQ_SSIP, IRQ_STIP,
    IRQ_UEIP, IRQ_USIP, IRQ_UTIP,
    IRQ_VSEIP, IRQ_VSSIP, IRQ_VSTIP, IRQ_SGEIP
  )

  def csrAccessPermissionCheck(addr: UInt, wen: Bool, mode: UInt, virt: Bool, hasH: Bool): UInt = {
    val readOnly = addr(11, 10) === "b11".U
    val lowestAccessPrivilegeLevel = addr(9,8)
    val priv = Mux(mode === ModeS, ModeH, mode)
    val ret = Wire(Bool()) //0.U: normal, 1.U: illegal_instruction, 2.U: virtual instruction
    when (lowestAccessPrivilegeLevel === ModeH && !hasH){
      ret := 1.U
    }.elsewhen (readOnly && wen) {
      ret := 1.U
    }.elsewhen (priv < lowestAccessPrivilegeLevel) {
      when(virt && lowestAccessPrivilegeLevel <= ModeH){
        ret := 2.U
      }.otherwise{
        ret := 1.U
      }
    }.otherwise{
      ret := 0.U
    }
    ret
  }

  def perfcntPermissionCheck(addr: UInt, mode: UInt, mmask: UInt, smask: UInt): Bool = {
    val index = UIntToOH(addr & 31.U)
    Mux(mode === ModeM, true.B, Mux(mode === ModeS, (index & mmask) =/= 0.U, (index & mmask & smask) =/= 0.U))
  }

  def dcsrPermissionCheck(addr: UInt, mModeCanWrite: UInt, debug: Bool): Bool = {
    // debug mode write only regs
    val isDebugReg = addr(11, 4) === "h7b".U
    Mux(!mModeCanWrite && isDebugReg, debug, true.B)
  }

  def triggerPermissionCheck(addr: UInt, mModeCanWrite: UInt, debug: Bool): Bool = {
    val isTriggerReg = addr(11, 4) === "h7a".U
    Mux(!mModeCanWrite && isTriggerReg, debug, true.B)
  }
}
object CSRConst extends HasCSRConst


package xiangshan.backend.fu.wrapper

import org.chipsalliance.cde.config.Parameters
import chisel3._
import utility.{SignExt, ZeroExt}
import xiangshan.RedirectLevel
import xiangshan.backend.fu.{FuConfig, FuncUnit, JumpDataModule, PipedFuncUnit}
import xiangshan.backend.datapath.DataConfig.VAddrData

class JumpUnit(cfg: FuConfig)(implicit p: Parameters) extends PipedFuncUnit(cfg) {
  private val jumpDataModule = Module(new JumpDataModule)

  private val flushed = io.in.bits.ctrl.robIdx.needFlush(io.flush)

  // associated with AddrData's position of JmpCfg.srcData
  private val src = io.in.bits.data.src(0)
  private val pc = Mux(io.instrAddrTransType.get.shouldBeSext,
    SignExt(io.in.bits.data.pc.get, cfg.destDataBits),
    ZeroExt(io.in.bits.data.pc.get, cfg.destDataBits)
  )
  private val imm = io.in.bits.data.imm
  private val func = io.in.bits.ctrl.fuOpType
  private val isRVC = io.in.bits.ctrl.preDecode.get.isRVC

  jumpDataModule.io.src := src
  jumpDataModule.io.pc := pc
  jumpDataModule.io.imm := imm
  jumpDataModule.io.nextPcOffset := io.in.bits.data.nextPcOffset.get
  jumpDataModule.io.func := func
  jumpDataModule.io.isRVC := isRVC

  val jmpTarget = io.in.bits.ctrl.predictInfo.get.target
  val predTaken = io.in.bits.ctrl.predictInfo.get.taken

  val redirect = io.out.bits.res.redirect.get.bits

  redirect.fullTarget := jumpDataModule.io.target
  redirect.cfiUpdate.predTaken := true.B
  redirect.cfiUpdate.taken := true.B
  redirect.cfiUpdate.target := jumpDataModule.io.target
  redirect.cfiUpdate.pc := io.in.bits.data.pc.get
  redirect.cfiUpdate.isMisPred := jumpDataModule.io.target(VAddrData().dataWidth - 1, 0) =/= jmpTarget || !predTaken
  redirect.cfiUpdate.backendIAF := io.instrAddrTransType.get.checkAccessFault(jumpDataModule.io.target)
  redirect.cfiUpdate.backendIPF := io.instrAddrTransType.get.checkPageFault(jumpDataModule.io.target)
  redirect.cfiUpdate.backendIGPF := io.instrAddrTransType.get.checkGuestPageFault(jumpDataModule.io.target)
//  redirect.debug_runahead_checkpoint_id := uop.debugInfo.runahead_checkpoint_id // Todo: assign it

  io.in.ready := io.out.ready
  io.out.valid := io.in.valid
  io.out.bits.res.data := jumpDataModule.io.result
  connect0LatencyCtrlSingal
}

  val redirectValid = io.out.bits.res.redirect.get.valid
  redirectValid := io.in.valid && !jumpDataModule.io.isAuipc
  redirect := 0.U.asTypeOf(redirect)
  redirect.level := RedirectLevel.flushAfter


/***************************************************************************************
* Copyright (c) 2020-2021 Institute of Computing Technology, Chinese Academy of Sciences
* Copyright (c) 2020-2021 Peng Cheng Laboratory
*
* XiangShan is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

package xiangshan.backend.regcache

import org.chipsalliance.cde.config.Parameters
import chisel3._
import chisel3.util._
import utils._
import utility._
import xiangshan._
import xiangshan.backend.Bundles._
import xiangshan.backend.BackendParams
import xiangshan.backend.issue.SchdBlockParams
import freechips.rocketchip.util.SeqToAugmentedSeq

class RegCacheTagTable(numReadPorts: Int)(implicit p: Parameters) extends XSModule {

  val io = IO(new RegCacheTagTableIO(numReadPorts))

  println(s"[RegCacheTagTable] readPorts: ${numReadPorts}, " +

  println(s"[RegCacheTagTable] addrWidth: ${RegCacheIdxWidth}, tagWidth: ${IntPhyRegIdxWidth}")

  private val IntRegCacheReadSize = numReadPorts
  private val IntRegCacheWriteSize = backendParams.getIntExuRCWriteSize
  private val MemRegCacheReadSize = numReadPorts
  private val MemRegCacheWriteSize = backendParams.getMemExuRCWriteSize

  val IntRCTagTable = Module(new RegCacheTagModule("IntRCTagTable", IntRegCacheSize, IntRegCacheReadSize, IntRegCacheWriteSize,
                                                   RegCacheIdxWidth - 1, IntPhyRegIdxWidth))

  val MemRCTagTable = Module(new RegCacheTagModule("MemRCTagTable", MemRegCacheSize, MemRegCacheReadSize, MemRegCacheWriteSize,
                                                   RegCacheIdxWidth - 1, IntPhyRegIdxWidth))

  // read
  io.readPorts
  .lazyZip(IntRCTagTable.io.readPorts.lazyZip(MemRCTagTable.io.readPorts))
  .foreach{ case (r_in, (r_int, r_mem)) =>
    r_int.ren  := r_in.ren
    r_mem.ren  := r_in.ren
    r_int.tag  := r_in.tag
    r_mem.tag  := r_in.tag
    val matchAlloc = io.allocPregs.map(x => x.valid && r_in.tag === x.bits).reduce(_ || _)
    r_in.valid := (r_int.valid || r_mem.valid) && !matchAlloc
    r_in.addr  := Mux(r_int.valid, Cat("b0".U, r_int.addr), Cat("b1".U, r_mem.addr))
  }

  // write
  val wakeupFromIQNeedWriteRC = io.wakeupFromIQ.filter(_.bits.params.needWriteRegCache)
  val shiftLoadDependency = Wire(Vec(wakeupFromIQNeedWriteRC.size, Vec(LoadPipelineWidth, UInt(LoadDependencyWidth.W))))

  require(wakeupFromIQNeedWriteRC.size == IntRegCacheWriteSize + MemRegCacheWriteSize, "wakeup size should be equal to RC write size")

  shiftLoadDependency.zip(wakeupFromIQNeedWriteRC.map(_.bits.loadDependency)).zip(backendParams.intSchdParams.get.wakeUpInExuSources.map(_.name)).foreach {
    case ((deps, originalDeps), name) => deps.zip(originalDeps).zipWithIndex.foreach {
      case ((dep, originalDep), deqPortIdx) =>
        if (backendParams.getLdExuIdx(backendParams.allExuParams.find(_.name == name).get) == deqPortIdx)
          dep := 1.U
        else
          dep := originalDep << 1
    }
  }

  (IntRCTagTable.io.writePorts ++ MemRCTagTable.io.writePorts).lazyZip(wakeupFromIQNeedWriteRC).lazyZip(shiftLoadDependency)
  .foreach{ case (w, wakeup, ldDp) =>
    w.wen  := wakeup.valid && wakeup.bits.rfWen && !LoadShouldCancel(Some(wakeup.bits.loadDependency), io.ldCancel) && !(wakeup.bits.is0Lat && io.og0Cancel(wakeup.bits.params.exuIdx))
    w.addr := wakeup.bits.rcDest.get(RegCacheIdxWidth - 2, 0)
    w.tag  := wakeup.bits.pdest
    w.loadDependency := ldDp
  }

  // cancel
  val allocVec = (IntRCTagTable.io.tagVec ++ MemRCTagTable.io.tagVec).map{ t =>
    io.allocPregs.map(a => a.valid && a.bits === t).asUInt.orR
  }

  val replaceVec = IntRCTagTable.io.tagVec.map{ t =>
    IntRCTagTable.io.writePorts.map(w => w.wen && w.tag === t).asUInt.orR
  }       ++       MemRCTagTable.io.tagVec.map{ t =>
    MemRCTagTable.io.writePorts.map(w => w.wen && w.tag === t).asUInt.orR
  }

  val ldCancelVec = (IntRCTagTable.io.loadDependencyVec ++ MemRCTagTable.io.loadDependencyVec).map{ ldDp =>
    LoadShouldCancel(Some(ldDp), io.ldCancel)
  }

  val cancelVec = allocVec.lazyZip(replaceVec).lazyZip(ldCancelVec).lazyZip((IntRCTagTable.io.validVec ++ MemRCTagTable.io.validVec))
  .map{ case (alloc, rep, ldCancel, v) =>
    (alloc || rep || ldCancel) && v
  }

  (IntRCTagTable.io.cancelVec ++ MemRCTagTable.io.cancelVec).zip(cancelVec).foreach{ case (cancelIn, cancel) =>
    cancelIn := cancel
  }
}

class RegCacheTagTableIO(numReadPorts: Int)(implicit p: Parameters) extends XSBundle {

  val readPorts = Vec(numReadPorts, new RCTagTableReadPort(RegCacheIdxWidth, IntPhyRegIdxWidth))

  val wakeupFromIQ: MixedVec[ValidIO[IssueQueueIQWakeUpBundle]] = Flipped(backendParams.intSchdParams.get.genIQWakeUpInValidBundle)

  // set preg state to invalid
  val allocPregs = Vec(RenameWidth, Flipped(ValidIO(UInt(IntPhyRegIdxWidth.W))))

  // cancelFromDatapath
  val og0Cancel = Input(ExuVec())

  // cancelFromMem
  val ldCancel = Vec(backendParams.LdExuCnt, Flipped(new LoadCancelIO))
}

s"writePorts: ${backendParams.getIntExuRCWriteSize} + ${backendParams.getMemExuRCWriteSize}")


/***************************************************************************************
* Copyright (c) 2020-2021 Institute of Computing Technology, Chinese Academy of Sciences
* Copyright (c) 2020-2021 Peng Cheng Laboratory
*
* XiangShan is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

package xiangshan.cache

import org.chipsalliance.cde.config.Parameters
import chisel3._
import chisel3.util._
import utility.XSDebug

class AtomicsReplayEntry(implicit p: Parameters) extends DCacheModule
{
  val io = IO(new Bundle {
    val lsu  = Flipped(new AtomicWordIO)
    val pipe_req  = Decoupled(new MainPipeReq)
    val pipe_resp = Flipped(ValidIO(new MainPipeResp))
    val block_lr = Input(Bool())

    val block_addr  = Output(Valid(UInt()))
  })

  val s_invalid :: s_pipe_req :: s_pipe_resp :: s_resp :: Nil = Enum(4)
  val state = RegInit(s_invalid)

  val req = Reg(new DCacheWordReqWithVaddr)

  // assign default values to output signals
  io.lsu.req.ready     := state === s_invalid
  io.lsu.resp.valid    := false.B
  io.lsu.resp.bits     := DontCare

  io.pipe_req.valid    := false.B
  io.pipe_req.bits     := DontCare

  io.block_addr.valid := state =/= s_invalid
  io.block_addr.bits  := req.addr

  XSDebug(state =/= s_invalid, "AtomicsReplayEntry: state: %d block_addr: %x\n", state, io.block_addr.bits)

  // --------------------------------------------
  // s_invalid: receive requests
  when (state === s_invalid) {
    when (io.lsu.req.fire) {
      req   := io.lsu.req.bits
      state := s_pipe_req
    }
  }

  // --------------------------------------------
  // replay
  when (state === s_pipe_req) {
    io.pipe_req.valid := Mux(
      io.pipe_req.bits.cmd === M_XLR,
      !io.block_lr, // block lr to survive in lr storm
      true.B
    )

    val pipe_req = io.pipe_req.bits
    pipe_req := DontCare
    pipe_req.miss := false.B
    pipe_req.probe := false.B
    pipe_req.probe_need_data := false.B
    pipe_req.source := AMO_SOURCE.U
    pipe_req.cmd    := req.cmd
    pipe_req.addr   := get_block_addr(req.addr)
    pipe_req.vaddr  := get_block_addr(req.vaddr)
    pipe_req.word_idx  := get_word(req.addr)
    pipe_req.amo_data  := req.data
    pipe_req.amo_mask  := req.mask

    when (io.pipe_req.fire) {
      state := s_pipe_resp
      assert(!io.pipe_req.bits.vaddr === 0.U)
    }
  }

  val resp_data  = Reg(UInt())
  val resp_id    = Reg(UInt())
  val resp_error = Reg(Bool())
  when (state === s_pipe_resp) {
    // when not miss
    // everything is OK, simply send response back to sbuffer
    // when miss and not replay
    // wait for missQueue to handling miss and replaying our request
    // when miss and replay
    // req missed and fail to enter missQueue, manually replay it later
    // TODO: add assertions:
    // 1. add a replay delay counter?
    // 2. when req gets into MissQueue, it should not miss any more
    when (io.pipe_resp.fire) {
      when (io.pipe_resp.bits.miss) {
        when (io.pipe_resp.bits.replay) {
          state := s_pipe_req
        }
      } .otherwise {
        resp_data  := io.pipe_resp.bits.data
        resp_id    := io.pipe_resp.bits.id
        resp_error := io.pipe_resp.bits.error
        state := s_resp
      }
    }
  }

  // --------------------------------------------
  when (state === s_resp) {
    io.lsu.resp.valid := true.B
    io.lsu.resp.bits  := DontCare
    io.lsu.resp.bits.data  := resp_data
    io.lsu.resp.bits.id    := resp_id
    io.lsu.resp.bits.error := resp_error

  // debug output
  // when (io.lsu.req.fire) {
  //   io.lsu.req.bits.dump()
  // }

  // when (io.lsu.resp.fire) {
  //   io.lsu.resp.bits.dump()
  // }

//  when (io.pipe_req.fire) {
//    io.pipe_req.bits.dump()
//  }
//
//  when (io.pipe_resp.fire) {
//    io.pipe_resp.bits.dump()
//  }
}

    when (io.lsu.resp.fire) {
      state := s_invalid
    }


/***************************************************************************************
* Copyright (c) 2020-2021 Institute of Computing Technology, Chinese Academy of Sciences
* Copyright (c) 2020-2021 Peng Cheng Laboratory
*
* XiangShan is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/
package xiangshan.mem

import org.chipsalliance.cde.config.Parameters
import chisel3._
import chisel3.util._
import utils._
import utility._
import xiangshan._

class FreeList(size: Int, allocWidth: Int, freeWidth: Int, enablePreAlloc: Boolean = false, moduleName: String = "")(implicit p: Parameters) extends XSModule
  with HasCircularQueuePtrHelper
  with HasPerfEvents
{
  val io = IO(new Bundle() {
    val allocateReq = Input(Vec(allocWidth, Bool()))
    val allocateSlot = Output(Vec(allocWidth, UInt()))
    val canAllocate = Output(Vec(allocWidth, Bool()))
    val doAllocate = Input(Vec(allocWidth, Bool()))

    val free = Input(UInt(size.W))

    val validCount = Output(UInt())
    val empty = Output(Bool())
  })

  println(s"FreeList: $moduleName, size " + size)

  val freeList = RegInit(VecInit(
    // originally {0, 1, ..., size - 1} are free.
    Seq.tabulate(size)(i => i.U(log2Up(size).W))
  ))

  class FreeListPtr extends CircularQueuePtr[FreeListPtr](size)
  object FreeListPtr {
    def apply(f: Boolean, v: Int): FreeListPtr = {
      val ptr = Wire(new FreeListPtr)
      ptr.flag := f.B
      ptr.value := v.U
      ptr
    }
  }

  val headPtr  = RegInit(FreeListPtr(false, 0))
  val headPtrNext = Wire(new FreeListPtr)
  val tailPtr = RegInit(FreeListPtr(true, 0))
  val tailPtrNext = Wire(new FreeListPtr)

  // legality check
  def getRemBits(input: UInt)(rem: Int): UInt = {

  }

  // free logic
  val freeMask = RegInit(0.U(size.W))
  val freeSelMask = Wire(UInt(size.W))
  val freeSelMaskVec = Wire(Vec(freeWidth, UInt(size.W)))

  // update freeMask
  require((size % freeWidth) == 0)
  freeSelMask := freeSelMaskVec.reduce(_|_)
  freeMask := (io.free | freeMask) & ~freeSelMask

  val remFreeSelMaskVec = VecInit(Seq.tabulate(freeWidth)(rem => getRemBits((freeMask & ~freeSelMask))(rem)))
  val remFreeSelIndexOHVec = VecInit(Seq.tabulate(freeWidth)(fport => {
    val highIndexOH = PriorityEncoderOH(remFreeSelMaskVec(fport))
    val freeIndexOHVec = Wire(Vec(size, Bool()))
    freeIndexOHVec.foreach(e => e := false.B)
    for (i <- 0 until size / freeWidth) {
      freeIndexOHVec(i * freeWidth + fport) := highIndexOH(i)
    }
    freeIndexOHVec.asUInt
  }))

  val freeReq = GatedRegNext(VecInit(remFreeSelMaskVec.map(_.asUInt.orR)))
  val freeSlotOH = GatedRegNext(remFreeSelIndexOHVec)
  val doFree = freeReq.asUInt.orR

  for (i <- 0 until freeWidth) {
    val offset = PopCount(freeReq.take(i))
    val enqPtr = tailPtr + offset

    when (freeReq(i)) {
      freeList(enqPtr.value) := OHToUInt(freeSlotOH(i))
    }

    freeSelMaskVec(i) := Mux(freeReq(i), freeSlotOH(i), 0.U)
  }

  tailPtrNext := tailPtr + PopCount(freeReq)
  tailPtr := Mux(doFree, tailPtrNext, tailPtr)

  // allocate
  val doAllocate = io.doAllocate.asUInt.orR
  val numAllocate = PopCount(io.doAllocate)
  val freeSlotCnt = RegInit(size.U(log2Up(size + 1).W))

  for (i <- 0 until allocWidth) {
    val offset = PopCount(io.allocateReq.take(i))

    if (enablePreAlloc) {
      val deqPtr = headPtr + numAllocate + offset
      io.canAllocate(i) := RegEnable(isBefore(deqPtr, tailPtr), enablePreAlloc.B)
      io.allocateSlot(i) := RegEnable(freeList(deqPtr.value), enablePreAlloc.B)
    } else {
      val deqPtr = headPtr + offset
      io.canAllocate(i) := isBefore(deqPtr, tailPtr)
      io.allocateSlot(i) := freeList(deqPtr.value)
    }

  }

  headPtrNext := headPtr + numAllocate
  headPtr := Mux(doAllocate, headPtrNext, headPtr)
  freeSlotCnt := distanceBetween(tailPtrNext, headPtrNext)

  io.empty := freeSlotCnt === 0.U
  io.validCount := size.U - freeSlotCnt

  XSPerfAccumulate("empty", io.empty)
  val perfEvents: Seq[(String, UInt)] = Seq(
    ("empty", io.empty)
  )
  generatePerfEvent()

  // unique check
  val enableFreeListCheck = false
  if (enableFreeListCheck) {
    val differentFlag = tailPtr.flag ^ headPtr.flag
    val headMask = UIntToMask(headPtr.value, size)
    val tailMask = UIntToMask(tailPtr.value, size)
    val validMask1 = Mux(differentFlag, ~tailMask, tailMask ^ headMask)
    val validMask2 = Mux(differentFlag, headMask, 0.U(size.W))
    val validMask = ~(validMask1 | validMask2)
    for (i <- 0 until size) {
      for (j <- i+1 until size) {
        if (i != j) {
          XSError(validMask(i) && validMask(j) && freeList(i) === freeList(j),s"Found same entry in free list! (i=$i j=$j)\n")
        }
      }
    }
  }

  // end
}

VecInit((0 until size / freeWidth).map(i => { input(freeWidth * i + rem) })).asUInt


/***************************************************************************************
* Copyright (c) 2024 Beijing Institute of Open Source Chip (BOSC)
* Copyright (c) 2020-2024 Institute of Computing Technology, Chinese Academy of Sciences
* Copyright (c) 2020-2021 Peng Cheng Laboratory
*
* XiangShan is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*
*
* Acknowledgement
*
* This implementation is inspired by several key papers:
* [1] Jean-Loup Baer, and Tien-Fu Chen. "[An effective on-chip preloading scheme to reduce data access penalty.]
* (https://doi.org/10.1145/125826.125932)" ACM/IEEE Conference on Supercomputing. 1991.
***************************************************************************************/

package xiangshan.mem.prefetch

import org.chipsalliance.cde.config.Parameters
import chisel3._
import chisel3.util._
import utils._
import utility._
import xiangshan._
import xiangshan.mem.L1PrefetchReq
import xiangshan.mem.Bundles.LsPrefetchTrainBundle
import xiangshan.mem.trace._
import xiangshan.cache.HasDCacheParameters
import xiangshan.cache.mmu._
import scala.collection.SeqLike

trait HasStridePrefetchHelper extends HasL1PrefetchHelper {
  val STRIDE_FILTER_SIZE = 6
  val STRIDE_ENTRY_NUM = 10
  val STRIDE_BITS = 10 + BLOCK_OFFSET
  val STRIDE_VADDR_BITS = 10 + BLOCK_OFFSET
  val STRIDE_CONF_BITS = 2

  // detail control
  val ALWAYS_UPDATE_PRE_VADDR = true
  val AGGRESIVE_POLICY = false // if true, prefetch degree is greater than 1, 1 otherwise
  val STRIDE_LOOK_AHEAD_BLOCKS = 2 // aggressive degree
  val LOOK_UP_STREAM = false // if true, avoid collision with stream

  val STRIDE_WIDTH_BLOCKS = if(AGGRESIVE_POLICY) STRIDE_LOOK_AHEAD_BLOCKS else 1

  def MAX_CONF = (1 << STRIDE_CONF_BITS) - 1
}

class StrideMetaBundle(implicit p: Parameters) extends XSBundle with HasStridePrefetchHelper {
  val pre_vaddr = UInt(STRIDE_VADDR_BITS.W)
  val stride = UInt(STRIDE_BITS.W)
  val confidence = UInt(STRIDE_CONF_BITS.W)
  val hash_pc = UInt(HASH_TAG_WIDTH.W)

  def reset(index: Int) = {
    pre_vaddr := 0.U
    stride := 0.U
    confidence := 0.U
    hash_pc := index.U
  }

  def tag_match(valid1: Bool, valid2: Bool, new_hash_pc: UInt): Bool = {
    valid1 && valid2 && hash_pc === new_hash_pc
  }

  def alloc(vaddr: UInt, alloc_hash_pc: UInt) = {
    pre_vaddr := vaddr(STRIDE_VADDR_BITS - 1, 0)
    stride := 0.U
    confidence := 0.U
    hash_pc := alloc_hash_pc
  }

  def update(vaddr: UInt, always_update_pre_vaddr: Bool) = {
    val new_vaddr = vaddr(STRIDE_VADDR_BITS - 1, 0)
    val new_stride = new_vaddr - pre_vaddr
    val new_stride_blk = block_addr(new_stride)
    // NOTE: for now, disable negtive stride
    val stride_valid = new_stride_blk =/= 0.U && new_stride_blk =/= 1.U && new_stride(STRIDE_VADDR_BITS - 1) === 0.U
    val stride_match = new_stride === stride
    val low_confidence = confidence <= 1.U
    val can_send_pf = stride_valid && stride_match && confidence === MAX_CONF.U

    when(stride_valid) {
      when(stride_match) {
        confidence := Mux(confidence === MAX_CONF.U, confidence, confidence + 1.U)
      }.otherwise {
        confidence := Mux(confidence === 0.U, confidence, confidence - 1.U)
        when(low_confidence) {
          stride := new_stride
        }
      }
      pre_vaddr := new_vaddr
    }
    when(always_update_pre_vaddr) {
      pre_vaddr := new_vaddr
    }

    (can_send_pf, new_stride)
  }

}

class StrideMetaArray(implicit p: Parameters) extends XSModule with HasStridePrefetchHelper {
  val io = IO(new XSBundle {
    val enable = Input(Bool())
    // TODO: flush all entry when process changing happens, or disable stream prefetch for a while
    val flush = Input(Bool())
    val dynamic_depth = Input(UInt(32.W)) // TODO: enable dynamic stride depth
    val train_req = Flipped(DecoupledIO(new PrefetchReqBundle))
    val l1_prefetch_req = ValidIO(new StreamPrefetchReqBundle)
    val l2_l3_prefetch_req = ValidIO(new StreamPrefetchReqBundle)
    // query Stream component to see if a stream pattern has already been detected
    val stream_lookup_req  = ValidIO(new PrefetchReqBundle)
    val stream_lookup_resp = Input(Bool())
  })

  val array = Reg(Vec(STRIDE_ENTRY_NUM, new StrideMetaBundle))
  val valids = RegInit(VecInit(Seq.fill(STRIDE_ENTRY_NUM)(false.B)))

  def reset_array(i: Int): Unit = {
    valids(i) := false.B
    //only need to rest control signals for firendly area
    // array(i).reset(i)
  }

  val replacement = ReplacementPolicy.fromString("plru", STRIDE_ENTRY_NUM)

  // s0: hash pc -> cam all entries
  val s0_can_accept = Wire(Bool())
  val s0_valid = io.train_req.fire
  val s0_vaddr = io.train_req.bits.vaddr
  val s0_pc = io.train_req.bits.pc
  val s0_pc_hash = pc_hash_tag(s0_pc)
  val s0_pc_match_vec = VecInit(array zip valids map { case (e, v) => e.tag_match(v, s0_valid, s0_pc_hash) }).asUInt
  val s0_hit = s0_pc_match_vec.orR
  val s0_index = Mux(s0_hit, OHToUInt(s0_pc_match_vec), replacement.way)
  io.train_req.ready := s0_can_accept
  io.stream_lookup_req.valid := s0_valid
  io.stream_lookup_req.bits  := io.train_req.bits

  when(s0_valid) {
    replacement.access(s0_index)
  }

  assert(PopCount(s0_pc_match_vec) <= 1.U)
  XSPerfAccumulate("s0_valid", s0_valid)
  XSPerfAccumulate("s0_hit", s0_valid && s0_hit)
  XSPerfAccumulate("s0_miss", s0_valid && !s0_hit)

  // s1: alloc or update
  val s1_valid = GatedValidRegNext(s0_valid)
  val s1_index = RegEnable(s0_index, s0_valid)
  val s1_pc_hash = RegEnable(s0_pc_hash, s0_valid)
  val s1_vaddr = RegEnable(s0_vaddr, s0_valid)
  val s1_hit = RegEnable(s0_hit, s0_valid)
  val s1_alloc = s1_valid && !s1_hit
  val s1_update = s1_valid && s1_hit
  val s1_stride = array(s1_index).stride
  val s1_new_stride = WireInit(0.U(STRIDE_BITS.W))
  val s1_can_send_pf = WireInit(false.B)
  s0_can_accept := !(s1_valid && s1_pc_hash === s0_pc_hash)

  val always_update = Constantin.createRecord(s"always_update${p(XSCoreParamsKey).HartId}", initValue = ALWAYS_UPDATE_PRE_VADDR)

  when(s1_alloc) {
    valids(s1_index) := true.B
    array(s1_index).alloc(
      vaddr = s1_vaddr,
      alloc_hash_pc = s1_pc_hash
    )
  }.elsewhen(s1_update) {
    val res = array(s1_index).update(s1_vaddr, always_update)
    s1_can_send_pf := res._1
    s1_new_stride := res._2
  }

  val l1_stride_ratio = l1_stride_ratio_const(3, 0)
  val l2_stride_ratio_const = Constantin.createRecord(s"l2_stride_ratio${p(XSCoreParamsKey).HartId}", initValue = 5)
  val l2_stride_ratio = l2_stride_ratio_const(3, 0)
  // s2: calculate L1 & L2 pf addr
  val s2_valid = GatedValidRegNext(s1_valid && s1_can_send_pf)
  val s2_vaddr = RegEnable(s1_vaddr, s1_valid && s1_can_send_pf)
  val s2_stride = RegEnable(s1_stride, s1_valid && s1_can_send_pf)
  val s2_l1_depth = s2_stride << l1_stride_ratio
  val s2_l1_pf_vaddr = (s2_vaddr + s2_l1_depth)(VAddrBits - 1, 0)
  val s2_l2_depth = s2_stride << l2_stride_ratio
  val s2_l2_pf_vaddr = (s2_vaddr + s2_l2_depth)(VAddrBits - 1, 0)
  val s2_l1_pf_req_bits = (new StreamPrefetchReqBundle).getStreamPrefetchReqBundle(
    valid = s2_valid,
    vaddr = s2_l1_pf_vaddr,
    width = STRIDE_WIDTH_BLOCKS,
    decr_mode = false.B,
    sink = SINK_L1,
    source = L1_HW_PREFETCH_STRIDE,
    // TODO: add stride debug db, not useful for now
    t_pc = 0xdeadbeefL.U,
    t_va = 0xdeadbeefL.U
    )
  val s2_l2_pf_req_bits = (new StreamPrefetchReqBundle).getStreamPrefetchReqBundle(
    valid = s2_valid,
    vaddr = s2_l2_pf_vaddr,
    width = STRIDE_WIDTH_BLOCKS,
    decr_mode = false.B,
    sink = SINK_L2,
    source = L1_HW_PREFETCH_STRIDE,
    // TODO: add stride debug db, not useful for now
    t_pc = 0xdeadbeefL.U,
    t_va = 0xdeadbeefL.U
    )

  // s3: send l1 pf out
  val s3_valid = if (LOOK_UP_STREAM) GatedValidRegNext(s2_valid) && !io.stream_lookup_resp else GatedValidRegNext(s2_valid)
  val s3_l1_pf_req_bits = RegEnable(s2_l1_pf_req_bits, s2_valid)
  val s3_l2_pf_req_bits = RegEnable(s2_l2_pf_req_bits, s2_valid)

  // s4: send l2 pf out
  val s4_valid = GatedValidRegNext(s3_valid)
  val s4_l2_pf_req_bits = RegEnable(s3_l2_pf_req_bits, s3_valid)

  io.l1_prefetch_req.valid := s3_valid
  io.l1_prefetch_req.bits := s3_l1_pf_req_bits
  io.l2_l3_prefetch_req.valid := s4_valid
  io.l2_l3_prefetch_req.bits := s4_l2_pf_req_bits

  XSPerfAccumulate("pf_valid", PopCount(Seq(io.l1_prefetch_req.valid, io.l2_l3_prefetch_req.valid)))
  XSPerfAccumulate("l1_pf_valid", s3_valid)
  XSPerfAccumulate("l2_pf_valid", s4_valid)
  XSPerfAccumulate("detect_stream", io.stream_lookup_resp)
  XSPerfHistogram("high_conf_num", PopCount(VecInit(array.map(_.confidence === MAX_CONF.U))).asUInt, true.B, 0, STRIDE_ENTRY_NUM, 1)
  for(i <- 0 until STRIDE_ENTRY_NUM) {
    XSPerfAccumulate(s"entry_${i}_update", i.U === s1_index && s1_update)
    for(j <- 0 until 4) {
      XSPerfAccumulate(s"entry_${i}_disturb_${j}", i.U === s1_index && s1_update &&
                                                   j.U === s1_new_stride &&
                                                   array(s1_index).confidence === MAX_CONF.U &&
                                                   array(s1_index).stride =/= s1_new_stride
      )
    }
  }

  for(i <- 0 until STRIDE_ENTRY_NUM) {
    when(GatedValidRegNext(io.flush)) {
      reset_array(i)
    }
  }
}

val l1_stride_ratio_const = Constantin.createRecord(s"l1_stride_ratio${p(XSCoreParamsKey).HartId}", initValue = 2)