/***************************************************************************************
* 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.BackendParams

class RegCache()(implicit p: Parameters, params: BackendParams) extends XSModule {

  val io = IO(new RegCacheIO())

  println(s"[RegCache] readPorts: ${params.getIntExuRCReadSize} + ${params.getMemExuRCReadSize}, " +
    s"writePorts: ${params.getIntExuRCWriteSize} + ${params.getMemExuRCWriteSize}")

  println(s"[RegCache] dataWidth: ${params.intSchdParams.get.rfDataWidth}, addrWidth: ${RegCacheIdxWidth}, tagWidth: ${params.intSchdParams.get.pregIdxWidth}")

  require(RegCacheIdxWidth == (log2Up(IntRegCacheSize) + 1), "IntRegCache should be half of the whole RegCache")
  require(RegCacheIdxWidth == (log2Up(MemRegCacheSize) + 1), "MemRegCache should be half of the whole RegCache")

  private val IntRegCacheReadSize = params.getIntExuRCReadSize + params.getMemExuRCReadSize
  private val IntRegCacheWriteSize = params.getIntExuRCWriteSize
  private val MemRegCacheReadSize = params.getIntExuRCReadSize + params.getMemExuRCReadSize
  private val MemRegCacheWriteSize = params.getMemExuRCWriteSize

  val IntRegCache = Module(new RegCacheDataModule("IntRegCache", IntRegCacheSize, IntRegCacheReadSize, IntRegCacheWriteSize, 
                                                  params.intSchdParams.get.rfDataWidth, RegCacheIdxWidth - 1, params.intSchdParams.get.pregIdxWidth))

  val MemRegCache = Module(new RegCacheDataModule("MemRegCache", MemRegCacheSize, MemRegCacheReadSize, MemRegCacheWriteSize, 
                                                  params.intSchdParams.get.rfDataWidth, RegCacheIdxWidth - 1, params.intSchdParams.get.pregIdxWidth))

  val IntRegCacheAgeTimer = Module(new RegCacheAgeTimer(IntRegCacheSize, IntRegCacheReadSize, IntRegCacheWriteSize, RegCacheIdxWidth - 1))

  val MemRegCacheAgeTimer = Module(new RegCacheAgeTimer(MemRegCacheSize, MemRegCacheReadSize, MemRegCacheWriteSize, RegCacheIdxWidth - 1))

  val IntRegCacheRepRCIdx = RegCacheAgeDetector(IntRegCacheSize, IntRegCacheWriteSize, IntRegCacheAgeTimer.io.ageInfo)
  val MemRegCacheRepRCIdx = RegCacheAgeDetector(MemRegCacheSize, MemRegCacheWriteSize, MemRegCacheAgeTimer.io.ageInfo)

  IntRegCacheAgeTimer.io.validInfo := IntRegCache.io.validInfo
  MemRegCacheAgeTimer.io.validInfo := MemRegCache.io.validInfo

  io.readPorts
  .lazyZip(IntRegCache.io.readPorts.lazyZip(MemRegCache.io.readPorts))
  .lazyZip(IntRegCacheAgeTimer.io.readPorts.lazyZip(MemRegCacheAgeTimer.io.readPorts))
  .foreach{ case (r_in, (r_int, r_mem), (r_int_at, r_mem_at)) => 
    val in_addr = RegEnable(r_in.addr, r_in.ren)
    val int_ren = GatedValidRegNext(r_in.ren & ~r_in.addr(RegCacheIdxWidth - 1))
    val mem_ren = GatedValidRegNext(r_in.ren & r_in.addr(RegCacheIdxWidth - 1))
    r_int.ren  := int_ren
    r_mem.ren  := mem_ren
    r_int.addr := in_addr(RegCacheIdxWidth - 2, 0)
    r_mem.addr := in_addr(RegCacheIdxWidth - 2, 0)
    r_in.data  := Mux(in_addr(RegCacheIdxWidth - 1), r_mem.data, r_int.data)
    r_int_at.ren  := int_ren
    r_mem_at.ren  := mem_ren
    r_int_at.addr := in_addr(RegCacheIdxWidth - 2, 0)
    r_mem_at.addr := in_addr(RegCacheIdxWidth - 2, 0)
  }

  val writePorts = Wire(chiselTypeOf(io.writePorts))

  IntRegCache.io.writePorts.zip(writePorts.take(IntRegCacheWriteSize)).foreach{ case (w_int, w_in) => 
    w_int.wen  := w_in.wen
    w_int.addr := w_in.addr(RegCacheIdxWidth - 2, 0)
    w_int.data := w_in.data
    w_int.tag.foreach(_ := w_in.tag.get)
  }

  MemRegCache.io.writePorts.zip(writePorts.takeRight(MemRegCacheWriteSize)).foreach{ case (w_mem, w_in) => 
    w_mem.wen  := w_in.wen
    w_mem.addr := w_in.addr(RegCacheIdxWidth - 2, 0)
    w_mem.data := w_in.data
    w_mem.tag.foreach(_ := w_in.tag.get)
  }

  IntRegCacheAgeTimer.io.writePorts.zip(writePorts.take(IntRegCacheWriteSize)).foreach{ case (w_int, w_in) => 
    w_int.wen  := w_in.wen
    w_int.addr := w_in.addr(RegCacheIdxWidth - 2, 0)
  }

  MemRegCacheAgeTimer.io.writePorts.zip(writePorts.takeRight(MemRegCacheWriteSize)).foreach{ case (w_mem, w_in) => 
    w_mem.wen  := w_in.wen
    w_mem.addr := w_in.addr(RegCacheIdxWidth - 2, 0)
  }

  io.toWakeupQueueRCIdx.zipWithIndex.foreach{ case (rcIdx, i) => 
    if (i < IntRegCacheWriteSize) {
      rcIdx := Cat("b0".U, IntRegCacheRepRCIdx(i))
    }
    else {
      rcIdx := Cat("b1".U, MemRegCacheRepRCIdx(i - IntRegCacheWriteSize))
    }
  }

  val delayToWakeupQueueRCIdx = RegNextN(io.toWakeupQueueRCIdx, 3)
  writePorts := io.writePorts
  writePorts.zip(delayToWakeupQueueRCIdx).foreach{ case (w, rcIdx) => 
    w.addr := rcIdx
  }
}

class RegCacheIO()(implicit p: Parameters, params: BackendParams) extends XSBundle {

  val readPorts = Vec(params.getIntExuRCReadSize + params.getMemExuRCReadSize, 
    new RCReadPort(params.intSchdParams.get.rfDataWidth, RegCacheIdxWidth))

  val writePorts = Vec(params.getIntExuRCWriteSize + params.getMemExuRCWriteSize, 
    new RCWritePort(params.intSchdParams.get.rfDataWidth, RegCacheIdxWidth, params.intSchdParams.get.pregIdxWidth, params.debugEn))

  val toWakeupQueueRCIdx = Vec(params.getIntExuRCWriteSize + params.getMemExuRCWriteSize, 
     Output(UInt(RegCacheIdxWidth.W)))
}
