All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dfhdl.core.DFVal.scala Maven / Gradle / Ivy

package dfhdl.core
import dfhdl.compiler.ir
import dfhdl.internals.*
import ir.DFVal.Func.Op as FuncOp
import ir.DFVal.Alias.History.Op as HistoryOp
import ir.DFDecimal.NativeType

import scala.annotation.unchecked.uncheckedVariance
import scala.annotation.{implicitNotFound, targetName}
import scala.quoted.*
import DFOpaque.Abstract as DFOpaqueA
import dfhdl.compiler.ir.MemberGetSet
import dfhdl.compiler.printing.{DefaultPrinter, Printer}
import scala.annotation.tailrec

import scala.reflect.ClassTag
final class DFVal[+T <: DFTypeAny, +M <: ModifierAny](val irValue: ir.DFVal | DFError)
    extends AnyVal
    with DFMember[ir.DFVal]
    with Selectable:

  def selectDynamic(name: String)(using DFC): Any = trydf {
    val ir.DFStruct(structName, fieldMap) = this.asIR.dfType: @unchecked
    val dfType = fieldMap(name)
    DFVal.Alias
      .SelectField(this, name)
      .asIR
      .asVal[DFTypeAny, ModifierAny]
  }

  transparent inline def ==[R](
      inline that: R
  )(using DFC): DFValTP[DFBool, Any] = ${
    DFVal.equalityMacro[T, M, R, FuncOp.===.type]('this, 'that)
  }
  transparent inline def !=[R](
      inline that: R
  )(using DFC): DFValTP[DFBool, Any] = ${
    DFVal.equalityMacro[T, M, R, FuncOp.=!=.type]('this, 'that)
  }
end DFVal

type DFValAny = DFVal[DFTypeAny, ModifierAny]
type DFVarAny = DFVal[DFTypeAny, Modifier.Mutable]
type DFDclAny = DFVal[DFTypeAny, Modifier.Dcl]
type DFConstAny = DFVal[DFTypeAny, Modifier.CONST]
type DFValOf[+T <: DFTypeAny] = DFVal[T, ModifierAny]
type DFConstOf[+T <: DFTypeAny] = DFVal[T, Modifier.CONST]
type DFValTP[+T <: DFTypeAny, +P] = DFVal[T, Modifier[Any, Any, Any, P]]
type DFVarOf[+T <: DFTypeAny] = DFVal[T, Modifier.Mutable]

extension (using quotes: Quotes)(tpe: quotes.reflect.TypeRepr)
  def isConstTpe: quotes.reflect.TypeRepr =
    import quotes.reflect.*
    def isConstBool(tpe: TypeRepr): Boolean = tpe.asType match
      case '[DFConstOf[t]] => true
      case '[DFValOf[t]]   => false
      case '[NonEmptyTuple] =>
        tpe.getTupleArgs.forall(isConstBool)
      case '[SameElementsVector[t]] => isConstBool(TypeRepr.of[t])
      case '[BoolSelWrapper[sp, ot, of]] =>
        List(TypeRepr.of[sp], TypeRepr.of[ot], TypeRepr.of[of]).forall(isConstBool)
      case '[DFVal.NOTHING] => false
      case _                => true
    if (isConstBool(tpe)) TypeRepr.of[CONST]
    else TypeRepr.of[NOTCONST]

extension (using quotes: Quotes)(term: quotes.reflect.Term)
  def getNonConstTerm: Option[quotes.reflect.Term] =
    import quotes.reflect.*
    term match
      case Apply(fun, args)    => (fun :: args).view.flatMap(_.getNonConstTerm).headOption
      case NamedArg(_, expr)   => expr.getNonConstTerm
      case Inlined(_, _, expr) => expr.getNonConstTerm
      case Block(_, expr)      => expr.getNonConstTerm
      case TypeApply(expr, _)  => expr.getNonConstTerm
      case Typed(expr, _)      => expr.getNonConstTerm
      case _ =>
        term.tpe.asType match
          case '[DFConstOf[?]]  => None
          case '[DFValOf[?]]    => Some(term)
          case '[DFVal.NOTHING] => Some(term)
          case _                => None
    end match
  end getNonConstTerm
end extension

infix type <>[T, M] = T match
  case Int => // Int can be a constant literal or just "Int" representing SInt[32]
    IsConst[T] match
      case true  => DFVector.ComposedModifier[T, M]
      case false => DFInt32 <> M
  // This case handles a vector declaration where operator precedence puts an embedded parameter
  // as type for `<>`. E.g.:
  // val len: Int <> CONST = 20
  // val vec1: UInt[4] X len.type <> VAR = all(0)
  // val vec2: UInt[4] X (len.type + 1) <> VAR = all(0)
  // In both cases above the `<>` priority over `X` priority creates a type composition like:
  // `UInt[4] X (len.type <> VAR)`
  // So in this case we construct DFVector.ComposedModifier[T, M] to later used in `X`
  // to properly construct the vector type.
  case DFConstInt32 | IntP.Sig => DFVector.ComposedModifier[T, M]
  case _ =>
    M match
      case DFRET => DFC ?=> DFValOf[DFType.Of[T]]
      case VAL   => DFValOf[DFType.Of[T]]
      case CONST => DFConstOf[DFType.Of[T]]

infix type X[T, M] = M match
  case DFVector.ComposedModifier[d, m] => <>[DFVector[DFType.Of[T], Tuple1[d]], m]
  case _                               => DFVector[DFType.Of[T], Tuple1[M]]
type JUSTVAL[T] = <>[T, VAL]

extension [V <: ir.DFVal](dfVal: V)
  inline def asVal[T <: DFTypeAny, M <: ModifierAny]: DFVal[T, M] =
    DFVal[T, M, V](dfVal)
  inline def asValOf[T <: DFTypeAny]: DFValOf[T] =
    DFVal[T, ModifierAny, V](dfVal)
  inline def asValTP[T <: DFTypeAny, P]: DFValTP[T, P] =
    DFVal[T, Modifier[Any, Any, Any, P], V](dfVal)
  inline def asValAny: DFValAny =
    DFVal[DFTypeAny, ModifierAny, V](dfVal)
  inline def asVarOf[T <: DFTypeAny]: DFVarOf[T] =
    DFVal[T, Modifier.Mutable, V](dfVal)
  inline def asVarAny: DFVarAny =
    DFVal[DFTypeAny, Modifier.Mutable, V](dfVal)
  inline def asDclAny: DFDclAny =
    DFVal[DFTypeAny, Modifier.Dcl, V](dfVal)
  inline def asConstAny[T <: DFTypeAny]: DFConstOf[DFTypeAny] =
    DFVal[DFTypeAny, Modifier.CONST, V](dfVal)
  inline def asConstOf[T <: DFTypeAny]: DFConstOf[T] =
    DFVal[T, Modifier.CONST, V](dfVal)
end extension

extension (dfVal: DFValAny)
  inline def asVal[T <: DFTypeAny, M <: ModifierAny]: DFVal[T, M] =
    dfVal.asInstanceOf[DFVal[T, M]]
  inline def asValOf[T <: DFTypeAny]: DFValOf[T] =
    dfVal.asInstanceOf[DFVal[T, ModifierAny]]
  inline def asValTP[T <: DFTypeAny, P]: DFValTP[T, P] =
    dfVal.asInstanceOf[DFVal[T, Modifier[Any, Any, Any, P]]]
  inline def asVarOf[T <: DFTypeAny]: DFVarOf[T] =
    dfVal.asInstanceOf[DFVal[T, Modifier.Mutable]]
  inline def asVarAny: DFVarAny =
    dfVal.asInstanceOf[DFVal[DFTypeAny, Modifier.Mutable]]
  inline def asDclAny: DFDclAny =
    dfVal.asInstanceOf[DFVal[DFTypeAny, Modifier.Dcl]]
  inline def asConstOf[T <: DFTypeAny]: DFConstOf[T] =
    dfVal.asInstanceOf[DFVal[T, Modifier.CONST]]
end extension

