diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala
index f17073d71..b6b57c036 100644
--- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala
+++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala
@@ -308,7 +308,7 @@ final case class DB(
}
// 2. Build port copies (reusing origToDupMap from step 1)
val pbnsTypes = pbnsByDesign.getOrElse(dupDesign, Map.empty)
- portEntries += dupDesign -> ListMap.from(origPortMap(origDesign).view.map { (name, dcl) =>
+ portEntries += dupDesign -> ListMap.from(origPortMap.getOrElse(origDesign, ListMap.empty).view.map { (name, dcl) =>
val dfType = pbnsTypes.getOrElse(name, dcl.dfType)
val dupOwnerDomain = origToDupMap(dcl.getOwnerDomain)
name -> dcl.copy(ownerRef = DFRef.DuplicationRef(dupOwnerDomain), dfType = dfType)
diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala
index 23f1699ec..47cf626a9 100644
--- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala
+++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala
@@ -20,6 +20,7 @@ case object BackendPrepStage
ConnectUnused,
VHDLProcToVerilog,
ExplicitNamedVars,
+ ExplicitCondExprAssign,
DropLocalDcls,
DropOutportRead,
GlobalizePortVectorParams,
diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitCondExprAssign.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitCondExprAssign.scala
index bbc5dbd54..39a476a6f 100644
--- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitCondExprAssign.scala
+++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitCondExprAssign.scala
@@ -5,10 +5,44 @@ import dfhdl.compiler.ir.*
import dfhdl.compiler.patching.*
import dfhdl.internals.*
import dfhdl.options.CompilerOptions
+import dfhdl.core.DomainType.ED
-/* This stage transforms an assignment from a conditional expression to a statement.*/
+//format: off
+/** This stage transforms an assignment from a conditional expression to a statement.
+ *
+ * ==Rule 1: Conditional expression to statement==
+ *
+ * Converts assignments from conditional expressions into conditional statements.
+ * {{{
+ * // Before
+ * val z = SInt(16) <> VAR
+ * z := (if (x > 0) 5 else x)
+ *
+ * // After
+ * val z = SInt(16) <> VAR
+ * if (x > sd"16'0") z := sd"16'5"
+ * else z := x
+ * }}}
+ *
+ * ==Rule 2: ED domain process wrapping==
+ *
+ * When the conditional statement is in an ED domain and outside a process,
+ * it is wrapped in a `process(all)` block.
+ * {{{
+ * // Before (EDDesign, outside process)
+ * val y = SInt(16) <> OUT
+ * y <>(if (x > sd"16'0") sd"16'5" else x)
+ *
+ * // After
+ * val y = SInt(16) <> OUT
+ * process(all):
+ * if (x > sd"16'0") y := sd"16'5"
+ * else y := x
+ * }}}
+ */
+//format: on
case object ExplicitCondExprAssign extends Stage:
- def dependencies: List[Stage] = List()
+ def dependencies: List[Stage] = List(ExplicitNamedVars)
def nullifies: Set[Stage] = Set(DropUnreferencedAnons)
def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB =
@@ -53,6 +87,7 @@ case object ExplicitCondExprAssign extends Stage:
val removeNetPatch = net -> Patch.Remove()
removeNetPatch :: ch.patchChains(headerVar, op)
end extension
+ // Phase 1: transform conditional expressions to statements
val patchList = designDB.members.view
// collect all the assignments from anonymous conditionals
.flatMap {
@@ -63,7 +98,32 @@ case object ExplicitCondExprAssign extends Stage:
header.patchChainsNet(toVal, net, DFNet.Op.Assignment)
case _ => Nil
}.toList
- designDB.patch(patchList)
+ val phase1DB = designDB.patch(patchList)
+ // Phase 2: wrap ED-domain non-process conditional statements in process(all)
+ locally {
+ given MemberGetSet = phase1DB.getSet
+ val wrapPatches: List[(DFMember, Patch)] = phase1DB.members.view.flatMap {
+ case ch: DFConditional.Header
+ if ch.dfType == DFUnit && ch.isInEDDomain && !ch.isInProcess
+ && !ch.getOwnerBlock.isInstanceOf[DFConditional.Block] =>
+ val chain = phase1DB.conditionalChainTable(ch)
+ val chainBlocksAndMembers: List[DFMember] =
+ chain.flatMap(cb => cb :: cb.members(MemberView.Flattened))
+ val allToWrap: List[DFMember] = ch :: chainBlocksAndMembers
+ val owner = ch.getOwnerBlock
+ val wrapDsn = new MetaDesign(
+ ch,
+ Patch.Add.Config.Before,
+ domainType = ED
+ ):
+ process(all):
+ plantMembers(owner, allToWrap)
+ val removals = allToWrap.map(_ -> Patch.Remove(isMoved = true))
+ wrapDsn.patch :: removals
+ case _ => None
+ }.toList
+ phase1DB.patch(wrapPatches)
+ }
end transform
end ExplicitCondExprAssign
diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala
index 2b109d65f..dccd5bd58 100644
--- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala
+++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala
@@ -177,16 +177,25 @@ case object NamedAnonMultiref extends NamedAliases, NoCheckStage:
else Nil
//Names anonymous conditional expressions, as long as they are not referenced by an ident which indicates that
-//they are themselves inside another conditional expression
+//they are themselves inside another conditional expression, and as long as they are not directly assigned to
+//a declaration or connected to an output port
case object NamedAnonCondExpr extends NamedAliases:
- override def dependencies: List[Stage] = List(ExplicitCondExprAssign)
+ override def dependencies: List[Stage] = List()
def criteria(dfVal: DFVal)(using MemberGetSet, CompilerOptions): List[DFVal] = dfVal match
case dfVal: DFConditional.Header if dfVal.isAnonymous && dfVal.dfType != DFUnit =>
- val isReferencedByIdent =
- dfVal.getReadDeps.collectFirst { case Ident(_) => true }.getOrElse(false)
- if (isReferencedByIdent) Nil
- else List(dfVal)
+ val nameIt =
+ dfVal.getReadDeps.collectFirst {
+ // directly assigned to a declaration (variable or output port)
+ case DFNet.Assignment(toVal = _: DFVal.Dcl) => false
+ // directly connected to an output port
+ case DFNet.Connection(toVal = DclOut()) => false
+ // is referenced by an ident, which means it is used in another conditional expression
+ case Ident(_) => false
+ }.getOrElse(true)
+ if (nameIt) List(dfVal)
+ else Nil
case dfVal => Nil
+end NamedAnonCondExpr
extension [T: HasDB](t: T)
def namedAnonMultiref(using CompilerOptions): DB =
diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala
index e18df5e26..2fa56ef63 100644
--- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala
+++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala
@@ -11,8 +11,8 @@ import scala.annotation.tailrec
import scala.collection.mutable
case object ToED extends Stage:
def dependencies: List[Stage] =
- List(DropUnreferencedAnons, ToRT, DropRTProcess, NameRegAliases, ExplicitNamedVars, AddClkRst,
- SimpleOrderMembers)
+ List(DropUnreferencedAnons, ToRT, DropRTProcess, NameRegAliases, ExplicitNamedVars,
+ ExplicitCondExprAssign, AddClkRst, SimpleOrderMembers)
def nullifies: Set[Stage] = Set(DropUnreferencedAnons)
def transform(designDB: DB)(using getSet: MemberGetSet, co: CompilerOptions): DB =
given RefGen = RefGen.fromGetSet
diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala
index 66b31d69f..b95608717 100644
--- a/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala
+++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala
@@ -55,7 +55,8 @@ class ExplicitCondExprAssignSpec extends StageSpec(stageCreatesUnrefAnons = true
val z2 = SInt(16) <> VAR
z2 := z match
case 1 | 2 =>
- val zz: SInt[4] <> VAL = z match
+ val zz = SInt(4) <> VAR
+ zz := z match
case 1 => 5
case 2 => 3
if (x < 11) zz + 3
@@ -79,11 +80,11 @@ class ExplicitCondExprAssignSpec extends StageSpec(stageCreatesUnrefAnons = true
| val z2 = SInt(16) <> VAR
| z match
| case sd"16'1" | sd"16'2" =>
- | val zz: SInt[4] <> VAL =
- | z match
- | case sd"16'1" => sd"4'5"
- | case sd"16'2" => sd"4'3"
- | end match
+ | val zz = SInt(4) <> VAR
+ | z match
+ | case sd"16'1" => zz := sd"4'5"
+ | case sd"16'2" => zz := sd"4'3"
+ | end match
| if (x < sd"16'11") z2 := (zz +^ sd"4'3").resize(16)
| else z2 := zz.resize(16)
| case _ => z2 := z + sd"16'12"
@@ -96,19 +97,22 @@ class ExplicitCondExprAssignSpec extends StageSpec(stageCreatesUnrefAnons = true
test("AES xtime example") {
class xtime extends DFDesign:
val lhs = Bits(8) <> IN
- val shifted = lhs << 1
- val o = Bits(8) <> OUT
- o <> ((
- if (lhs(7)) shifted ^ h"1b"
- else shifted
- ): Bits[8] <> VAL)
+ val shifted = Bits(8) <> VAR
+ shifted := lhs << 1
+ val o = Bits(8) <> OUT
+ o <>
+ ((
+ if (lhs(7)) shifted ^ h"1b"
+ else shifted
+ ): Bits[8] <> VAL)
end xtime
val id = (new xtime).explicitCondExprAssign
assertCodeString(
id,
"""|class xtime extends DFDesign:
| val lhs = Bits(8) <> IN
- | val shifted = lhs << 1
+ | val shifted = Bits(8) <> VAR
+ | shifted := lhs << 1
| val o = Bits(8) <> OUT
| if (lhs(7)) o := shifted ^ h"1b"
| else o := shifted
@@ -150,4 +154,68 @@ class ExplicitCondExprAssignSpec extends StageSpec(stageCreatesUnrefAnons = true
|end LRShiftFlat""".stripMargin
)
}
+ test("ED domain conditional expression connection outside process") {
+ class Top extends EDDesign:
+ val x = Bits(8) <> IN
+ val y = Bits(8) <> OUT
+ y <>
+ ((
+ if (x(7)) x ^ h"1b"
+ else x
+ ): Bits[8] <> VAL)
+ end Top
+ val result = (new Top).explicitCondExprAssign
+ assertCodeString(
+ result,
+ """|class Top extends EDDesign:
+ | val x = Bits(8) <> IN
+ | val y = Bits(8) <> OUT
+ | process(all):
+ | if (x(7)) y := x ^ h"1b"
+ | else y := x
+ |end Top
+ |""".stripMargin
+ )
+ }
+
+ test("ED domain conditional expression connection to child input port") {
+ class Child extends EDDesign:
+ val x = Bits(8) <> IN
+ val y = Bits(8) <> OUT
+ y <> x
+ class Top extends EDDesign:
+ val a = Bits(8) <> IN
+ val b = Bits(8) <> OUT
+ val child_inst = Child()
+ child_inst.x <>
+ ((
+ if (a(7)) a ^ h"1b"
+ else a
+ ): Bits[8] <> VAL)
+ b <> child_inst.y
+ end Top
+ val result = (new Top).explicitCondExprAssign
+ assertCodeString(
+ result,
+ """|class Child extends EDDesign:
+ | val x = Bits(8) <> IN
+ | val y = Bits(8) <> OUT
+ | y <> x
+ |end Child
+ |
+ |class Top extends EDDesign:
+ | val a = Bits(8) <> IN
+ | val b = Bits(8) <> OUT
+ | val child_inst = Child()
+ | val x_part = Bits(8) <> VAR
+ | process(all):
+ | if (a(7)) x_part := a ^ h"1b"
+ | else x_part := a
+ | child_inst.x <> x_part
+ | b <> child_inst.y
+ |end Top
+ |""".stripMargin
+ )
+ }
+
end ExplicitCondExprAssignSpec
diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala
index 8a2e76d42..07286e397 100644
--- a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala
+++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala
@@ -131,8 +131,10 @@ class ExplicitNamedVarsSpec extends StageSpec:
| val shifted = Bits(8) <> VAR
| shifted := lhs << 1
| val o = Bits(8) <> OUT
- | if (lhs(7)) o := shifted ^ h"1b"
- | else o := shifted
+ | o <> ((
+ | if (lhs(7)) shifted ^ h"1b"
+ | else shifted
+ | ): Bits[8] <> VAL)
|end xtime""".stripMargin
)
}
diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala
index beeebd8dd..fab327b6e 100644
--- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala
+++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala
@@ -1733,4 +1733,78 @@ class PrintCodeStringSpec extends StageSpec:
|end Foo""".stripMargin
)
}
+
+ test("empty deduplicated designs") {
+ class Mid() extends EDDesign
+ class Foo extends EDDesign:
+ val inst_a = Mid()
+ val inst_b = Mid()
+ val top = (new Foo).getCodeString
+ assertNoDiff(
+ top,
+ """|class Mid extends EDDesign
+ |
+ |class Foo extends EDDesign:
+ | val inst_a = Mid()
+ | val inst_b = Mid()
+ |end Foo""".stripMargin
+ )
+ }
+
+ test("default design parameter value leak regression") {
+ class child(
+ val A: Int <> CONST = 8,
+ val B: Int <> CONST = 0,
+ val C: Int <> CONST = 5
+ ) extends EDDesign:
+ val din = Bits(A) <> IN
+ val dout = Bits(A) <> OUT
+ dout <> din
+
+ class Foo(
+ val W: Int <> CONST = 8
+ ) extends EDDesign:
+ val din = Bits(W) <> IN
+ val dout = Bits(W) <> OUT
+
+ // Instance 1: all params explicit, B = 1
+ val c1 = child(A = W, B = 1, C = 7)
+ c1.din <> din
+ c1.dout <> dout
+
+ // Instance 2: B omitted (uses default 0), C explicit
+ val c2 = child(A = W, C = 7)
+ c2.din <> din
+ val top = (new Foo).getCodeString
+ assertNoDiff(
+ top,
+ """|class child(
+ | val A: Int <> CONST = 8,
+ | val B: Int <> CONST = 0,
+ | val C: Int <> CONST = 5
+ |) extends EDDesign:
+ | val din = Bits(A) <> IN
+ | val dout = Bits(A) <> OUT
+ | dout <> din
+ |end child
+ |
+ |class Foo(val W: Int <> CONST = 8) extends EDDesign:
+ | val din = Bits(W) <> IN
+ | val dout = Bits(W) <> OUT
+ | val c1 = child(
+ | A = W,
+ | B = 1,
+ | C = 7
+ | )
+ | c1.din <> din
+ | dout <> c1.dout
+ | val c2 = child(
+ | A = W,
+ | B = 0,
+ | C = 7
+ | )
+ | c2.din <> din
+ |end Foo""".stripMargin
+ )
+ }
end PrintCodeStringSpec
diff --git a/compiler/stages/src/test/scala/StagesSpec/UniqueDesignsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/UniqueDesignsSpec.scala
index d943f63f4..c5a03b516 100644
--- a/compiler/stages/src/test/scala/StagesSpec/UniqueDesignsSpec.scala
+++ b/compiler/stages/src/test/scala/StagesSpec/UniqueDesignsSpec.scala
@@ -58,4 +58,118 @@ class UniqueDesignsSpec extends StageSpec:
|""".stripMargin
)
}
+ test("Identical parameterized instances with computed sub-param should share") {
+ class Leaf(val W: Int <> CONST = 8, val F: Int <> CONST = 4) extends EDDesign:
+ val x = SInt(W) <> IN
+ val y = SInt(W) <> OUT
+ process(all):
+ y := x
+
+ class Mid(val FP_WIDTH: Int <> CONST = 25, val FP_INT: Int <> CONST = 4) extends EDDesign:
+ val x = SInt(FP_WIDTH) <> IN
+ val y = SInt(FP_WIDTH) <> OUT
+ val leaf_inst = Leaf(W = FP_WIDTH, F = FP_WIDTH - FP_INT)
+ leaf_inst.x <> x
+ leaf_inst.y <> y
+
+ class Top extends EDDesign:
+ val x1 = SInt(25) <> IN
+ val y1 = SInt(25) <> OUT
+ val x2 = SInt(25) <> IN
+ val y2 = SInt(25) <> OUT
+ val inst_a = Mid(FP_WIDTH = 25, FP_INT = 4)
+ val inst_b = Mid(FP_WIDTH = 25, FP_INT = 4)
+ inst_a.x <> x1
+ inst_a.y <> y1
+ inst_b.x <> x2
+ inst_b.y <> y2
+ val id = (new Top).uniqueDesigns
+ assertCodeString(
+ id,
+ """|class Leaf(
+ | val W: Int <> CONST = 8,
+ | val F: Int <> CONST = 4
+ |) extends EDDesign:
+ | val x = SInt(W) <> IN
+ | val y = SInt(W) <> OUT
+ | process(all):
+ | y := x
+ |end Leaf
+ |
+ |class Mid(
+ | val FP_WIDTH: Int <> CONST = 25,
+ | val FP_INT: Int <> CONST = 4
+ |) extends EDDesign:
+ | val x = SInt(FP_WIDTH) <> IN
+ | val y = SInt(FP_WIDTH) <> OUT
+ | val leaf_inst = Leaf(
+ | W = FP_WIDTH,
+ | F = FP_WIDTH - FP_INT
+ | )
+ | leaf_inst.x <> x
+ | y <> leaf_inst.y
+ |end Mid
+ |
+ |class Top extends EDDesign:
+ | val x1 = SInt(25) <> IN
+ | val y1 = SInt(25) <> OUT
+ | val x2 = SInt(25) <> IN
+ | val y2 = SInt(25) <> OUT
+ | val inst_a = Mid(
+ | FP_WIDTH = 25,
+ | FP_INT = 4
+ | )
+ | val inst_b = Mid(
+ | FP_WIDTH = 25,
+ | FP_INT = 4
+ | )
+ | inst_a.x <> x1
+ | y1 <> inst_a.y
+ | inst_b.x <> x2
+ | y2 <> inst_b.y
+ |end Top
+ |""".stripMargin
+ )
+ }
+ test("Identical instances should share a single design") {
+ class ID extends DFDesign:
+ val x = SInt(16) <> IN
+ val y = SInt(16) <> OUT
+ y := x
+
+ class IDTop extends DFDesign:
+ val x1 = SInt(16) <> IN
+ val y1 = SInt(16) <> OUT
+ val x2 = SInt(16) <> IN
+ val y2 = SInt(16) <> OUT
+ val id1 = new ID
+ val id2 = new ID
+ id1.x <> x1
+ id1.y <> y1
+ id2.x <> x2
+ id2.y <> y2
+ val id = (new IDTop).uniqueDesigns
+ assertCodeString(
+ id,
+ """|class ID extends DFDesign:
+ | val x = SInt(16) <> IN
+ | val y = SInt(16) <> OUT
+ | y := x
+ |end ID
+ |
+ |class IDTop extends DFDesign:
+ | val x1 = SInt(16) <> IN
+ | val y1 = SInt(16) <> OUT
+ | val x2 = SInt(16) <> IN
+ | val y2 = SInt(16) <> OUT
+ | val id1 = ID()
+ | val id2 = ID()
+ | id1.x <> x1
+ | y1 <> id1.y
+ | id2.x <> x2
+ | y2 <> id2.y
+ |end IDTop
+ |""".stripMargin
+ )
+ }
end UniqueDesignsSpec
diff --git a/core/src/main/scala/dfhdl/core/DFBits.scala b/core/src/main/scala/dfhdl/core/DFBits.scala
index afbe695bb..be1f3a998 100644
--- a/core/src/main/scala/dfhdl/core/DFBits.scala
+++ b/core/src/main/scala/dfhdl/core/DFBits.scala
@@ -342,7 +342,25 @@ object DFBits:
type Out = DFValTP[DFBits[OutW], OutP]
def conv(from: R)(using DFC): Out = apply(from)
def apply(value: R)(using DFC): Out
- object Candidate:
+ trait CandidateLP:
+ given fromIf[
+ C <: DFValOf[DFBoolOrBit],
+ T,
+ F,
+ TW <: IntP,
+ TP,
+ FP,
+ R <: IfWrapper[C, T, F]
+ ](using
+ tTC: Candidate[T] { type OutW = TW; type OutP = TP },
+ fTC: DFVal.TC[DFBits[TW], F] { type OutP = FP }
+ ): Candidate[R] with
+ type OutW = TW
+ type OutP = TP | FP
+ def apply(value: R)(using DFC): Out = value.unwrap
+ end fromIf
+ end CandidateLP
+ object Candidate extends CandidateLP:
type Exact = Exact0[DFC, Candidate]
type Aux[R, W <: IntP, P] = Candidate[R] { type OutW = W; type OutP = P }
type Dud[V] = Candidate[V]:
diff --git a/core/src/main/scala/dfhdl/core/DFBoolOrBit.scala b/core/src/main/scala/dfhdl/core/DFBoolOrBit.scala
index 4e48acfb9..0223dc89c 100644
--- a/core/src/main/scala/dfhdl/core/DFBoolOrBit.scala
+++ b/core/src/main/scala/dfhdl/core/DFBoolOrBit.scala
@@ -23,7 +23,7 @@ object DFBoolOrBit:
def conv(from: R)(using DFC): Out = apply(from)
def apply(arg: R)(using DFC): Out
object Candidate:
- type Types = DFValOf[DFBoolOrBit] | Boolean | BitNum
+ type Types = DFValOf[DFBoolOrBit] | Boolean | BitNum | IfWrapper[?, ?, ?]
type Aux[R, T <: DFBoolOrBit, P] = Candidate[R] { type OutT = T; type OutP = P }
type Exact = Exact0[DFC, Candidate]
given fromBoolean[R <: Boolean]: Candidate[R] with
@@ -40,6 +40,22 @@ object DFBoolOrBit:
type OutT = T
type OutP = P
def apply(arg: R)(using DFC): Out = arg
+ given fromIf[
+ C <: DFValOf[DFBoolOrBit],
+ T,
+ F,
+ TT <: DFBoolOrBit,
+ TP,
+ FP,
+ R <: IfWrapper[C, T, F]
+ ](using
+ tTC: Candidate[T] { type OutT = TT; type OutP = TP },
+ fTC: DFVal.TC[TT, F] { type OutP = FP }
+ ): Candidate[R] with
+ type OutT = TT
+ type OutP = TP | FP
+ def apply(value: R)(using DFC): Out = value.unwrap
+ end fromIf
end Candidate
private def b2b[T <: DFBoolOrBit, RP](
diff --git a/core/src/main/scala/dfhdl/core/DFDecimal.scala b/core/src/main/scala/dfhdl/core/DFDecimal.scala
index d2a3e33f0..42def4adb 100644
--- a/core/src/main/scala/dfhdl/core/DFDecimal.scala
+++ b/core/src/main/scala/dfhdl/core/DFDecimal.scala
@@ -789,6 +789,29 @@ object DFXInt:
compiletime.error(
"Cannot apply an enum entry value to a DFHDL decimal variable."
)
+ given fromIf[
+ C <: DFValOf[DFBoolOrBit],
+ T,
+ F,
+ TS <: Boolean,
+ TW <: IntP,
+ TN <: NativeType,
+ TP,
+ FP,
+ R <: IfWrapper[C, T, F]
+ ](using
+ tTC: Candidate[T] { type OutS = TS; type OutW = TW; type OutN = TN; type OutP = TP },
+ fTC: DFVal.TC[DFXInt[TS, TW, TN], F] { type OutP = FP }
+ ): Candidate[R] with
+ type OutS = TS
+ type OutW = TW
+ type OutN = TN
+ type OutP = TP | FP
+ type OutSMask = TS
+ type OutWMask = TW
+ type IsScalaInt = false
+ def apply(value: R)(using DFC): Out = value.unwrap
+ end fromIf
end Candidate
extension [S <: Boolean, W <: IntP, N <: NativeType](dfVal: DFValOf[DFXInt[S, W, N]])
diff --git a/core/src/main/scala/dfhdl/core/DFDouble.scala b/core/src/main/scala/dfhdl/core/DFDouble.scala
index b97d484aa..c482e2a95 100644
--- a/core/src/main/scala/dfhdl/core/DFDouble.scala
+++ b/core/src/main/scala/dfhdl/core/DFDouble.scala
@@ -28,6 +28,21 @@ object TDFDouble:
given fromDFDoubleVal[P, R <: DFValTP[DFDouble, P]]: Candidate[R] with
type OutP = P
def apply(arg: R)(using DFC): Out = arg
+ given fromIf[
+ C <: DFValOf[DFBoolOrBit],
+ T,
+ F,
+ TP,
+ FP,
+ R <: IfWrapper[C, T, F]
+ ](using
+ tTC: Candidate[T] { type OutP = TP },
+ fTC: DFVal.TC[DFDouble, F] { type OutP = FP }
+ ): Candidate[R] with
+ type OutP = TP | FP
+ def apply(value: R)(using DFC): Out = value.unwrap
+ end fromIf
+ end Candidate
object TC:
import DFVal.TC
@@ -51,12 +66,13 @@ object TDFDouble:
object Ops:
given evOpArithDFDouble[
- Op <: FuncOp.+.type | FuncOp.-.type | FuncOp.*.type | FuncOp./.type | FuncOp.max.type | FuncOp.min.type,
+ Op <: FuncOp.+.type | FuncOp.-.type | FuncOp.*.type | FuncOp./.type | FuncOp.max.type |
+ FuncOp.min.type,
LPA,
- L <: DFValTP[DFDouble, LPA] | Double,
+ L <: DFValTP[DFDouble, LPA] | Double | IfWrapper[?, ?, ?],
LP,
RPA,
- R <: DFValTP[DFDouble, RPA] | Double,
+ R <: DFValTP[DFDouble, RPA] | Double | IfWrapper[?, ?, ?],
RP
](using
icL: Candidate.Aux[L, LP],
diff --git a/core/src/main/scala/dfhdl/core/DFIf.scala b/core/src/main/scala/dfhdl/core/DFIf.scala
index ae37cb192..3bad4e43d 100644
--- a/core/src/main/scala/dfhdl/core/DFIf.scala
+++ b/core/src/main/scala/dfhdl/core/DFIf.scala
@@ -14,6 +14,28 @@ protected[core] def analyzeControlRet(ret: Any)(using DFC): DFTypeAny = Exact.st
case _ =>
DFUnit
+extension [
+ C <: DFValOf[DFBoolOrBit],
+ T,
+ F,
+ TT <: DFTypeAny,
+ TP,
+ FT <: DFTypeAny,
+ FP
+](ifWrapper: IfWrapper[C, T, F])(using
+ tcT: Exact0.TC[T, DFC] { type Out = DFValTP[TT, TP] },
+ tcF: DFVal.TC[TT, F] { type OutP = FP }
+)
+ def unwrap(using dfc: DFC): DFValTP[TT, TP | FP] =
+ val dfcAnon = dfc.anonymize
+ lazy val onTrue = tcT.conv(ifWrapper.onTrue())(using dfcAnon)
+ def onFalse = tcF(onTrue.dfType, ifWrapper.onFalse())(using dfcAnon).asValTP[TT, FP]
+ DFIf.fromBranches[DFValTP[TT, TP | FP]](
+ List((ifWrapper.cond, () => onTrue)),
+ Some(() => onFalse)
+ )(using dfcAnon)
+end extension
+
object DFIf:
def singleBranch[R](
condOption: Option[DFValOf[DFBoolOrBit]],
diff --git a/core/src/main/scala/dfhdl/core/DFString.scala b/core/src/main/scala/dfhdl/core/DFString.scala
index ce34b86c4..6842a77fc 100644
--- a/core/src/main/scala/dfhdl/core/DFString.scala
+++ b/core/src/main/scala/dfhdl/core/DFString.scala
@@ -28,6 +28,21 @@ object TDFString:
given fromDFStringVal[P, R <: DFValTP[DFString, P]]: Candidate[R] with
type OutP = P
def apply(arg: R)(using DFC): Out = arg
+ given fromIf[
+ C <: DFValOf[DFBoolOrBit],
+ T,
+ F,
+ TP,
+ FP,
+ R <: IfWrapper[C, T, F]
+ ](using
+ tTC: Candidate[T] { type OutP = TP },
+ fTC: DFVal.TC[DFString, F] { type OutP = FP }
+ ): Candidate[R] with
+ type OutP = TP | FP
+ def apply(value: R)(using DFC): Out = value.unwrap
+ end fromIf
+ end Candidate
object TC:
import DFVal.TC
@@ -59,9 +74,9 @@ object TDFString:
object Ops:
given evOpArithDFString[
LP,
- L <: DFValOf[DFString] | String,
+ L <: DFValOf[DFString] | String | IfWrapper[?, ?, ?],
RP,
- R <: DFValOf[DFString] | String
+ R <: DFValOf[DFString] | String | IfWrapper[?, ?, ?]
](using
icL: Candidate.Aux[L, LP],
icR: Candidate.Aux[R, RP]
diff --git a/core/src/test/scala/CoreSpec/DFBitsSpec.scala b/core/src/test/scala/CoreSpec/DFBitsSpec.scala
index ef2a4ab9e..7e0b58c1a 100644
--- a/core/src/test/scala/CoreSpec/DFBitsSpec.scala
+++ b/core/src/test/scala/CoreSpec/DFBitsSpec.scala
@@ -43,6 +43,11 @@ class DFBitsSpec extends DFSpec:
|val t19 = Bits(4) <> VAR
|val t20 = Bits(clog2(param + 1)) <> VAR
|val t21: Bits[16] <> CONST = (t14 << t5.uint.toInt) | (t14 >> t5.uint.toInt)
+ |val ifTest =
+ | h"20" ^ ((
+ | if (t14 == h"0000") t13
+ | else t5.repeat(2)
+ | ): Bits[8] <> VAL)
|""".stripMargin
} {
@inline def foo(arg: Bits[4] <> CONST): Unit <> DFRET =
@@ -88,6 +93,7 @@ class DFBitsSpec extends DFSpec:
val t19 = Bits.to(8) <> VAR
val t20 = Bits.to(param) <> VAR
val t21: Bits[16] <> CONST = t14 << t5 | t14 >> t5
+ val ifTest = h"8'20" ^ (if (t14 == all(0)) t13 else (t5, t5))
}
}
test("Assignment") {
diff --git a/core/src/test/scala/CoreSpec/DFBoolOrBitSpec.scala b/core/src/test/scala/CoreSpec/DFBoolOrBitSpec.scala
index 0d48b2cb8..43665b734 100644
--- a/core/src/test/scala/CoreSpec/DFBoolOrBitSpec.scala
+++ b/core/src/test/scala/CoreSpec/DFBoolOrBitSpec.scala
@@ -55,6 +55,11 @@ class DFBoolOrBitSpec extends DFSpec:
|val t14: Boolean <> CONST = false
|val t15: Boolean <> CONST = true
|val t16: Boolean <> CONST = false
+ |val ifTest =
+ | bt || ((
+ | if (bl) bl
+ | else true
+ | ): Boolean <> VAL).bit
|""".stripMargin
) {
val t1 = bt && bl
@@ -93,6 +98,7 @@ class DFBoolOrBitSpec extends DFSpec:
)(
"""t1.toScalaBoolean"""
)
+ val ifTest = bt || (if (bl) bl else 1)
}
}
diff --git a/core/src/test/scala/CoreSpec/DFDecimalSpec.scala b/core/src/test/scala/CoreSpec/DFDecimalSpec.scala
index 675208ce3..5e0d73b55 100644
--- a/core/src/test/scala/CoreSpec/DFDecimalSpec.scala
+++ b/core/src/test/scala/CoreSpec/DFDecimalSpec.scala
@@ -135,6 +135,11 @@ class DFDecimalSpec extends DFSpec:
|val absTest2: SInt[7] <> CONST = abs(sintNeg42)
|val numSignedBit: Bit <> CONST = sint42(6)
|val numSignedBitP = s8p(param - 1)
+ |val ifTest =
+ | d"8'20" + ((
+ | if (u8 > d"8'5") u8
+ | else d"8'22"
+ | ): UInt[8] <> VAL)
|""".stripMargin
} {
val c: UInt[8] <> CONST = 1
@@ -284,6 +289,7 @@ class DFDecimalSpec extends DFSpec:
) {
d"${strNeg42}"
}
+ val ifTest = d"8'20" + (if (u8 > 5) u8 else 22)
}
assertDSLErrorLog(
"""|Cannot apply this operation between an unsigned value (LHS) and a signed value (RHS).
diff --git a/core/src/test/scala/CoreSpec/DFDoubleSpec.scala b/core/src/test/scala/CoreSpec/DFDoubleSpec.scala
index 090ec31c0..77c602d62 100644
--- a/core/src/test/scala/CoreSpec/DFDoubleSpec.scala
+++ b/core/src/test/scala/CoreSpec/DFDoubleSpec.scala
@@ -39,6 +39,11 @@ class DFDoubleSpec extends DFSpec:
|val t7 = d1 min d2
|val t8: Double <> CONST = 1.0
|val t9: Double <> CONST = -2.5
+ |val ifTest =
+ | 1.0 + ((
+ | if (d1 > 0.0) d1
+ | else -2.0
+ | ): Double <> VAL)
|""".stripMargin
) {
val t1 = d1 + d2
@@ -57,6 +62,7 @@ class DFDoubleSpec extends DFSpec:
)(
"""t1.toScalaDouble"""
)
+ val ifTest = 1.0 + (if (d1 > 0.0) d1 else -2.0)
}
}
diff --git a/core/src/test/scala/CoreSpec/DFStringSpec.scala b/core/src/test/scala/CoreSpec/DFStringSpec.scala
index b324b60c9..819cdecf4 100644
--- a/core/src/test/scala/CoreSpec/DFStringSpec.scala
+++ b/core/src/test/scala/CoreSpec/DFStringSpec.scala
@@ -11,6 +11,11 @@ class DFStringSpec extends DFSpec:
|val t2: String <> CONST = s1 + (" World")
|val t4: Boolean <> CONST = s1 == s2
|val t5: Boolean <> CONST = s1 != "Hello"
+ |val ifTest =
+ | s1 + ((
+ | if (t4) s2
+ | else " Everyone"
+ | ): String <> VAL)
|""".stripMargin
) {
val s1: String <> CONST = "Hello"
@@ -24,6 +29,7 @@ class DFStringSpec extends DFSpec:
assert(t1.toScalaString == "HelloWorld")
assert(t4.toScalaBoolean == false)
assert(t5.toScalaBoolean == false)
+ val ifTest = s1 + (if (t4) s2 else " Everyone")
}
}
end DFStringSpec
diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md
index 779cb3749..6736fbd3c 100644
--- a/docs/transitioning/from-verilog/index.md
+++ b/docs/transitioning/from-verilog/index.md
@@ -49,6 +49,42 @@ end AndGate
///
+/// admonition | `@top` Annotation
+ type: verilog
+The `@top` annotation marks a design as a compilation entry point. The compiler generates a `main` method that elaborates, compiles, and emits HDL output for that design. In a typical design flow, only the testbench or top-level wrapper carries `@top`, and sub-modules are elaborated transitively.
+
+When translating and verifying modules one at a time (bottom-up), you may need to compile each module independently. In that case, every module being compiled must have `@top` so the compiler can generate an entry point for it. `@top`-annotated designs require **all** parameters to have default values.
+
+When a `@top`-annotated design is instantiated as a child of another design, the annotation has no effect. So the question is not whether `@top` causes harm, but whether it is practical: requiring default values for all parameters is annoying for reusable internal components and is not recommended. Mark only the designs you actually need to compile standalone.
+
+
+
+```sv linenums="0" title="Verilog"
+module lfsr #(
+ parameter LEN = 8
+)(
+ input clk,
+ output [LEN-1:0] data
+);
+ // ...
+endmodule
+```
+
+```scala linenums="0" title="DFHDL"
+@top class lfsr(
+ val LEN: Int <> CONST = 8
+) extends EDDesign:
+ val clk = Bit <> IN
+ val data = Bits(LEN) <> OUT
+ // ...
+end lfsr
+```
+
+
+
+Without `@top`, running the compiled output fails with `ClassNotFoundException: top_`. See [Design Hierarchy][design-hierarchy] for full details on `@top` and `@top(false)`.
+///
+
/// admonition | Parameter Declarations
type: verilog
@@ -101,6 +137,32 @@ end Concat
///
+/// admonition | Unconnected Output Ports
+ type: verilog
+
+
+```sv linenums="0" title="Verilog"
+child_mod child_inst(
+ .clk (clk),
+ .din (din),
+ .debug (), // unconnected
+ .dout (dout)
+);
+```
+
+```scala linenums="0" title="DFHDL"
+val child_inst = child_mod()
+child_inst.clk <> clk
+child_inst.din <> din
+child_inst.debug <> OPEN // unconnected
+child_inst.dout <> dout
+```
+
+
+
+Use `OPEN` to explicitly mark an output port as unconnected. This is equivalent to Verilog’s empty port connection (`.port()`). See [Open (Unconnected) Ports][open-ports] for more details.
+///
+
/// admonition | logic/reg/wire
type: verilog
@@ -118,6 +180,20 @@ val v = Bits(8) <> VAR init b"8’1011"
///
+/// admonition | Choosing `UInt` vs `Bits` vs `SInt` for Verilog `logic`
+ type: verilog
+Verilog `logic` and `wire` are untyped bit vectors. When translating to DFHDL, choose the type based on how the signal is used:
+
+| Verilog usage pattern | DFHDL type |
+|---|---|
+| Signed values, subtraction below zero | `SInt` |
+| Exact bit-width required (addresses, masks, bitwise ops) | `Bits` |
+| Unsigned arithmetic (counters, addition, comparison with integers) | `UInt` |
+| FSM state encoding | `enum extends Encoded` |
+
+When a signal is used in both arithmetic and bitwise contexts, prefer `UInt` and convert with `.bits` for bitwise operations. When exact bit-width must be preserved (e.g., an address bus that is also incremented), prefer `Bits` to avoid accidental width extension.
+///
+
## Types and Literals
/// admonition | Numeric Literals
@@ -183,7 +259,50 @@ val cnt = UInt.to(SCALE) <> VAR init 0
If the Verilog counter resets *when it reaches* `N` (i.e., `counter == N`), the variable must be able to hold the value `N`, so use `UInt.to(N)`. If the counter only ever holds values `0..N-1`, use `UInt.until(N)`. For many values of `N`, both produce the same bit width (`clog2(N) == clog2(N+1)`), so the bug is silent until formal verification catches an out-of-range comparison.
///
-See [UInt/SInt constructors][DFDecimal] and [Bits constructors][DFBits] for details. See also the [`clog2` anti-pattern warning][int-param-ops].
+**Reusing computed types and extracting widths:**
+You can name a DFHDL type and reuse it across multiple declarations. You can also extract the `.width` from an existing DFHDL value (not from a type constructor) to derive new widths:
+
+
+
+```sv linenums="0" title="Verilog"
+module Sum#(parameter int MAX = 16)(
+ input wire logic [A_WIDTH - 1:0] a,
+ input wire logic [A_WIDTH - 1:0] b,
+ output logic [IPW - 1:0] sum
+);
+ localparam int A_WIDTH = $clog2(MAX);
+ localparam int IPW = A_WIDTH + 1;
+ assign sum = a + b;
+endmodule
+```
+
+```scala linenums="0" title="DFHDL"
+class Sum(val MAX: Int <> CONST = 16)
+ extends EDDesign:
+ // Name and reuse a type
+ val MyUInt = UInt.until(MAX)
+ val a = MyUInt <> IN
+ val b = MyUInt <> IN
+
+ // Extract width from an existing value
+ val IPW = a.width + 1
+ val sum = UInt(IPW) <> OUT
+ sum <> a + b
+```
+
+
+
+`.width` works on any DFHDL value. Use it to derive widths from existing declarations:
+
+```scala
+val a = UInt.until(MAX) <> IN
+val a_width = a.width // equivalent to clog2(MAX)
+val extended = UInt(a_width + 1) <> OUT
+```
+
+Using `UInt.to`/`UInt.until` with `.width` on values is preferred over calling `clog2` directly (see the [`clog2` anti-pattern warning][int-param-ops]).
+
+See [UInt/SInt constructors][DFDecimal] and [Bits constructors][DFBits] for details.
///
## Processes and Sequential Logic
@@ -220,22 +339,22 @@ process(all):
// Sequential (clocked)
-process(clk):
- if (clk.rising)
- counter :== counter + 1
+process(clk.rising):
+ counter :== counter + 1
// Sequential with async reset
-process(clk, rst):
+process(clk.rising, rst.rising):
if (rst)
q :== 0
- else if (clk.rising)
+ else
q :== d
```
- `always @(*)` becomes `process(all):`
-- `always @(posedge clk)` becomes `process(clk):` with `if (clk.rising)` inside
+- `always @(posedge clk)` becomes `process(clk.rising):`
+- `always @(posedge clk, negedge rst)` becomes `process(clk.rising, rst.falling):`
- Verilog blocking `=` becomes DFHDL `:=` (use in combinational processes)
- Verilog non-blocking `<=` becomes DFHDL `:==` (use in clocked processes)
///
@@ -279,9 +398,8 @@ endmodule
val clk = Bit <> IN
val din = Bit <> IN
val dout = Bit <> OUT init 1
- process(clk):
- if (clk.rising)
- dout :== din
+ process(clk.rising):
+ dout :== din
```
@@ -314,13 +432,12 @@ enum State extends Encoded:
import State.*
val state = State <> VAR init Ready
-process(clk):
- if (clk.rising)
- state match
- case Ready => if (go) state :== Aim
- case Aim => state :== Fire
- case Fire => state :== Ready
- case _ => state :== Ready
+process(clk.rising):
+ state match
+ case Ready => if (go) state :== Aim
+ case Aim => state :== Fire
+ case Fire => state :== Ready
+ case _ => state :== Ready
```
@@ -358,16 +475,15 @@ always @(posedge clk)
```scala linenums="0" title="DFHDL"
val state = State <> VAR // no init
-process(clk):
- if (clk.rising)
- if (rst)
- state :== Ready
- else
- state match
- case Ready => if (go) state :== Aim
- case Aim => state :== Fire
- case Fire => state :== Ready
- case _ => state :== Ready
+process(clk.rising):
+ if (rst)
+ state :== Ready
+ else
+ state match
+ case Ready => if (go) state :== Aim
+ case Aim => state :== Fire
+ case Fire => state :== Ready
+ case _ => state :== Ready
```
@@ -495,6 +611,37 @@ val none_set = !v.| // Bit: NOR reduce
See [Bit Reduction Operations][reduction-ops] for full details.
///
+/// admonition | Bit Replication (`{N{expr}}`)
+ type: verilog
+Verilog's replication operator `{N{expr}}` maps to `.repeat(N)` in DFHDL:
+
+
+
+```sv linenums="0" title="Verilog"
+parameter W = 8;
+logic [7:0] all_ones = {8{1'b1}};
+logic [7:0] all_zeros = {8{1'b0}};
+logic [15:0] doubled = {2{all_ones}};
+
+// Parametric replication
+logic [W-1:0] fill_one = {W{1'b1}};
+```
+
+```scala linenums="0" title="DFHDL"
+val W: Int <> CONST = 8
+val all_ones = b"1".repeat(8)
+val all_zeros = b"0".repeat(8)
+val doubled = all_ones.repeat(2)
+
+// Parametric replication
+val fill_one = b"1".repeat(W)
+```
+
+
+
+`.repeat` works on any `Bits` value, including single-bit literals.
+///
+
/// admonition | Part-Select Notation (`-:` and `+:`)
type: verilog
Verilog's descending and ascending part-select notation maps to DFHDL's `(hi, lo)` range slice, or use the convenience methods `.msbits(n)` and `.lsbits(n)`:
@@ -524,31 +671,109 @@ val bit5 = data(5) // single bit
```
+
+Bit-slicing and single-bit access work on `Bits`, `UInt`, and `SInt` values with the same syntax. You do **not** need to convert `SInt` to `Bits` before slicing:
+
+```scala
+val prod = SInt(16) <> VAR
+val top8 = prod(15, 8) // 8-bit slice, returns Bits
+val sign = prod(15) // single bit access
+```
///
/// admonition | Arithmetic with Signed Values and Constants
type: verilog
-DFHDL arithmetic requires the LHS to be at least as wide as the RHS and to have compatible sign. A plain Scala `Int` literal is unsigned, so it cannot appear on the LHS of arithmetic with `SInt`. The best practice is to use **sized signed literals** (`sd"W'value"`) with the target operation width:
+**Arithmetic operand compatibility:**
+DFHDL enforces sign and width constraints at compile time. The LHS must be at least as wide and at least as signed as the RHS. When the LHS is signed and the RHS is unsigned, the RHS is implicitly widened by 1 bit (for the sign bit), so the LHS must be wide enough to accommodate that.
+
+| LHS | RHS | Result | Constraints | Valid | Invalid |
+|-----|-----|--------|-------------|-------|---------|
+| `UInt[W1]` | `UInt[W2]` | `UInt[W1]` | `W1 >= W2` | `d"8'5" + d"4'3"` | `d"4'5" + d"8'3"` |
+| `UInt[W]` | `Int` | `UInt[W]` | `Int` >= 0, fits in W bits | `d"8'5" + 3` | `d"8'5" + (-1)` |
+| `SInt[W1]` | `SInt[W2]` | `SInt[W1]` | `W1 >= W2` | `sd"8'1" + sd"4'3"` | `sd"4'5" + sd"8'3"` |
+| `SInt[W]` | `Int` | `SInt[W]` | `Int` fits in W bits | `sd"8'5" + (-3)` | |
+| `SInt[W1]` | `UInt[W2]` | `SInt[W1]` | `W1 >= W2 + 1` | `sd"8'5" + d"4'3"` | `sd"4'5" + d"4'3"` |
+| `UInt[W]` | `SInt[W2]` | **Error** | Unsigned cannot accept signed RHS | | `d"8'5" + sd"4'3"` |
+| `Int` | `Int` | `Int` | Elaboration-time arithmetic | `3 + 5` | |
+| `Int` (>= 0) | `UInt[W]` | `UInt[Int]` | `W` fits in `Int`'s width | `180 - d"4'3"` | `2 - d"8'200"` |
+| `Int` (< 0) | `SInt[W]` | `SInt[Int]` | `W` fits in `Int`'s width | `(-5) + sd"4'3"` | |
+
+The constraint is on **width**, not value. A small value in a wide type is valid as LHS: `d"8'1" + d"4'15"` works because `W1=8 >= W2=4`.
+
+**Comparison operand compatibility:**
+Comparisons (`==`, `!=`, `<`, `>`, `<=`, `>=`) require both operands to have the **same signedness and width**. When comparing with an `Int`, its actual width must not exceed the DFHDL value's width.
+
+| LHS | RHS | Constraints | Valid | Invalid |
+|-----|-----|-------------|-------|---------|
+| `UInt[W]` | `UInt[W]` | Same width | `d"8'5" == d"8'3"` | `d"8'5" == d"4'3"` |
+| `UInt[W]` | `Int` | `Int` >= 0, fits in W bits | `d"8'5" == 3` | `d"4'5" == 300` |
+| `SInt[W]` | `SInt[W]` | Same width | `sd"8'5" < sd"8'3"` | `sd"8'5" < sd"4'3"` |
+| `SInt[W]` | `Int` | `Int` fits in W bits | `sd"8'5" == (-3)` | |
+| `Int` (>= 0) | `UInt[W]` | Fits in W bits | `3 == d"8'5"` | `300 == d"4'5"` |
+| `Int` (< 0) | `SInt[W]` | Fits in W bits | `(-3) == sd"8'5"` | |
+| `UInt[W]` | `SInt[W2]` | **Error** | | `d"8'5" == sd"8'3"` |
+| `SInt[W]` | `UInt[W2]` | **Error** | | `sd"8'5" == d"8'3"` |
+
+To compare values of different widths, use `.resize(W)` to match widths first. To compare values of different signedness, convert explicitly (e.g., `.bits.sint` or `.signed`).
+
+**UInt-to-SInt conversion methods:**
+
+- `.signed` -- converts `UInt[W]` to `SInt[W+1]` by adding a sign bit. The value is preserved (always non-negative).
+- `.bits.sint` -- converts `UInt[W]` to `SInt[W]` by reinterpreting the bit pattern. The width stays the same, but the value may become negative if the MSB is set.
+Use `.resize(W)` to widen a narrower operand before arithmetic.
+
+**Mixed-width signed arithmetic examples:**
-```scala
-// Verilog: y <= 2 * mul_val + y0; (all signed, 16-bit)
-// ERROR: plain 2 is unsigned
-y :== 2 * mul_val + y0
-// CORRECT: use a sized signed literal
-y :== sd"16'2" * mul_val + y0
+
-// Verilog: err <= 2 - (2 * r0); (signed, 18-bit result)
-// ERROR: sd"2" is SInt[3], narrower than RHS
-err :== sd"2" - (r0.resize(CORDW + 2) * 2)
-// CORRECT: match the LHS width to the operation width
-err :== sd"${CORDW + 2}'2" - (r0.resize(CORDW + 2) * 2)
+```sv linenums="0" title="Verilog"
+module MixedArith #(
+ parameter W = 16
+)(
+ input wire clk,
+ input wire [2:0] idx,
+ input wire signed [W-1:0] operand,
+ output wire signed [W-1:0] result,
+ output reg signed [W+1:0] acc
+);
+ // Combinational: UInt expr assigned to SInt
+ assign result = 100 - 3 * idx;
+
+ // Clocked: wide accumulator, narrow operand
+ always @(posedge clk)
+ if (acc <= operand)
+ acc <= acc + 2 * operand + 1;
+endmodule
+```
+
+```scala linenums="0" title="DFHDL"
+class MixedArith(
+ val W: Int <> CONST = 16
+) extends EDDesign:
+ val clk = Bit <> IN
+ val idx = UInt(3) <> IN
+ val operand = SInt(W) <> IN
+ val result = SInt(W) <> OUT
+ val acc = SInt(W + 2) <> OUT
+
+ // Forcing SInt arithmetic
+ result <> sd"${W}'100" - idx * 3
+
+ process(clk.rising):
+ // Resize narrow operand to match wider acc
+ if (acc <= operand.resize(W + 2))
+ // switch operand and literal multiplication
+ // order to force SInt arithmetic
+ acc :== acc + operand * 2 + 1
+end MixedArith
```
-The general rule: the **wider** operand must be on the LHS, and when mixing constants with signed DFHDL values, use `sd"W'value"` with the appropriate width `W`.
+
See [Arithmetic Operations][arithmetic-ops] and [Carry Arithmetic][carry-ops] for full details.
///
+
## Parametric Constants
/// admonition | Parametric-Width Bits Constants
@@ -583,6 +808,41 @@ class MyDesign(
See the [Parameter Declarations](#parameter-declarations) section above for a complete inter-dependent parameters example.
///
+## Generate Loops and Conditionals
+
+/// admonition | `generate for` Loops
+ type: verilog
+Verilog `generate for` loops map to Scala `for` loops at design scope. Each iteration is unrolled at elaboration time -- the generated HDL has no loop construct, only the unrolled instances.
+
+
+
+```sv linenums="0" title="Verilog"
+genvar i;
+generate
+ for (i = 0; i < N; i = i + 1)
+ begin : BLK
+ filter #(
+ .WIDTH(BASE_W - 2*i)
+ ) u_filter (...);
+ end
+endgenerate
+```
+
+```scala linenums="0" title="DFHDL"
+for i <- 0 until N.toScalaInt do
+ val u_filter = filter(
+ WIDTH = BASE_W - 2 * i
+ )
+ // connect ports...
+```
+
+
+
+Note that `N` must be convertible to a Scala `Int` at elaboration time (use `.toScalaInt` on `Int <> CONST` parameters).
+
+**Important difference from Verilog:** DFHDL type-checks **both** branches of elaboration-time `if` expressions, regardless of the parameter value. Both branches must be valid for all parameter values. See [Loops][loops] for details and workarounds.
+///
+
## Common Pitfalls
/// admonition | Scala Reserved Keywords as DFHDL Port or Variable Names
@@ -628,7 +888,7 @@ class foo extends EDDesign:
-
+Beyond Scala keywords, Verilog module names may also conflict with DFHDL built-in functions brought in by `import dfhdl.*` (e.g., `abs`, `max`, `min`) or with other class names in the same design hierarchy. See [Naming][naming] for the full list of reserved names and resolution patterns (`@targetName`, type aliases, backtick escaping).
///
/// admonition | `Bits` Initialization or Assignment
diff --git a/docs/user-guide/connectivity/index.md b/docs/user-guide/connectivity/index.md
index 888ca77db..f53a9545a 100755
--- a/docs/user-guide/connectivity/index.md
+++ b/docs/user-guide/connectivity/index.md
@@ -293,7 +293,7 @@ trait TopInit extends DFDesign {
We learn from the above that port initial conditions are often overridden due to connections. So why should we apply initial conditions to a port? Answer: If we want to define what happens when a port is open (unconnected). Read the next two sections for more information.
-###Open (Unconnected) Ports
+###Open (Unconnected) Ports {#connectivity-open-ports}
Ports have two connection sides: a consumer side and a producer side. Typically ports have both sides connected, except for top-level ports. When either port side is unconnected, we refer to it as *open*, and expect the following behavior:
@@ -301,29 +301,26 @@ Ports have two connection sides: a consumer side and a producer side. Typically
* When the port producer side is open (unless it is a top-level output port), the port is considered as not used, and is pruned during compilation. All dataflow streams that are only used by this port will be pruned as well.
-**Note**: the current compiler implementation does not warn of open ports.
-
-Example:
+To explicitly mark a port as unconnected, use the `OPEN` keyword with the `<>` connection operator:
```scala
-trait IOInit2 extends DFDesign {
- val i1 = DFUInt(8) <> IN init 5
- val o1 = DFUInt(8) <> OUT
- val i2 = DFUInt(8) <> IN
- val o2 = DFUInt(8) <> OUT init 2
- o1 <> i1
-}
-trait TopIO2 extends DFDesign {
- val i = DFUInt(8) <> IN
- val o = DFUInt(8) <> OUT //Will generate infinite tokens of 2, due to io.o2 init
- val io = new IO5 {}
- o <> io.o2
- i <> io.i1
- io.i2 <> 5
-}
+class Sensor extends EDDesign:
+ val din = UInt(8) <> IN
+ val dout = UInt(8) <> OUT
+ val debug = UInt(8) <> OUT
+ dout <> din
+ debug <> din
+
+class Top extends EDDesign:
+ val din = UInt(8) <> IN
+ val dout = UInt(8) <> OUT
+ val sensor_inst = Sensor()
+ sensor_inst.din <> din
+ sensor_inst.dout <> dout
+ sensor_inst.debug <> OPEN // explicitly unconnected
```
-
+**Note**: `OPEN` can only be used with the `<>` connection operator. Using it with `:=` assignment will result in a compile error.
### Initial Condition Cyclic Loop Errors
diff --git a/docs/user-guide/design-hierarchy/index.md b/docs/user-guide/design-hierarchy/index.md
index ff83bee21..e0fd10216 100644
--- a/docs/user-guide/design-hierarchy/index.md
+++ b/docs/user-guide/design-hierarchy/index.md
@@ -745,6 +745,7 @@ Where:
- A variable
- A port of the parent design
- A port of another child design instance
+ - `OPEN` - to explicitly leave an output port unconnected (see [Open Ports](#open-ports) below)
The `<>` connection operator has no explicit directionality - it automatically infers producer/consumer relationships based on the connected value types and scope. See the [connectivity][connectivity] section for details.
@@ -905,6 +906,73 @@ children = [
///
///
+#### Open (Unconnected) Ports {#open-ports}
+
+To leave a child design's output port unconnected, use the `OPEN` keyword.
+In the following example, the `debug` output port of `Sensor` is left unconnected:
+
+```scala linenums="0"
+class Sensor extends EDDesign:
+ val din = UInt(8) <> IN
+ val dout = UInt(8) <> OUT
+ val debug = UInt(8) <> OUT
+ dout <> din
+ debug <> din
+
+class Top extends EDDesign:
+ val din = UInt(8) <> IN
+ val dout = UInt(8) <> OUT
+ val sensor_inst = Sensor()
+ sensor_inst.din <> din
+ sensor_inst.dout <> dout
+ sensor_inst.debug <> OPEN // explicitly unconnected
+```
+
+/// admonition
+ type: note
+`OPEN` can only be used with the `<>` connection operator. Using it with `:=` assignment will result in a compile error.
+///
+
+/// admonition | Verilog Equivalent
+ type: verilog
+
+```sv linenums="0" title="Generated Verilog"
+module Top(
+ input wire logic [7:0] din,
+ output logic [7:0] dout
+);
+ Sensor sensor_inst(
+ .din /*<--*/ (din),
+ .dout /*-->*/ (dout),
+ .debug /*-->*/ (/*open*/)
+ );
+endmodule
+```
+///
+
+/// admonition | VHDL Equivalent
+ type: vhdl
+
+```vhdl linenums="0" title="Generated VHDL"
+entity Top is
+port (
+ din : in unsigned(7 downto 0);
+ dout : out unsigned(7 downto 0)
+);
+end Top;
+
+architecture Top_arch of Top is
+begin
+ sensor_inst : entity work.Sensor(Sensor_arch)
+ port map (
+ din => din,
+ dout => dout,
+ debug => open
+ );
+end Top_arch;
+```
+///
+
### Via Connection Composition
Via connection composition is a legacy mechanism that connects child design ports within the child design instantiation. It exists for compatibility with Verilog module instantiation and VHDL component instantiation. The DFHDL compiler automatically transforms direct connections into via connections.
@@ -918,7 +986,7 @@ val _childDesignName_ = new _designClass_(_params_):
_childPort_ <> _connectedValue_
```
-The `#!scala new` keyword and colon `:` syntax creates an anonymous class instance. Port connections must be made within this instantiation block, similar to Verilog module and VHDL component instantiation. This means connected values must be declared before they are used in the connection operation.
+The `#!scala new` keyword and colon `:` syntax creates an anonymous class instance. Port connections must be made within this instantiation block, similar to Verilog module and VHDL component instantiation. This means connected values must be declared before they are used in the connection operation. As with direct composition, `OPEN` can be used as a connected value to leave an output port unconnected (see [Open Ports](#open-ports)).
/// admonition | Handling port name collisions between parent and child designs
type: tip
diff --git a/docs/user-guide/loops/index.md b/docs/user-guide/loops/index.md
new file mode 100644
index 000000000..9d3098a94
--- /dev/null
+++ b/docs/user-guide/loops/index.md
@@ -0,0 +1,50 @@
+# Loops
+
+DFHDL supports loops in several contexts with different semantics depending on the domain and placement.
+
+## Elaboration-Time Loops (Generate Loops)
+
+Scala `for` loops at design scope (outside processes) run at elaboration time. They unroll into repeated hardware -- equivalent to Verilog `generate for`. The generated HDL contains no loop; each iteration produces distinct instances.
+
+```scala
+class Pipeline(
+ val STAGES: Int <> CONST = 4
+) extends EDDesign:
+ val din = UInt(8) <> IN
+ val dout = UInt(8) <> OUT
+
+ // Elaboration-time loop: unrolls into STAGES instances
+ val stages = for i <- 0 until STAGES.toScalaInt yield
+ val s = new Stage(IDX = i)
+ s
+ // Connect chain...
+end Pipeline
+```
+
+When a design containing an elaboration-time loop is instantiated with different parameter values, DFHDL creates distinct elaborated designs (with enumerated names), each with a different number of unrolled instances.
+
+### Elaboration-Time Conditionals
+
+Unlike Verilog `generate if`, DFHDL type-checks **both** branches of an `if` expression at elaboration time, regardless of the parameter value. This means both branches must be type-correct for all possible parameter values:
+
+```scala
+// PROBLEM: when DEPTH == 1, the else branch has an invalid slice
+if (DEPTH == 1)
+ out := in
+else
+ out := (in, data(WIDTH - 1, ELEM_WIDTH)) // invalid range when DEPTH=1
+
+// SOLUTION: use .resize or guard index computations
+if (DEPTH == 1)
+ out := in.resize(WIDTH)
+else
+ out := (in, data.msbits(WIDTH - ELEM_WIDTH))
+```
+
+## ED Domain Loops
+
+In ED designs, `for` and `while` loops inside processes produce combinational or sequential logic depending on the process type. These loops are unrolled by the compiler.
+
+## RT Domain Loops
+
+In RT designs, `for` and `while` loops inside processes create synthesizable procedural FSMs. The compiler transforms the loop body into state machine transitions. See [Processes][processes] for details on RT domain process semantics.
diff --git a/docs/user-guide/naming/index.md b/docs/user-guide/naming/index.md
index 21c5eefaf..fd6825238 100644
--- a/docs/user-guide/naming/index.md
+++ b/docs/user-guide/naming/index.md
@@ -1 +1,73 @@
-# Naming
\ No newline at end of file
+# Naming
+
+## Reserved Names
+
+When translating from Verilog, signal and module names may conflict with names already in scope from Scala or DFHDL. There are two categories of conflicts:
+
+### Scala Reserved Keywords
+
+Scala keywords cannot be used directly as identifiers. Use backtick escaping:
+
+`val`, `var`, `def`, `type`, `class`, `object`, `trait`, `enum`, `match`, `case`, `if`, `else`, `for`, `while`, `do`, `return`, `throw`, `try`, `catch`, `finally`, `yield`, `import`, `export`, `new`, `this`, `super`, `true`, `false`, `null`, `then`, `end`, `given`, `using`, `extension`, `with`, `abstract`, `final`, `override`, `sealed`, `lazy`, `private`, `protected`
+
+```scala
+// Verilog signal named "val"
+val `val` = SInt(16) <> OUT
+`val` <> 42
+```
+
+### DFHDL Built-in Names
+
+`import dfhdl.*` brings DFHDL built-in functions and types into scope. If a user-defined class has the same name as a built-in, the built-in shadows the class. Known built-ins that commonly conflict with Verilog module names:
+
+`abs`, `clog2`, `max`, `min`, `all`, `Bit`, `Bits`, `UInt`, `SInt`
+
+```scala
+// Module named "abs" conflicts with dfhdl.abs
+class abs(val DATA_WIDTH: Int <> CONST = 8) extends EDDesign:
+ // ...
+
+// In the parent design, `abs(...)` resolves to the built-in function.
+// Fix: create a type alias before instantiation
+type AbsModule = abs
+val u_abs = AbsModule(DATA_WIDTH = 16)
+```
+
+## Resolution Patterns
+
+### Backtick Escaping
+
+For Scala keywords used as signal names:
+
+```scala
+val `type` = UInt(8) <> IN
+val `match` = Bit <> OUT
+```
+
+### `@targetName` Annotation
+
+When a Scala-side name must differ from the generated HDL name, use `@targetName` to set the hardware name explicitly. This is useful when:
+
+- A port name conflicts with a sub-module class name in the same design
+- You want to rename a Scala identifier but preserve the original Verilog port name
+
+```scala
+import scala.annotation.targetName
+
+// Port "kernel" conflicts with class "kernel" in scope
+@targetName("kernel")
+val kernel_out = Bits(WIDTH) <> OUT
+// Generated HDL port is still named "kernel"
+
+// The class "kernel" remains available for instantiation
+val u_kernel = kernel()
+```
+
+### Type Alias for Class Name Conflicts
+
+When a class name conflicts with a DFHDL built-in function:
+
+```scala
+type AbsModule = abs // alias resolves the class, not the function
+val u_abs = AbsModule(DATA_WIDTH = 8)
+```
diff --git a/docs/user-guide/processes/index.md b/docs/user-guide/processes/index.md
index b1ccf6321..0ed094bf5 100644
--- a/docs/user-guide/processes/index.md
+++ b/docs/user-guide/processes/index.md
@@ -101,11 +101,16 @@ class CombAndSeq extends EDDesign:
process(x):
y := x + 1
- // Sequential logic: runs on clock (and optionally reset) events
- val r = UInt(8) <> VAR init 0
+ // Sequential logic, Verilog style: runs on clock (and optionally reset) events
+ val r1 = UInt(8) <> VAR init 0
+ process(clk.rising):
+ r1 :== x
+
+ // Sequential logic, VHDL style: runs on clock (and optionally reset) events
+ val r2 = UInt(8) <> VAR init 0
process(clk):
if (clk.rising)
- r :== x
+ r2 :== x
```
You can list multiple signals, including edge-qualified signals (see [Edge sensitivity](#edge-sensitivity)).
@@ -197,7 +202,7 @@ Use `:=` for combinational (e.g. in `process(all)` or combinational branches). U
## Local variables
-You can declare local variables inside a process with `VAL` or `VAR`; they are visible only within that process and help structure combinational or sequential logic.
+You can declare local variables inside a process with `VAR`; they are visible only within that process and help structure combinational or sequential logic.
```scala
process(all):
@@ -209,6 +214,35 @@ process(all):
y := z
```
+You can also use plain Scala `val` declarations (without `<> VAR` or `<> CONST`) inside process blocks to name intermediate sub-expressions. These are DFHDL values created inline -- they do not declare new ports or variables but serve as readable names for parts of a computation:
+
+```scala
+process(clk):
+ if (clk.rising)
+ val sum = a + b // intermediate DFHDL value
+ val overflow = sum(8) // single-bit check
+ if (overflow) result :== max_val
+ else result :== sum.resize(8)
+```
+
+Do not use `<> CONST` or `<> VAR` modifiers inside processes for these intermediates -- plain `val name = expr` is sufficient.
+
+/// admonition | Local `VAR` in clocked processes become registers
+ type: warning
+Local `VAR` declared inside a clocked `process(clk):` block are synthesized as **flip-flop registers** in the generated Verilog, not combinational wires. This is because the DFHDL compiler treats any variable written inside a clocked process as sequential storage.
+
+```scala
+process(clk):
+ if (clk.rising)
+ // This VAR becomes a register in Verilog:
+ val temp = UInt(8) <> VAR
+ temp := x + 1
+ y :== temp
+```
+
+If you need a purely combinational intermediate inside a clocked process, use a plain Scala `val` (without `<> VAR`) for simple expressions, or compute the intermediate in a separate `process(all):` block and read the result in the clocked process.
+///
+
## Relation to design domains
| Domain | Processes |
diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md
index 8d766c44b..f3157037f 100755
--- a/docs/user-guide/type-system/index.md
+++ b/docs/user-guide/type-system/index.md
@@ -545,7 +545,7 @@ class Counter(val width: Int <> CONST = 8) extends RTDesign:
val cnt = UInt(width) <> OUT.REG init 0
```
-- `Int <> CONST` for integer parameters (used for widths, lengths, counts)
+- `Int <> CONST` for integer parameters (used for widths, lengths, counts). Accepts any Scala `Int` value (-2^31^ to 2^31^-1).
- Typed constants like `Bits[8] <> CONST` and `UInt[8] <> CONST` are also possible
- Default values are optional
@@ -1039,7 +1039,7 @@ sd"8'255" // Error: width too small to represent value with sign bit
// Basic declarations
val u8 = UInt(8) <> VAR // 8-bit unsigned
val s8 = SInt(8) <> VAR // 8-bit signed
-val param: Int <> CONST = 42 // Constant parameter
+val param: Int <> CONST = -3 // Constant parameter
// Arithmetic
val sum = u8 + s8.uint // Addition with casting
diff --git a/internals/src/main/scala/dfhdl/internals/Exact.scala b/internals/src/main/scala/dfhdl/internals/Exact.scala
index ec2e6549a..14a9a6800 100644
--- a/internals/src/main/scala/dfhdl/internals/Exact.scala
+++ b/internals/src/main/scala/dfhdl/internals/Exact.scala
@@ -3,6 +3,14 @@ import scala.quoted.*
import util.NotGiven
import scala.collection.concurrent.TrieMap
+final class IfWrapper[C, OT, OF] private (
+ val cond: C,
+ val onTrue: () => OT,
+ val onFalse: () => OF
+)
+object IfWrapper:
+ def apply[C, OT, OF](cond: C, onTrue: => OT, onFalse: => OF): IfWrapper[C, OT, OF] =
+ new IfWrapper(cond, () => onTrue, () => onFalse)
final class ExactInfo[Q <: Quotes & Singleton](using val quotes: Q)(val term: quotes.reflect.Term):
import quotes.reflect.*
val exactTpe: quotes.reflect.TypeRepr =
@@ -55,6 +63,20 @@ extension [Q <: Quotes & Singleton](using quotes: Q)(term: quotes.reflect.Term)
val AppliedType(tycon, _) = t.tpe.runtimeChecked
val tupleTypeArgs = tpes.map(_.asTypeTree)
Apply(TypeApply(fun, tupleTypeArgs), terms)
+ case ifTerm @ If(Apply(Apply(Ident("BooleanHack"), List(cond)), List(_)), onTrue, onFalse) =>
+ ifTerm.tpe match
+ case OrType(_, _) =>
+ val condType = cond.tpe.asTypeOf[Any]
+ val onTrueInfo = onTrue.exactInfo
+ val onFalseInfo = onFalse.exactInfo
+ '{
+ IfWrapper[condType.Underlying, onTrueInfo.Underlying, onFalseInfo.Underlying](
+ ${ cond.asExprOf[Any] },
+ ${ onTrueInfo.exactExpr },
+ ${ onFalseInfo.exactExpr }
+ )
+ }.asTerm
+ case _ => ifTerm
case t => t
end match
end exactTerm
@@ -240,6 +262,12 @@ object Exact1:
end convMacro
end Exact1
+//this scrutinee hack is needed to work around a Scala compiler bug that causes a huge type signature being generated.
+//not yet minimized as an issue (e.g., try calling `.sint` on an `SInt` value without the diagnostics plugin to pretify it)
+transparent inline def cleanTypeHack[T](inline value: T): T =
+ inline value match
+ case skipContextHack: T => skipContextHack
+
/////////////////////////////////////////////////////////////////////////////////
// ExactOp1
/////////////////////////////////////////////////////////////////////////////////
@@ -249,7 +277,10 @@ trait ExactOp1[Op, Ctx, OutUB, LHS]:
type ExactOp1Aux[Op, Ctx, OutUB, LHS, O <: OutUB] =
ExactOp1[Op, Ctx, OutUB, LHS] { type Out = O }
transparent inline def exactOp1[Op, Ctx, OutUB](inline lhs: Any)(using ctx: Ctx): OutUB =
- ${ exactOp1Macro[Op, Ctx, OutUB]('lhs)('ctx) }
+ cleanTypeHack(exactOp1BeforeTypeHack[Op, Ctx, OutUB](lhs))
+transparent inline def exactOp1BeforeTypeHack[Op, Ctx, OutUB](inline lhs: Any)(using
+ ctx: Ctx
+): OutUB = ${ exactOp1Macro[Op, Ctx, OutUB]('lhs)('ctx) }
private def exactOp1Macro[Op, Ctx, OutUB](lhs: Expr[Any])(ctx: Expr[Ctx])(using
Quotes,
Type[Op],
@@ -279,6 +310,13 @@ transparent inline def exactOp2[Op, Ctx, OutUB](
inline bothWays: Boolean = false
)(using
ctx: Ctx
+): OutUB = cleanTypeHack(exactOp2BeforeTypeHack[Op, Ctx, OutUB](lhs, rhs, bothWays))
+transparent inline def exactOp2BeforeTypeHack[Op, Ctx, OutUB](
+ inline lhs: Any,
+ inline rhs: Any,
+ inline bothWays: Boolean = false
+)(using
+ ctx: Ctx
): OutUB = ${ exactOp2Macro[Op, Ctx, OutUB]('lhs, 'rhs, 'bothWays)('ctx) }
private def exactOp2Macro[Op, Ctx, OutUB](
lhs: Expr[Any],
@@ -354,6 +392,11 @@ transparent inline def exactOp3[Op, Ctx, OutUB](
inline lhs: Any,
inline mhs: Any,
inline rhs: Any
+)(using ctx: Ctx): OutUB = cleanTypeHack(exactOp3BeforeTypeHack[Op, Ctx, OutUB](lhs, mhs, rhs))
+transparent inline def exactOp3BeforeTypeHack[Op, Ctx, OutUB](
+ inline lhs: Any,
+ inline mhs: Any,
+ inline rhs: Any
)(using ctx: Ctx): OutUB = ${ exactOp3Macro[Op, Ctx, OutUB]('lhs, 'mhs, 'rhs)('ctx) }
private def exactOp3Macro[Op, Ctx, OutUB](
lhs: Expr[Any],
diff --git a/internals/src/main/scala/dfhdl/internals/helpers.scala b/internals/src/main/scala/dfhdl/internals/helpers.scala
index c68a0c70b..95c5a4d12 100644
--- a/internals/src/main/scala/dfhdl/internals/helpers.scala
+++ b/internals/src/main/scala/dfhdl/internals/helpers.scala
@@ -574,6 +574,26 @@ def debugMacro(msg: => Any, fileName: String = "lib\\src\\test\\scala\\Playgroun
if (Symbol.spliceOwner.pos.get.sourceFile.path.toString.endsWith(fileName))
println(msg)
+// gets the left and right types of a union type T, if T is not a union type, report an error
+trait Union[T]:
+ type L
+ type R
+object Union:
+ object Success extends Union[Any]:
+ type L = Nothing; type R = Nothing
+ type Aux[T, L0, R0] = Union[T] { type L = L0; type R = R0 }
+ transparent inline given [T]: Union[T] = ${ unionMacro[T] }
+ def unionMacro[T: Type](using Quotes): Expr[Union[T]] =
+ import quotes.reflect.*
+ TypeRepr.of[T] match
+ case OrType(ltpe, rtpe) =>
+ val lType = ltpe.asTypeOf[Any]
+ val rType = rtpe.asTypeOf[Any]
+ '{ Success.asInstanceOf[Union.Aux[T, lType.Underlying, rType.Underlying]] }
+ case _ =>
+ report.errorAndAbort(s"Type ${Type.show[T]} is not a union type.")
+end Union
+
// trait CompiletimeErrorPos[M <: String, S <: Int, E <: Int]
// object CompiletimeErrorPos:
// inline given [M <: String, S <: Int, E <: Int]: CompiletimeErrorPos[M, S, E] =
diff --git a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala
index 00d84d7c8..a60879f00 100755
--- a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala
+++ b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala
@@ -363,6 +363,15 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase:
}
named
end match
+ case Block(stats, _)
+ if stats.exists {
+ case vd: ValDef => vd.name.toString == "skipContextHack"
+ case _ => false
+ } =>
+ stats.collectFirst {
+ case vd: ValDef if vd.name.toString.startsWith("$scrutinee") =>
+ nameValOrDef(vd.rhs, ownerTree, typeFocus, inlinedSrcPos)
+ }.getOrElse(false)
case block: Block =>
// debug("Block expr")
nameValOrDef(block.expr, ownerTree, typeFocus, inlinedSrcPos)
diff --git a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala
index 72ffa9038..7df39403e 100644
--- a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala
+++ b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala
@@ -259,8 +259,11 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase:
tree match
case Apply(fun, args) =>
val updatedArgs = args.map { a =>
+ val strippedNamedArg = a match
+ case NamedArg(_, arg) => arg
+ case arg => arg
val uniqueName = NameKinds.UniqueName.fresh(s"arg_plugin".toTermName)
- val valDef = SyntheticValDef(uniqueName, a)
+ val valDef = SyntheticValDef(uniqueName, strippedNamedArg)
valDefs = valDef :: valDefs
ref(valDef.symbol)
}
@@ -306,7 +309,8 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase:
end DFValIdent
tree.rhs match
case DFValIdent(rhs)
- if !tree.symbol.flags.is(InlineProxy) && tree.tpt.tpe.dfValTpeOpt.nonEmpty =>
+ if !tree.name.toString.contains("$") &&
+ !tree.symbol.flags.is(InlineProxy) && tree.tpt.tpe.dfValTpeOpt.nonEmpty =>
val dfc = dfcArgStack.headOption.getOrElse(ref(emptyNoEODFCSym))
val updatedRHS =
ref(dfhdlDFValIdentSym)
diff --git a/plugin/src/main/scala/plugin/PreTyperPhase.scala b/plugin/src/main/scala/plugin/PreTyperPhase.scala
index 714d972bf..a1d1c5ea0 100644
--- a/plugin/src/main/scala/plugin/PreTyperPhase.scala
+++ b/plugin/src/main/scala/plugin/PreTyperPhase.scala
@@ -23,16 +23,16 @@ import annotation.tailrec
import reporting.*
// not used, but can be potentially useful for modified the reported compiler errors
-// class CustomReporter(
-// val orig: Reporter
-// ) extends Reporter:
-// override def flush()(using ctx: Context): Unit = orig.flush()
-// override def doReport(dia: Diagnostic)(using ctx: Context): Unit =
-// val updatedMsg = dia.msg.toString
-// val updatedDia = Diagnostic(dia.msg.mapMsg(x => updatedMsg), dia.pos, dia.level)
-// orig.doReport(updatedDia)
-// end doReport
-// end CustomReporter
+class CustomReporter(
+ val orig: Reporter
+) extends Reporter:
+ override def flush()(using ctx: Context): Unit = orig.flush()
+ override def doReport(dia: Diagnostic)(using ctx: Context): Unit =
+ val updatedMsg = dia.msg.toString
+ val updatedDia = Diagnostic(dia.msg.mapMsg(x => updatedMsg), dia.pos, dia.level)
+ orig.doReport(updatedDia)
+ end doReport
+end CustomReporter
/** This is a pre-typer phase that does very minor things:
* - change infix operator precedence of type signature: `a X b <> c` to be `(a X b) <> c`
@@ -42,7 +42,7 @@ import reporting.*
* and `a <> b match {...}` to be `a <> (b match {...})`
* - change process{} to process.forever{}
*/
-class PreTyperPhase(setting: Setting) extends PluginPhase:
+class PreTyperPhase(setting: Setting) extends CommonPhase:
import untpd.*
val phaseName = "PreTyper"
@@ -54,7 +54,7 @@ class PreTyperPhase(setting: Setting) extends PluginPhase:
// that can cause compiler errors
override def run(using Context): Unit = {}
- def debug(str: => Any*): Unit =
+ def debug2(str: => Any*): Unit =
if (debugFlag) println(str.mkString(", "))
val opSet = Set("|", "||", "&", "&&", "^", "<<", ">>", "==", "!=", "<", ">", "<=", ">=")
@@ -151,26 +151,123 @@ class PreTyperPhase(setting: Setting) extends PluginPhase:
t
end match
end transform
+ object DFType:
+ def unapply(arg: Type)(using Context): Option[(String, List[Type])] =
+ arg.simple match
+ case AppliedType(dfTypeCore, List(n, argsTp))
+ if dfTypeCore.typeSymbol == requiredClass("dfhdl.core.DFType") =>
+ val nameStr = n.typeSymbol.name.toString
+ argsTp match
+ case AppliedType(_, args) => Some(nameStr, args)
+ case _ => Some(nameStr, Nil)
+ case _ => None
+ end DFType
+ object DFBool:
+ def unapply(arg: Type)(using Context): Boolean =
+ arg match
+ case DFType("DFBool$", Nil) => true
+ case _ => false
+ object DFBit:
+ def unapply(arg: Type)(using Context): Boolean =
+ arg match
+ case DFType("DFBit$", Nil) => true
+ case _ => false
+ object DFBits:
+ def unapply(arg: Type)(using Context): Option[Type] =
+ arg match
+ case DFType("DFBits", w :: Nil) => Some(w)
+ case _ => None
+ object DFDecimal:
+ def unapply(arg: Type)(using Context): Option[(Type, Type, Type)] =
+ arg match
+ // ignoring the fourth native argument, since it's not needed for matching
+ case DFType("DFDecimal", s :: w :: f :: _ :: Nil) => Some(s, w, f)
+ case _ => None
+ object DFXInt:
+ def unapply(arg: Type)(using Context): Option[(Boolean, Type)] =
+ arg match
+ case DFDecimal(
+ ConstantType(Constant(sign: Boolean)),
+ widthTpe,
+ ConstantType(Constant(fractionWidth: Int))
+ ) if fractionWidth == 0 =>
+ Some(sign, widthTpe)
+ case _ => None
+ object DFUInt:
+ def unapply(arg: Type)(using Context): Option[Type] =
+ arg match
+ case DFXInt(sign, widthTpe) if !sign => Some(widthTpe)
+ case _ => None
+ object DFSInt:
+ def unapply(arg: Type)(using Context): Option[Type] =
+ arg match
+ case DFXInt(sign, widthTpe) if sign => Some(widthTpe)
+ case _ => None
+ object DFEnum:
+ def unapply(arg: Type)(using Context): Option[Type] =
+ arg match
+ case DFType("DFEnum", e :: Nil) => Some(e)
+ case _ => None
+ object DFStruct:
+ def unapply(arg: Type)(using Context): Option[Type] =
+ arg match
+ case DFType("DFStruct", t :: Nil) => Some(t)
+ case _ => None
+
+ object DFVal:
+ private def stripAndType(tpeOpt: Option[Type])(using Context): Option[Type] =
+ tpeOpt.map(tpe =>
+ tpe.simple match
+ case AndType(t1, _) => t1
+ case _ => tpe
+ )
+ def unapply(arg: Type)(using Context): Option[Type] =
+ val dfValClsRef = requiredClassRef("dfhdl.core.DFVal")
+ val ret = arg.simple match
+ case AppliedType(t, List(dfType, _)) if t <:< dfValClsRef =>
+ Some(dfType)
+ case AppliedType(t, List(arg, mod))
+ if t.typeSymbol.name.toString == "<>" &&
+ (mod <:< requiredClassRef("dfhdl.VAL") || mod <:< requiredClassRef("dfhdl.DFRET")) =>
+ arg match
+ case dfType @ DFType(_, _) => Some(dfType)
+ case _ => None
+ case _ =>
+ None
+ stripAndType(ret)
+ end unapply
+ end DFVal
// not used, but can be potentially useful for modified the reported compiler errors
- // override def initContext(ctx: FreshContext): Unit =
- // import dotty.tools.dotc.printing.*
- // import dotty.tools.dotc.printing.Texts.Text
- // def foo(ctx: Context): Printer =
- // new PlainPrinter(ctx):
- // override def toText(tp: Type): Text =
- // if (tp <:< defn.IntType)
- // "Int2"
- // else
- // super.toText(tp)
- // ctx.setPrinterFn(foo)
- // val typerState = ctx.typerState.setReporter(new CustomReporter(ctx.reporter))
- // ctx.setTyperState(typerState)
+ override def initContext(ctx: FreshContext): Unit =
+ import dotty.tools.dotc.printing.*
+ import dotty.tools.dotc.printing.Texts.Text
+ def foo(ctx: Context): Printer =
+ new RefinedPrinter(ctx):
+ override def toText(tp: Type): Text =
+ tp match
+ case DFVal(dfType) =>
+ val dfTypeText: Text = dfType match
+ case DFBool() => "Boolean"
+ case DFBit() => "Bit"
+ case DFBits(w) => s"Bits[${w.show}]"
+ case DFUInt(w) => s"UInt[${w.show}]"
+ case DFSInt(w) => s"SInt[${w.show}]"
+ case DFDecimal(s, w, f) => s"Decimal[${s.show}, ${w.show}, ${f.show}]"
+ case DFEnum(e) => s"Enum[${e.show}]"
+ case DFStruct(t) => s"Struct[${t.show}]"
+ case _ => super.toText(tp)
+ dfTypeText ~ " <> Val"
+ case _ => super.toText(tp)
+ ctx.setPrinterFn(foo)
+ val typerState = ctx.typerState.setReporter(new CustomReporter(ctx.reporter))
+ ctx.setTyperState(typerState)
+ end initContext
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] =
val parsed = super.runOn(units)
parsed.foreach { cu =>
- // debugFlag = cu.source.file.path.contains("Playground.scala")
+ debugFlag = cu.source.file.path.contains("Playground.scala")
cu.untpdTree = `fix<>andOpPrecedence`.transform(cu.untpdTree)
cu.untpdTree = `fixXand<>Precedence`.transform(cu.untpdTree)
}