package xiangshan.backend.fu.NewCSR

import chisel3._
import chisel3.util._
import freechips.rocketchip.rocket.CSRs
import org.chipsalliance.cde.config.Parameters
import xiangshan.backend.fu.PMAConfigEntry
import xiangshan.backend.fu.util.CSRConst
import xiangshan.{HasPMParameters, PMParameKey}
import CSRConfig._

trait PMAConst extends PMPConst

abstract class PMABundle(implicit val p: Parameters) extends Bundle with PMAConst
abstract class PMAModule(implicit val p: Parameters) extends Module with PMAConst

class PMAEntryHandleModule(implicit p: Parameters) extends PMAModule with PMAInit with PMAReadWrite {
  val io = IO(new PMAEntryHandleIOBundle)

  val pmaCfg  = io.in.pmaCfg

  val ren   = io.in.ren
  val wen   = io.in.wen
  val addr  = io.in.addr
  val wdata = io.in.wdata

  require(NumPMA >= 16, "The number of PMA should be greater than or equal to 16.")

  val pmaAddrInit = VecInit(Seq.fill(p(PMParameKey).NumPMA)(0.U((PMPAddrWidth-PMPOffBits).W)))
  val pmaMaskInit = VecInit(Seq.fill(p(PMParameKey).NumPMA)(0.U(PMPAddrWidth.W)))

  pmaAddrInit.zip(pmaMaskInit).zip(pmaInit).foreach { case ((addr, mask), init) =>
    addr := genAddr(init).U((PMPAddrWidth-PMPOffBits).W)
    mask := genMask(init.a, genAddr(init))
  }

  val pmaAddr = RegInit(pmaAddrInit)
  val pmaMask = RegInit(pmaMaskInit)

  val pmaEntry = Wire(Vec(p(PMParameKey).NumPMA, new PMAEntry))

  for (i <- pmaEntry.indices) {
    pmaEntry(i).gen(pmaCfg(i), pmaAddr(i), pmaMask(i))
  }

  // write pmaCfg
  val cfgVec = WireInit(VecInit(Seq.fill(8)(0.U.asTypeOf(new PMACfgBundle))))
  for (i <- 0 until (p(PMParameKey).NumPMA/8+1) by 2) {
    when (wen && (addr === (CSRConst.PmacfgBase + i).U)) {
      for (j <- cfgVec.indices) {
        val cfgOldTmp = pmaEntry(8*i/2+j).cfg
        val cfgNewTmp = Wire(new PMACfgBundle)
        cfgNewTmp := wdata(8*(j+1)-1, 8*j)
        cfgVec(j) := cfgOldTmp
        when (!cfgOldTmp.L.asBool) {
          cfgVec(j) := cfgNewTmp
          cfgVec(j).W := cfgNewTmp.W.asBool && cfgNewTmp.R.asBool
          if (CoarserGrain) {
            cfgVec(j).A := Cat(cfgNewTmp.A.asUInt(1), cfgNewTmp.A.asUInt.orR)
          }
          when (PMPCfgAField.isNa4OrNapot(cfgVec(j))) {
            pmaMask(8*i/2+j) := pmaEntry(8*i/2+j).matchMask(cfgVec(j), pmaEntry(8*i/2+j).addr)
          }
        }
      }
    }
  }

  io.out.pmaCfgWdata := Cat(cfgVec.map(_.asUInt).reverse)


  val pmaAddrR = Wire(Vec(p(PMParameKey).NumPMA, UInt(64.W)))

