// SPDX-License-Identifier: Apache-2.0

/** Circuit generators for working with one-hot representations.
  */

package chisel3.util

import chisel3._

/** Returns the bit position of the sole high bit of the input bitvector.
  *
  * Inverse operation of [[UIntToOH]].
  *
  * @example {{{
  * OHToUInt("b0100".U) // results in 2.U
  * }}}
  *
  * @note assumes exactly one high bit, results undefined otherwise
  */
object OHToUInt {
  def apply(in: Seq[Bool]): UInt = apply(Cat(in.reverse), in.size)
  def apply(in: Vec[Bool]): UInt = apply(in.asUInt, in.size)
  def apply(in: Bits):      UInt = apply(in, in.getWidth)

  def apply(in: Bits, width: Int): UInt = {
    if (width <= 2) {
      Log2(in, width)
    } else {
      val mid = 1 << (log2Ceil(width) - 1)
      val hi = in(width - 1, mid)
      val lo = in(mid - 1, 0)
      Cat(hi.orR, apply(hi | lo, mid))
    }
  }
}

/** Returns the bit position of the least-significant high bit of the input bitvector.
  *
  * @example {{{
  * PriorityEncoder("b0110".U) // results in 1.U
  * }}}
  *
  * Multiple bits may be high in the input.
  */
object PriorityEncoder {
  def apply(in: Seq[Bool]): UInt = PriorityMux(in, (0 until in.size).map(_.asUInt))
  def apply(in: Bits):      UInt = apply(in.asBools)
}

/** Returns the one hot encoding of the input UInt.
  *
  * @example {{{
  * UIntToOH(2.U) // results in "b0100".U
  * }}}
  */
object UIntToOH {
  def apply(in: UInt): UInt = 1.U << in
  def apply(in: UInt, width: Int): UInt = width match {
    case 0 => 0.U(0.W)
    case 1 => 1.U(1.W)
    case _ =>
      val shiftAmountWidth = log2Ceil(width)
      val shiftAmount = in.pad(shiftAmountWidth)(shiftAmountWidth - 1, 0)
      (1.U << shiftAmount)(width - 1, 0)
  }
}

/** Returns a bit vector in which only the least-significant 1 bit in the input vector, if any,
  * is set.
  *
  * @example {{{
  * PriorityEncoderOH(Seq(false.B, true.B, true.B, false.B)) // results in Seq(false.B, true.B, false.B, false.B)
  * }}}
  */
object PriorityEncoderOH {
  private def encode(in: Seq[Bool]): UInt = {
    val outs = Seq.tabulate(in.size)(i => (BigInt(1) << i).asUInt(in.size.W))
    PriorityMux(in :+ true.B, outs :+ 0.U(in.size.W))
  }
  def apply(in: Seq[Bool]): Seq[Bool] = {
    val enc = encode(in)
    Seq.tabulate(in.size)(enc(_))
  }
  def apply(in: Bits): UInt = encode((0 until in.getWidth).map(i => in(i)))
}
