// SPDX-License-Identifier: Apache-2.0

package chisel3

import chisel3.experimental.dataview.reify

import scala.language.experimental.macros
import chisel3.experimental.{Analog, BaseModule, DataMirror, FixedPoint, Interval}
import chisel3.internal.Builder.pushCommand
import chisel3.internal._
import chisel3.internal.firrtl._
import chisel3.internal.sourceinfo.{DeprecatedSourceInfo, SourceInfo, SourceInfoTransform, UnlocatableSourceInfo}

import scala.collection.immutable.LazyList // Needed for 2.12 alias
import scala.reflect.ClassTag
import scala.util.Try

/** User-specified directions.
  */
sealed abstract class SpecifiedDirection
object SpecifiedDirection {

  /** Default user direction, also meaning 'not-flipped'
    */
  case object Unspecified extends SpecifiedDirection

  /** Node and its children are forced as output
    */
  case object Output extends SpecifiedDirection

  /** Node and its children are forced as inputs
    */
  case object Input extends SpecifiedDirection

  /** Mainly for containers, children are flipped.
    */
  case object Flip extends SpecifiedDirection

  def flip(dir: SpecifiedDirection): SpecifiedDirection = dir match {
    case Unspecified => Flip
    case Flip        => Unspecified
    case Output      => Input
    case Input       => Output
  }

  /** Returns the effective SpecifiedDirection of this node given the parent's effective SpecifiedDirection
    * and the user-specified SpecifiedDirection of this node.
    */
  def fromParent(parentDirection: SpecifiedDirection, thisDirection: SpecifiedDirection): SpecifiedDirection =
    (parentDirection, thisDirection) match {
      case (SpecifiedDirection.Output, _)                  => SpecifiedDirection.Output
      case (SpecifiedDirection.Input, _)                   => SpecifiedDirection.Input
      case (SpecifiedDirection.Unspecified, thisDirection) => thisDirection
      case (SpecifiedDirection.Flip, thisDirection)        => SpecifiedDirection.flip(thisDirection)
    }

  private[chisel3] def specifiedDirection[T <: Data](
    source: T
  )(dir:    SpecifiedDirection
  )(
    implicit compileOptions: CompileOptions
  ): T = {
    if (compileOptions.checkSynthesizable) {
      requireIsChiselType(source)
    }
    val out = source.cloneType.asInstanceOf[T]
    out.specifiedDirection = dir
    out
  }

}

/** Resolved directions for both leaf and container nodes, only visible after
  * a node is bound (since higher-level specifications like Input and Output
  * can override directions).
  */
sealed abstract class ActualDirection

object ActualDirection {

  /** The object does not exist / is empty and hence has no direction
    */
  case object Empty extends ActualDirection

  /** Undirectioned, struct-like
    */
  case object Unspecified extends ActualDirection

  /** Output element, or container with all outputs (even if forced)
    */
  case object Output extends ActualDirection

  /** Input element, or container with all inputs (even if forced)
    */
  case object Input extends ActualDirection

  sealed abstract class BidirectionalDirection
  case object Default extends BidirectionalDirection
  case object Flipped extends BidirectionalDirection

  case class Bidirectional(dir: BidirectionalDirection) extends ActualDirection

  def fromSpecified(direction: SpecifiedDirection): ActualDirection = direction match {
    case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => ActualDirection.Unspecified
    case SpecifiedDirection.Output                                => ActualDirection.Output
    case SpecifiedDirection.Input                                 => ActualDirection.Input
  }

  /** Determine the actual binding of a container given directions of its children.
    * Returns None in the case of mixed specified / unspecified directionality.
    */
  def fromChildren(
    childDirections:    Set[ActualDirection],
    containerDirection: SpecifiedDirection
  ): Option[ActualDirection] = {
    if (childDirections == Set()) { // Sadly, Scala can't do set matching
      ActualDirection.fromSpecified(containerDirection) match {
        case ActualDirection.Unspecified => Some(ActualDirection.Empty) // empty direction if relative / no direction
        case dir                         => Some(dir) // use assigned direction if specified
      }
    } else if (childDirections == Set(ActualDirection.Unspecified)) {
      Some(ActualDirection.Unspecified)
    } else if (childDirections == Set(ActualDirection.Input)) {
      Some(ActualDirection.Input)
    } else if (childDirections == Set(ActualDirection.Output)) {
      Some(ActualDirection.Output)
    } else if (
      childDirections.subsetOf(
        Set(
          ActualDirection.Output,
          ActualDirection.Input,
          ActualDirection.Bidirectional(ActualDirection.Default),
          ActualDirection.Bidirectional(ActualDirection.Flipped)
        )
      )
    ) {
      containerDirection match {
        case SpecifiedDirection.Unspecified => Some(ActualDirection.Bidirectional(ActualDirection.Default))
        case SpecifiedDirection.Flip        => Some(ActualDirection.Bidirectional(ActualDirection.Flipped))
        case _                              => throw new RuntimeException("Unexpected forced Input / Output")
      }
    } else {
      None
    }
  }
}

