/***************************************************************************************
* 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 chisel3._
import chisel3.util._
import org.chipsalliance.cde.config.Parameters
import xiangshan._
import xiangshan.cache._
import xiangshan.cache.{DCacheWordIO, DCacheLineIO, MemoryOpConstants}
import xiangshan.mem._
import xiangshan.backend.rob.RobPtr
import utils._
import utility._

// Data module define
// These raw data modules are like SyncDataModuleTemplate, but support cam-like ops
abstract class LqRawDataModule[T <: Data] (
  gen: T,
  numEntries: Int,
  numRead: Int,
  numWrite: Int,
  numWBank: Int,
  numWDelay: Int,
  numCamPort: Int = 0,
  enableCacheLineCheck: Boolean = false
)(implicit p: Parameters) extends XSModule {
  val io = IO(new Bundle() {
    val ren   = Input(Vec(numRead, Bool()))
    val raddr = Input(Vec(numRead, UInt(log2Up(numEntries).W)))
    val rdata = Output(Vec(numRead, gen))
    val wen   = Input(Vec(numWrite, Bool()))
    val waddr = Input(Vec(numWrite, UInt(log2Up(numEntries).W)))
    val wdata = Input(Vec(numWrite, gen))
    // violation cam: hit if addr is in the same cacheline
    val violationMdata = Input(Vec(numCamPort, gen)) // addr
    // This `store` writes the whole `cacheline`.(cbo zero).
    val violationCheckLine = OptionWrapper(enableCacheLineCheck, Input(Vec(numCamPort, Bool())))
    val violationMmask = Output(Vec(numCamPort, Vec(numEntries, Bool()))) // cam result mask
    // refill cam: hit if addr is in the same cacheline
    val releaseMdata = Input(Vec(numCamPort, gen))
    val releaseMmask = Output(Vec(numCamPort, Vec(numEntries, Bool())))  // cam result mask
    // release violation cam: hit if addr is in the same cacheline
    val releaseViolationMdata = Input(Vec(numCamPort, gen))
    val releaseViolationMmask = Output(Vec(numCamPort, Vec(numEntries, Bool()))) // cam result mask result
  })

  require(isPow2(numWBank), "write bank must be a power of two!")
  require(numWBank >= 2, "write bank must be greater than or equal to two!")
  require(numWDelay >= 1, "write delay must be greater than or equal to one!")
  require(numCamPort >= 0, "camport must be greater than or equal to zero!")
  require((numEntries % numWBank == 0), "numEntries must be divided by numWBank!")

  val numEntryPerBank = numEntries / numWBank
  val dataWidth = gen.getWidth

  val data = Reg(Vec(numEntries, gen))
  // read ports
  for (i <- 0 until numRead) {
    io.rdata(i) := RegEnable(data(io.raddr(i)), io.ren(i))
  }

  // write ports
  val writeAddrDec = io.waddr.map(a => UIntToOH(a))
  def selectBankMask(in: UInt, bank: Int): UInt = {
    in((bank + 1) * numEntryPerBank - 1, bank * numEntryPerBank)
  }
  for (bank <- 0 until numWBank) {
    // write ports
    // s0: write to bank level buffer
    val s0_bankWriteAddrDec = writeAddrDec.map(a => selectBankMask(a, bank))
    val s0_bankWriteEn = io.wen.zip(s0_bankWriteAddrDec).map(w => w._1 && w._2.orR)
    s0_bankWriteAddrDec.zipWithIndex.map(a =>
      a._1.suggestName("s0_bankWriteAddrDec" + bank + "_" + a._2)
    )
    s0_bankWriteEn.zipWithIndex.map(a =>
      a._1.suggestName("s0_bankWriteEn" + bank + "_" + a._2)
    )
    // sx: write data to entries
    val sx_bankWriteAddrDec_resp = (0 until numWrite).map(w => DelayNWithValid(s0_bankWriteAddrDec(w), io.wen(w), numWDelay - 1))
    val sx_bankWriteAddrDec = (0 until numWrite).map(w => sx_bankWriteAddrDec_resp(w)._2)
    val sx_bankWriteEn = s0_bankWriteEn.map(w => DelayN(w, numWDelay - 1))
     val sx_writeData_resp = (0 until numWrite).map(w => DelayNWithValid(io.wdata(w), io.wen(w), numWDelay - 1))
     val sx_writeData =  (0 until numWrite).map(w => sx_writeData_resp(w)._2)

    sx_bankWriteAddrDec.zipWithIndex.map(a =>
      a._1.suggestName("sx_bankWriteAddrDec" + bank + "_" + a._2)
    )
    sx_bankWriteEn.zipWithIndex.map(a =>
      a._1.suggestName("sx_bankWriteEn" + bank + "_" + a._2)
    )
    sx_writeData.zipWithIndex.map(a =>
      a._1.suggestName("sx_writeData" + bank + "_" + a._2)
    )

    // entry write
    for (entry <- 0 until numEntryPerBank) {
      // write ports
      val sx_entryWriteEnVec = sx_bankWriteEn.zip(sx_bankWriteAddrDec).map(w => w._1 && w._2(entry))
      val sx_entryWriteEn = VecInit(sx_entryWriteEnVec).asUInt.orR
      val sx_entryWriteData = Mux1H(sx_entryWriteEnVec, sx_writeData)
      when (sx_entryWriteEn) {
        data(bank * numEntryPerBank + entry) := sx_entryWriteData
      }
      sx_entryWriteEnVec.zipWithIndex.map(a =>
        a._1.suggestName("sx_entryWriteEnVec" + bank + "_" + entry + "_" + a._2)
      )
      sx_entryWriteEn.suggestName("sx_entryWriteEn" + bank + "_" + entry)
      sx_entryWriteData.suggestName("sx_entryWriteData" + bank + "_" + entry)
    }
  }

  // DataModuleTemplate should not be used when there're any write conflicts
  for (i <- 0 until numWrite) {
    for (j <- i+1 until numWrite) {
      assert(!(io.wen(i) && io.wen(j) && io.waddr(i) === io.waddr(j)))
    }
  }
}

// Load queue physical address module
class LqPAddrModule[T <: UInt](
  gen: T,
  numEntries: Int,
  numRead: Int,
  numWrite: Int,
  numWBank: Int,
  numWDelay: Int = 1,
  numCamPort: Int = 1,
  enableCacheLineCheck: Boolean = false, // Check the entire cacheline. when enabled, set `paddrOffset` correctly.
  paddrOffset: Int // The least significant `paddrOffset` bits of paddr are neglected.
)(implicit p: Parameters) extends LqRawDataModule(gen, numEntries, numRead, numWrite, numWBank, numWDelay, numCamPort, enableCacheLineCheck)
  with HasDCacheParameters
{
  // content addressed match
  // 128-bits aligned
  val needCacheLineCheck = enableCacheLineCheck && DCacheLineOffset > paddrOffset
  for (i <- 0 until numCamPort) {
    for (j <- 0 until numEntries) {
      if (needCacheLineCheck) {
        val cacheLineOffset = DCacheLineOffset - paddrOffset
        val cacheLineHit    = io.violationMdata(i)(dataWidth - 1, cacheLineOffset) === data(j)(dataWidth - 1, cacheLineOffset)
        val lowAddrHit      = io.violationMdata(i)(cacheLineOffset - 1, 0) === data(j)(cacheLineOffset - 1, 0)
        io.violationMmask(i)(j) := cacheLineHit && (io.violationCheckLine.get(i) || lowAddrHit)
      } else {
        io.violationMmask(i)(j) := io.violationMdata(i) === data(j)
      }

    }
  }

  // content addressed match
  // cacheline aligned
  for (i <- 0 until numCamPort) {
    for (j <- 0 until numEntries) {
      io.releaseViolationMmask(i)(j) := io.releaseViolationMdata(i) === data(j)
    }
  }

  // content addressed match
  // cacheline aligned
  for (i <- 0 until numCamPort) {
    for (j <- 0 until numEntries) {
      io.releaseMmask(i)(j) := io.releaseMdata(i) === data(j)
    }
  }
}

// Load queue data module
class LqVAddrModule[T <: UInt](
  gen: T,
  numEntries: Int,
  numRead: Int,
  numWrite: Int,
  numWBank: Int,
  numWDelay: Int = 1,
  numCamPort: Int = 1)(implicit p: Parameters) extends LqRawDataModule(gen, numEntries, numRead, numWrite, numWBank, numWDelay, numCamPort)
  with HasDCacheParameters
{
  // content addressed match
  for (i <- 0 until numCamPort) {
    for (j <- 0 until numEntries) {
      io.violationMmask(i)(j) := io.violationMdata(i)(VAddrBits-1, DCacheVWordOffset) === data(j)(VAddrBits-1, DCacheVWordOffset)
    }
  }

  // content addressed match
  for (i <- 0 until numCamPort) {
    for (j <- 0 until numEntries) {
      io.releaseMmask(i)(j) := io.releaseMdata(i)(VAddrBits-1, DCacheLineOffset) === data(j)(VAddrBits-1, DCacheLineOffset)
    }
  }
}

// Load queue mask module
class LqMaskModule[T <: UInt](
  gen: T,
  numEntries: Int,
  numRead: Int,
  numWrite: Int,
  numWBank: Int,
  numWDelay: Int = 1,
  numCamPort: Int = 1)(implicit p: Parameters) extends LqRawDataModule(gen, numEntries, numRead, numWrite, numWBank, numWDelay, numCamPort)
  with HasDCacheParameters
{
  // content addressed match
  for (i <- 0 until numCamPort) {
    for (j <- 0 until numEntries) {
      io.violationMmask(i)(j) := (io.violationMdata(i) & data(j)).orR
    }
  }
  // content addressed match
  // cacheline aligned
  for (i <- 0 until numCamPort) {
    for (j <- 0 until numEntries) {
      io.releaseViolationMmask(i)(j) := (io.releaseViolationMdata(i) & data(j)).orR
    }
  }

  // content addressed match
  for (i <- 0 until numCamPort) {
    for (j <- 0 until numEntries) {
      io.releaseMmask(i)(j) := (io.releaseMdata(i) & data(j)).orR
    }
  }
}