def DFValConversionMacro[T <: DFTypeAny, P, R](
    from: Expr[R]
)(using Quotes, Type[T], Type[P], Type[R]): Expr[DFValTP[T, P]] =
  import quotes.reflect.*
  val fromExactInfo = from.exactInfo
  lazy val nonConstTermOpt = from.asTerm.getNonConstTerm
  if (TypeRepr.of[P] =:= TypeRepr.of[CONST] && nonConstTermOpt.nonEmpty)
    nonConstTermOpt.get.compiletimeErrorPosExpr("Applied argument must be a constant.")
  else
    '{
      import DFStruct.apply
      given bitsNoType: DFBits[Int] = DFNothing.asInstanceOf[DFBits[Int]]
      given uintNoType: DFUInt[Int] = DFNothing.asInstanceOf[DFUInt[Int]]
      given sintNoType: DFSInt[Int] = DFNothing.asInstanceOf[DFSInt[Int]]
      val tc = compiletime.summonInline[DFVal.TC[T, fromExactInfo.Underlying]]
      val dfc = compiletime.summonInline[DFC]
      val dfType = compiletime.summonInline[T]
      trydf {
        tc(dfType, ${ fromExactInfo.exactExpr })(using dfc).asValTP[T, P]
      }(using dfc, compiletime.summonInline[CTName])
    }
  end if
end DFValConversionMacro