package experimental {
  import chisel3.internal.requireIsHardware // Fix ambiguous import

  /** Experimental hardware construction reflection API
    */
  object DataMirror {
    def widthOf(target:              Data): Width = target.width
    def specifiedDirectionOf(target: Data): SpecifiedDirection = target.specifiedDirection
    def directionOf(target: Data): ActualDirection = {
      requireIsHardware(target, "node requested directionality on")
      target.direction
    }

    private def hasBinding[B <: ConstrainedBinding: ClassTag](target: Data) = {
      target.topBindingOpt match {
        case Some(b: B) => true
        case _ => false
      }
    }

    /** Check if a given `Data` is an IO port
      * @param x the `Data` to check
      * @return `true` if x is an IO port, `false` otherwise
      */
    def isIO(x: Data): Boolean = hasBinding[PortBinding](x)

    /** Check if a given `Data` is a Wire
      * @param x the `Data` to check
      * @return `true` if x is a Wire, `false` otherwise
      */
    def isWire(x: Data): Boolean = hasBinding[WireBinding](x)

    /** Check if a given `Data` is a Reg
      * @param x the `Data` to check
      * @return `true` if x is a Reg, `false` otherwise
      */
    def isReg(x: Data): Boolean = hasBinding[RegBinding](x)

    /** Check if two Chisel types are the same type.
      * Internally, this is dispatched to each Chisel type's
      * `typeEquivalent` function for each type to determine
      * if the types are intended to be equal.
      *
      * For most types, different parameters should ensure
      * that the types are different.
      * For example, `UInt(8.W)` and `UInt(16.W)` are different.
      * Likewise, Records check that both Records have the same
      * elements with the same types.
      *
      * @param x First Chisel type
      * @param y Second Chisel type
      * @return true if the two Chisel types are equal.
      */
    def checkTypeEquivalence(x: Data, y: Data): Boolean = x.typeEquivalent(y)

    /** Returns the ports of a module
      * {{{
      * class MyModule extends Module {
      *   val io = IO(new Bundle {
      *     val in = Input(UInt(8.W))
      *     val out = Output(Vec(2, UInt(8.W)))
      *   })
      *   val extra = IO(Input(UInt(8.W)))
      *   val delay = RegNext(io.in)
      *   io.out(0) := delay
      *   io.out(1) := delay + extra
      * }
      * val mod = Module(new MyModule)
      * DataMirror.modulePorts(mod)
      * // returns: Seq(
      * //   "clock" -> mod.clock,
      * //   "reset" -> mod.reset,
      * //   "io" -> mod.io,
      * //   "extra" -> mod.extra
      * // )
      * }}}
      */
    def modulePorts(target: BaseModule): Seq[(String, Data)] = target.getChiselPorts

    /** Returns a recursive representation of a module's ports with underscore-qualified names
      * {{{
      * class MyModule extends Module {
      *   val io = IO(new Bundle {
      *     val in = Input(UInt(8.W))
      *     val out = Output(Vec(2, UInt(8.W)))
      *   })
      *   val extra = IO(Input(UInt(8.W)))
      *   val delay = RegNext(io.in)
      *   io.out(0) := delay
      *   io.out(1) := delay + extra
      * }
      * val mod = Module(new MyModule)
      * DataMirror.fullModulePorts(mod)
      * // returns: Seq(
      * //   "clock" -> mod.clock,
      * //   "reset" -> mod.reset,
      * //   "io" -> mod.io,
      * //   "io_out" -> mod.io.out,
      * //   "io_out_0" -> mod.io.out(0),
      * //   "io_out_1" -> mod.io.out(1),
      * //   "io_in" -> mod.io.in,
      * //   "extra" -> mod.extra
      * // )
      * }}}
      * @note The returned ports are redundant. An [[Aggregate]] port will be present along with all
      *       of its children.
      * @see [[DataMirror.modulePorts]] for a non-recursive representation of the ports.
      */
    def fullModulePorts(target: BaseModule): Seq[(String, Data)] = {
      def getPortNames(name: String, data: Data): Seq[(String, Data)] = Seq(name -> data) ++ (data match {
        case _: Element => Seq()
        case r: Record  => r.elements.toSeq.flatMap { case (eltName, elt) => getPortNames(s"${name}_${eltName}", elt) }
        case v: Vec[_]  => v.zipWithIndex.flatMap { case (elt, index) => getPortNames(s"${name}_${index}", elt) }
      })
      modulePorts(target).flatMap {
        case (name, data) =>
          getPortNames(name, data).toList
      }
    }

    // Internal reflection-style APIs, subject to change and removal whenever.
    object internal {
      def isSynthesizable(target: Data): Boolean = target.isSynthesizable
      // For those odd cases where you need to care about object reference and uniqueness
      def chiselTypeClone[T <: Data](target: Data): T = {
        target.cloneTypeFull.asInstanceOf[T]
      }
    }
  }
}

