package util

import Chisel._

import cde.{Parameters}

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

abstract class AbstractBBReg 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 AsyncResetReg  extends AbstractBBReg
class AsyncSetReg extends AbstractBBReg

class SimpleRegIO(val w: Int) extends Bundle{

  val d = UInt(INPUT, width = w)
  val q = UInt(OUTPUT, width = w)

  val en = Bool(INPUT)

}

class AsyncResetRegVec(val w: Int, val init: BigInt) extends Module {

  val io = new SimpleRegIO(w)

  val bb_d = Mux(io.en, io.d, io.q)

  val async_regs: List[AbstractBBReg] = List.tabulate(w)(
    i => Module (
      if (((init >> i) % 2) > 0)
        new AsyncSetReg
      else
        new AsyncResetReg)
  )

  io.q := async_regs.map(_.io.q).asUInt

  for ((reg, idx) <- async_regs.zipWithIndex) {
    reg.io.clk := clock
    reg.io.rst := reset
    reg.io.d   := bb_d(idx)
    reg.io.en  := io.en
  }

}

object AsyncResetReg {
  def apply(d: Bool, clk: Clock, rst: Bool, init: Boolean): Bool = {
    val reg: AbstractBBReg =
      if (init) Module (new AsyncSetReg)
      else Module(new AsyncResetReg)
    reg.io.d := d
    reg.io.clk := clk
    reg.io.rst := rst
    reg.io.en  := Bool(true)
    reg.io.q
  }

  def apply(d: Bool, clk: Clock, rst: Bool): Bool = apply(d, clk, rst, false)

  def apply(updateData: UInt, resetData: BigInt, enable: Bool): UInt = {
    val w = updateData.getWidth max resetData.bitLength
    val reg = Module(new AsyncResetRegVec(w, resetData))
    reg.io.d := updateData
    reg.io.en := enable
    reg.io.q
  }

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

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

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