sealed protected trait DFValLP:
  /* TODO: IMPORTANT IMPLICIT CONVERSION ISSUE
     -----------------------------------------
    Currently, the following issue affects the implicit conversion mechanism:
    https://github.com/lampepfl/dotty/issues/19388
    Because of this, we need to explicitly have a solid return type on the conversion definition
    to prevent returning `Nothing`. We have `CONST=:=ISCONST[true]` and `NOTCONST=:=Any`.
    For `NOTCONST`, what actually is happening is that the implicit conversion mechanism
    returns `ISCONST[Boolean]` and therefore we can detect it is not a constant. Because the
    modifier `M` of `DFVal[+T, +M]` is covariant, the compiler accepts
    `DFVal[T, NOTCONST] <:< DFVal[T, ISCONST[Boolean]]` and all is well.
   */
  type CommonR = DFValAny | Bubble | DFVal.NOTHING | BoolSelWrapper[?, ?, ?]

  implicit transparent inline def DFBitsValConversion[
      W <: IntP,
      P <: Boolean,
      R <: CommonR | SameElementsVector[?] | NonEmptyTuple
  ](
      inline from: R
  ): DFValTP[DFBits[W], ISCONST[P]] = ${
    DFValConversionMacro[DFBits[W], ISCONST[P], R]('from)
  }
  // TODO: candidate should be fixed to cause UInt[?]->SInt[Int] conversion
  implicit transparent inline def DFXIntValConversion[
      S <: Boolean,
      W <: IntP,
      N <: NativeType,
      P <: Boolean,
      R <: CommonR | Int
  ](
      inline from: R
  ): DFValTP[DFXInt[S, W, N], ISCONST[P]] = ${
    DFValConversionMacro[DFXInt[S, W, N], ISCONST[P], R]('from)
  }
  implicit transparent inline def DFOpaqueValConversion[
      TFE <: DFOpaque.Abstract,
      P <: Boolean,
      R <: CommonR
  ](
      inline from: R
  ): DFValTP[DFOpaque[TFE], ISCONST[P]] = ${
    DFValConversionMacro[DFOpaque[TFE], ISCONST[P], R]('from)
  }
  implicit transparent inline def DFStructValConversion[
      F <: DFStruct.Fields,
      P <: Boolean,
      R <: CommonR | DFStruct.Fields
  ](
      inline from: R
  ): DFValTP[DFStruct[F], ISCONST[P]] = ${
    DFValConversionMacro[DFStruct[F], ISCONST[P], R]('from)
  }
  implicit transparent inline def DFTupleValConversion[
      T <: NonEmptyTuple,
      P <: Boolean,
      R <: CommonR | NonEmptyTuple
  ](
      inline from: R
  ): DFValTP[DFTuple[T], ISCONST[P]] = ${
    DFValConversionMacro[DFTuple[T], ISCONST[P], R]('from)
  }
  implicit transparent inline def DFVectorValConversion[
      T <: DFTypeAny,
      D <: IntP,
      P <: Boolean,
      R <: CommonR | Iterable[?] | SameElementsVector[?]
  ](
      inline from: R
  ): DFValTP[DFVector[T, Tuple1[D]], ISCONST[P]] = ${
    DFValConversionMacro[DFVector[T, Tuple1[D]], ISCONST[P], R]('from)
  }
  implicit transparent inline def DFBitValConversion[
      P <: Boolean,
      R <: CommonR | Int | Boolean
  ](
      inline from: R
  ): DFValTP[DFBit, ISCONST[P]] = ${
    DFValConversionMacro[DFBit, ISCONST[P], R]('from)
  }
  implicit transparent inline def DFBoolValConversion[
      P <: Boolean,
      R <: CommonR | Int | Boolean
  ](
      inline from: R
  ): DFValTP[DFBool, ISCONST[P]] = ${
    DFValConversionMacro[DFBool, ISCONST[P], R]('from)
  }
  given DFUnitValConversion[R <: CommonR | Unit | NonEmptyTuple](using
      dfc: DFC
  ): Conversion[R, DFValOf[DFUnit]] = from => DFUnitVal().asInstanceOf[DFValOf[DFUnit]]
  given ConstToNonConstAccept[T <: DFTypeAny, P]: Conversion[DFValTP[T, P], DFValTP[T, NOTCONST]] =
    from => from.asValTP[T, NOTCONST]
end DFValLP
object DFVal extends DFValLP:
  // constructing a front-end DFVal value class object. if it's a global value, then
  // we need to save the DFC, instead of the actual member IR object
  inline def apply[T <: DFTypeAny, M <: ModifierAny, IR <: ir.DFVal | DFError](
      irValue: IR
  ): DFVal[T, M] = new DFVal[T, M](irValue)
  inline def unapply(arg: DFValAny): Option[ir.DFVal] = Some(arg.asIR)
  object OrTupleOrStruct:
    def unapply(arg: Any)(using DFC): Option[DFValAny] =
      arg match
        case dfVal: DFValAny     => Some(dfVal)
        case DFTuple.Val(dfVal)  => Some(dfVal)
        case DFStruct.Val(dfVal) => Some(dfVal)
        case _                   => None

  def equalityMacro[T <: DFTypeAny, M <: ModifierAny, R, Op <: FuncOp](
      dfVal: Expr[DFVal[T, M]],
      arg: Expr[R]
  )(using Quotes, Type[T], Type[M], Type[R], Type[Op]): Expr[DFValTP[DFBool, Any]] =
    import quotes.reflect.*
    if (TypeRepr.of[T].typeSymbol equals defn.NothingClass) return '{
      compiletime.error("This is fake")
    }
    val exactInfo = arg.exactInfo
    val lpType = dfVal.asTerm.tpe.isConstTpe.asTypeOf[Any]
    val rpType = exactInfo.exactTpe.isConstTpe.asTypeOf[Any]
    '{
      val c = compiletime.summonInline[DFVal.Compare[T, exactInfo.Underlying, Op, false]]
      val dfc = compiletime.summonInline[DFC]
      c($dfVal, ${ exactInfo.exactExpr })(using
        dfc,
        compiletime.summonInline[ValueOf[Op]],
        new ValueOf[false](false)
      )
        // TODO: May not be need if this issue is solved:
        // https://github.com/lampepfl/dotty/issues/19554
        .asValTP[DFBool, lpType.Underlying | rpType.Underlying]
    }
  end equalityMacro

  // Enabling equality with Int, Boolean, and Tuples.
  // just to give a better error message via the compiler plugin.
  // See the method `rejectBadPrimitiveOps` in `MetaContextGenPhase.scala`
  given [T <: DFTypeAny, M <: ModifierAny]: CanEqual[Int, DFVal[T, M]] =
    CanEqual.derived
  given [T <: DFTypeAny, M <: ModifierAny]: CanEqual[Boolean, DFVal[T, M]] =
    CanEqual.derived
  given [T <: DFTypeAny, M <: ModifierAny]: CanEqual[Tuple, DFVal[T, M]] =
    CanEqual.derived
  // Enabling encoding comparison
  given [T <: DFTypeAny, M <: ModifierAny]: CanEqual[DFEncoding, DFVal[T, M]] =
    CanEqual.derived

  given __refined_dfVal[T <: FieldsOrTuple, A, I, P](using
      r: DFStruct.Val.Refiner[T, A, I, P]
  ): Conversion[DFVal[DFStruct[T], Modifier[A, Any, I, P]], r.Out] = _.asInstanceOf[r.Out]

  trait ConstCheck[P]
  given [P](using
      AssertGiven[
        P =:= CONST,
        "Only a DFHDL constant is convertible to a Scala value, but this DFHDL value is not a constant."
      ]
  ): ConstCheck[P] with {}

  extension [D, T <: ir.DFType, P](lhs: DFValTP[DFType[ir.DFType.Aux[T, Option[D]], ?], P])
    protected[core] def toScalaValue(using dfc: DFC, check: ConstCheck[P]): D =
      import dfc.getSet
      val lhsIR = lhs.asIR
      def error(errMsg: String): Nothing =
        exitWithError(
          s"""|Scala value access error!
              |Position:  ${lhsIR.meta.position}
              |Hierarchy: ${lhsIR.getOwnerDesign.getFullName}
              |Message:   ${errMsg}""".stripMargin
        )
      lhsIR.getConstData.asInstanceOf[Option[Option[D]]]
        .getOrElse(error("Cannot fetch a Scala value from a non-constant DFHDL value."))
        .getOrElse(error("Cannot fetch a Scala value from a bubble (invalid) DFHDL value."))

  trait InitCheck[I]
  given [I](using
      initializableOnly: AssertGiven[
        I =:= Modifier.Initializable,
        "Can only initialize a DFHDL port or variable that are not already initialized."
      ]
  ): InitCheck[I] with {}

  extension [T <: DFTypeAny, M <: ModifierAny](dfVal: DFVal[T, M])
    @metaContextForward(0)
    infix def tag[CT <: ir.DFTag: ClassTag](customTag: CT)(using
        dfc: DFC
    ): DFVal[T, M] =
      import dfc.getSet
      dfVal.asIR
        .setTags(_.tag(customTag))
        .setMeta(m => if (m.isAnonymous && !dfc.getMeta.isAnonymous) dfc.getMeta else m)
        .asVal[T, M]
    @metaContextForward(0)
    infix def tag[CT <: ir.DFTag: ClassTag](condCustomTag: Conditional[CT])(using
        dfc: DFC
    ): DFVal[T, M] = if (condCustomTag.isActive) dfVal.tag(condCustomTag.getArg) else dfVal
    def hasTag[CT <: ir.DFTag: ClassTag](using dfc: DFC): Boolean =
      import dfc.getSet
      dfVal.asIR.tags.hasTagOf[CT]
    @metaContextForward(0)
    infix def setName(name: String)(using dfc: DFC): DFVal[T, M] =
      import dfc.getSet
      dfVal.asIR
        .setMeta(m =>
          if (m.isAnonymous && !dfc.getMeta.isAnonymous) dfc.getMeta.setName(name)
          else m.setName(name)
        )
        .asVal[T, M]
    def anonymize(using dfc: DFC): DFVal[T, M] =
      import dfc.getSet
      dfVal.asIR match
        case dfValIR: (ir.DFVal.Alias | ir.DFVal.Const | ir.DFVal.Func) =>
          dfValIR.setMeta(m => m.anonymize).asVal[T, M]
        case _ => dfVal
    def inDFCPosition(using DFC): Boolean = dfVal.asIR.meta.position == dfc.getMeta.position
    def anonymizeInDFCPosition(using DFC): DFVal[T, M] =
      if (inDFCPosition) dfVal.anonymize else dfVal
    @metaContextForward(0)
    def nameInDFCPosition(using dfc: DFC): DFVal[T, M] =
      import dfc.getSet
      val dfValIR = dfVal.asIR
      if (inDFCPosition && dfValIR.isAnonymous && !dfc.isAnonymous)
        dfValIR.setMeta(_ => dfc.getMeta).asVal[T, M]
      else dfVal
  end extension

  case object ExtendTag extends ir.DFTagOf[ir.DFVal]
  type ExtendTag = ExtendTag.type
  case object TruncateTag extends ir.DFTagOf[ir.DFVal]
  type TruncateTag = TruncateTag.type

  @metaContextForward(0)
  trait InitValue[T <: DFTypeAny]:
    def enable: Boolean
    def apply(dfType: T)(using dfc: DFC): DFConstOf[T]
  object InitValue:
    transparent inline implicit def fromValue[T <: DFTypeAny, V](
        inline value: V
    ): InitValue[T] = ${ fromValueMacro[T, V]('value) }

    def fromValueMacro[T <: DFTypeAny, V](
        value: Expr[V]
    )(using Quotes, Type[T], Type[V]): Expr[InitValue[T]] =
      import quotes.reflect.*
      val (argExpr, enableExpr) = value match
        case '{ Conditional.Ops.@@[t]($x)($y) } => (x, y)
        case _                                  => (value, '{ true })
      argExpr.asTerm.getNonConstTerm match
        case Some(term) => term.compiletimeErrorPosExpr("Init value must be a constant.")
        case None =>
          val exactInfo = argExpr.exactInfo
          '{
            val tc = compiletime.summonInline[DFVal.TC[T, exactInfo.Underlying]]
            new InitValue[T]:
              def enable: Boolean = $enableExpr
              def apply(dfType: T)(using dfc: DFC): DFConstOf[T] =
                tc(dfType, ${ exactInfo.exactExpr })(using dfc).asConstOf[T]
          }
    end fromValueMacro
  end InitValue

  @metaContextForward(0)
  trait InitTupleValues[T <: NonEmptyTuple]:
    def enable: Boolean
    def apply(dfType: DFTuple[T])(using dfc: DFC): List[DFConstOf[DFTuple[T]]]
  object InitTupleValues:
    transparent inline implicit def fromValue[T <: NonEmptyTuple, V](
        inline value: V
    ): InitTupleValues[T] = ${ fromValueMacro[T, V]('value) }

    def fromValueMacro[T <: NonEmptyTuple, V](
        value: Expr[V]
    )(using Quotes, Type[T], Type[V]): Expr[InitTupleValues[T]] =
      import quotes.reflect.*
      val (argExpr, enableExpr) = value match
        case '{ Conditional.Ops.@@[t]($x)($y) } => (x, y)
        case _                                  => (value, '{ true })
      val term = argExpr.asTerm.underlyingArgument
      term.getNonConstTerm match
        case Some(term) => term.compiletimeErrorPosExpr("Init value must be a constant.")
        case _ =>
          val tTpe = TypeRepr.of[T]
          extension (lhs: TypeRepr)
            def tupleSigMatch(
                rhs: TypeRepr,
                tupleAndNonTupleMatch: Boolean
            ): Boolean =
              import quotes.reflect.*
              (lhs.asType, rhs.asType) match
                case ('[DFTuple[t]], '[Any]) =>
                  TypeRepr.of[t].tupleSigMatch(rhs, tupleAndNonTupleMatch)
                case ('[Tuple], '[Tuple]) =>
                  val lArgs = lhs.getTupleArgs
                  val rArgs = rhs.getTupleArgs
                  if (lArgs.length != rArgs.length) false
                  else
                    (lArgs lazyZip rArgs).forall((l, r) => l.tupleSigMatch(r, true))
                case ('[Tuple], '[Any]) => tupleAndNonTupleMatch
                case ('[Any], '[Tuple]) => tupleAndNonTupleMatch
                case _                  => true
            end tupleSigMatch
          end extension

          val vTpe = term.tpe
          val multiElements = vTpe.asTypeOf[Any] match
            case '[NonEmptyTuple] =>
              vTpe.getTupleArgs.forall(va => tTpe.tupleSigMatch(va, false))
            case _ => false
          // In the case we have a multiple elements in the tuple value that match the signature
          // of the DFHDL type, then each element is considered as a candidate
          if (multiElements)
            val Apply(_, vArgsTerm) = term: @unchecked
            def inits(dfType: Expr[DFTuple[T]], dfc: Expr[DFC]): List[Expr[DFConstOf[DFTuple[T]]]] =
              vArgsTerm.map { a =>
                val aExactInfo = a.exactInfo
                '{
                  val tc = compiletime.summonInline[DFVal.TC[DFTuple[T], aExactInfo.Underlying]]
                  tc($dfType, ${ aExactInfo.exactExpr })(using $dfc).asConstOf[DFTuple[T]]
                }
              }
            '{
              new InitTupleValues[T]:
                def enable: Boolean = $enableExpr
                def apply(dfType: DFTuple[T])(using dfc: DFC): List[DFConstOf[DFTuple[T]]] =
                  List(${ Expr.ofList(inits('dfType, 'dfc)) }*)
            }
          // otherwise the entire tuple is considered as a candidate.
          else
            val vExactInfo = term.exactInfo
            '{
              val tc = compiletime.summonInline[DFVal.TC[DFTuple[T], vExactInfo.Underlying]]
              new InitTupleValues[T]:
                def enable: Boolean = $enableExpr
                def apply(dfType: DFTuple[T])(using dfc: DFC): List[DFConstOf[DFTuple[T]]] =
                  List(tc(dfType, ${ vExactInfo.exactExpr })(using dfc).asConstOf[DFTuple[T]])
            }
          end if
      end match
    end fromValueMacro
  end InitTupleValues

  extension [T <: DFTypeAny, A, C, I, P, R](dfVal: DFVal[T, Modifier[A, C, I, P]])
    private[dfhdl] def initForced(initValues: List[DFConstOf[T]])(using
        dfc: DFC
    ): DFVal[T, Modifier[A, C, Modifier.Initialized, P]] =
      import dfc.getSet
      require(
        dfVal.asIR.isAnonymous,
        s"Cannot initialize a named value ${dfVal.asIR.getFullName}. Initialization is only supported at the declaration of the value."
      )
      val modifier = Modifier[A, C, I, P](dfVal.asIR.asInstanceOf[ir.DFVal.Dcl].modifier)
      // We do not need to replace the original Dcl, because it was anonymous and not added to the
      // mutable DB. Also see comment in `DFVal.Dcl`.
      DFVal.Dcl(dfVal.dfType, modifier, initValues)
        .asVal[T, Modifier[A, C, Modifier.Initialized, P]]
    end initForced

    infix def init(
        initValues: InitValue[T]*
    )(using DFC, InitCheck[I]): DFVal[T, Modifier[A, C, Modifier.Initialized, P]] = trydf {
      val tvList =
        initValues.view.filter(_.enable).map(tv => tv(dfVal.dfType)(using dfc.anonymize)).toList
      dfVal.initForced(tvList)
    }
  end extension
  extension [T <: NonEmptyTuple, A, C, I, P](dfVal: DFVal[DFTuple[T], Modifier[A, C, I, P]])
    infix def init(
        initValues: InitTupleValues[T]
    )(using DFC, InitCheck[I]): DFVal[DFTuple[T], Modifier[A, C, Modifier.Initialized, P]] =
      trydf {
        if (initValues.enable)
          dfVal.initForced(initValues(dfVal.dfType)(using dfc.anonymize))
        else dfVal.initForced(Nil)
      }

  extension [W <: IntP, D1 <: IntP, A, C, I, P](
      dfVal: DFVal[DFVector[DFBits[W], Tuple1[D1]], Modifier[A, C, I, P]]
  )
    infix def initFile(
        path: String,
        format: ir.InitFileFormat = ir.InitFileFormat.Auto
    )(using
        DFC,
        InitCheck[I]
    ): DFVal[DFVector[DFBits[W], Tuple1[D1]], Modifier[A, C, Modifier.Initialized, P]] = trydf:
      val vectorType = dfVal.dfType
      import DFVector.{lengthInt, cellType}
      val data = ir.InitFileFormat.readInitFile(
        path,
        format,
        vectorType.lengthInt,
        vectorType.cellType.widthInt
      )
      val initFileConst = DFVal.Const(vectorType, data)
      dfVal.initForced(List(initFileConst))
    // TODO: for now, we read the data immediately. In the future, incremental compilation will make
    // it beneficial to wait for the backend last stages to do so.
    // infix def initFile(
    //     path: String,
    //     format: ir.InitFileFormat = ir.InitFileFormat.Auto
    // )(using
    //     DFC,
    //     InitCheck[I]
    // ): DFVal[DFVector[DFBits[W], D], Modifier[A, C, Modifier.Initialized, P]] =
    //   val initFileFunc =
    //     DFVal.Func(dfVal.dfType, DFVal.Func.Op.InitFile(format, path), List.empty[ir.DFVal])(using
    //       dfc.anonymize
    //     ).asConstOf[DFVector[DFBits[W], D]]
    //   dfVal.initForced(List(initFileFunc))
  end extension

  implicit def BooleanHack(from: DFValOf[DFBoolOrBit])(using DFC): Boolean =
    ???

  // opaque values need special conversion that does not try to summon the opaque dftype
  // because it can be abstract in extension methods that are applied generically on an abstract
  // opaque super-type. E.g.:
  // ```
  // abstract class MyAbsOpaque extends Opaque
  // case class MyOpaque extends MyAbsOpaque
  // extension (a : MyAbsOpaque <> VAL) def foo : Unit = {}
  // val a = MyOpaque <> VAR
  // a.foo //here we currently access `foo` through conversion to MyAbsOpaque
  //       //because DFOpaque is not completely covariant due to bug
  //       //https://github.com/lampepfl/dotty/issues/15704
  // ```
  given DFOpaqueValConversion[T <: DFOpaque.Abstract, R <: DFOpaque.Abstract](using
      DFC,
      R <:< T
  ): Conversion[DFValOf[DFOpaque[R]], DFValOf[DFOpaque[T]]] = from =>
    from.asInstanceOf[DFValOf[DFOpaque[T]]]

  object Const:
    def apply[IRT <: ir.DFType, D, T <: DFType[ir.DFType.Aux[IRT, D], ?]](
        dfType: T,
        data: D,
        named: Boolean = false
    )(using
        DFC
    ): DFConstOf[T] = forced(dfType, data, named)
    def forced[T <: DFTypeAny](
        dfType: T,
        data: Any,
        named: Boolean = false
    )(using DFC): DFConstOf[T] =
      val meta = if (named) dfc.getMeta else dfc.getMeta.anonymize
      ir.DFVal
        .Const(dfType.asIR.dropUnreachableRefs, data, dfc.ownerOrEmptyRef, meta, dfc.tags)
        .addMember
        .asConstOf[T]
  end Const

  type OPEN = OPEN.type
  object OPEN:
    protected[dfhdl] def apply[T <: DFTypeAny](dfType: T)(using DFC): DFValOf[T] =
      ir.DFVal.OPEN(dfType.asIR.dropUnreachableRefs, dfc.owner.ref)
        .addMember
        .asValOf[T]

  type NOTHING = NOTHING.type
  object NOTHING:
    protected[dfhdl] def apply[T <: DFTypeAny](dfType: T)(using DFC): DFValOf[T] =
      ir.DFVal.NOTHING(dfType.asIR.dropUnreachableRefs, dfc.owner.ref, dfc.getMeta, dfc.tags)
        .addMember
        .asValOf[T]

  object Dcl:
    def apply[T <: DFTypeAny, M <: ModifierAny](
        dfType: T,
        modifier: M,
        initValues: List[DFConstOf[T]] = Nil
    )(using
        DFC
    ): DFVal[T, M] =
      val modifierIR = modifier.asIR
      val dfTypeIR = dfType.asIR.dropUnreachableRefs
      // Anonymous Dcls are only supposed to be followed by an `init` that will construct the
      // fully initialized Dcl. The reason for this behavior is that we do not want to add the
      // Dcl to the mutable DB and only after add its initialization value to the DB, thereby
      // violating the reference order rule (we can only reference values that appear before).
      if (dfc.isAnonymous && initValues.isEmpty)
        ir.DFVal.Dcl(
          dfTypeIR, modifierIR, Nil, ir.DFRef.OneWay.Empty, dfc.getMeta, dfc.tags
        ).asVal[T, M]
      else
        val dcl: ir.DFVal.Dcl = ir.DFVal.Dcl(
          dfTypeIR,
          modifierIR,
          initValues.map(_.asIR.refTW[ir.DFVal.Dcl]),
          dfc.owner.ref,
          dfc.getMeta,
          dfc.tags
        )
        dcl.addMember.asVal[T, M]
    end apply
  end Dcl

  object Func:
    export ir.DFVal.Func.Op
    def apply[T <: DFTypeAny, P](
        dfType: T,
        op: FuncOp,
        args: List[DFValTP[?, P]]
    )(using DFC): DFValTP[T, P] =
      args.foreach(_.anonymizeInDFCPosition)
      apply(dfType, op, args.map(_.asIR))
    @targetName("applyFromIR")
    def apply[T <: DFTypeAny, P](
        dfType: T,
        op: FuncOp,
        args: List[ir.DFVal]
    )(using DFC): DFValTP[T, P] =
      val func: ir.DFVal = ir.DFVal.Func(
        dfType.asIR.dropUnreachableRefs,
        op,
        args.map(_.refTW[ir.DFVal]),
        dfc.ownerOrEmptyRef,
        dfc.getMeta,
        dfc.tags
      )
      func.addMember.asValTP[T, P]
    end apply
  end Func

  object Alias:
    object AsIs:
      def apply[AT <: DFTypeAny, VT <: DFTypeAny, M <: ModifierAny](
          aliasType: AT,
          relVal: DFVal[VT, M],
          forceNewAlias: Boolean = false
      )(using dfc: DFC): DFVal[AT, M] =
        relVal.asIR match
          // anonymous constant are replaced by a different constant
          // after its data value was converted according to the alias
          case const: ir.DFVal.Const
              if (const.isAnonymous || relVal.inDFCPosition) && !forceNewAlias =>
            val updatedData = ir.dataConversion(aliasType.asIR, const.dfType)(
              const.data.asInstanceOf[const.dfType.Data]
            )(using dfc.getSet)
            dfc.mutableDB.setMember(
              const,
              _.copy(
                dfType = aliasType.asIR.dropUnreachableRefs,
                data = updatedData,
                meta = dfc.getMeta
              )
            ).asVal[AT, M]
          // remove redundant intermediate casting when the final result needs to be `.bits` anyways
          case asIs: ir.DFVal.Alias.AsIs
              if aliasType.asIR.isInstanceOf[ir.DFBits] && asIs.isAnonymous &&
                dfc.isAnonymous && !forceNewAlias && asIs.tags.isEmpty =>
            import dfc.getSet
            asIs.relValRef.get.asVal[AT, M]
          // named constants or other non-constant values are referenced
          // in a new alias construct
          case _ =>
            forced(aliasType.asIR, relVal.anonymizeInDFCPosition.asIR).asVal[AT, M]
      end apply
      def forced(aliasType: ir.DFType, relVal: ir.DFVal)(using DFC): ir.DFVal =
        val alias: ir.DFVal.Alias.AsIs =
          ir.DFVal.Alias.AsIs(
            aliasType.dropUnreachableRefs,
            relVal.refTW[ir.DFVal.Alias.AsIs],
            dfc.ownerOrEmptyRef,
            dfc.getMeta,
            dfc.tags
          )
        alias.addMember
      def ident[T <: DFTypeAny, M <: ModifierAny](relVal: DFVal[T, M])(using
          dfc: DFC
      ): DFVal[T, M] =
        import ir.DFVal.Alias.IdentTag
        apply(relVal.dfType, relVal, forceNewAlias = true)(using dfc.tag(IdentTag))
      def bind[T <: DFTypeAny, M <: ModifierAny](relVal: DFVal[T, M], bindName: String)(using
          dfc: DFC
      ): DFVal[T, M] =
        import ir.DFConditional.DFCaseBlock.Pattern
        ident(relVal)(using dfc.setName(bindName).tag(Pattern.Bind.Tag))
      def designParam[T <: DFTypeAny, M <: ModifierAny](relVal: DFVal[T, M])(using
          DFC
      ): DFVal[T, M] =
        import ir.DFVal.Alias.DesignParamTag
        forced(relVal.dfType.asIR, relVal.asIR)(using dfc.tag(DesignParamTag)).asVal[T, M]
    end AsIs
    object History:
      def apply[T <: DFTypeAny](
          relVal: DFValOf[T],
          step: Int,
          op: HistoryOp,
          initOption: Option[DFConstOf[T]]
      )(using DFC): DFValOf[T] =
        val alias: ir.DFVal.Alias.History =
          ir.DFVal.Alias.History(
            relVal.dfType.asIR.dropUnreachableRefs,
            relVal.asIR.refTW[ir.DFVal.Alias.History],
            step,
            op,
            initOption.map(_.asIR.refTW[ir.DFVal.Alias.History]),
            dfc.owner.ref,
            dfc.getMeta,
            dfc.tags
          )
        alias.addMember.asValOf[T]
      end apply
    end History
    object ApplyRange:
      def apply[W <: IntP, M <: ModifierAny, H <: Int, L <: Int](
          relVal: DFVal[DFBits[W], M],
          relBitHigh: Inlined[H],
          relBitLow: Inlined[L]
      )(using DFC): DFVal[DFBits[H - L + 1], M] =
        forced(relVal.asIR, relBitHigh, relBitLow).asVal[DFBits[H - L + 1], M]
      end apply
      def forced(
          relVal: ir.DFVal,
          relBitHigh: Int,
          relBitLow: Int
      )(using DFC): ir.DFVal =
        relVal match
          // anonymous constant are replace by a different constant
          // after its data value was converted according to the alias
          case const: ir.DFVal.Const if const.isAnonymous =>
            val updatedData =
              ir.selBitRangeData(
                const.data.asInstanceOf[(BitVector, BitVector)],
                relBitHigh,
                relBitLow
              )
            Const.forced(DFBits(relBitHigh - relBitLow + 1), updatedData).asIR
          // named constants or other non-constant values are referenced
          // in a new alias construct
          case _ =>
            val alias: ir.DFVal.Alias.ApplyRange =
              ir.DFVal.Alias.ApplyRange(
                relVal.refTW[ir.DFVal.Alias.ApplyRange],
                relBitHigh,
                relBitLow,
                dfc.ownerOrEmptyRef,
                dfc.getMeta,
                dfc.tags
              )
            alias.addMember
      end forced
    end ApplyRange
    object ApplyIdx:
      def apply[
          T <: DFTypeAny,
          W <: IntP,
          M <: ModifierAny
      ](
          dfType: T,
          relVal: DFVal[DFTypeAny, M],
          relIdx: DFValOf[DFInt32]
      )(using DFC): DFVal[T, M] =
        val alias: ir.DFVal.Alias.ApplyIdx =
          ir.DFVal.Alias.ApplyIdx(
            dfType.asIR.dropUnreachableRefs,
            relVal.asIR.refTW[ir.DFVal.Alias.ApplyIdx],
            relIdx.asIR.refTW[ir.DFVal.Alias.ApplyIdx],
            dfc.ownerOrEmptyRef,
            dfc.getMeta,
            dfc.tags
          )
        alias.addMember.asVal[T, M]
      end apply
    end ApplyIdx
    object SelectField:
      def apply[T <: DFTypeAny, M <: ModifierAny](
          relVal: DFVal[DFTypeAny, M],
          fieldName: String
      )(using dfc: DFC): DFVal[T, M] =
        val relValIR = relVal.asIR
        val dfStructType = relValIR.dfType.asInstanceOf[ir.DFStruct]
        relValIR match
          // in case the referenced value is anonymous and concatenates fields
          // of values, then we just directly reference the relevant
          // value.
          case ir.DFVal.Func(_, FuncOp.++, args, _, meta, _) if meta.isAnonymous =>
            import dfc.getSet
            args(dfStructType.fieldIndex(fieldName)).get.asVal[T, M]
          // for all other case create a selector
          case _ =>
            val dfTypeIR = dfStructType.fieldMap(fieldName).dropUnreachableRefs
            val alias: ir.DFVal.Alias.SelectField =
              ir.DFVal.Alias.SelectField(
                dfTypeIR,
                relValIR.refTW[ir.DFVal.Alias.SelectField],
                fieldName,
                dfc.owner.ref,
                dfc.getMeta,
                dfc.tags
              )
            alias.addMember.asVal[T, M]
        end match
      end apply
    end SelectField
  end Alias

  trait TC[T <: DFTypeAny, R] extends TCConv[T, R, DFValAny]:
    type OutP
    type Out = DFValTP[T, OutP]
    final def apply(dfType: T, value: R)(using DFC): Out = trydf:
      conv(dfType, value)

  trait TCLPLP:
    // Reject OPEN with a dedicated message
    transparent inline given fromOPEN[T <: DFTypeAny]: TC[T, OPEN] =
      compiletime.error("`OPEN` cannot be used here.")

  trait TCLP extends TCLPLP:
    // Accept any bubble value
    given fromBubble[T <: DFTypeAny, V <: Bubble]: TC[T, V] with
      type OutP = CONST
      def conv(dfType: T, value: V)(using DFC): Out = Bubble.constValOf(dfType, named = true)
    // Accept NOTHING for any DFType, unless not in DF domain, and then we limit it to Bits or Bit type
    given fromNOTHING[T <: DFTypeAny](using dt: DomainType)(using
        AssertGiven[
          dt.type <:< DomainType.DF | T <:< DFBit | T <:< DFType[ir.DFBits, Args],
          "`NOTHING` can only be assigned to either `Bits` or `Bit` DFHDL values outside of a dataflow (DF) domain."
        ]
    ): TC[T, NOTHING] with
      type OutP = NOTCONST
      def conv(dfType: T, value: NOTHING)(using DFC): Out = NOTHING(dfType)
    transparent inline given errorDMZ[T <: DFTypeAny, R](using
        t: ShowType[T],
        r: ShowType[R]
    ): TC[T, R] =
      Error.call[
        (
            "Unsupported value of type `",
            r.Out,
            "` for DFHDL receiver type `",
            t.Out,
            "`."
        )
      ]
    given sameValType[T <: DFTypeAny, P, V <: DFValTP[T, P]]: TC[T, V] with
      type OutP = P
      def conv(dfType: T, value: V)(using dfc: DFC): DFValTP[T, P] =
        import dfc.getSet
        given Printer = DefaultPrinter
        val ret: DFValAny =
          if (dfType != value.dfType)
            (dfType.asIR, value.dfType.asIR) match
              case (_: ir.DFBits, _: ir.DFBits) =>
                DFBits.Val.TC(dfType.asFE[DFBits[Int]], value.asValOf[DFBits[Int]])
              case (ir.DFXInt(_, _, _), ir.DFXInt(_, _, _)) =>
                DFXInt.Val.TC(
                  dfType.asFE[DFXInt[Boolean, Int, NativeType]],
                  value.asValOf[DFXInt[Boolean, Int, NativeType]]
                )
              case _ =>
                throw new IllegalArgumentException(
                  s"Unsupported value of type `${value.dfType.codeString}` for DFHDL receiver type `${dfType.codeString}`."
                )
          else value
        ret.asValTP[T, P]
      end conv
    end sameValType
  end TCLP
  object TC extends TCLP:
    type Exact[T <: DFTypeAny] = Exact1[DFTypeAny, T, [t <: DFTypeAny] =>> t, DFC, TC]
    type Aux[T <: DFTypeAny, R, OutP0] = TC[T, R] { type OutP = OutP0 }
    export DFBoolOrBit.Val.TC.given
    export DFBits.Val.TC.given
    export DFDecimal.Val.TC.given
    export DFEnum.Val.TC.given
    export DFVector.Val.TC.given
    export DFTuple.Val.TC.given
    export DFStruct.Val.TC.given
    export DFOpaque.Val.TC.given
  end TC

  trait TC_Or_OPEN[T <: DFTypeAny, R] extends TC[T, R]
  object TC_Or_OPEN:
    type Exact[T <: DFTypeAny] = Exact1[DFTypeAny, T, [t <: DFTypeAny] =>> t, DFC, TC_Or_OPEN]
    given fromOPEN[T <: DFTypeAny]: TC_Or_OPEN[T, OPEN] with
      type OutP = NOTCONST
      def conv(dfType: T, from: OPEN)(using DFC): Out = DFVal.OPEN(dfType)
    given fromTC[T <: DFTypeAny, R, TC <: DFVal.TC[T, R]](using tc: TC): TC_Or_OPEN[T, R] with
      type OutP = tc.OutP
      def conv(dfType: T, from: R)(using DFC): Out = tc(dfType, from)

  trait Compare[T <: DFTypeAny, V, Op <: FuncOp, C <: Boolean] extends TCConv[T, V, DFValAny]:
    type OutP
    type Out = DFValTP[T, OutP]
    final protected def func[P1, P2](arg1: DFValTP[?, P1], arg2: DFValTP[?, P2])(using
        DFC,
        ValueOf[Op],
        ValueOf[C]
    ): DFValTP[DFBool, P1 | P2] =
      val list = if (valueOf[C]) List(arg2, arg1) else List(arg1, arg2)
      DFVal.Func(DFBool, valueOf[Op], list)
    def apply[P](dfVal: DFValTP[T, P], arg: V)(using
        DFC,
        ValueOf[Op],
        ValueOf[C]
    ): DFValTP[DFBool, P | OutP] = trydf:
      val dfValArg = conv(dfVal.dfType, arg)(using dfc.anonymize)
      func(dfVal, dfValArg)
  end Compare
  trait CompareLP:
    transparent inline given errorDMZ[
        T <: DFTypeAny,
        R,
        Op <: FuncOp,
        C <: Boolean
    ](using
        t: ShowType[T],
        r: ShowType[R]
    ): Compare[T, R, Op, C] =
      Error.call[
        (
            "Cannot compare DFHDL value of type `",
            t.Out,
            "` with value of type `",
            r.Out,
            "`."
        )
      ]
    given sameValType[T <: DFTypeAny, P, R <: DFValTP[T, P], Op <: FuncOp, C <: Boolean](using
        ValueOf[Op],
        ValueOf[C]
    ): Compare[T, R, Op, C] with
      type OutP = P
      def conv(dfType: T, arg: R)(using dfc: DFC): Out =
        import dfc.getSet
        given Printer = DefaultPrinter
        require(
          dfType == arg.dfType,
          s"Cannot compare DFHDL value type `${dfType.codeString}` with DFHDL value type `${arg.dfType.codeString}`."
        )
        arg
  end CompareLP
  object Compare extends CompareLP:
    export DFBoolOrBit.Val.Compare.given
    export DFBits.Val.Compare.given
    export DFDecimal.Val.Compare.given
    export DFEnum.Val.Compare.given
    export DFVector.Val.Compare.given
    export DFTuple.Val.Compare.given
    export DFStruct.Val.Compare.given
  end Compare

  trait DFDomainOnly
  given (using domain: DomainType)(using
      AssertGiven[
        domain.type <:< DomainType.DF,
        "This construct is only available in a dataflow domain."
      ]
  ): DFDomainOnly with {}
  trait RTDomainOnly
  given (using domain: DomainType)(using
      AssertGiven[
        domain.type <:< DomainType.RT,
        "This construct is only available in a register-transfer domain."
      ]
  ): RTDomainOnly with {}
  trait PrevInitCheck[I]
  given [I](using
      AssertGiven[
        I =:= Modifier.Initialized,
        "Value must be an initialized declaration or `.prev` must have an initialization argument.\nE.g.: `x.prev(step, init)`.\nIt's possible to apply a bubble initialization with `init = ?`"
      ]
  ): PrevInitCheck[I] with {}
  trait RegInitCheck[I]
  given [I](using
      AssertGiven[
        I =:= Modifier.Initialized,
        "Value must be an initialized declaration or `.reg` must have an initialization argument.\nE.g.: `x.reg(step, init)`.\nIt's possible to apply an unknown initialization with `init = ?`"
      ]
  ): RegInitCheck[I] with {}
  // exporting apply here to reduce common case of possible ambiguities when additional apply methods are defined
  export DFBits.Val.Ops.apply
  export DFVector.Val.Ops.apply
  export DFTuple.Val.Ops.apply
  object Ops:
    extension [T <: DFTypeAny, A, C, I, S <: Int, V](dfVal: DFVal[T, Modifier[A, C, I, Any]])
      def prev(step: Inlined[S], init: InitValue[T])(using
          dfc: DFC,
          dfOnly: DFDomainOnly,
          check: Arg.Positive.Check[S]
      ): DFValOf[T] = trydf {
        check(step)
        val initOpt = Some(init(dfVal.dfType)(using dfc.anonymize))
        DFVal.Alias.History(dfVal, step, HistoryOp.State, initOpt)
      }
      def prev(step: Inlined[S])(using
          dfc: DFC,
          dfOnly: DFDomainOnly,
          initCheck: PrevInitCheck[I],
          check: Arg.Positive.Check[S]
      ): DFValOf[T] = trydf {
        check(step)
        DFVal.Alias.History(dfVal, step, HistoryOp.State, None)
      }
      inline def prev(using DFDomainOnly, PrevInitCheck[I], DFC): DFValOf[T] = dfVal.prev(1)
      def pipe(
          step: Inlined[S]
      )(using dfOnly: DFDomainOnly, dfc: DFC, check: Arg.Positive.Check[S]): DFValOf[T] = trydf {
        check(step)
        DFVal.Alias.History(
          dfVal,
          step,
          HistoryOp.Pipe,
          // pipe always has a bubble for initialization
          Some(Bubble.constValOf(dfVal.dfType, named = false))
        )
      }
      inline def pipe(using DFC, DFDomainOnly): DFValOf[T] = dfVal.pipe(1)
      def reg(step: Inlined[S])(using
          dfc: DFC,
          rtOnly: RTDomainOnly,
          initCheck: RegInitCheck[I],
          check: Arg.Positive.Check[S]
      ): DFValOf[T] = trydf {
        check(step)
        DFVal.Alias.History(dfVal, step, HistoryOp.State, None)
      }
      def reg(step: Inlined[S], init: InitValue[T])(using
          dfc: DFC,
          rtOnly: RTDomainOnly,
          check: Arg.Positive.Check[S]
      ): DFValOf[T] = trydf {
        check(step)
        val initOpt = Some(init(dfVal.dfType)(using dfc.anonymize))
        DFVal.Alias.History(dfVal, step, HistoryOp.State, initOpt)
      }
      inline def reg(using DFC, RTDomainOnly, RegInitCheck[I]): DFValOf[T] = dfVal.reg(1)
      def width(using DFC): DFConstInt32 = dfVal.widthIntParam.toDFConst
      def widthInt(using DFC): Int = dfVal.widthIntParam.toScalaInt
    end extension

    extension [T <: DFTypeAny, A, C, I, P](dfVal: DFVal[T, Modifier[A, C, I, P]])
      def bits(using w: Width[T])(using DFC): DFValTP[DFBits[w.Out], P] = trydf {
        DFVal.Alias.AsIs(DFBits(dfVal.widthIntParam), dfVal)
      }
      def genNewVar(using DFC): DFVarOf[T] = trydf {
        val meta = dfVal.asIR.meta.copy(nameOpt = dfc.nameOpt, docOpt = None)
        DFVal.Dcl(dfVal.dfType, Modifier.VAR)(using dfc.setMeta(meta))
      }
    end extension
  end Ops
end DFVal

extension [T <: DFTypeAny](dfVar: DFValOf[T])
  def assign[R <: DFTypeAny](rhs: DFValOf[R])(using DFC): Unit =
    DFNet(dfVar.asIR, DFNet.Op.Assignment, rhs.asIR)
  def nbassign[R <: DFTypeAny](rhs: DFValOf[R])(using DFC): Unit =
    DFNet(dfVar.asIR, DFNet.Op.NBAssignment, rhs.asIR)

extension [T <: DFTypeAny](lhs: DFValOf[T])
  def connect[R <: DFTypeAny](rhs: DFValOf[R])(using DFC): Unit =
    val op = if (dfc.lateConstruction) DFNet.Op.ViaConnection else DFNet.Op.Connection
    DFNet(lhs.asIR, op, rhs.asIR)

trait VarsTuple[T <: NonEmptyTuple]:
  type Width <: Int
object VarsTuple:
  transparent inline given [T <: NonEmptyTuple]: VarsTuple[T] = ${ evMacro[T] }
  def evMacro[T <: NonEmptyTuple](using Quotes, Type[T]): Expr[VarsTuple[T]] =
    import quotes.reflect.*
    val tTpe = TypeRepr.of[T]
    def varsCheck(tpe: TypeRepr): Option[String] =
      tpe.asTypeOf[Any] match
        case '[DFVarOf[t]] => None
        case '[NonEmptyTuple] =>
          tpe.getTupleArgs.view.map(varsCheck).collectFirst { case Some(v) => v }
        case _ =>
          println(tpe.widen.dealias.show)
          Some(s"All tuple elements must be mutable but found an immutable type `${tpe.showType}`")
    varsCheck(tTpe) match
      case Some(err) => '{ compiletime.error(${ Expr(err) }) }
      case None =>
        import Width.calcValWidth
        val widthType = tTpe.calcValWidth.asTypeOf[Int]
        '{
          new VarsTuple[T]:
            type Width = widthType.Underlying
        }
  end evMacro
end VarsTuple

final class REG_DIN[T <: DFTypeAny](val irValue: DFError.REG_DIN[T]) extends AnyVal:
  def :=(rhs: DFVal.TC.Exact[T])(using DFC): Unit = trydf {
    val dfVar = irValue.dfVar
    dfVar.assign(rhs(dfVar.dfType))
  }

object DFVarOps:
  protected type NotREG[A] = AssertGiven[
    util.NotGiven[A <:< Modifier.AssignableREG],
    "Cannot assign to a register output; it is immutable.\nTo assign to the register's input, apply `.din` on the LHS argument of the assignment."
  ]
  protected type IsREG[A] = AssertGiven[
    A <:< Modifier.AssignableREG,
    "This is not an assignable register."
  ]
  protected type VarOnly[A] = AssertGiven[
    A <:< Modifier.Assignable,
    "Cannot assign to an immutable value."
  ]
  protected type LocalOrNonED[A] = AssertGiven[
    (A <:< DFC.Scope.Process) | util.NotGiven[A <:< DomainType.ED],
    "Blocking assignments `:=` are not allowed for a non-local variable in this domain.\nChange the assignment to a non-blocking assignment `:==` or the position of the defined variable."
  ]
  protected type NotLocalVar[A] = AssertGiven[
    util.NotGiven[A <:< DFC.Scope.Process],
    "Non-blocking assignments `:==` are not allowed for a local variable (defined inside the process block).\nChange the assignment to a blocking assignment `:=` or the position of the defined variable."
  ]
  protected type EDDomainOnly[A] = AssertGiven[
    A <:< DomainType.ED,
    "Non-blocking assignments `:==` are allowed only inside an event-driven (ED) domain.\nChange the assignment to a regular assignment `:=` or the logic domain to ED."
  ]
  protected type `InsideProcess:=`[D, A] = AssertGiven[
    DFC.Scope.Process | util.NotGiven[A <:< DomainType.ED] | D <:< DomainType.RT,
    "Blocking assignments `:=` are only allowed inside a process under an event-driven (ED) domain.\nChange the assignment to a connection `<>` or place it in a process."
  ]
  protected type `InsideProcess:==`[D, A] = AssertGiven[
    DFC.Scope.Process | util.NotGiven[A <:< DomainType.ED],
    "Non-blocking assignments `:==` are only allowed inside a process under an event-driven (ED) domain.\nChange the assignment to a connection `<>` or place it in a process."
  ]
  protected type RTDomainOnly[A] = AssertGiven[
    A <:< DomainType.RT,
    "`.din` selection is only allowed under register-transfer (RT) domains."
  ]
  extension [T <: DFTypeAny, A](dfVar: DFVal[T, Modifier[A, Any, Any, Any]])
    def :=(rhs: DFVal.TC.Exact[T])(using DFC)(using dt: DomainType)(using
        notREG: NotREG[A],
        varOnly: VarOnly[A],
//        localOrNonED: LocalOrNonED[A],
        insideProcess: `InsideProcess:=`[dt.type, A]
    ): Unit = trydf {
      dfVar.assign(rhs(dfVar.dfType))
    }
    def :==(rhs: DFVal.TC.Exact[T])(using DFC)(using dt: DomainType)(using
        varOnly: VarOnly[A],
        edDomainOnly: EDDomainOnly[dt.type],
//        notLocalVar: NotLocalVar[A],
        insideProcess: `InsideProcess:==`[dt.type, A]
    ): Unit = trydf {
      dfVar.nbassign(rhs(dfVar.dfType))
    }
    def din(using dt: DomainType)(using IsREG[A], RTDomainOnly[dt.type], DFC): REG_DIN[T] =
      new REG_DIN[T](DFError.REG_DIN(dfVar.asVarOf[T]))
  end extension
  extension [T <: NonEmptyTuple](dfVarTuple: T)
    def :=[R](rhs: Exact[R])(using
        vt: VarsTuple[T]
    )(using tc: DFVal.TC[DFBits[vt.Width], R], dfc: DFC): Unit = trydf:
      import dfc.getSet
      given dfcAnon: DFC = dfc.anonymize
      def flattenDFValTuple(tpl: Tuple): List[ir.DFVal] =
        tpl.toList.flatMap {
          case dfVal: DFValAny => Some(dfVal.asIR)
          case tpl: Tuple      => flattenDFValTuple(tpl)
        }
      def flattenConcatArgs(arg: ir.DFVal): List[ir.DFVal] =
        import dfc.getSet
        arg match
          case func: ir.DFVal.Func if func.op == FuncOp.++ && func.isAnonymous =>
            func.args.flatMap(ar => flattenConcatArgs(ar.get))
          case _ => List(arg)
      @tailrec def assignRecur(
          // the remaining variables to assign to from most-significant to least
          dfVars: List[ir.DFVal],
          // the remaining arguments to read for assignment from most-significant to least
          args: List[ir.DFVal],
          // the most-significant bits width read so far from the left-most argument
          argReadWidth: Int,
          // the current accumulated concatenated arguments in reverse order
          concat: List[ir.DFVal]
      ): Unit =
        dfVars match
          case dfVar :: nextVars =>
            val concatWidth = concat.map(_.width).sum
            val missingConcatWidth = dfVar.width - concatWidth
            assert(missingConcatWidth >= 0)
            // widths match so we can assign
            if (missingConcatWidth == 0)
              // only one element in concatenation, so there is no need for concatenation
              // and we assign it directly.
              val concatVal =
                if (concat.size == 1) concat.head.asValAny
                else
                  DFVal.Func(DFBits(dfVar.dfType.width), FuncOp.++, concat.reverse)
              // non-bits variables need to be casted to
              val assignVal = dfVar.dfType match
                // no need to cast
                case _: ir.DFBits => concatVal
                // casting required
                case dfType => DFVal.Alias.AsIs.forced(dfType, concatVal.asIR).asValAny
              dfVar.asValAny.assign(assignVal)
              assignRecur(nextVars, args, argReadWidth, Nil)
            // missing more bits to complete assignment, so moving arg element into concat list
            else
              val arg = args.head
              val argLeftoverWidth = arg.width - argReadWidth
              val extraWidth = argLeftoverWidth - missingConcatWidth
              if (extraWidth <= 0)
                val concatArg =
                  // the entire arg should be used
                  if (argReadWidth == 0) arg
                  // partially reading the leftover arg
                  else DFVal.Alias.ApplyRange.forced(arg, argLeftoverWidth - 1, 0)
                // moving the head argument and stacking it in `concat`.
                // we use the concat in reverse later on.
                assignRecur(dfVars, args.drop(1), 0, concatArg :: concat)
              // with the current arg, there will be too many bits are in concat, so we need to
              // split the head and move the extra bits to the args list
              else
                val concatArg = DFVal.Alias.ApplyRange.forced(
                  arg,
                  argLeftoverWidth - 1,
                  extraWidth
                )
                // the args order are from msbits to lsbits, so when we end up with extra bits
                // those will be the lsbits that to be given back to the args list and just the
                // remaining msbits are placed in the concat list
                assignRecur(dfVars, args, argReadWidth + concatArg.width, concatArg :: concat)
              end if
            end if
          case Nil => // done!
      val dfVarsIR = flattenDFValTuple(dfVarTuple)
      val width =
        dfVarsIR.map(_.asValAny.widthIntParam).reduce(_ + _).asInstanceOf[IntParam[vt.Width]]
      val argsIR = flattenConcatArgs(tc(DFBits(width), rhs).asIR)
      val argsBitsIR = argsIR.map { arg =>
        arg.dfType match
          case _: ir.DFBits => arg
          case dfType       => DFVal.Alias.AsIs.forced(ir.DFBits(dfType.width), arg)
      }
      assignRecur(dfVarsIR, argsBitsIR, 0, Nil)
end DFVarOps

object DFPortOps:
  protected type ConnectableOnly[C] = AssertGiven[
    C <:< Modifier.Connectable,
    "The LHS of a connection must be a connectable DFHDL value (var/port)."
  ]
  extension [T <: DFTypeAny, C](dfPort: DFVal[T, Modifier[Any, C, Any, Any]])
    def <>(rhs: DFVal.TC_Or_OPEN.Exact[T])(using DFC)(using
        connectableOnly: ConnectableOnly[C]
    ): ConnectPlaceholder =
      trydf { dfPort.connect(rhs(dfPort.dfType)) }
      ConnectPlaceholder
  end extension
end DFPortOps

extension (dfVal: ir.DFVal)
  protected[dfhdl] def cloneAnonValueAndDepsHere(using dfc: DFC): ir.DFVal =
    import dfc.getSet
    if (dfVal.isAnonymous)
      val dfcForClone = dfc.setMeta(dfVal.meta).setTags(dfVal.tags)
      val dfType = dfVal.dfType.asFE[DFTypeAny]
      val cloned = dfVal match
        case const: ir.DFVal.Const =>
          DFVal.Const.forced(dfType, const.data)(using dfcForClone)
        case func: ir.DFVal.Func =>
          val clonedArgs = func.args.map(_.get.cloneAnonValueAndDepsHere)
          DFVal.Func(dfType, func.op, clonedArgs)(using dfcForClone)
        case alias: ir.DFVal.Alias.Partial =>
          val clonedRelValIR = alias.relValRef.get.cloneAnonValueAndDepsHere
          val clonedRelVal = clonedRelValIR.asValAny
          alias match
            case alias: ir.DFVal.Alias.AsIs =>
              DFVal.Alias.AsIs(dfType, clonedRelVal, forceNewAlias = true)(using dfcForClone)
            case alias: ir.DFVal.Alias.ApplyRange =>
              DFVal.Alias.ApplyRange(
                clonedRelVal.asValOf[DFBits[Int]],
                alias.relBitHigh,
                alias.relBitLow
              )(using dfcForClone)
            case alias: ir.DFVal.Alias.ApplyIdx =>
              val clonedIdx = alias.relIdx.get.cloneAnonValueAndDepsHere.asValOf[DFInt32]
              DFVal.Alias.ApplyIdx(dfType, clonedRelVal, clonedIdx)(using dfcForClone)
            case alias: ir.DFVal.Alias.SelectField =>
              DFVal.Alias.SelectField(clonedRelVal, alias.fieldName)(using dfcForClone)
        case _ => throw new IllegalArgumentException(s"Unsupported cloning for: $dfVal")
      cloned.asIR
    else dfVal
    end if




© 2015 - 2024 Weber Informatics LLC | Privacy Policy