/** Creates a clone of the super-type of the input elements. Super-type is defined as:
  * - for Bits type of the same class: the cloned type of the largest width
  * - Bools are treated as UInts
  * - For other types of the same class are are the same: clone of any of the elements
  * - Otherwise: fail
  */
private[chisel3] object cloneSupertype {
  def apply[T <: Data](
    elts:        Seq[T],
    createdType: String
  )(
    implicit sourceInfo: SourceInfo,
    compileOptions:      CompileOptions
  ): T = {
    require(!elts.isEmpty, s"can't create $createdType with no inputs")

    val filteredElts = elts.filter(_ != DontCare)
    require(!filteredElts.isEmpty, s"can't create $createdType with only DontCare inputs")

    if (filteredElts.head.isInstanceOf[Bits]) {
      val model: T = filteredElts.reduce { (elt1: T, elt2: T) =>
        ((elt1, elt2) match {
          case (elt1: Bool, elt2: Bool) => elt1
          case (elt1: Bool, elt2: UInt) => elt2 // TODO: what happens with zero width UInts?
          case (elt1: UInt, elt2: Bool) => elt1 // TODO: what happens with zero width UInts?
          case (elt1: UInt, elt2: UInt) =>
            // TODO: perhaps redefine Widths to allow >= op?
            if (elt1.width == (elt1.width.max(elt2.width))) elt1 else elt2
          case (elt1: SInt, elt2: SInt) => if (elt1.width == (elt1.width.max(elt2.width))) elt1 else elt2
          case (elt1: FixedPoint, elt2: FixedPoint) => {
            (elt1.binaryPoint, elt2.binaryPoint, elt1.width, elt2.width) match {
              case (KnownBinaryPoint(bp1), KnownBinaryPoint(bp2), KnownWidth(w1), KnownWidth(w2)) =>
                val maxBinaryPoint = bp1.max(bp2)
                val maxIntegerWidth = (w1 - bp1).max(w2 - bp2)
                FixedPoint((maxIntegerWidth + maxBinaryPoint).W, (maxBinaryPoint).BP)
              case (KnownBinaryPoint(bp1), KnownBinaryPoint(bp2), _, _) =>
                FixedPoint(Width(), (bp1.max(bp2)).BP)
              case _ => FixedPoint()
            }
          }
          case (elt1: Interval, elt2: Interval) =>
            val range = if (elt1.range.width == elt1.range.width.max(elt2.range.width)) elt1.range else elt2.range
            Interval(range)
          case (elt1, elt2) =>
            throw new AssertionError(
              s"can't create $createdType with heterogeneous types ${elt1.getClass} and ${elt2.getClass}"
            )
        }).asInstanceOf[T]
      }
      model.cloneTypeFull
    } else {
      for (elt <- filteredElts.tail) {
        require(
          elt.getClass == filteredElts.head.getClass,
          s"can't create $createdType with heterogeneous types ${filteredElts.head.getClass} and ${elt.getClass}"
        )
        require(
          elt.typeEquivalent(filteredElts.head),
          s"can't create $createdType with non-equivalent types ${filteredElts.head} and ${elt}"
        )
      }
      filteredElts.head.cloneTypeFull
    }
  }
}

// Returns pairs of all fields, element-level and containers, in a Record and their path names
private[chisel3] object getRecursiveFields {
  def apply(data: Data, path: String): Seq[(Data, String)] = data match {
    case data: Record =>
      data.elements.map {
        case (fieldName, fieldData) =>
          getRecursiveFields(fieldData, s"$path.$fieldName")
      }.fold(Seq(data -> path)) {
        _ ++ _
      }
    case data: Vec[_] =>
      data.getElements.zipWithIndex.map {
        case (fieldData, fieldIndex) =>
          getRecursiveFields(fieldData, path = s"$path($fieldIndex)")
      }.fold(Seq(data -> path)) {
        _ ++ _
      }
    case data: Element => Seq(data -> path)
  }

  def lazily(data: Data, path: String): Seq[(Data, String)] = data match {
    case data: Record =>
      LazyList(data -> path) ++
        data.elements.view.flatMap {
          case (fieldName, fieldData) =>
            getRecursiveFields(fieldData, s"$path.$fieldName")
        }
    case data: Vec[_] =>
      LazyList(data -> path) ++
        data.getElements.view.zipWithIndex.flatMap {
          case (fieldData, fieldIndex) =>
            getRecursiveFields(fieldData, path = s"$path($fieldIndex)")
        }
    case data: Element => LazyList(data -> path)
  }
}