  for (i <- 0 until p(PMParameKey).NumPMA) {
    pmaAddrR(i) := pmaEntry(i).addr
    // write pmaAddr
    when (wen && (addr === (CSRConst.PmaaddrBase + i).U)) {
      if (i != (p(PMParameKey).NumPMA - 1)) {
        val addrNextLocked: Bool = PMPCfgLField.addrLocked(pmaEntry(i).cfg, pmaEntry(i + 1).cfg)
        pmaMask(i) := Mux(!addrNextLocked, pmaEntry(i).matchMask(wdata), pmaEntry(i).mask)
        pmaAddr(i) := Mux(!addrNextLocked, wdata, pmaEntry(i).addr)
      } else {
        val addrLocked: Bool = PMPCfgLField.addrLocked(pmaEntry(i).cfg)
        pmaMask(i) := Mux(!addrLocked, pmaEntry(i).matchMask(wdata), pmaEntry(i).mask)
        pmaAddr(i) := Mux(!addrLocked, wdata, pmaEntry(i).addr)
      }
    }
    // read pmaAddr
    when (ren && (addr === (CSRConst.PmaaddrBase + i).U)) {
      pmaAddrR(i) := pmaEntry(i).readAddr(pmaEntry(i).cfg, pmaEntry(i).addr)
    }
  }

  io.out.pmaAddrRData := pmaAddrR

}

class PMAEntryHandleIOBundle(implicit p: Parameters) extends PMABundle {
  val in = Input(new Bundle {
    val wen = Bool()
    val ren = Bool()
    val addr = UInt(12.W)
    val wdata = UInt(64.W)
    val pmaCfg = Vec(NumPMA, new PMACfgBundle)
  })

  val out = Output(new Bundle {
    val pmaCfgWdata = UInt(PMXLEN.W)
    val pmaAddrRData = Vec(NumPMA, UInt(64.W))
  })
}

trait PMAReadWrite extends PMAConst with PMPReadWrite {
  def shift_addr(addr: BigInt): BigInt = {
    addr >> 2
  }

  def get_napot(base: BigInt, range: BigInt): BigInt = {
    val PlatformGrainBytes = 1 << PlatformGrain
    if ((base % PlatformGrainBytes) != 0) {
      println("base: %x", base)
    }
    if ((range % PlatformGrainBytes) != 0) {
      println("range: %x", range)
    }
    require((base % PlatformGrainBytes) == 0)
    require((range % PlatformGrainBytes) == 0)

    (base + (range/2 - 1)) >> PMPOffBits
  }

  def matchMask(cfgA0: Bool, paddr: UInt): UInt = {
    val matchMaskCAddr = Cat(paddr, cfgA0) | (((1 << PlatformGrain) - 1) >> PMPOffBits).U((paddr.getWidth + 1).W)
    Cat(matchMaskCAddr & (~(matchMaskCAddr + 1.U)).asUInt, ((1 << PMPOffBits) - 1).U(PMPOffBits.W))
  }

  def readAddr(cfg: PMACfgBundle, addr: UInt): UInt = {
    val G = PlatformGrain - PMPOffBits
    require(G >= 0)
    if (G == 0) {
      addr
    } else if (G >= 2) {
      Mux(PMPCfgAField.isNa4OrNapot(cfg), setLowBits(addr, G-1), clearLowBits(addr, G))
    } else {
      Mux(PMPCfgAField.isOffOrTor(cfg), clearLowBits(addr, G), addr)
    }
  }

  def genAddr(init: PMAConfigEntry): BigInt = {
    if (init.a < 2) {
      shift_addr(init.base_addr)
    } else {
      get_napot(init.base_addr, init.range)
    }
  }

  def genMask(a: BigInt, initAddr: BigInt) = {
    val matchMaskAddr = (initAddr << 1) | (a & 0x1) | (((1 << PlatformGrain) - 1) >> PMPOffBits)
    val mask = ((matchMaskAddr & ~(matchMaskAddr + 1)) << PMPOffBits) | ((1 << PMPOffBits) - 1)
    mask.U(PMPAddrWidth.W)
  }
}

class PMAEntry(implicit p: Parameters) extends PMABundle with PMAReadWrite {
  val cfg  = new PMACfgBundle
  val addr = UInt((PMPAddrWidth-PMPOffBits).W)
  val mask = UInt(PMPAddrBits.W)

  def gen(cfg: PMACfgBundle, addr: UInt, mask: UInt) = {
    this.cfg := cfg
    this.addr := addr
    this.mask := mask
  }

  def matchMask(paddr: UInt): UInt = {
    matchMask(cfg.A.asUInt(0), paddr)
  }
}
