// See LICENSE.SiFive for license details.

package util

import Chisel._

import config._

class SimpleRegIO(val w: Int) extends Bundle {
  val d = UInt(INPUT, width = w)
  val q = UInt(OUTPUT, width = w)
  val en = Bool(INPUT)

  override def cloneType = new SimpleRegIO(w).asInstanceOf[this.type]
}

class IndexedRegIO(val n: Int, val w: Int) extends Bundle {
  val i = UInt(INPUT, width = log2Up(n))
  val d = UInt(INPUT, width = w)
  val q = UInt(OUTPUT, width = w)
  val en = Bool(INPUT)
  val clk = Clock(INPUT)

  override def cloneType = new IndexedRegIO(n, w).asInstanceOf[this.type]
}

class BlackBoxedRegs(nRegs: Int, width: Int) extends BlackBox(Map(
    "NREGS"    -> chisel3.core.IntParam(nRegs),
    "WIDTH"    -> chisel3.core.IntParam(width),
    "IDXWIDTH" -> chisel3.core.IntParam(log2Up(nRegs)))) {
  val io = new IndexedRegIO(nRegs, width)
}

// Converts between RegField's desired SimpleRegIO format and
// a parameterized, indexed blackbox IO
class BlackBoxedRegsWrapper(nRegs: Int, width: Int) extends Module {
  val io = new Bundle {
    val v = Vec(nRegs, new SimpleRegIO(width))
  }
  val regs = Module(new BlackBoxedRegs(nRegs, width))
  val idx = OHToUInt(io.v.map(_.en))
  regs.io.clk := clock
  regs.io.i := idx
  regs.io.d := io.v(idx).d
  regs.io.en := io.v(idx).en
  io.v(idx).q := regs.io.q
}

////////

/** This black-boxes an Async Reset
  *  (or Set)
  * Register.
  *  
  * Because Chisel doesn't support
  * parameterized black boxes, 
  * we unfortunately have to 
  * instantiate a number of these.
  *  
  *  We also have to hard-code the set/
  *  reset behavior.
  *  
  *  Do not confuse an asynchronous
  *  reset signal with an asynchronously
  *  reset reg. You should still 
  *  properly synchronize your reset 
  *  deassertion.
  *  
  *  @param d Data input
  *  @param q Data Output
  *  @param clk Clock Input
  *  @param rst Reset Input
  *  @param en Write Enable Input
  *  
  */

class AsyncResetReg extends BlackBox {
  val io = new Bundle {
    val d = Bool(INPUT)
    val q = Bool(OUTPUT)
    val en = Bool(INPUT)

    val clk = Clock(INPUT)
    val rst = Bool(INPUT)
  }
}

class AsyncResetRegVec(val w: Int, val init: BigInt) extends Module {
  val io = new SimpleRegIO(w)

  val async_regs = List.fill(w)(Module(new AsyncResetReg))

  val q = for ((reg, idx) <- async_regs.zipWithIndex) yield {
    def maybeInvert(x: Bool) = if (((init >> idx) & 1) == 1) !x else x
    reg.io.clk := clock
    reg.io.rst := reset
    reg.io.d   := maybeInvert(io.d(idx))
    reg.io.en  := io.en
    reg.suggestName(s"reg_$idx")
    maybeInvert(reg.io.q)
  }

  io.q := q.asUInt
}

object AsyncResetReg {
  // Create Single Registers
  def apply(d: Bool, clk: Clock, rst: Bool, init: Boolean, name: Option[String]): Bool = {
    def maybeInvert(x: Bool) = if (init) !x else x
    val reg = Module(new AsyncResetReg)
    reg.io.d := maybeInvert(d)
    reg.io.clk := clk
    reg.io.rst := rst
    reg.io.en  := Bool(true)
    name.foreach(reg.suggestName(_))
    maybeInvert(reg.io.q)
  }

  def apply(d: Bool, clk: Clock, rst: Bool): Bool = apply(d, clk, rst, false, None)
  def apply(d: Bool, clk: Clock, rst: Bool, name: String): Bool = apply(d, clk, rst, false, Some(name))

  // Create Vectors of Registers
  def apply(updateData: UInt, resetData: BigInt, enable: Bool, name: Option[String] = None): UInt = {
    val w = updateData.getWidth max resetData.bitLength
    val reg = Module(new AsyncResetRegVec(w, resetData))
    name.foreach(reg.suggestName(_))
    reg.io.d := updateData
    reg.io.en := enable
    reg.io.q
  }
  def apply(updateData: UInt, resetData: BigInt, enable: Bool, name: String): UInt = apply(updateData,
    resetData, enable, Some(name))


  def apply(updateData: UInt, resetData: BigInt): UInt = apply(updateData, resetData, enable=Bool(true))
  def apply(updateData: UInt, resetData: BigInt, name: String): UInt = apply(updateData, resetData, enable=Bool(true), Some(name))

  def apply(updateData: UInt, enable: Bool): UInt = apply(updateData, resetData=BigInt(0), enable)
  def apply(updateData: UInt, enable: Bool, name: String): UInt = apply(updateData, resetData=BigInt(0), enable, Some(name))

  def apply(updateData: UInt): UInt = apply(updateData, resetData=BigInt(0), enable=Bool(true))
  def apply(updateData: UInt, name:String): UInt = apply(updateData, resetData=BigInt(0), enable=Bool(true), Some(name))
}
