package xiangshan.backend.rename

import org.chipsalliance.cde.config.Parameters
import chisel3._
import chisel3.util._
import utility.{CircularQueuePtr, HasCircularQueuePtrHelper, XSError}
import xiangshan.{XSCoreParamsKey, XSModule}


class SnapshotPtr(implicit p: Parameters) extends CircularQueuePtr[SnapshotPtr](
  p => p(XSCoreParamsKey).RenameSnapshotNum
)

object SnapshotGenerator extends HasCircularQueuePtrHelper {
  def apply[T <: Data](enqData: T, enq: Bool, deq: Bool, redirect: Bool, flushVec: Vec[Bool])(implicit p: Parameters): Vec[T] = {
    val snapshotGen = Module(new SnapshotGenerator(enqData))
    snapshotGen.io.enq := enq
    snapshotGen.io.enqData := enqData
    snapshotGen.io.deq := deq
    snapshotGen.io.redirect := redirect
    snapshotGen.io.flushVec := flushVec
    snapshotGen.io.snapshots
  }
}

class SnapshotGenerator[T <: Data](dataType: T)(implicit p: Parameters) extends XSModule
  with HasCircularQueuePtrHelper {

  class SnapshotGeneratorIO extends Bundle {
    val enq = Input(Bool())
    val enqData = Input(chiselTypeOf(dataType))
    val deq = Input(Bool())
    val redirect = Input(Bool())
    val flushVec = Input(Vec(RenameSnapshotNum, Bool()))
    val snapshots = Output(Vec(RenameSnapshotNum, chiselTypeOf(dataType)))
    val enqPtr = Output(new SnapshotPtr)
    val deqPtr = Output(new SnapshotPtr)
    val valids = Output(Vec(RenameSnapshotNum, Bool()))
  }

  val io = IO(new SnapshotGeneratorIO)

  val snapshots = Reg(Vec(RenameSnapshotNum, chiselTypeOf(dataType)))
  val snptEnqPtr = RegInit(0.U.asTypeOf(new SnapshotPtr))
  val snptDeqPtr = RegInit(0.U.asTypeOf(new SnapshotPtr))
  val snptValids = RegInit(VecInit.fill(RenameSnapshotNum)(false.B))

  io.snapshots := snapshots
  io.enqPtr := snptEnqPtr
  io.deqPtr := snptDeqPtr
  io.valids := snptValids

  when(!io.redirect && !isFull(snptEnqPtr, snptDeqPtr) && io.enq) {
    snapshots(snptEnqPtr.value) := io.enqData
    snptValids(snptEnqPtr.value) := true.B
    snptEnqPtr := snptEnqPtr + 1.U
  }
  when(!io.redirect && io.deq) {
    snptValids(snptDeqPtr.value) := false.B
    snptDeqPtr := snptDeqPtr + 1.U
  }
  XSError(!io.redirect && io.deq && isEmpty(snptEnqPtr, snptDeqPtr), "snapshots should not be empty when dequeue!\n")

  snptValids.zip(io.flushVec).foreach { case (valid, flush) =>
    when(flush) { valid := false.B }
  }
  when((Cat(io.flushVec) & Cat(snptValids)).orR) {
    val newEnqPtrCandidate = (0 until RenameSnapshotNum).map(snptDeqPtr + _.U)
    val newEnqPtrQualified = Wire(Vec(RenameSnapshotNum, Bool()))
    newEnqPtrQualified.head := !snptValids(newEnqPtrCandidate.head.value) || io.flushVec(newEnqPtrCandidate.head.value)
    newEnqPtrQualified.tail zip newEnqPtrCandidate.tail.zip(newEnqPtrCandidate.drop(1)).map {
      case (thiz, last) => snptValids(last.value) && (!snptValids(thiz.value) || io.flushVec(thiz.value))
    } foreach (x => x._1 := x._2)
    snptEnqPtr := MuxCase(newEnqPtrCandidate.last, newEnqPtrQualified.zip(newEnqPtrCandidate).dropRight(1))
  }
}