// Returns pairs of corresponding fields between two Records of the same type
// TODO it seems wrong that Elements are checked for typeEquivalence in Bundle and Vec lit creation
private[chisel3] object getMatchedFields {
  def apply(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match {
    case (x: Element, y: Element) =>
      require(x.typeEquivalent(y))
      Seq(x -> y)
    case (x: Record, y: Record) =>
      (x.elements
        .zip(y.elements))
        .map {
          case ((xName, xElt), (yName, yElt)) =>
            require(xName == yName) // assume fields returned in same, deterministic order
            getMatchedFields(xElt, yElt)
        }
        .fold(Seq(x -> y)) {
          _ ++ _
        }
    case (x: Vec[_], y: Vec[_]) =>
      (x.getElements
        .zip(y.getElements))
        .map {
          case (xElt, yElt) =>
            getMatchedFields(xElt, yElt)
        }
        .fold(Seq(x -> y)) {
          _ ++ _
        }
  }
}

/** Returns the chisel type of a hardware object, allowing other hardware to be constructed from it.
  */
object chiselTypeOf {
  def apply[T <: Data](target: T): T = {
    requireIsHardware(target)
    target.cloneTypeFull.asInstanceOf[T]
  }
}

/**
  * Input, Output, and Flipped are used to define the directions of Module IOs.
  *
  * Note that they currently clone their source argument, including its bindings.
  *
  * Thus, an error will be thrown if these are used on bound Data
  */
object Input {
  def apply[T <: Data](source: T)(implicit compileOptions: CompileOptions): T = {
    SpecifiedDirection.specifiedDirection(source)(SpecifiedDirection.Input)
  }
}
object Output {
  def apply[T <: Data](source: T)(implicit compileOptions: CompileOptions): T = {
    SpecifiedDirection.specifiedDirection(source)(SpecifiedDirection.Output)
  }
}

object Flipped {
  def apply[T <: Data](source: T)(implicit compileOptions: CompileOptions): T = {
    SpecifiedDirection.specifiedDirection(source)(SpecifiedDirection.flip(source.specifiedDirection))
  }
}

/** This forms the root of the type system for wire data types. The data value
  * must be representable as some number (need not be known at Chisel compile
  * time) of bits, and must have methods to pack / unpack structured data to /
  * from bits.
  *
  * @groupdesc Connect Utilities for connecting hardware components
  * @define coll data
  */
abstract class Data extends HasId with NamedComponent with SourceInfoDoc {
  // This is a bad API that punches through object boundaries.
  @deprecated("pending removal once all instances replaced", "chisel3")
  private[chisel3] def flatten: IndexedSeq[Element] = {
    this match {
      case elt: Aggregate => elt.getElements.toIndexedSeq.flatMap { _.flatten }
      case elt: Element   => IndexedSeq(elt)
      case elt => throwException(s"Cannot flatten type ${elt.getClass}")
    }
  }

  override def autoSeed(name: String): this.type = {
    topBindingOpt match {
      // Ports are special in that the autoSeed will keep the first name, not the last name
      case Some(PortBinding(m)) if hasAutoSeed && Builder.currentModule.contains(m) => this
      case _                                                                        => super.autoSeed(name)
    }
  }

  // User-specified direction, local at this node only.
  // Note that the actual direction of this node can differ from child and parent specifiedDirection.
  private var _specifiedDirection:         SpecifiedDirection = SpecifiedDirection.Unspecified
  private[chisel3] def specifiedDirection: SpecifiedDirection = _specifiedDirection
  private[chisel3] def specifiedDirection_=(direction: SpecifiedDirection) = {
    if (_specifiedDirection != SpecifiedDirection.Unspecified) {
      this match {
        // Anything flies in compatibility mode
        case t: Record if !t.compileOptions.dontAssumeDirectionality =>
        case _ => throw RebindingException(s"Attempted reassignment of user-specified direction to $this")
      }
    }
    _specifiedDirection = direction
  }

  /** This overwrites a relative SpecifiedDirection with an explicit one, and is used to implement
    * the compatibility layer where, at the elements, Flip is Input and unspecified is Output.
    * DO NOT USE OUTSIDE THIS PURPOSE. THIS OPERATION IS DANGEROUS!
    */
  private[chisel3] def _assignCompatibilityExplicitDirection: Unit = {
    (this, _specifiedDirection) match {
      case (_: Analog, _) => // nothing to do
      case (_, SpecifiedDirection.Unspecified)                       => _specifiedDirection = SpecifiedDirection.Output
      case (_, SpecifiedDirection.Flip)                              => _specifiedDirection = SpecifiedDirection.Input
      case (_, SpecifiedDirection.Input | SpecifiedDirection.Output) => // nothing to do
    }
  }

  // Binding stores information about this node's position in the hardware graph.
  // This information is supplemental (more than is necessary to generate FIRRTL) and is used to
  // perform checks in Chisel, where more informative error messages are possible.
  private var _binding: Option[Binding] = None
  // Only valid after node is bound (synthesizable), crashes otherwise
  protected[chisel3] def binding: Option[Binding] = _binding
  protected def binding_=(target: Binding) {
    if (_binding.isDefined) {
      throw RebindingException(s"Attempted reassignment of binding to $this, from: ${target}")
    }
    _binding = Some(target)
  }

  // Similar to topBindingOpt except it explicitly excludes SampleElements which are bound but not
  // hardware
  private[chisel3] final def isSynthesizable: Boolean = _binding.map {
    case ChildBinding(parent) => parent.isSynthesizable
    case _: TopBinding => true
    case (_: SampleElementBinding[_] | _: MemTypeBinding[_]) => false
  }.getOrElse(false)

  private[chisel3] def topBindingOpt: Option[TopBinding] = _binding.flatMap {
    case ChildBinding(parent) => parent.topBindingOpt
    case bindingVal: TopBinding => Some(bindingVal)
    case SampleElementBinding(parent) => parent.topBindingOpt
    case _: MemTypeBinding[_] => None
  }

  private[chisel3] def topBinding: TopBinding = topBindingOpt.get

  /** Binds this node to the hardware graph.
    * parentDirection is the direction of the parent node, or Unspecified (default) if the target
    * node is the top-level.
    * binding and direction are valid after this call completes.
    */
  private[chisel3] def bind(target: Binding, parentDirection: SpecifiedDirection = SpecifiedDirection.Unspecified): Unit

  // Both _direction and _resolvedUserDirection are saved versions of computed variables (for
  // efficiency, avoid expensive recomputation of frequent operations).
  // Both are only valid after binding is set.

  // Direction of this node, accounting for parents (force Input / Output) and children.
  private var _direction: Option[ActualDirection] = None

  private[chisel3] def direction: ActualDirection = _direction.get
  private[chisel3] def direction_=(actualDirection: ActualDirection) {
    if (_direction.isDefined) {
      throw RebindingException(s"Attempted reassignment of resolved direction to $this")
    }
    _direction = Some(actualDirection)
  }

  private[chisel3] def stringAccessor(chiselType: String): String = {
    topBindingOpt match {
      case None => chiselType
      // Handle DontCares specially as they are "literal-like" but not actually literals
      case Some(DontCareBinding()) => s"$chiselType(DontCare)"
      case Some(topBinding) =>
        val binding: String = _bindingToString(topBinding)
        val name = earlyName
        val mod = parentNameOpt.map(_ + ".").getOrElse("")

        s"$mod$name: $binding[$chiselType]"
    }
  }

  // User-friendly representation of the binding as a helper function for toString.
  // Provides a unhelpful fallback for literals, which should have custom rendering per
  // Data-subtype.
  // TODO Is this okay for sample_element? It *shouldn't* be visible to users
  @deprecated("This was never intended to be visible to user-defined types", "Chisel 3.5.0")
  protected def bindingToString: String = _bindingToString(topBinding)

  private[chisel3] def _bindingToString(topBindingOpt: TopBinding): String =
    topBindingOpt match {
      case OpBinding(_, _)           => "OpResult"
      case MemoryPortBinding(_, _)   => "MemPort"
      case PortBinding(_)            => "IO"
      case RegBinding(_, _)          => "Reg"
      case WireBinding(_, _)         => "Wire"
      case DontCareBinding()         => "(DontCare)"
      case ElementLitBinding(litArg) => "(unhandled literal)"
      case BundleLitBinding(litMap)  => "(unhandled bundle literal)"
      case VecLitBinding(litMap)     => "(unhandled vec literal)"
      case _                         => ""
    }

  private[chisel3] def earlyName: String = Arg.earlyLocalName(this)

  private[chisel3] def parentNameOpt: Option[String] = this._parent.map(_.name)

  // Return ALL elements at root of this type.
  // Contasts with flatten, which returns just Bits
  // TODO: refactor away this, this is outside the scope of Data
  private[chisel3] def allElements: Seq[Element]

  private[chisel3] def badConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit =
    throwException(s"cannot connect ${this} and ${that}")
  private[chisel3] def connect(
    that: Data
  )(
    implicit sourceInfo:   SourceInfo,
    connectCompileOptions: CompileOptions
  ): Unit = {
    if (connectCompileOptions.checkSynthesizable) {
      requireIsHardware(this, "data to be connected")
      requireIsHardware(that, "data to be connected")
      this.topBinding match {
        case _: ReadOnlyBinding => throwException(s"Cannot reassign to read-only $this")
        case _ => // fine
      }
      try {
        MonoConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.referenceUserModule)
      } catch {
        case MonoConnectException(message) =>
          throwException(
            s"Connection between sink ($this) and source ($that) failed @: $message"
          )
      }
    } else {
      this.legacyConnect(that)
    }
  }
  private[chisel3] def bulkConnect(
    that: Data
  )(
    implicit sourceInfo:   SourceInfo,
    connectCompileOptions: CompileOptions
  ): Unit = {
    if (connectCompileOptions.checkSynthesizable) {
      requireIsHardware(this, s"data to be bulk-connected")
      requireIsHardware(that, s"data to be bulk-connected")
      (this.topBinding, that.topBinding) match {
        case (_: ReadOnlyBinding, _: ReadOnlyBinding) => throwException(s"Both $this and $that are read-only")
        // DontCare cannot be a sink (LHS)
        case (_: DontCareBinding, _) => throw BiConnect.DontCareCantBeSink
        case _ => // fine
      }
      try {
        BiConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.referenceUserModule)
      } catch {
        case BiConnectException(message) =>
          throwException(
            s"Connection between left ($this) and source ($that) failed @$message"
          )
      }
    } else {
      this.legacyConnect(that)
    }
  }

  /** Whether this Data has the same model ("data type") as that Data.
    * Data subtypes should overload this with checks against their own type.
    */
  private[chisel3] def typeEquivalent(that: Data): Boolean

  private def requireVisible(): Unit = {
    val mod = topBindingOpt.flatMap(_.location)
    topBindingOpt match {
      case Some(tb: TopBinding) if (mod == Builder.currentModule) =>
      case Some(pb: PortBinding)
          if (mod.flatMap(Builder.retrieveParent(_, Builder.currentModule.get)) == Builder.currentModule) =>
      case Some(_: UnconstrainedBinding) =>
      case _ =>
        throwException(s"operand '$this' is not visible from the current module")
    }
    if (!MonoConnect.checkWhenVisibility(this)) {
      throwException(s"operand has escaped the scope of the when in which it was constructed")
    }
  }

  // Internal API: returns a ref that can be assigned to, if consistent with the binding
  private[chisel3] def lref: Node = {
    requireIsHardware(this)
    requireVisible()
    topBindingOpt match {
      case Some(binding: ReadOnlyBinding) =>
        throwException(s"internal error: attempted to generate LHS ref to ReadOnlyBinding $binding")
      case Some(binding: TopBinding) => Node(this)
      case opt => throwException(s"internal error: unknown binding $opt in generating LHS ref")
    }
  }

  // Internal API: returns a ref, if bound
  private[chisel3] final def ref: Arg = {
    def materializeWire(): Arg = {
      if (!Builder.currentModule.isDefined) throwException(s"internal error: cannot materialize ref for $this")
      implicit val compileOptions = ExplicitCompileOptions.Strict
      implicit val sourceInfo = UnlocatableSourceInfo
      WireDefault(this).ref
    }
    requireIsHardware(this)
    topBindingOpt match {
      // DataView
      case Some(ViewBinding(target)) => reify(target).ref
      case Some(AggregateViewBinding(viewMap)) =>
        viewMap.get(this) match {
          case None => materializeWire() // FIXME FIRRTL doesn't have Aggregate Init expressions
          // This should not be possible because Element does the lookup in .topBindingOpt
          case x: Some[_] => throwException(s"Internal Error: In .ref for $this got '$topBindingOpt' and '$x'")
        }
      // Literals
      case Some(ElementLitBinding(litArg)) => litArg
      case Some(BundleLitBinding(litMap)) =>
        litMap.get(this) match {
          case Some(litArg) => litArg
          case _            => materializeWire() // FIXME FIRRTL doesn't have Bundle literal expressions
        }
      case Some(VecLitBinding(litMap)) =>
        litMap.get(this) match {
          case Some(litArg) => litArg
          case _            => materializeWire() // FIXME FIRRTL doesn't have Vec literal expressions
        }
      case Some(DontCareBinding()) =>
        materializeWire() // FIXME FIRRTL doesn't have a DontCare expression so materialize a Wire
      // Non-literals
      case Some(binding: TopBinding) =>
        if (Builder.currentModule.isDefined) {
          // This is allowed (among other cases) for evaluating args of Printf / Assert / Printable, which are
          // partially resolved *after* elaboration completes. If this is resolved, the check should be unconditional.
          requireVisible()
        }
        Node(this)
      case opt => throwException(s"internal error: unknown binding $opt in generating LHS ref")
    }
  }

  // Recursively set the parent of the start Data and any children (eg. in an Aggregate)
  private[chisel3] def setAllParents(parent: Option[BaseModule]): Unit = {
    def rec(data: Data): Unit = {
      data._parent = parent
      data match {
        case _:   Element =>
        case agg: Aggregate =>
          agg.getElements.foreach(rec)
      }
    }
    rec(this)
  }

  private[chisel3] def width: Width
  private[chisel3] def legacyConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit

  /** Internal API; Chisel users should look at chisel3.chiselTypeOf(...).
    *
    * cloneType must be defined for any Chisel object extending Data.
    * It is responsible for constructing a basic copy of the object being cloned.
    *
    * @return a copy of the object.
    */
  def cloneType: this.type

  /** Internal API; Chisel users should look at chisel3.chiselTypeOf(...).
    *
    * Returns a copy of this data type, with hardware bindings (if any) removed.
    * Directionality data is still preserved.
    */
  private[chisel3] def cloneTypeFull: this.type = {
    val clone = this.cloneType.asInstanceOf[this.type] // get a fresh object, without bindings
    // Only the top-level direction needs to be fixed up, cloneType should do the rest
    clone.specifiedDirection = specifiedDirection
    clone
  }

  /** Connect this $coll to that $coll mono-directionally and element-wise.
    *
    * This uses the [[MonoConnect]] algorithm.
    *
    * @param that the $coll to connect to
    * @group Connect
    */
  final def :=(that: => Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = {
    prefix(this) {
      this.connect(that)(sourceInfo, connectionCompileOptions)
    }
  }

  /** Connect this $coll to that $coll bi-directionally and element-wise.
    *
    * This uses the [[BiConnect]] algorithm.
    *
    * @param that the $coll to connect to
    * @group Connect
    */
  final def <>(that: => Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = {
    prefix(this) {
      this.bulkConnect(that)(sourceInfo, connectionCompileOptions)
    }
  }

  def isLit: Boolean = litOption.isDefined

  @deprecated(
    "Calling this function with an empty argument list is invalid in Scala 3. Use the form without parentheses instead",
    "Chisel 3.5"
  )
  def isLit(dummy: Int*): Boolean = isLit

  /**
    * If this is a literal that is representable as bits, returns the value as a BigInt.
    * If not a literal, or not representable as bits (for example, is or contains Analog), returns None.
    */
  def litOption: Option[BigInt]

  @deprecated(
    "Calling this function with an empty argument list is invalid in Scala 3. Use the form without parentheses instead",
    "Chisel 3.5"
  )
  def litOption(dummy: Int*): Option[BigInt] = litOption

  /**
    * Returns the literal value if this is a literal that is representable as bits, otherwise crashes.
    */
  def litValue: BigInt = litOption.get

  @deprecated(
    "Calling this function with an empty argument list is invalid in Scala 3. Use the form without parentheses instead",
    "Chisel 3.5"
  )
  def litValue(dummy: Int*): BigInt = litValue

  /** Returns the width, in bits, if currently known. */
  final def getWidth: Int =
    if (isWidthKnown) width.get else throwException(s"Width of $this is unknown!")

  /** Returns whether the width is currently known. */
  final def isWidthKnown: Boolean = width.known

  /** Returns Some(width) if the width is known, else None. */
  final def widthOption: Option[Int] = if (isWidthKnown) Some(getWidth) else None

  /** Does a reinterpret cast of the bits in this node into the format that provides.
    * Returns a new Wire of that type. Does not modify existing nodes.
    *
    * x.asTypeOf(that) performs the inverse operation of x := that.toBits.
    *
    * @note bit widths are NOT checked, may pad or drop bits from input
    * @note that should have known widths
    */
  def asTypeOf[T <: Data](that: T): T = macro SourceInfoTransform.thatArg

  /** @group SourceInfoTransformMacro */
  def do_asTypeOf[T <: Data](that: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = {
    val thatCloned = Wire(that.cloneTypeFull)
    thatCloned.connectFromBits(this.asUInt)
    thatCloned
  }

  /** Assigns this node from Bits type. Internal implementation for asTypeOf.
    */
  private[chisel3] def connectFromBits(
    that: Bits
  )(
    implicit sourceInfo: SourceInfo,
    compileOptions:      CompileOptions
  ): Unit

  /** Reinterpret cast to UInt.
    *
    * @note value not guaranteed to be preserved: for example, a SInt of width
    * 3 and value -1 (0b111) would become an UInt with value 7
    * @note Aggregates are recursively packed with the first element appearing
    * in the least-significant bits of the result.
    */
  final def asUInt: UInt = macro SourceInfoTransform.noArg

  @deprecated(
    "Calling this function with an empty argument list is invalid in Scala 3. Use the form without parentheses instead",
    "Chisel 3.5"
  )
  final def asUInt(dummy: Int*): UInt = macro SourceInfoTransform.noArgDummy

  /** @group SourceInfoTransformMacro */
  def do_asUInt(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt

  /** Default pretty printing */
  def toPrintable: Printable
}

trait WireFactory {

  /** Construct a [[Wire]] from a type template
    * @param t The template from which to construct this wire
    */
  def apply[T <: Data](t: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = {
    if (compileOptions.declaredTypeMustBeUnbound) {
      requireIsChiselType(t, "wire type")
    }
    val x = t.cloneTypeFull

    // Bind each element of x to being a Wire
    x.bind(WireBinding(Builder.forcedUserModule, Builder.currentWhen))

    pushCommand(DefWire(sourceInfo, x))
    if (!compileOptions.explicitInvalidate || Builder.currentModule.get.isInstanceOf[ImplicitInvalidate]) {
      pushCommand(DefInvalid(sourceInfo, x.ref))
    }

    x
  }
}

/** Utility for constructing hardware wires
  *
  * The width of a `Wire` (inferred or not) is copied from the type template
  * {{{
  * val w0 = Wire(UInt()) // width is inferred
  * val w1 = Wire(UInt(8.W)) // width is set to 8
  *
  * val w2 = Wire(Vec(4, UInt())) // width is inferred
  * val w3 = Wire(Vec(4, UInt(8.W))) // width of each element is set to 8
  *
  * class MyBundle {
  *   val unknown = UInt()
  *   val known   = UInt(8.W)
  * }
  * val w4 = Wire(new MyBundle)
  * // Width of w4.unknown is inferred
  * // Width of w4.known is set to 8
  * }}}
  */
object Wire extends WireFactory

/** Utility for constructing hardware wires with a default connection
  *
  * The two forms of `WireDefault` differ in how the type and width of the resulting [[Wire]] are
  * specified.
  *
  * ==Single Argument==
  * The single argument form uses the argument to specify both the type and default connection. For
  * non-literal [[Bits]], the width of the [[Wire]] will be inferred. For literal [[Bits]] and all
  * non-Bits arguments, the type will be copied from the argument. See the following examples for
  * more details:
  *
  * 1. Literal [[Bits]] initializer: width will be set to match
  * {{{
  * val w1 = WireDefault(1.U) // width will be inferred to be 1
  * val w2 = WireDefault(1.U(8.W)) // width is set to 8
  * }}}
  *
  * 2. Non-Literal [[Element]] initializer - width will be inferred
  * {{{
  * val x = Wire(UInt())
  * val y = Wire(UInt(8.W))
  * val w1 = WireDefault(x) // width will be inferred
  * val w2 = WireDefault(y) // width will be inferred
  * }}}
  *
  * 3. [[Aggregate]] initializer - width will be set to match the aggregate
  *
  * {{{
  * class MyBundle {
  *   val unknown = UInt()
  *   val known   = UInt(8.W)
  * }
  * val w1 = Wire(new MyBundle)
  * val w2 = WireDefault(w1)
  * // Width of w2.unknown is inferred
  * // Width of w2.known is set to 8
  * }}}
  *
  * ==Double Argument==
  * The double argument form allows the type of the [[Wire]] and the default connection to be
  * specified independently.
  *
  * The width inference semantics for `WireDefault` with two arguments match those of [[Wire]]. The
  * first argument to `WireDefault` is the type template which defines the width of the `Wire` in
  * exactly the same way as the only argument to [[Wire]].
  *
  * More explicitly, you can reason about `WireDefault` with multiple arguments as if it were defined
  * as:
  * {{{
  * def WireDefault[T <: Data](t: T, init: T): T = {
  *   val x = Wire(t)
  *   x := init
  *   x
  * }
  * }}}
  *
  * @note The `Default` in `WireDefault` refers to a `default` connection. This is in contrast to
  * [[RegInit]] where the `Init` refers to a value on reset.
  */
object WireDefault {

  private def applyImpl[T <: Data](
    t:    T,
    init: Data
  )(
    implicit sourceInfo: SourceInfo,
    compileOptions:      CompileOptions
  ): T = {
    implicit val noSourceInfo = UnlocatableSourceInfo
    val x = Wire(t)
    requireIsHardware(init, "wire initializer")
    x := init
    x
  }

  /** Construct a [[Wire]] with a type template and a [[chisel3.DontCare]] default
    * @param t The type template used to construct this [[Wire]]
    * @param init The default connection to this [[Wire]], can only be [[DontCare]]
    * @note This is really just a specialized form of `apply[T <: Data](t: T, init: T): T` with [[DontCare]] as `init`
    */
  def apply[T <: Data](
    t:    T,
    init: DontCare.type
  )(
    implicit sourceInfo: SourceInfo,
    compileOptions:      CompileOptions
  ): T = {
    applyImpl(t, init)
  }

  /** Construct a [[Wire]] with a type template and a default connection
    * @param t The type template used to construct this [[Wire]]
    * @param init The hardware value that will serve as the default value
    */
  def apply[T <: Data](t: T, init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = {
    applyImpl(t, init)
  }

  /** Construct a [[Wire]] with a default connection
    * @param init The hardware value that will serve as a type template and default value
    */
  def apply[T <: Data](init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = {
    val model = (init match {
      // If init is a literal without forced width OR any non-literal, let width be inferred
      case init: Bits if !init.litIsForcedWidth.getOrElse(false) => init.cloneTypeWidth(Width())
      case _ => init.cloneTypeFull
    }).asInstanceOf[T]
    apply(model, init)
  }
}

/** RHS (source) for Invalidate API.
  * Causes connection logic to emit a DefInvalid when connected to an output port (or wire).
  */
final case object DontCare extends Element {
  // This object should be initialized before we execute any user code that refers to it,
  //  otherwise this "Chisel" object will end up on the UserModule's id list.
  // We make it private to chisel3 so it has to be accessed through the package object.

  private[chisel3] override val width: Width = UnknownWidth()

  bind(DontCareBinding(), SpecifiedDirection.Output)
  override def cloneType: this.type = DontCare

  override def toString: String = "DontCare()"

  override def litOption: Option[BigInt] = None

  def toPrintable: Printable = PString("DONTCARE")

  private[chisel3] def connectFromBits(
    that: Bits
  )(
    implicit sourceInfo: SourceInfo,
    compileOptions:      CompileOptions
  ): Unit = {
    Builder.error("connectFromBits: DontCare cannot be a connection sink (LHS)")
  }

  def do_asUInt(implicit sourceInfo: chisel3.internal.sourceinfo.SourceInfo, compileOptions: CompileOptions): UInt = {
    Builder.error("DontCare does not have a UInt representation")
    0.U
  }
  // DontCare's only match themselves.
  private[chisel3] def typeEquivalent(that: Data): Boolean = that == DontCare
}
