From 7757da51e4a5fa02f5cf76949d23e426a122a50b Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 8 Feb 2026 12:13:15 +0200 Subject: [PATCH 001/115] add `FALL_THROUGH` loop tagging --- .../scala/dfhdl/compiler/ir/DFMember.scala | 28 ++++++--- .../main/scala/dfhdl/compiler/ir/DFTags.scala | 2 + .../compiler/printing/DFOwnerPrinter.scala | 4 ++ .../StagesSpec/PrintCodeStringSpec.scala | 51 ++++++++++++++++ core/src/main/scala/dfhdl/core/DFWhile.scala | 61 +++++++++++-------- core/src/main/scala/dfhdl/hdl.scala | 2 +- 6 files changed, 113 insertions(+), 35 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index ce140841b..1d098af42 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -585,7 +585,8 @@ object DFVal: else Some(calcFuncData(dfType, op, argTypes, argData)) protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: Func => - this.dfType =~ that.dfType && this.op == that.op && (this.args + this.dfType =~ that.dfType && this.op == that.op && + (this.args .lazyZip(that.args) .forall((l, r) => l =~ r)) && this.meta =~ that.meta && this.tags =~ that.tags @@ -812,11 +813,13 @@ object DFVal: protected def setMeta(meta: Meta): this.type = copy(meta = meta).asInstanceOf[this.type] protected def setTags(tags: DFTags): this.type = copy(tags = tags).asInstanceOf[this.type] override lazy val getRefs: List[DFRef.TwoWayAny] = - dfType.getRefs ++ meta.getRefs ++ List(relValRef) ++ (idxHighRef match - case ref: DFRef.TypeRef => List(ref); - case _ => Nil) ++ (idxLowRef match - case ref: DFRef.TypeRef => List(ref); - case _ => Nil) + dfType.getRefs ++ meta.getRefs ++ List(relValRef) ++ + (idxHighRef match + case ref: DFRef.TypeRef => List(ref); + case _ => Nil) ++ + (idxLowRef match + case ref: DFRef.TypeRef => List(ref); + case _ => Nil) def updateDFType(dfType: DFType): this.type = this def copyWithoutGlobalCtx: this.type = copy().asInstanceOf[this.type] def copyWithNewRefs(using RefGen): this.type = copy( @@ -950,7 +953,8 @@ final case class DFRange( ) extends DFMember derives ReadWriter: protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: DFRange => - this.startRef =~ that.startRef && this.endRef =~ that.endRef && this.stepRef =~ that.stepRef && + this.startRef =~ that.startRef && this.endRef =~ that.endRef && + this.stepRef =~ that.stepRef && this.op == that.op && this.meta =~ that.meta && this.tags =~ that.tags case _ => false @@ -1299,7 +1303,8 @@ object DFConditional: protected def `prot_=~`(that: Pattern)(using MemberGetSet): Boolean = that match case that: Struct => - this.name == that.name && this.fieldPatterns + this.name == that.name && + this.fieldPatterns .lazyZip(that.fieldPatterns) .forall(_ =~ _) case _ => false @@ -1338,7 +1343,8 @@ object DFConditional: protected def `prot_=~`(that: Pattern)(using MemberGetSet): Boolean = that match case that: BindSI => - this.op == that.op && this.parts == that.parts && this.refs + this.op == that.op && this.parts == that.parts && + this.refs .lazyZip(that.refs) .forall(_ =~ _) case _ => false @@ -1411,6 +1417,7 @@ end DFConditional object DFLoop: sealed trait Block extends DFBlock derives ReadWriter: def isCombinational(using MemberGetSet): Boolean = this.hasTagOf[CombinationalTag] + def isFallThrough(using MemberGetSet): Boolean = this.hasTagOf[FallThroughTag] final case class DFForBlock( iteratorRef: DFForBlock.IteratorRef, rangeRef: DFForBlock.RangeRef, @@ -1708,7 +1715,8 @@ object TextOut: case _ => Nil protected def `prot_=~`(that: Op)(using MemberGetSet): Boolean = (this, that) match case (thisAssert: Assert, thatAssert: Assert) => - thisAssert.assertionRef =~ thatAssert.assertionRef && thisAssert.severity == thatAssert.severity + thisAssert.assertionRef =~ thatAssert.assertionRef && + thisAssert.severity == thatAssert.severity case _ => this equals that def copyWithNewRefs(using RefGen): this.type = this match case Assert(assertionRef, severity) => diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala index ec2d15bb3..95291920f 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala @@ -14,6 +14,8 @@ case object BindTag extends DFTag type BindTag = BindTag.type case object CombinationalTag extends DFTag type CombinationalTag = CombinationalTag.type +case object FallThroughTag extends DFTag +type FallThroughTag = FallThroughTag.type case class DefaultRTDomainCfgTag(cfg: RTDomainCfg.Explicit) extends DFTag case object ExtendTag extends DFTag type ExtendTag = ExtendTag.type diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala index 14a5b9b2a..1f414696c 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala @@ -361,8 +361,10 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: s"def $name: $defType =\n${body.hindent}\nend $name" def csDFForBlock(forBlock: DFLoop.DFForBlock): String = val csCOMB_LOOP = if (forBlock.isCombinational) "COMB_LOOP" else "" + val csFALL_THROUGH = if (forBlock.isFallThrough) "FALL_THROUGH" else "" val body = sn"""|${csCOMB_LOOP} + |${csFALL_THROUGH} |${csDFOwnerBody(forBlock)}""" val named = forBlock.meta.nameOpt.map(n => s"val $n = ").getOrElse("") //format: off @@ -372,8 +374,10 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: //format: on def csDFWhileBlock(whileBlock: DFLoop.DFWhileBlock): String = val csCOMB_LOOP = if (whileBlock.isCombinational) "COMB_LOOP" else "" + val csFALL_THROUGH = if (whileBlock.isFallThrough) "FALL_THROUGH" else "" val body = sn"""|${csCOMB_LOOP} + |${csFALL_THROUGH} |${csDFOwnerBody(whileBlock)}""" val named = whileBlock.meta.nameOpt.map(n => s"val $n = ").getOrElse("") sn"""|${named}while (${whileBlock.guardRef.refCodeString}) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index c7230c505..e933b6c53 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -1171,6 +1171,57 @@ class PrintCodeStringSpec extends StageSpec: |end Foo""".stripMargin ) } + test("for/while loop printing with FALL_THROUGH") { + class Foo extends RTDesign: + val matrix = Bits(10) X 8 X 8 <> OUT.REG + process: + for ( + i <- 0 until 8; + if i % 2 == 0; + j <- 0 until 8; + if j % 2 == 0; + k <- 0 until 10 + if k % 2 == 0 + ) + FALL_THROUGH + matrix(i)(j)(k).din := 1 + val ii = UInt.until(8) <> VAR init 0 + while (ii != 7) + FALL_THROUGH + matrix(ii)(0)(0).din := 0 + ii := ii + 1 + 10.sec.wait + end Foo + val top = (new Foo).getCodeString + assertNoDiff( + top, + """|class Foo extends RTDesign: + | val matrix = Bits(10) X 8 X 8 <> OUT.REG + | process: + | for (i <- 0 until 8) + | FALL_THROUGH + | if ((i % 2) == 0) + | for (j <- 0 until 8) + | FALL_THROUGH + | if ((j % 2) == 0) + | for (k <- 0 until 10) + | FALL_THROUGH + | if ((k % 2) == 0) matrix(i)(j)(k).din := 1 + | end for + | end if + | end for + | end if + | end for + | val ii = UInt(3) <> VAR init d"3'0" + | while (ii != d"3'7") + | FALL_THROUGH + | matrix(ii.toInt)(0)(0).din := 0 + | ii := ii + d"3'1" + | end while + | 10.sec.wait + |end Foo""".stripMargin + ) + } test("while loop printing") { class Foo extends EDDesign: val x = Bit <> OUT diff --git a/core/src/main/scala/dfhdl/core/DFWhile.scala b/core/src/main/scala/dfhdl/core/DFWhile.scala index e14009bf2..67bca0d9d 100644 --- a/core/src/main/scala/dfhdl/core/DFWhile.scala +++ b/core/src/main/scala/dfhdl/core/DFWhile.scala @@ -2,6 +2,7 @@ package dfhdl.core import dfhdl.compiler.ir import dfhdl.internals.* import scala.annotation.implicitNotFound +import scala.reflect.ClassTag object DFWhile: object Block: @@ -21,27 +22,39 @@ object DFWhile: dfc.exitOwner() end DFWhile -//to be used inside an RT loop to indicate that the loop is combinational -def COMB_LOOP(using - dfc: DFC, - @implicitNotFound( - "`COMB_LOOP` is only allowed under register-transfer (RT) domains." - ) rt: DomainType.RT -): Unit = - import dfc.getSet - var ownerIR = dfc.owner.asIR - var stop = false - var lineEnd = -1 - while (!stop) - ownerIR match - case cb: ir.DFConditional.Block => ownerIR = cb.getOwner - case lb: ir.DFLoop.Block => - if (lineEnd == -1) - lineEnd = lb.meta.position.lineEnd - else if (lineEnd != lb.meta.position.lineEnd) - stop = true - if (!stop) - ownerIR.setTags(_.tag(ir.CombinationalTag)) - ownerIR = lb.getOwner - case _ => stop = true -end COMB_LOOP +protected[dfhdl] object LoopOps: + private def loopTag[CT <: ir.DFTag: ClassTag](tag: CT)(using DFC): Unit = + import dfc.getSet + var ownerIR = dfc.owner.asIR + var stop = false + var lineEnd = -1 + while (!stop) + ownerIR match + case cb: ir.DFConditional.Block => ownerIR = cb.getOwner + case lb: ir.DFLoop.Block => + if (lineEnd == -1) + lineEnd = lb.meta.position.lineEnd + else if (lineEnd != lb.meta.position.lineEnd) + stop = true + if (!stop) + ownerIR.setTags(_.tag(tag)) + ownerIR = lb.getOwner + case _ => stop = true + end loopTag + + // to be used inside an RT loop to indicate that the loop is combinational + def COMB_LOOP(using + dfc: DFC, + @implicitNotFound( + "`COMB_LOOP` is only allowed under register-transfer (RT) domains." + ) rt: DomainType.RT + ): Unit = loopTag(ir.CombinationalTag) + + // to be used inside an RT loop to indicate that the loop should fall through to the next step if the guard is false + def FALL_THROUGH(using + dfc: DFC, + @implicitNotFound( + "`FALL_THROUGH` is only allowed under register-transfer (RT) domains." + ) rt: DomainType.RT + ): Unit = loopTag(ir.FallThroughTag) +end LoopOps diff --git a/core/src/main/scala/dfhdl/hdl.scala b/core/src/main/scala/dfhdl/hdl.scala index f02b2fe13..61ee08459 100644 --- a/core/src/main/scala/dfhdl/hdl.scala +++ b/core/src/main/scala/dfhdl/hdl.scala @@ -24,7 +24,7 @@ protected object hdl: export internals.CommonOps.* export core.{dfType} export core.DFPhysical.Val.Ops.* - export core.COMB_LOOP + export core.LoopOps.* type Time = core.DFTime val Time = core.DFTime type Freq = core.DFFreq From 4374cb4adfa92ca27ba86fe1978feb8ac580f693 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 8 Feb 2026 12:36:17 +0200 Subject: [PATCH 002/115] Enhance documentation for `FALL_THROUGH` loop tagging to clarify its behavior in RT loops, specifying that it allows falling through without consuming cycles. --- core/src/main/scala/dfhdl/core/DFWhile.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/dfhdl/core/DFWhile.scala b/core/src/main/scala/dfhdl/core/DFWhile.scala index 67bca0d9d..29c251dfd 100644 --- a/core/src/main/scala/dfhdl/core/DFWhile.scala +++ b/core/src/main/scala/dfhdl/core/DFWhile.scala @@ -50,7 +50,8 @@ protected[dfhdl] object LoopOps: ) rt: DomainType.RT ): Unit = loopTag(ir.CombinationalTag) - // to be used inside an RT loop to indicate that the loop should fall through to the next step if the guard is false + // to be used inside an RT loop to indicate that the loop should fall through to the + // next step if the guard is false without consuming any cycles def FALL_THROUGH(using dfc: DFC, @implicitNotFound( From 1b75fb0f213bc0b2e955a5eb79c812f898ce5074 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 8 Feb 2026 12:49:05 +0200 Subject: [PATCH 003/115] update dependencies --- .scalafmt.conf | 2 +- build.sbt | 6 +++--- .../hello-world/scala-project/.scalafmt.conf | 2 +- project/build.properties | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 1db933e40..d815d9c92 100755 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.10.5 +version = 3.10.6 runner.dialect = scala3 maxColumn = 100 diff --git a/build.sbt b/build.sbt index fc5d2ccf1..86a28b5b9 100755 --- a/build.sbt +++ b/build.sbt @@ -145,9 +145,9 @@ lazy val dependencies = new { private val scodecV = "1.2.4" private val munitV = "1.2.2" - private val airframelogV = "2025.1.27" - private val oslibV = "0.11.7" - private val scallopV = "5.3.0" + private val airframelogV = "2026.1.0" + private val oslibV = "0.11.8" + private val scallopV = "6.0.0" private val upickleV = "4.4.2" val scodec = "org.scodec" %% "scodec-bits" % scodecV diff --git a/docs/getting-started/hello-world/scala-project/.scalafmt.conf b/docs/getting-started/hello-world/scala-project/.scalafmt.conf index fcc9a13d3..63a221d96 100644 --- a/docs/getting-started/hello-world/scala-project/.scalafmt.conf +++ b/docs/getting-started/hello-world/scala-project/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.10.5 +version = 3.10.6 runner.dialect = scala3 maxColumn = 100 diff --git a/project/build.properties b/project/build.properties index c8cca6491..4d5412b8f 100755 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.12.1 \ No newline at end of file +sbt.version = 1.12.2 \ No newline at end of file From f012d416b2bfbee88120812f1f378f1bac78c09c Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 8 Feb 2026 19:07:25 +0200 Subject: [PATCH 004/115] add `fallThrough` condition method for step blocks --- .../scala/dfhdl/compiler/ir/DFMember.scala | 5 +- .../compiler/printing/DFOwnerPrinter.scala | 8 ++- .../StagesSpec/PrintCodeStringSpec.scala | 6 +- core/src/main/scala/dfhdl/core/Step.scala | 24 ++++--- .../src/main/scala/plugin/LoopFSMPhase.scala | 63 +++++++++++++------ 5 files changed, 74 insertions(+), 32 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index 1d098af42..0bbe38d4c 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -1081,9 +1081,10 @@ object StepBlock: extension (stepBlock: StepBlock) def isOnEntry(using MemberGetSet): Boolean = stepBlock.getName == "onEntry" def isOnExit(using MemberGetSet): Boolean = stepBlock.getName == "onExit" + def isFallThrough(using MemberGetSet): Boolean = stepBlock.getName == "fallThrough" def isRegular(using MemberGetSet): Boolean = stepBlock.getName match - case "onEntry" | "onExit" => false - case _ => true + case "onEntry" | "onExit" | "fallThrough" => false + case _ => true final case class Goto( stepRef: Goto.Ref, diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala index 1f414696c..e831737c8 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala @@ -357,8 +357,12 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: def csStepBlock(stepBlock: StepBlock): String = val body = csDFOwnerBody(stepBlock) val name = stepBlock.getName - val defType = if (stepBlock.isRegular) "Step" else "Unit" - s"def $name: $defType =\n${body.hindent}\nend $name" + val defType = + if (stepBlock.isRegular) ": Step" + else if (stepBlock.isFallThrough) + printer.csDFValType(stepBlock.getVeryLastMember.get.asInstanceOf[DFVal].dfType) + else ": Unit" + s"def $name$defType =\n${body.hindent}\nend $name" def csDFForBlock(forBlock: DFLoop.DFForBlock): String = val csCOMB_LOOP = if (forBlock.isCombinational) "COMB_LOOP" else "" val csFALL_THROUGH = if (forBlock.isFallThrough) "FALL_THROUGH" else "" diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index e933b6c53..de8891825 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -992,7 +992,8 @@ class PrintCodeStringSpec extends StageSpec: 10.ms.wait if (i) S_2 else S_1 def S_2: Step = - def onEntry = + def fallThrough = !i + def onEntry = x := 0 def onExit = x := 1 @@ -1015,6 +1016,9 @@ class PrintCodeStringSpec extends StageSpec: | else S_1 | end S_1 | def S_2: Step = + | def fallThrough: Bit <> VAL = + | !i + | end fallThrough | def onEntry: Unit = | x := 0 | end onEntry diff --git a/core/src/main/scala/dfhdl/core/Step.scala b/core/src/main/scala/dfhdl/core/Step.scala index 0d0b04842..b63a4e6e2 100644 --- a/core/src/main/scala/dfhdl/core/Step.scala +++ b/core/src/main/scala/dfhdl/core/Step.scala @@ -71,20 +71,28 @@ object Step extends Step: run dfc.exitOwner() - // this is called by the compiler plugin and replaces the step's onEntry/onExit `def` with - // a step block that has the name "onEntry"/"onExit" which is special-cases and not treated - // as a normal step, but just a container for the onEntry/onExit code. - def pluginOnEntryExit(meta: ir.Meta)( - run: => Unit + // this is called by the compiler plugin and replaces the step's onEntry/onExit/fallThrough `def` with + // a step block that has the name "onEntry"/"onExit"/"fallThrough" which is special-cases and not treated + // as a normal step, but just a container for the onEntry/onExit/fallThrough code. + def pluginOnEntryExitFallThrough(meta: ir.Meta)( + run: => Any )(using dfc: DFC): Unit = - val onEntryExit = ir.StepBlock( + import dfc.getSet + val onEntryExitFallThrough = ir.StepBlock( dfc.owner.ref, meta, dfc.tags ).addMember - dfc.enterOwner(onEntryExit.asFE) - run + dfc.enterOwner(onEntryExitFallThrough.asFE) + val ret = run + if (onEntryExitFallThrough.isFallThrough) + Exact.strip(ret) match + case v: DFValAny => + // adding ident placement as the last member of the fall-through block + DFVal.Alias.AsIs.ident(v)(using dfc.anonymize) + case _ => // do nothing dfc.exitOwner() + end pluginOnEntryExitFallThrough // this is called by the compiler plugin and replaces references (calls) to the step `def`. // for the process this is considered as a goto statement. diff --git a/plugin/src/main/scala/plugin/LoopFSMPhase.scala b/plugin/src/main/scala/plugin/LoopFSMPhase.scala index 0cdacfde7..857da3870 100644 --- a/plugin/src/main/scala/plugin/LoopFSMPhase.scala +++ b/plugin/src/main/scala/plugin/LoopFSMPhase.scala @@ -36,7 +36,8 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: var processAnonDefSym: Symbol = uninitialized var processScopeCtxSym: Symbol = uninitialized var stepType: Type = uninitialized - var pluginOnEntryExitSym: Symbol = uninitialized + var fallThroughType: Type = uninitialized + var pluginOnEntryExitFallThroughSym: Symbol = uninitialized var waitSym: Symbol = uninitialized var dfcStack: List[Tree] = Nil val processStepDefs = mutable.LinkedHashMap.empty[Symbol, DefDef] @@ -85,12 +86,12 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: report.error("Process blocks must only declare step `def`s or no step`def`s at all.", srcPos) enum CheckType derives CanEqual: - case None, Return, Loop, OnEntryExit + case None, Return, Loop, OnEntryExitFallThrough def processStatCheck(tree: Tree, returnCheck: CheckType)(using Context): Unit = tree match case Block(stats, expr) => returnCheck match - case CheckType.OnEntryExit => + case CheckType.OnEntryExitFallThrough => (expr :: stats).foreach(t => processStatCheck(t, returnCheck)) case _ => processStatCheck(stats, tree.srcPos) @@ -98,8 +99,8 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: case Literal(Constant(_: Unit)) => case _ => stats.headOption match - case Some(OnEntryDef() | OnExitDef()) => - case Some(dd: DefDef) => + case Some(OnEntryDef() | OnExitDef() | FallThroughDef()) => + case Some(dd: DefDef) => // allDefsErrMsg(expr.srcPos) case _ => processStatCheck(expr, returnCheck) @@ -130,13 +131,16 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: // |For a purely combinational loop, call `0.cy.wait` at the end of the loop block.""".stripMargin, // tree.srcPos // ) - case CheckType.OnEntryExit => + case CheckType.OnEntryExitFallThrough => tree match case dd: DefDef => - report.error("onEntry/onExit must not contain any other `def`s.", dd.srcPos) + report.error( + "onEntry/onExit/fallThrough must not contain any other `def`s.", + dd.srcPos + ) case Goto() | Wait() => report.error( - "onEntry/onExit `def`s cannot have `wait` or step goto statements.", + "onEntry/onExit/fallThrough `def`s cannot have `wait` or step goto statements.", tree.srcPos ) case _ => @@ -151,17 +155,16 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: case _: DefDef => true case _ => false }).runtimeChecked - val (onEntryExit: List[DefDef], stepDefs: List[DefDef]) = allDefs.partition { - case OnEntryDef() => true - case OnExitDef() => true - case _ => false + val (onEntryExitFallThrough: List[DefDef], stepDefs: List[DefDef]) = allDefs.partition { + case OnEntryDef() | OnExitDef() | FallThroughDef() => true + case _ => false } // if (stepDefs.nonEmpty && allStepBlocks.nonEmpty) allDefsErrMsg(srcPos) // checking onEntry and onExit defs syntax - onEntryExit.foreach { dd => + onEntryExitFallThrough.foreach { dd => if (dd.paramss != Nil) - report.error(s"An ${dd.name} def must not have arguments.", dd.srcPos) + report.error(s"`def ${dd.name}` must not have arguments.", dd.srcPos) } var errFound = false // checking process defs syntax and caching the process def symbols @@ -177,7 +180,9 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: } if (!errFound) stepDefs.foreach { dd => processStatCheck(dd.rhs, returnCheck = CheckType.Return) } - onEntryExit.foreach { dd => processStatCheck(dd.rhs, returnCheck = CheckType.OnEntryExit) } + onEntryExitFallThrough.foreach { dd => + processStatCheck(dd.rhs, returnCheck = CheckType.OnEntryExitFallThrough) + } allStepBlocks.foreach { step => processStatCheck(step, returnCheck = CheckType.None) } end processStatCheck @@ -271,7 +276,8 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: List(domainTypeTree) ) if anonfun.toString.startsWith("$anonfun") && process.toString == "process" && - forever.toString == "forever" && domainTypeTree.tpe.widenDealias.typeSymbol.name.toString == "RT" => + forever.toString == "forever" && + domainTypeTree.tpe.widenDealias.typeSymbol.name.toString == "RT" => processAnonDefSym = dd.symbol processScopeCtxSym = scopeCtx.symbol Some(ProcessForever(scopeCtx, dd.rhs)) @@ -307,6 +313,16 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: object OnExitDef: def unapply(tree: DefDef)(using Context): Boolean = tree.name.toString == "onExit" + object FallThroughDef: + def unapply(tree: DefDef)(using Context): Boolean = + if (tree.name.toString == "fallThrough") + if (!(tree.tpt.tpe <:< fallThroughType)) + report.error( + s"`def fallThrough` must return a DFHDL Boolean or Bit value.", + tree.srcPos + ) + true + else false object Goto: def unapply(tree: Ident)(using Context): Boolean = processStepDefs.contains(tree.symbol) @@ -353,8 +369,8 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: .appliedTo(Literal(Constant(dd.name.toString))) .appliedTo(dd.rhs.changeOwner(dd.symbol, ctx.owner)) .appliedTo(dfcStack.head, ref(processScopeCtxSym)) - case dd @ (OnEntryDef() | OnExitDef()) => - ref(pluginOnEntryExitSym) + case dd @ (OnEntryDef() | OnExitDef() | FallThroughDef()) => + ref(pluginOnEntryExitFallThroughSym) .appliedTo(dd.genMeta) .appliedTo(dd.rhs.changeOwner(dd.symbol, ctx.owner)) .appliedTo(dfcStack.head) @@ -372,7 +388,16 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: fromBooleanSym = requiredMethod("dfhdl.core.r__For_Plugin.fromBoolean") customWhileSym = requiredMethod("dfhdl.core.DFWhile.plugin") stepType = requiredClassRef("dfhdl.core.Step") - pluginOnEntryExitSym = requiredMethod("dfhdl.core.Step.pluginOnEntryExit") + val dfTypeType = requiredClassRef("dfhdl.core.DFType") + val noArgsType = requiredClassRef("dfhdl.core.NoArgs") + val dfBoolOrBitType = + dfTypeType.appliedTo(requiredClassRef("dfhdl.compiler.ir.DFBoolOrBit"), noArgsType) + val modifierType = requiredClassRef("dfhdl.core.Modifier") + val modifierAnyType = + modifierType.appliedTo(List(defn.AnyType, defn.AnyType, defn.AnyType, defn.AnyType)) + val dfValType = requiredClassRef("dfhdl.core.DFVal") + fallThroughType = dfValType.appliedTo(dfBoolOrBitType, modifierAnyType) + pluginOnEntryExitFallThroughSym = requiredMethod("dfhdl.core.Step.pluginOnEntryExitFallThrough") processStepDefs.clear() ctx end prepareForUnit From 87b6e40f01ecfab162f1e7ddae973eaa20047118 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 8 Feb 2026 19:07:38 +0200 Subject: [PATCH 005/115] reenable refCheck --- .../src/main/scala/dfhdl/compiler/stages/SanityCheck.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala index be16ae08b..a38f79e9f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala @@ -234,7 +234,7 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: end orderCheck def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = - // refCheck() + refCheck() memberExistenceCheck() ownershipCheck(designDB.top, designDB.membersNoGlobals.drop(1)) orderCheck() From 407a02df495a88661a5eba3bc09a4ed1ac100c0d Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 9 Feb 2026 17:45:03 +0200 Subject: [PATCH 006/115] update DropRTProcess stage to handle fallThrough blocks --- .../dfhdl/compiler/stages/DropRTProcess.scala | 63 +++++-- .../scala/StagesSpec/DropRTProcessSpec.scala | 155 ++++++++++++++++++ .../dfhdl/compiler/patching/MetaDesign.scala | 7 +- 3 files changed, 210 insertions(+), 15 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala index 47019d6f8..c38cbbb92 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala @@ -65,43 +65,78 @@ case object DropRTProcess extends Stage: ) prevBlockOrHeader = block } - val removedOnEntryExitMembers = mutable.Set.empty[DFMember] + val removedOnEntryExitFallThroughMembers = mutable.Set.empty[DFMember] val gotoDsns = gotos.map { g => new MetaDesign( g, Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove), dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) ): + import dfhdl.core.{DFIf, DFUnit, DFBool} val currentStepBlock = g.getOwnerStepBlock val nextStepBlock: StepBlock = g.stepRef.get match case stepBlock: StepBlock => stepBlock case Goto.ThisStep => currentStepBlock case Goto.NextStep => nextBlocks(currentStepBlock) case Goto.FirstStep => stateBlocks.head + def setState(nextStepBlock: StepBlock): Unit = + stateReg.din.:=(enumEntry(entries(nextStepBlock.getName)))(using + dfc.setMeta(g.meta) + ) if (currentStepBlock != nextStepBlock) - // add currentStepBlock-onExit and nextStepBlock onEntry members + // add currentStepBlock-onExit members currentStepBlock.members(MemberView.Folded).collectFirst { case onExit: StepBlock if onExit.isOnExit => onExit }.foreach { onExit => val onExitMembers = onExit.members(MemberView.Flattened) plantClonedMembers(onExit, onExitMembers) - removedOnEntryExitMembers += onExit - removedOnEntryExitMembers ++= onExitMembers - } - nextStepBlock.members(MemberView.Folded).collectFirst { - case onEntry: StepBlock if onEntry.isOnEntry => onEntry - }.foreach { onEntry => - val onEntryMembers = onEntry.members(MemberView.Flattened) - plantClonedMembers(onEntry, onEntryMembers) - removedOnEntryExitMembers += onEntry - removedOnEntryExitMembers ++= onEntryMembers + removedOnEntryExitFallThroughMembers += onExit + removedOnEntryExitFallThroughMembers ++= onExitMembers } + def handleNextStep(nextStepBlock: StepBlock): Unit = + // onEntry members will always activated, even if we fall-through the next step block + val nextStepBlockMembers = nextStepBlock.members(MemberView.Flattened) + nextStepBlockMembers.collectFirst { + case onEntry: StepBlock if onEntry.isOnEntry => onEntry + }.foreach { onEntry => + val onEntryMembers = onEntry.members(MemberView.Flattened) + plantClonedMembers(onEntry, onEntryMembers) + removedOnEntryExitFallThroughMembers += onEntry + removedOnEntryExitFallThroughMembers ++= onEntryMembers + } + setState(nextStepBlock) + // in case of circular fall-through, we stop + if (nextStepBlock != currentStepBlock) + val fallThroughCond = nextStepBlockMembers.collectFirst { + case fallThrough: StepBlock if fallThrough.isFallThrough => + val fallThroughMembers = fallThrough.members(MemberView.Flattened) + val fallThroughDFC = dfc.setMeta(fallThrough.meta.anonymize) + removedOnEntryExitFallThroughMembers += fallThrough + removedOnEntryExitFallThroughMembers ++= fallThroughMembers + // planting all members except the last one (the ident of the condition) + val clonedMemberMap = + plantClonedMembers(fallThrough, fallThroughMembers.dropRight(1)) + val Ident(origCond) = fallThroughMembers.last.runtimeChecked + val cond = clonedMemberMap.getOrElse(origCond, origCond).asInstanceOf[DFVal] + val ifBlock = DFIf.Block( + Some(cond.asValOf[DFBool]), + DFIf.Header(DFUnit)(using fallThroughDFC) + )(using fallThroughDFC) + dfc.enterOwner(ifBlock) + val fallThroughStepBlock = nextBlocks(nextStepBlock) + handleNextStep(fallThroughStepBlock) + dfc.exitOwner() + } + end if + end handleNextStep + handleNextStep(nextStepBlock) + else + setState(nextStepBlock) end if - stateReg.din.:=(enumEntry(entries(nextStepBlock.getName)))(using dfc.setMeta(g.meta)) } Iterator( List(dsn.patch, pbPatch), - removedOnEntryExitMembers.map(_ -> Patch.Remove()), + removedOnEntryExitFallThroughMembers.map(_ -> Patch.Remove()), caseDsns.map(_.patch), gotoDsns.map(_.patch) ).flatten diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala index 7f232682c..249d60a94 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala @@ -115,4 +115,159 @@ class DropRTProcessSpec extends StageSpec(): |end Foo""".stripMargin ) } + test("fall-through steps") { + class Foo extends RTDesign: + val x = Bit <> IN + val y = Bit <> OUT.REG init 0 + process: + def S0: Step = + y.din := 0 + NextStep + def S1: Step = + def fallThrough = x + def onEntry = + y.din := 1 + NextStep + def S2: Step = + def fallThrough = !x + def onEntry = + y.din := !y + NextStep + def S3: Step = + def fallThrough = x ^ x.reg(1, init = 0) + def onEntry = + y.din := y ^ y.reg + NextStep + def S4: Step = + y.din := 0 + if (x) S2 else S0 + end Foo + val top = (new Foo).dropRTProcess + assertCodeString( + top, + """|class Foo extends RTDesign: + | enum State(val value: UInt[3] <> CONST) extends Encoded.Manual(3): + | case S0 extends State(d"3'0") + | case S1 extends State(d"3'1") + | case S2 extends State(d"3'2") + | case S3 extends State(d"3'3") + | case S4 extends State(d"3'4") + | + | val x = Bit <> IN + | val y = Bit <> OUT.REG init 0 + | val state = State <> VAR.REG init State.S0 + | state match + | case State.S0 => + | y.din := 0 + | y.din := 1 + | state.din := State.S1 + | if (x) + | y.din := !y + | state.din := State.S2 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S3 + | if (x ^ x.reg(1, init = 0)) state.din := State.S4 + | end if + | end if + | case State.S1 => + | y.din := !y + | state.din := State.S2 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S3 + | if (x ^ x.reg(1, init = 0)) state.din := State.S4 + | end if + | case State.S2 => + | y.din := y ^ y.reg + | state.din := State.S3 + | if (x ^ x.reg(1, init = 0)) state.din := State.S4 + | case State.S3 => state.din := State.S4 + | case State.S4 => + | y.din := 0 + | if (x) + | y.din := !y + | state.din := State.S2 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S3 + | if (x ^ x.reg(1, init = 0)) state.din := State.S4 + | end if + | else state.din := State.S0 + | end if + | end match + |end Foo""".stripMargin + ) + } + test("circular fall-through steps") { + class Foo extends RTDesign: + val x = Bit <> IN + val y = Bit <> OUT.REG init 0 + process: + def S0: Step = + def fallThrough = x + def onEntry = + y.din := y + NextStep + def S1: Step = + def fallThrough = !x + def onEntry = + y.din := !y + NextStep + def S2: Step = + def fallThrough = x ^ x.reg(1, init = 0) + def onEntry = + y.din := y ^ y.reg + NextStep + end Foo + val top = (new Foo).dropRTProcess + assertCodeString( + top, + """|class Foo extends RTDesign: + | enum State(val value: UInt[2] <> CONST) extends Encoded.Manual(2): + | case S0 extends State(d"2'0") + | case S1 extends State(d"2'1") + | case S2 extends State(d"2'2") + | + | val x = Bit <> IN + | val y = Bit <> OUT.REG init 0 + | val state = State <> VAR.REG init State.S0 + | state match + | case State.S0 => + | y.din := !y + | state.din := State.S1 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S2 + | if (x ^ x.reg(1, init = 0)) + | y.din := y + | state.din := State.S0 + | end if + | end if + | case State.S1 => + | y.din := y ^ y.reg + | state.din := State.S2 + | if (x ^ x.reg(1, init = 0)) + | y.din := y + | state.din := State.S0 + | if (x) + | y.din := !y + | state.din := State.S1 + | end if + | end if + | case State.S2 => + | y.din := y + | state.din := State.S0 + | if (x) + | y.din := !y + | state.din := State.S1 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S2 + | end if + | end if + | end match + |end Foo""".stripMargin + ) + } end DropRTProcessSpec diff --git a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala index 94ea24d27..084d9be68 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala @@ -62,7 +62,10 @@ abstract class MetaDesign[+D <: DomainType]( else owner dfc.mutableDB.plantMember(updatedOwner, m, _ => cond) } - final def plantClonedMembers(baseOwner: ir.DFOwner, members: List[ir.DFMember]): Unit = + final def plantClonedMembers( + baseOwner: ir.DFOwner, + members: List[ir.DFMember] + ): Map[ir.DFMember, ir.DFMember] = val clonedMemberMap = members.map { m => m -> m.copyWithNewRefs }.toMap members.foreach { m => val cloned = clonedMemberMap(m) @@ -74,6 +77,8 @@ abstract class MetaDesign[+D <: DomainType]( dfc.mutableDB.newRefFor(clonedRef, clonedMemberMap.getOrElse(refMember, refMember)) } } + clonedMemberMap + end plantClonedMembers final def applyBlock(owner: ir.DFOwner)(block: => Unit): Unit = dfc.mutableDB.OwnershipContext.enter(owner) block From 1f9c03e3b559cfb9a1d6bd5618197dec152f420a Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 10 Feb 2026 10:34:58 +0200 Subject: [PATCH 007/115] update DropRTWaits stage to start stage numbering from 0 and add support for fall-through loops --- .../dfhdl/compiler/stages/DropRTWaits.scala | 42 +++- .../scala/StagesSpec/DropRTWaitsSpec.scala | 185 ++++++++++++++---- 2 files changed, 175 insertions(+), 52 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala index ebcbee597..304ec2ff4 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala @@ -17,19 +17,19 @@ import scala.collection.mutable * end S_N * ``` * where N is the step number. - * 2. Multiple single cycle waits are replaced with sequential step definitions (S_1, S_2, S_3, ...) + * 2. Multiple single cycle waits are replaced with sequential step definitions (S_0, S_1, S_2, ...) * ```scala + * def S_0: Step = + * NextStep + * end S_0 * def S_1: Step = * NextStep * end S_1 * def S_2: Step = * NextStep * end S_2 - * def S_3: Step = - * NextStep - * end S_3 * ``` - * where 1, 2, 3 are the step numbers. + * where 0, 1, 2 are the step numbers. * 3. While loop `while (condition) { body }` is replaced with: * ```scala * def S_N: Step = @@ -41,8 +41,22 @@ import scala.collection.mutable * } * end S_N * ``` - * 4. While loops with nested waits: waits inside while loops become step definitions with nested naming - * (S_1_1, S_1_2, etc.), and the while loop itself becomes a step definition + * where N is the step number. + * 4. While loops that are tagged with `FALL_THROUGH` are replaced with: + * ```scala + * def S_N: Step = + * def fallThrough = !condition + * if (condition) { + * body + * ThisStep + * } else { + * NextStep + * } + * end S_N + * ``` + * where N is the step number. + * 5. While loops with nested waits: waits inside while loops become step definitions with nested naming + * (S_0_0, S_0_1, etc.), and the while loop itself becomes a step definition (S_0) */ //format: on case object DropRTWaits extends Stage: @@ -56,7 +70,7 @@ case object DropRTWaits extends Stage: case pb: ProcessBlock if pb.isInRTDomain => val pbMembers = pb.members(MemberView.Flattened) // the nested step number is stored in a stack, the head of the list is the current step number - var stepNumberNest = List(1) + var stepNumberNest = List(0) // for nested while loops, we need to keep track of the exit members. // an exit member may have multiple patches that need to be applied in the LIFO order they are stacked. var exitMemberPatches = mutable.Map.empty[DFMember, List[Patch]] @@ -71,7 +85,7 @@ case object DropRTWaits extends Stage: case Some(patches) => exitMemberPatches += patch._1 -> (patch._2 :: patches) case None => exitMemberPatches += patch._1 -> List(patch._2) // starting a new step block with a new step number - stepNumberNest = 1 :: stepNumberNest + stepNumberNest = 0 :: stepNumberNest // checking if the step block has an exit member and returning the patches that need to be applied. def checkAndExitStepBlock(lastMember: DFMember): List[(DFMember, Patch)] = exitMemberPatches.get(lastMember) match @@ -114,7 +128,15 @@ case object DropRTWaits extends Stage: import dfhdl.core.{StepBlock, DFIf, DFBool, DFUnit} val step = StepBlock.forced(using dfc.setName(stepName)) dfc.enterOwner(step) - val cond = wb.guardRef.get.asValOf[DFBool] + val wbGuard = wb.guardRef.get + if (wb.isFallThrough) + val fallThrough = StepBlock.forced(using dfc.setName("fallThrough")) + dfc.enterOwner(fallThrough) + val clonedCond = !wbGuard.cloneAnonValueAndDepsHere.asValOf[DFBool] + dfhdl.core.DFVal.Alias.AsIs.ident(clonedCond)(using dfc.anonymize) + dfc.exitOwner() + end if + val cond = wbGuard.asValOf[DFBool] val ifBlock = DFIf.Block(Some(cond), DFIf.Header(DFUnit)) dfc.exitOwner() // creating the else part of the while loop step block, to be applied when the while loop exits. diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala index b2b5ce929..9ffb58f1d 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala @@ -4,13 +4,44 @@ import dfhdl.* import dfhdl.compiler.stages.dropRTWaits class DropRTWaitsSpec extends StageSpec(): - test("basic single cycle wait") { + test("empty RT process block") { + class Foo extends RTDesign: + process {} + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | process: + | + |end Foo""".stripMargin + ) + } + test("single statement in process block") { + class Foo extends RTDesign: + val i = Bit <> IN + val x = Bit <> OUT.REG + process: + x.din := i + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | val x = Bit <> OUT.REG + | process: + | x.din := i + |end Foo""".stripMargin + ) + } + test("basic single cycle wait before assignment") { class Foo extends RTDesign: val i = Bit <> IN val x = Bit <> OUT.REG process: - x.din := 1 1.cy.wait + x.din := i end Foo val top = (new Foo).dropRTWaits assertCodeString( @@ -19,10 +50,32 @@ class DropRTWaitsSpec extends StageSpec(): | val i = Bit <> IN | val x = Bit <> OUT.REG | process: - | x.din := 1 - | def S_1: Step = + | def S_0: Step = | NextStep - | end S_1 + | end S_0 + | x.din := i + |end Foo""".stripMargin + ) + } + test("basic single cycle wait after assignment") { + class Foo extends RTDesign: + val i = Bit <> IN + val x = Bit <> OUT.REG + process: + x.din := i + 1.cy.wait + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | val x = Bit <> OUT.REG + | process: + | x.din := i + | def S_0: Step = + | NextStep + | end S_0 |end Foo""".stripMargin ) } @@ -48,21 +101,21 @@ class DropRTWaitsSpec extends StageSpec(): | val x = Bit <> OUT.REG | process: | x.din := 1 + | def S_0: Step = + | NextStep + | end S_0 + | x.din := !x | def S_1: Step = | NextStep | end S_1 - | x.din := !x + | x.din := 0 | def S_2: Step = | NextStep | end S_2 - | x.din := 0 + | x.din := !x | def S_3: Step = | NextStep | end S_3 - | x.din := !x - | def S_4: Step = - | NextStep - | end S_4 |end Foo""".stripMargin ) } @@ -83,13 +136,13 @@ class DropRTWaitsSpec extends StageSpec(): | val x = Bit <> OUT.REG | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | process: - | def S_1: Step = + | def S_0: Step = | if (waitCnt1 != d"8'149") | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep | end if - | end S_1 + | end S_0 | waitCnt1.din := d"8'0" | x.din := !x |end Foo""".stripMargin @@ -115,22 +168,22 @@ class DropRTWaitsSpec extends StageSpec(): | val x = Bit <> OUT.REG | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | process: - | def S_1: Step = + | def S_0: Step = | if (waitCnt1 != d"8'149") | waitCnt1.din := waitCnt1 + d"8'1" - | def S_1_1: Step = + | def S_0_0: Step = | NextStep - | end S_1_1 - | def S_1_2: Step = + | end S_0_0 + | def S_0_1: Step = | NextStep - | end S_1_2 - | def S_1_3: Step = + | end S_0_1 + | def S_0_2: Step = | NextStep - | end S_1_3 + | end S_0_2 | ThisStep | else NextStep | end if - | end S_1 + | end S_0 | waitCnt1.din := d"8'0" | x.din := !x |end Foo""".stripMargin @@ -159,22 +212,70 @@ class DropRTWaitsSpec extends StageSpec(): | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | val waitCnt2 = UInt(8) <> VAR.REG init d"8'0" | process: - | def S_1: Step = + | def S_0: Step = | if (waitCnt1 != d"8'149") | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep | end if + | end S_0 + | waitCnt1.din := d"8'0" + | x.din := !x + | def S_1: Step = + | if (waitCnt2 != d"8'149") + | waitCnt2.din := waitCnt2 + d"8'1" + | ThisStep + | else NextStep + | end if | end S_1 + | waitCnt2.din := d"8'0" + | x.din := 1 + |end Foo""".stripMargin + ) + } + test("basic multiple while loops with fall-through") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + val waitCnt1 = UInt(8) <> VAR.REG init 0 + val waitCnt2 = UInt(8) <> VAR.REG init 0 + process: + while (waitCnt1 != 149) + FALL_THROUGH + waitCnt1.din := waitCnt1 + 1 + waitCnt1.din := 0 + x.din := !x + while (waitCnt2 != 149) + waitCnt2.din := waitCnt2 + 1 + waitCnt2.din := 0 + x.din := 1 + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" + | val waitCnt2 = UInt(8) <> VAR.REG init d"8'0" + | process: + | def S_0: Step = + | def fallThrough: Boolean <> VAL = + | !(waitCnt1 != d"8'149") + | end fallThrough + | if (waitCnt1 != d"8'149") + | waitCnt1.din := waitCnt1 + d"8'1" + | ThisStep + | else NextStep + | end if + | end S_0 | waitCnt1.din := d"8'0" | x.din := !x - | def S_2: Step = + | def S_1: Step = | if (waitCnt2 != d"8'149") | waitCnt2.din := waitCnt2 + d"8'1" | ThisStep | else NextStep | end if - | end S_2 + | end S_1 | waitCnt2.din := d"8'0" | x.din := 1 |end Foo""".stripMargin @@ -202,22 +303,22 @@ class DropRTWaitsSpec extends StageSpec(): | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | val waitCnt2 = UInt(8) <> VAR.REG init d"8'0" | process: - | def S_1: Step = + | def S_0: Step = | if (waitCnt1 != d"8'149") - | def S_1_1: Step = + | def S_0_0: Step = | if (waitCnt2 != d"8'149") | waitCnt2.din := waitCnt2 + d"8'1" | ThisStep | else NextStep | end if - | end S_1_1 + | end S_0_0 | waitCnt2.din := d"8'0" | x.din := !x | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep | end if - | end S_1 + | end S_0 | waitCnt1.din := d"8'0" |end Foo""".stripMargin ) @@ -245,25 +346,25 @@ class DropRTWaitsSpec extends StageSpec(): | process: | if (x) | x.din := !x - | def S_1: Step = + | def S_0: Step = | NextStep - | end S_1 + | end S_0 | else | x.din := !x + | def S_1: Step = + | NextStep + | end S_1 | def S_2: Step = | NextStep | end S_2 | def S_3: Step = | NextStep | end S_3 - | def S_4: Step = - | NextStep - | end S_4 | end if | x.din := !x - | def S_5: Step = + | def S_4: Step = | NextStep - | end S_5 + | end S_4 |end Foo""".stripMargin ) } @@ -294,30 +395,30 @@ class DropRTWaitsSpec extends StageSpec(): | process: | if (x) | x.din := !x - | def S_1: Step = + | def S_0: Step = | if (waitCnt1 != d"8'149") | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep | end if - | end S_1 + | end S_0 | waitCnt1.din := d"8'0" | else | x.din := !x + | def S_1: Step = + | NextStep + | end S_1 | def S_2: Step = | NextStep | end S_2 | def S_3: Step = | NextStep | end S_3 - | def S_4: Step = - | NextStep - | end S_4 | end if | x.din := !x - | def S_5: Step = + | def S_4: Step = | NextStep - | end S_5 + | end S_4 |end Foo""".stripMargin ) } From 9f978786d419345162a2c35596ed7772da899c8c Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 10 Feb 2026 10:35:10 +0200 Subject: [PATCH 008/115] Add getOwnerProcessBlock method to DFMember for retrieving owning ProcessBlock --- compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index 0bbe38d4c..a34594d7f 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -30,6 +30,9 @@ sealed trait DFMember extends Product, Serializable, HasRefCompare[DFMember] der final def getOwnerStepBlock(using MemberGetSet): StepBlock = getOwner match case b: StepBlock => b case o => o.getOwnerStepBlock + final def getOwnerProcessBlock(using MemberGetSet): ProcessBlock = getOwner match + case b: ProcessBlock => b + case o => o.getOwnerProcessBlock final def getOwnerDesign(using MemberGetSet): DFDesignBlock = getOwnerBlock match case d: DFDesignBlock => d From d8cd496b67f678198caec89cd2030e97f57b72ae Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 10 Feb 2026 11:28:21 +0200 Subject: [PATCH 009/115] Refactor Bind unapply method to use hasTagOf for clarity and improve formatting in collectRelMembers method --- .../dfhdl/compiler/analysis/DFValAnalysis.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala index 110b92bb8..c85426505 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala @@ -84,8 +84,7 @@ object AsOpaque: object Bind: def unapply(alias: DFVal.Alias)(using MemberGetSet): Option[DFVal] = - if (alias.getTagOf[BindTag].isDefined) - Some(alias.relValRef.get) + if (alias.hasTagOf[BindTag]) Some(alias.relValRef.get) else None object ClkEdge: @@ -415,11 +414,12 @@ extension (origVal: DFVal) forceIncludeOrigVal: Boolean )(using MemberGetSet): List[DFVal] = if (origVal.isAnonymous && !origVal.isGlobal || forceIncludeOrigVal) - origVal :: origVal.getRefs.map(_.get).view - .flatMap { - case dfVal: DFVal => dfVal.collectRelMembersRecur(false) - case _ => Nil - }.toList + origVal :: + origVal.getRefs.map(_.get).view + .flatMap { + case dfVal: DFVal => dfVal.collectRelMembersRecur(false) + case _ => Nil + }.toList else Nil @targetName("collectRelMembersDFVal") def collectRelMembers(includeOrigVal: Boolean)(using MemberGetSet): List[DFVal] = From ff1e8ca23f8fa98de22a6256a47b8f89613abd1b Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 10 Feb 2026 13:02:09 +0200 Subject: [PATCH 010/115] 1. add checks for forbidden named values except for iterators and binds inside RT processes 2. fix SimplifyRTOps stage accordingly 3. fix PrintCodeString tests accordingly 4. proper DFC propagation in TextOut to fix compiler crash (found when adding new test in ElaborationChecks) --- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 47 +++++++++++++++---- .../dfhdl/compiler/stages/SimplifyRTOps.scala | 20 +++++--- .../StagesSpec/PrintCodeStringSpec.scala | 12 +++-- .../scala/StagesSpec/SimplifyRTOpsSpec.scala | 11 ++--- core/src/main/scala/dfhdl/core/TextOut.scala | 21 +++++---- .../test/scala/ElaborationChecksSpec.scala | 29 ++++++++++++ 6 files changed, 103 insertions(+), 37 deletions(-) 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 0adc121ad..149842bd1 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -124,7 +124,8 @@ final case class DB( if (owner == namedDFTypeMember.getOwnerDesign) namedDFTypeMap // same design block -> nothing to do else - namedDFTypeMap + (dfType -> None) // used in more than one block -> global named type + namedDFTypeMap + + (dfType -> None) // used in more than one block -> global named type case Some(None) => namedDFTypeMap // known to be a global type // found new named type case None => @@ -782,7 +783,8 @@ final case class DB( case internal: DFDesignBlock => internal.usesClkRst.usesClk case _ => false } || reversedDependents.getOrElse(domainOwner, Set()).exists(_.usesClkRst.usesClk) || - domainOwner.isTop && (domainOwner.getExplicitCfg.clkCfg match + domainOwner.isTop && + (domainOwner.getExplicitCfg.clkCfg match case ClkCfg.Explicit(inclusionPolicy = ClkRstInclusionPolicy.AlwaysAtTop) => true case _ => false) @@ -794,7 +796,8 @@ final case class DB( case internal: DFDesignBlock => internal.usesClkRst.usesRst case _ => false } || reversedDependents.getOrElse(domainOwner, Set()).exists(_.usesClkRst.usesRst) || - domainOwner.isTop && (domainOwner.getExplicitCfg.rstCfg match + domainOwner.isTop && + (domainOwner.getExplicitCfg.rstCfg match case RstCfg.Explicit(inclusionPolicy = ClkRstInclusionPolicy.AlwaysAtTop) => true case _ => false) end extension @@ -1099,7 +1102,8 @@ final case class DB( domainOwner.getDomainClkConstraintsView.foreach { case constraints.IO(loc = loc: String) => locationMap.get(loc).foreach { prevPort => - locationCollisions += s"${prevPort} and ${domainOwner.getFullName} are both assigned to location `${loc}`" + locationCollisions += + s"${prevPort} and ${domainOwner.getFullName} are both assigned to location `${loc}`" } locationMap += loc -> domainOwner.getFullName foundLoc = true @@ -1121,14 +1125,17 @@ final case class DB( case constraints.IO(bitIdx = None, loc = loc: String) => bitSet.clear() locationMap.get(loc).foreach { prevPort => - locationCollisions += s"${prevPort} and ${port.getFullName} are both assigned to location `${loc}`" + locationCollisions += + s"${prevPort} and ${port.getFullName} are both assigned to location `${loc}`" } locationMap += loc -> port.getFullName if (port.width != 1) - locationCollisions += s"${port.getFullName} has mutliple bits assigned to location `${loc}`" + locationCollisions += + s"${port.getFullName} has mutliple bits assigned to location `${loc}`" case constraints.IO(bitIdx = bitIdx: Int, loc = loc: String) => locationMap.get(loc).foreach { prevPort => - locationCollisions += s"${prevPort} and ${port.getFullName}(${bitIdx}) are both assigned to location `${loc}`" + locationCollisions += + s"${prevPort} and ${port.getFullName}(${bitIdx}) are both assigned to location `${loc}`" } locationMap += loc -> s"${port.getFullName}(${bitIdx})" bitSet -= bitIdx @@ -1176,7 +1183,8 @@ final case class DB( case constraints.IO(dir = dir: Dir) => (dir, port.modifier.dir) match case (Dir.IN, Dir.OUT) | (Dir.OUT, Dir.IN) => - errors += s"${port.getFullName} direction (${port.modifier.dir}) has a resource direction ($dir) mismatch." + errors += + s"${port.getFullName} direction (${port.modifier.dir}) has a resource direction ($dir) mismatch." case _ => case _ => } @@ -1193,9 +1201,29 @@ final case class DB( |Make sure you connect the resource to the port with the correct direction. |""".stripMargin ) - end portResourceDirCheck + def stepBlockCheck(): Unit = + val namedRTProcessValues = members.view.collect { + case pb: ProcessBlock if pb.isInRTDomain => + getMembersOf(pb, MemberView.Flattened).view.collect { + case dfVal: DFVal if !dfVal.isAnonymous => dfVal + }.filter { dfVal => + !(dfVal.hasTagOf[BindTag] || dfVal.hasTagOf[IteratorTag]) + } + }.flatten + if (namedRTProcessValues.nonEmpty) + throw new IllegalArgumentException( + //format: off + s"""|Named DFHDL values are not allowed in RT process blocks. Found the following named values: + | ${namedRTProcessValues.map(v => s"${v.getName} at ${v.meta.position}").mkString("\n ")} + |To Fix: + |Use anonymous values instead. + |""".stripMargin + //format: on + ) + end stepBlockCheck + def check(): Unit = nameCheck() connectionTable // causes connectivity checks @@ -1207,6 +1235,7 @@ final case class DB( waitCheck() portLocationCheck() portResourceDirCheck() + stepBlockCheck() end check // There can only be a single connection to a value in a given range diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala index 9ed680225..6bf5374d1 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala @@ -19,8 +19,8 @@ import scala.collection.mutable */ //format: on case object SimplifyRTOps extends Stage: - def dependencies: List[Stage] = List(DropTimedRTWaits, DFHDLUniqueNames) - def nullifies: Set[Stage] = Set(DropUnreferencedAnons) + def dependencies: List[Stage] = List(DropTimedRTWaits) + def nullifies: Set[Stage] = Set(DropUnreferencedAnons, DFHDLUniqueNames) def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = given RefGen = RefGen.fromGetSet @@ -84,18 +84,24 @@ case object SimplifyRTOps extends Stage: case DFVal.Const(data = Some(value: BigInt)) if value == 1 => false case _ => true if (replaceWithWhile) + import dfhdl.core.{DFUInt, asValOf, dfType} + val iterType = cyclesVal.asValOf[DFUInt[Int]].dfType + val regDsn = new MetaDesign( + waitMember.getOwnerProcessBlock, + Patch.Add.Config.Before, + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) + ): + val waitCnt = iterType.<>(VAR.REG)(using dfc.setName("waitCnt")) val dsn = new MetaDesign( waitMember, Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement), dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) ): - val iterType = cyclesVal.asValOf[UInt[Int]].dfType + import regDsn.waitCnt // TODO: unclear why we cannot directly use 0 and 1 here, but there is indication that // the bug originates from a conversion from UInt[1] to the iterType. It could be that the // methods under core.DFVal.Alias are not working as intended because of meta-programming. - val initZero = dfhdl.core.DFVal.Const(iterType, Some(BigInt(0))) - val waitCnt = - iterType.<>(VAR.REG).initForced(List(initZero))(using dfc.setName("waitCnt")) + waitCnt.din := dfhdl.core.DFVal.Const(iterType, Some(BigInt(0))) // the upper bound for the while loop count val upperBound = cyclesVal match // the upper bound is reduced to a simpler form when the number of cycles is a constant anonymous value @@ -109,7 +115,7 @@ case object SimplifyRTOps extends Stage: dfc.enterOwner(whileBlock) waitCnt.din := waitCnt + dfhdl.core.DFVal.Const(iterType, Some(BigInt(1))) dfc.exitOwner() - Some(dsn.patch) + List(regDsn.patch, dsn.patch) else None end if }.flatten.toList diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index de8891825..16ddfe44c 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -1127,6 +1127,7 @@ class PrintCodeStringSpec extends StageSpec: test("for/while loop printing with COMB_LOOP") { class Foo extends RTDesign: val matrix = Bits(10) X 8 X 8 <> OUT.REG + val ii = UInt.until(8) <> VAR process: for ( i <- 0 until 8; @@ -1138,7 +1139,7 @@ class PrintCodeStringSpec extends StageSpec: ) COMB_LOOP matrix(i)(j)(k).din := 1 - val ii = UInt.until(8) <> VAR init 0 + ii := 0 while (ii != 7) COMB_LOOP matrix(ii)(0)(0).din := 0 @@ -1150,6 +1151,7 @@ class PrintCodeStringSpec extends StageSpec: top, """|class Foo extends RTDesign: | val matrix = Bits(10) X 8 X 8 <> OUT.REG + | val ii = UInt(3) <> VAR | process: | for (i <- 0 until 8) | COMB_LOOP @@ -1165,7 +1167,7 @@ class PrintCodeStringSpec extends StageSpec: | end for | end if | end for - | val ii = UInt(3) <> VAR init d"3'0" + | ii := d"3'0" | while (ii != d"3'7") | COMB_LOOP | matrix(ii.toInt)(0)(0).din := 0 @@ -1178,6 +1180,7 @@ class PrintCodeStringSpec extends StageSpec: test("for/while loop printing with FALL_THROUGH") { class Foo extends RTDesign: val matrix = Bits(10) X 8 X 8 <> OUT.REG + val ii = UInt.until(8) <> VAR process: for ( i <- 0 until 8; @@ -1189,7 +1192,7 @@ class PrintCodeStringSpec extends StageSpec: ) FALL_THROUGH matrix(i)(j)(k).din := 1 - val ii = UInt.until(8) <> VAR init 0 + ii := 0 while (ii != 7) FALL_THROUGH matrix(ii)(0)(0).din := 0 @@ -1201,6 +1204,7 @@ class PrintCodeStringSpec extends StageSpec: top, """|class Foo extends RTDesign: | val matrix = Bits(10) X 8 X 8 <> OUT.REG + | val ii = UInt(3) <> VAR | process: | for (i <- 0 until 8) | FALL_THROUGH @@ -1216,7 +1220,7 @@ class PrintCodeStringSpec extends StageSpec: | end for | end if | end for - | val ii = UInt(3) <> VAR init d"3'0" + | ii := d"3'0" | while (ii != d"3'7") | FALL_THROUGH | matrix(ii.toInt)(0)(0).din := 0 diff --git a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala index 32107d209..fd47cb269 100644 --- a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala @@ -64,8 +64,6 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): waitUntil(i) waitUntil(i.falling) waitUntil(i.rising) - val temp = i.rising - waitUntil(temp) x.din := 0 end Foo val top = (new Foo).simplifyRTOps @@ -84,9 +82,6 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): | end while | while (i.reg(1, init = 1) || (!i)) | end while - | val temp = (!i.reg(1, init = 1)) && i - | while (!temp) - | end while | x.din := 0 |end Foo""".stripMargin ) @@ -140,14 +135,16 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): """|class Foo extends RTDesign: | val x = Bit <> OUT.REG | val waitParam: UInt[26] <> CONST = d"26'50000000" + | val waitCnt = UInt(26) <> VAR.REG + | val waitCnt = UInt(26) <> VAR.REG | process: | x.din := 1 - | val waitCnt = UInt(26) <> VAR.REG init d"26'0" + | waitCnt.din := d"26'0" | while (waitCnt != d"26'49999999") | waitCnt.din := waitCnt + d"26'1" | end while | x.din := 0 - | val waitCnt = UInt(26) <> VAR.REG init d"26'0" + | waitCnt.din := d"26'0" | while (waitCnt != (waitParam - d"26'1")) | waitCnt.din := waitCnt + d"26'1" | end while diff --git a/core/src/main/scala/dfhdl/core/TextOut.scala b/core/src/main/scala/dfhdl/core/TextOut.scala index 99a09eae3..7f59a6d5e 100644 --- a/core/src/main/scala/dfhdl/core/TextOut.scala +++ b/core/src/main/scala/dfhdl/core/TextOut.scala @@ -42,33 +42,35 @@ object TextOut: transparent inline def print(inline msg: Any): Unit = compiletime.summonFrom { case given ScalaPrintsFlag => scala.Predef.print(msg) - case given DFC.Scope.Local => textOut(Op.Print, Some(msg)) - case _ => scala.Predef.print(msg) + case given DFC.Scope.Local => + textOut(Op.Print, Some(msg))(using compiletime.summonInline[DFC]) + case _ => scala.Predef.print(msg) } transparent inline def println(inline msg: Any): Unit = compiletime.summonFrom { case given ScalaPrintsFlag => scala.Predef.println(msg) - case given DFC.Scope.Local => textOut(Op.Println, Some(msg)) - case _ => scala.Predef.println(msg) + case given DFC.Scope.Local => + textOut(Op.Println, Some(msg))(using compiletime.summonInline[DFC]) + case _ => scala.Predef.println(msg) } transparent inline def println(): Unit = compiletime.summonFrom { case given ScalaPrintsFlag => scala.Predef.println() - case given DFC.Scope.Local => textOut(Op.Println, None) + case given DFC.Scope.Local => textOut(Op.Println, None)(using compiletime.summonInline[DFC]) case _ => scala.Predef.println() } inline def report(inline message: Any, severity: Severity = Severity.Info): Unit = - textOut(Op.Report(severity), Some(message)) + textOut(Op.Report(severity), Some(message))(using compiletime.summonInline[DFC]) inline def assert( inline assertion: Any, inline message: Any, severity: Severity )(using dfc: DFC): Unit = - assertDFHDL(assertion, Some(message), severity) + assertDFHDL(assertion, Some(message), severity)(using compiletime.summonInline[DFC]) transparent inline def assert(inline assertion: Any, inline message: => Any): Unit = compiletime.summonFrom { @@ -118,11 +120,11 @@ object TextOut: private inline def textOut( op: ir.TextOut.Op, inline msgOption: Option[Any] - ): Unit = ${ textOutMacro('op, 'msgOption) } + )(using dfc: DFC): Unit = ${ textOutMacro('op, 'msgOption)('dfc) } private def textOutMacro( op: Expr[ir.TextOut.Op], msgOption: Expr[Option[Any]] - )(using + )(dfc: Expr[DFC])(using Quotes ): Expr[Unit] = import quotes.reflect.* @@ -133,7 +135,6 @@ object TextOut: case _ => t var msgPartsExpr: Expr[List[String]] = '{ List.empty[String] } var msgArgsExpr: Expr[List[DFValAny]] = '{ List.empty[DFValAny] } - val dfc = Expr.summon[DFC].get recurse(msgOption.asTerm).asExpr match case '{ None } => case '{ Some($msg) } => diff --git a/lib/src/test/scala/ElaborationChecksSpec.scala b/lib/src/test/scala/ElaborationChecksSpec.scala index ecc699ddd..eaf7e1df4 100644 --- a/lib/src/test/scala/ElaborationChecksSpec.scala +++ b/lib/src/test/scala/ElaborationChecksSpec.scala @@ -555,4 +555,33 @@ class ElaborationChecksSpec extends DesignSpec: |To Fix: |Make sure you connect the resource to the port with the correct direction.""".stripMargin ) + test("named values in RT process blocks are forbidden"): + object Test: + @top(false) class Top extends RTDesign: + val x = Bit <> IN + val p = Bits(8) <> IN + process: + val y = Bit <> VAR + val z = x ^ x + for (i <- 0 until 10) + println(i) + def MyStep: Step = + val w = !x + FirstStep + p match + case b"101${v: B[2]}010" => + println(v) + case _ => + end Top + end Test + import Test.* + assertElaborationErrors(Top())( + s"""|Elaboration errors found! + |Named DFHDL values are not allowed in RT process blocks. Found the following named values: + | y at ${currentFilePos}ElaborationChecksSpec.scala:564:19 - 564:29 + | z at ${currentFilePos}ElaborationChecksSpec.scala:565:19 - 565:24 + | w at ${currentFilePos}ElaborationChecksSpec.scala:569:21 - 569:23 + |To Fix: + |Use anonymous values instead.""".stripMargin + ) end ElaborationChecksSpec From 595762af12852b991370d57a06e7c784827bfaa1 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 10 Feb 2026 13:02:20 +0200 Subject: [PATCH 011/115] Update DFC initialization in MetaDesign to include position from positionMember for improved context handling --- core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala index 084d9be68..0fea68954 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala @@ -32,7 +32,9 @@ abstract class MetaDesign[+D <: DomainType]( final override private[dfhdl] def initOwner: Design.Block = dfc.mutableDB.addMember(injectedOwner) injectedOwner.getThisOrOwnerDesign.asFE - final override protected def __dfc: DFC = DFC.emptyNoEO.copy(refGen = refGen) + // default context position is set according to the positionMember + final override protected def __dfc: DFC = + DFC.emptyNoEO.copy(refGen = refGen, position = positionMember.meta.position) injectedOwner match case design: ir.DFDesignBlock => // do nothing From 066be307eb74aeca75cae284b4aab390baa93804 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 11 Feb 2026 19:47:32 +0200 Subject: [PATCH 012/115] add DFHDL DFVal ident wrapper for trivial `val y = x` ident expressions (creating new identical named value) --- .../StagesSpec/PrintCodeStringSpec.scala | 6 ++-- .../main/scala/dfhdl/core/r__For_Plugin.scala | 2 ++ .../test/scala/CoreSpec/DFVectorSpec.scala | 3 +- .../scala/plugin/MetaContextPlacerPhase.scala | 29 ++++++++++++++++++- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index 16ddfe44c..aa27332e0 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -201,6 +201,7 @@ class PrintCodeStringSpec extends StageSpec: test("RTDesign with class extension and parameters") { val gp: Bit <> CONST = 1 + val gp2 = gp val i: SInt[16] <> CONST = 0 val i2 = i + 5 class ID(dp: Bit <> CONST) extends RTDesign: @@ -210,7 +211,7 @@ class PrintCodeStringSpec extends StageSpec: val flag = Bit <> IN init dp || gp y := x.reg.reg(2, init = c - i) - x end ID - class IDExt(dpNew: Bit <> CONST) extends ID(gp && dpNew): + class IDExt(dpNew: Bit <> CONST) extends ID(gp2 && dpNew): val z = Bit <> OUT z := dpNew @@ -218,11 +219,12 @@ class PrintCodeStringSpec extends StageSpec: assertNoDiff( id, """|val gp: Bit <> CONST = 1 + |val gp2: Bit <> CONST = gp |val i: SInt[16] <> CONST = sd"16'0" |val i2: SInt[16] <> CONST = i + sd"16'5" | |class IDExt( - | val dp: Bit <> CONST = gp && gp, + | val dp: Bit <> CONST = gp2 && gp, | val dpNew: Bit <> CONST = gp |) extends RTDesign: | val c: SInt[16] <> CONST = sd"16'3" diff --git a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala index d6adfdd9d..8919bdd3e 100644 --- a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala +++ b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala @@ -157,4 +157,6 @@ object r__For_Plugin: output.asInstanceOf[V] end if end designFromDef + def identVal[V <: DFValAny](value: V)(using DFC): V = + DFVal.Alias.AsIs.ident(value).asInstanceOf[V] end r__For_Plugin diff --git a/core/src/test/scala/CoreSpec/DFVectorSpec.scala b/core/src/test/scala/CoreSpec/DFVectorSpec.scala index bd3772e26..a368935d3 100644 --- a/core/src/test/scala/CoreSpec/DFVectorSpec.scala +++ b/core/src/test/scala/CoreSpec/DFVectorSpec.scala @@ -24,7 +24,8 @@ class DFVectorSpec extends DFSpec: |val v3 = UInt(8) X 4 X 4 <> VAR |v3 := all(all(d"8'0")) |v3(3)(1) := d"8'25" - |v3 := v3 + |val t = v3 + |v3 := t |val len: Int <> CONST = 3 |val v4 = UInt(8) X len <> VAR init all(d"8'0") |val v5: UInt[4] X len.type <> CONST = all(d"4'0") diff --git a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala index ebfd04491..72ffa9038 100644 --- a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala @@ -27,6 +27,7 @@ import dotty.tools.dotc.ast.Trees.Alternative is instantiated regularly the instance is transformed into an anonymous class instance with the override, otherwise all is required is to add the additional override to an existing anonymous DFHDL class instance. + Additionally, it transforms basic val x = y to val x = dfhdl.core.r__For_Plugin.identVal(y) if y is a DFVal */ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: import tpd._ @@ -49,6 +50,7 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: var noTopAnnotIsRequired: TypeRef = uninitialized var listMapEmptySym: TermSymbol = uninitialized var listMapSym: TermSymbol = uninitialized + var dfhdlDFValIdentSym: TermSymbol = uninitialized val defaultParamMap = mutable.Map.empty[ClassSymbol, Map[Int, Tree]] override def prepareForTypeDef(tree: TypeDef)(using Context): Context = val sym = tree.symbol @@ -237,7 +239,8 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: case Apply(Select(New(Ident(n)), _), _) if n == StdNames.tpnme.ANON_CLASS => tree case _ if ( - tree.fun.symbol.isClassConstructor && tpe.isParameterless && !ctx.owner.isClassConstructor && + tree.fun.symbol.isClassConstructor && tpe.isParameterless && + !ctx.owner.isClassConstructor && !ctx.owner.isClassConstructor && tpe.typeConstructor <:< hasDFCTpe ) => val cls = newNormalizedClassSymbol( @@ -291,6 +294,29 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: cpy.Block(tree)(stats = List(updatedTypeDef), expr = tree.expr) case _ => tree + // transform basic val x = y to val x = dfhdl.core.r__For_Plugin.identVal(y) if y is a DFVal + override def transformValDef(tree: ValDef)(using Context): ValDef = + object DFValIdent: + def unapply(tree: Tree)(using Context): Option[Tree] = + tree match + case ident @ Ident(name) if !name.toString.contains("$") => Some(tree) + case Select(DFValIdent(_), _) => Some(tree) + case This(DFValIdent(_)) => Some(tree) + case _ => None + end DFValIdent + tree.rhs match + case DFValIdent(rhs) + if !tree.symbol.flags.is(InlineProxy) && tree.tpt.tpe.dfValTpeOpt.nonEmpty => + val dfc = dfcArgStack.headOption.getOrElse(ref(emptyNoEODFCSym)) + val updatedRHS = + ref(dfhdlDFValIdentSym) + .appliedToType(rhs.tpe.widen) + .appliedTo(rhs) + .appliedTo(dfc) + cpy.ValDef(tree)(rhs = updatedRHS) + case _ => tree + end match + end transformValDef override def prepareForUnit(tree: Tree)(using Context): Context = super.prepareForUnit(tree) @@ -306,6 +332,7 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: noTopAnnotIsRequired = requiredClassRef("dfhdl.internals.NoTopAnnotIsRequired") listMapEmptySym = requiredMethod("scala.collection.immutable.ListMap.empty") listMapSym = requiredModule("scala.collection.immutable.ListMap") + dfhdlDFValIdentSym = requiredMethod("dfhdl.core.r__For_Plugin.identVal") dfcArgStack = Nil defaultParamMap.clear() ctx From 37e63acc226bedad372ca69e7f007041341015c0 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 11 Feb 2026 23:47:20 +0200 Subject: [PATCH 013/115] reenable RT process named variables, to be handled in a new stage --- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 22 ------------------- .../dfhdl/compiler/stages/SimplifyRTOps.scala | 16 +++++--------- .../StagesSpec/PrintCodeStringSpec.scala | 12 ++++------ .../scala/StagesSpec/SimplifyRTOpsSpec.scala | 6 ++--- 4 files changed, 11 insertions(+), 45 deletions(-) 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 149842bd1..2a64a2946 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -1203,27 +1203,6 @@ final case class DB( ) end portResourceDirCheck - def stepBlockCheck(): Unit = - val namedRTProcessValues = members.view.collect { - case pb: ProcessBlock if pb.isInRTDomain => - getMembersOf(pb, MemberView.Flattened).view.collect { - case dfVal: DFVal if !dfVal.isAnonymous => dfVal - }.filter { dfVal => - !(dfVal.hasTagOf[BindTag] || dfVal.hasTagOf[IteratorTag]) - } - }.flatten - if (namedRTProcessValues.nonEmpty) - throw new IllegalArgumentException( - //format: off - s"""|Named DFHDL values are not allowed in RT process blocks. Found the following named values: - | ${namedRTProcessValues.map(v => s"${v.getName} at ${v.meta.position}").mkString("\n ")} - |To Fix: - |Use anonymous values instead. - |""".stripMargin - //format: on - ) - end stepBlockCheck - def check(): Unit = nameCheck() connectionTable // causes connectivity checks @@ -1235,7 +1214,6 @@ final case class DB( waitCheck() portLocationCheck() portResourceDirCheck() - stepBlockCheck() end check // There can only be a single connection to a value in a given range diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala index 6bf5374d1..393954faf 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala @@ -84,24 +84,18 @@ case object SimplifyRTOps extends Stage: case DFVal.Const(data = Some(value: BigInt)) if value == 1 => false case _ => true if (replaceWithWhile) - import dfhdl.core.{DFUInt, asValOf, dfType} - val iterType = cyclesVal.asValOf[DFUInt[Int]].dfType - val regDsn = new MetaDesign( - waitMember.getOwnerProcessBlock, - Patch.Add.Config.Before, - dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) - ): - val waitCnt = iterType.<>(VAR.REG)(using dfc.setName("waitCnt")) val dsn = new MetaDesign( waitMember, Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement), dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) ): - import regDsn.waitCnt + val iterType = cyclesVal.asValOf[UInt[Int]].dfType // TODO: unclear why we cannot directly use 0 and 1 here, but there is indication that // the bug originates from a conversion from UInt[1] to the iterType. It could be that the // methods under core.DFVal.Alias are not working as intended because of meta-programming. - waitCnt.din := dfhdl.core.DFVal.Const(iterType, Some(BigInt(0))) + val initZero = dfhdl.core.DFVal.Const(iterType, Some(BigInt(0))) + val waitCnt = + iterType.<>(VAR.REG).initForced(List(initZero))(using dfc.setName("waitCnt")) // the upper bound for the while loop count val upperBound = cyclesVal match // the upper bound is reduced to a simpler form when the number of cycles is a constant anonymous value @@ -115,7 +109,7 @@ case object SimplifyRTOps extends Stage: dfc.enterOwner(whileBlock) waitCnt.din := waitCnt + dfhdl.core.DFVal.Const(iterType, Some(BigInt(1))) dfc.exitOwner() - List(regDsn.patch, dsn.patch) + Some(dsn.patch) else None end if }.flatten.toList diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index aa27332e0..40da45252 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -1129,7 +1129,6 @@ class PrintCodeStringSpec extends StageSpec: test("for/while loop printing with COMB_LOOP") { class Foo extends RTDesign: val matrix = Bits(10) X 8 X 8 <> OUT.REG - val ii = UInt.until(8) <> VAR process: for ( i <- 0 until 8; @@ -1141,7 +1140,7 @@ class PrintCodeStringSpec extends StageSpec: ) COMB_LOOP matrix(i)(j)(k).din := 1 - ii := 0 + val ii = UInt.until(8) <> VAR init 0 while (ii != 7) COMB_LOOP matrix(ii)(0)(0).din := 0 @@ -1153,7 +1152,6 @@ class PrintCodeStringSpec extends StageSpec: top, """|class Foo extends RTDesign: | val matrix = Bits(10) X 8 X 8 <> OUT.REG - | val ii = UInt(3) <> VAR | process: | for (i <- 0 until 8) | COMB_LOOP @@ -1169,7 +1167,7 @@ class PrintCodeStringSpec extends StageSpec: | end for | end if | end for - | ii := d"3'0" + | val ii = UInt(3) <> VAR init d"3'0" | while (ii != d"3'7") | COMB_LOOP | matrix(ii.toInt)(0)(0).din := 0 @@ -1182,7 +1180,6 @@ class PrintCodeStringSpec extends StageSpec: test("for/while loop printing with FALL_THROUGH") { class Foo extends RTDesign: val matrix = Bits(10) X 8 X 8 <> OUT.REG - val ii = UInt.until(8) <> VAR process: for ( i <- 0 until 8; @@ -1194,7 +1191,7 @@ class PrintCodeStringSpec extends StageSpec: ) FALL_THROUGH matrix(i)(j)(k).din := 1 - ii := 0 + val ii = UInt.until(8) <> VAR init 0 while (ii != 7) FALL_THROUGH matrix(ii)(0)(0).din := 0 @@ -1206,7 +1203,6 @@ class PrintCodeStringSpec extends StageSpec: top, """|class Foo extends RTDesign: | val matrix = Bits(10) X 8 X 8 <> OUT.REG - | val ii = UInt(3) <> VAR | process: | for (i <- 0 until 8) | FALL_THROUGH @@ -1222,7 +1218,7 @@ class PrintCodeStringSpec extends StageSpec: | end for | end if | end for - | ii := d"3'0" + | val ii = UInt(3) <> VAR init d"3'0" | while (ii != d"3'7") | FALL_THROUGH | matrix(ii.toInt)(0)(0).din := 0 diff --git a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala index fd47cb269..228b9bc41 100644 --- a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala @@ -135,16 +135,14 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): """|class Foo extends RTDesign: | val x = Bit <> OUT.REG | val waitParam: UInt[26] <> CONST = d"26'50000000" - | val waitCnt = UInt(26) <> VAR.REG - | val waitCnt = UInt(26) <> VAR.REG | process: | x.din := 1 - | waitCnt.din := d"26'0" + | val waitCnt = UInt(26) <> VAR.REG init d"26'0" | while (waitCnt != d"26'49999999") | waitCnt.din := waitCnt + d"26'1" | end while | x.din := 0 - | waitCnt.din := d"26'0" + | val waitCnt = UInt(26) <> VAR.REG init d"26'0" | while (waitCnt != (waitParam - d"26'1")) | waitCnt.din := waitCnt + d"26'1" | end while From 193e821da01ba57a2fc2e4b00e49f3c2d453ea5b Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 16 Feb 2026 09:33:24 +0200 Subject: [PATCH 014/115] fix ExplicitNamedVars stage for a simple named ident use-case --- .../compiler/stages/ExplicitNamedVars.scala | 9 +++++++ .../StagesSpec/ExplicitNamedVarsSpec.scala | 24 +++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala index 1896f5dbf..19227ef06 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala @@ -79,6 +79,15 @@ case object ExplicitNamedVars extends Stage: ) ) chPatchList ++ ch.patchChains(dsn.newVarIR) + // named ident requires removal of the ident itself + case ident @ Ident(value) => + val dsn = new MetaDesign( + ident, + Patch.Add.Config.ReplaceWithFirst(Patch.Replace.Config.FullReplacement) + ): + final val plantedNewVar = ident.asValAny.genNewVar(using dfc.setMeta(ident.meta)) + plantedNewVar.:=(value.asValAny) + List(dsn.patch) // all other named values case named => val anonIR = named.anonymize diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala index 8898a807e..d22d7347a 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala @@ -118,10 +118,10 @@ class ExplicitNamedVarsSpec extends StageSpec: val lhs = Bits(8) <> IN val shifted = lhs << 1 val o = Bits(8) <> OUT - o <> (( + o <> ( if (lhs(7)) shifted ^ h"1b" else shifted - ): Bits[8] <> VAL) + ) end xtime val id = (new xtime).explicitNamedVars assertCodeString( @@ -137,4 +137,24 @@ class ExplicitNamedVarsSpec extends StageSpec: ) } + test("Simple named ident") { + class ID extends DFDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val v = x + y := v + end ID + val id = (new ID).explicitNamedVars + assertCodeString( + id, + """|class ID extends DFDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val v = SInt(16) <> VAR + | v := x + | y := v + |end ID + |""".stripMargin + ) + } end ExplicitNamedVarsSpec From 1e1922cd94965449ab09c1b208b53d59114e886c Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 16 Feb 2026 19:37:26 +0200 Subject: [PATCH 015/115] patch `RefFilter.All` needs to be a case object --- core/src/main/scala/dfhdl/compiler/patching/Patch.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala index 272cb4917..9cfe474fd 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala @@ -41,7 +41,7 @@ object Patch: def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = refs -- rf(refs) // All references are replaced - object All extends RefFilter: + case object All extends RefFilter: def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = refs // Only references from outside the given owner are replaced final case class Outside(block: DFOwner) extends RefFilter: From d809e60b2db8201a8fa3c5c7addef5851becb354 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 17 Feb 2026 06:02:50 +0200 Subject: [PATCH 016/115] Refactor DFVal read dependencies to use a type alias for improved clarity and maintainability --- .../main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala index c85426505..20066762b 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala @@ -199,6 +199,8 @@ extension (member: DFMember) def originMembersNoTypeRef(using MemberGetSet): Set[DFMember] = getSet.designDB.originMemberTableNoTypeRef.getOrElse(member, Set()) +type DFValReadDep = TextOut | DFNet | DFVal | DFConditional.Block + extension (dfVal: DFVal) def getPartialAliases(using MemberGetSet): Set[DFVal.Alias.Partial] = dfVal.originMembers.flatMap { @@ -233,8 +235,8 @@ extension (dfVal: DFVal) dfVal.originMembers.view .collect { case dfVal: DFVal => dfVal } .exists(dfVal => cond(dfVal) || dfVal.existsInComposedReadDeps(cond)) - def getReadDeps(using MemberGetSet): Set[TextOut | DFNet | DFVal | DFConditional.Block] = - val fromRefs: Set[TextOut | DFNet | DFVal | DFConditional.Block] = + def getReadDeps(using MemberGetSet): Set[DFValReadDep] = + val fromRefs: Set[DFValReadDep] = dfVal.originMembersNoTypeRef.flatMap { case net: DFNet => net match From 3d7b8e990eaf2f58dcdd818edc0ed65d17d6832c Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 17 Feb 2026 09:10:40 +0200 Subject: [PATCH 017/115] fix and expand patching edge cases update ExplicitNamedVars to create registers in RT process --- .../compiler/stages/ExplicitNamedVars.scala | 165 +++++++++++++----- .../StagesSpec/ExplicitNamedVarsSpec.scala | 76 ++++++++ .../scala/dfhdl/compiler/patching/Patch.scala | 64 ++++--- .../patching/ReplacementContext.scala | 7 +- 4 files changed, 242 insertions(+), 70 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala index 19227ef06..4375b701a 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala @@ -6,6 +6,28 @@ import dfhdl.compiler.patching.* import dfhdl.internals.* import dfhdl.options.CompilerOptions +/** This stage turns all named values to variables that get assigned. As a result, conditional + * expressions (if/match) are converted to statements. + * + * Additionally, this stage creates explicit register and variable declarations for named value + * expressions under RT process blocks. The named expression becomes a register when the + * declaration and usage are in different steps. Otherwise, it is a variable declaration. If the + * named expression is accessed both in the same step and in another step, then both variable and + * register declarations are created. Examples: + * {{{ + * val x = UInt(16) <> IN + * val y = UInt(16) <> OUT.REG init 0 + * process: + * def S0: Step = + * val v = x // becomes a variable declaration + * y.din := v + * NextStep + * val vr = x // becomes a register declaration + * def S1: Step = + * y.din := vr + 1 + * NextStep + * }}} + */ case object ExplicitNamedVars extends Stage: def dependencies: List[Stage] = List(NamedAnonCondExpr) def nullifies: Set[Stage] = Set(DropLocalDcls) @@ -22,7 +44,6 @@ case object ExplicitNamedVars extends Stage: case _ => false case _ => false } - final val WhenNotHeader = !WhenHeader extension (ch: DFConditional.Header) // recursive call to patch conditional block chains @@ -43,6 +64,24 @@ case object ExplicitNamedVars extends Stage: } end extension + extension (member: DFMember) + def getProcessOrStepBlockOwner(using MemberGetSet): Option[ProcessBlock | StepBlock] = + @scala.annotation.tailrec + def recur(current: DFMember): Option[ProcessBlock | StepBlock] = current match + case pb: ProcessBlock => Some(pb) + case sb: StepBlock => Some(sb) + case _: DFDesignBlock => None + case m => recur(m.getOwner) + recur(member.getOwner) + extension (dfVal: DFVal) + def getRegOrVarDeps(using MemberGetSet): (regs: Set[DFValReadDep], vars: Set[DFValReadDep]) = + val ownerOpt = dfVal.getProcessOrStepBlockOwner + ownerOpt match + case Some(owner) => + dfVal.getReadDeps.partition { _.getProcessOrStepBlockOwner.get != owner } + case None => (regs = Set(), vars = dfVal.getReadDeps) + end extension + def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = given RefGen = RefGen.fromGetSet val patchList = @@ -54,57 +93,97 @@ case object ExplicitNamedVars extends Stage: case _: DFVal.Dcl => None // ignoring constant declarations (named constants or derived constants) case DclConst() => None - // named if / match expressions will be changed to statements - case ch: DFConditional.Header => - // removing name and type from header - val updatedCH = ch match + // all other named values + case named => + // anonymized value + val anonValueIR = named match + // named ident requires removal of the ident itself + case Ident(value) => value + // removing name and type from header case mh: DFConditional.DFMatchHeader => mh.copy(dfType = DFUnit).anonymize - case ih: DFConditional.DFIfHeader => ih.copy(dfType = DFUnit).anonymize - // this variable will replace the header as a value + // removing name and type from header + case ih: DFConditional.DFIfHeader => ih.copy(dfType = DFUnit).anonymize + case _ => named.anonymize + val readDeps = named.getRegOrVarDeps + // println(s"regs: ${readDeps.regs}") + // println(s"vars: ${readDeps.vars}") + val regUse = readDeps.regs.nonEmpty + // var is also used if there are no dependencies at all + val varUse = readDeps.vars.nonEmpty || readDeps.regs.isEmpty && readDeps.vars.isEmpty val dsn = new MetaDesign( - ch, - Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement, WhenHeader) + named, + Patch.Add.Config.Before, + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) ): - final val plantedNewVar = ch.asValAny.genNewVar(using dfc.setName(ch.getName)) - val newVarIR = plantedNewVar.asIR - plantMember(updatedCH) - val chPatchList = List( - // replacing all the references of header as a conditional header - dsn.patch, - // replacing all the references of header as a value - ch -> Patch.Replace( - dsn.newVarIR, - Patch.Replace.Config.ChangeRefOnly, - WhenNotHeader - ) - ) - chPatchList ++ ch.patchChains(dsn.newVarIR) - // named ident requires removal of the ident itself - case ident @ Ident(value) => - val dsn = new MetaDesign( - ident, - Patch.Add.Config.ReplaceWithFirst(Patch.Replace.Config.FullReplacement) - ): - final val plantedNewVar = ident.asValAny.genNewVar(using dfc.setMeta(ident.meta)) - plantedNewVar.:=(value.asValAny) - List(dsn.patch) - // all other named values - case named => - val anonIR = named.anonymize - val dsn = new MetaDesign(named, Patch.Add.Config.ReplaceWithFirst()): - final val plantedNewVar = named.asValAny.genNewVar(using dfc.setMeta(named.meta)) - plantedNewVar.:=(plantMember(anonIR).asValAny)(using - dfc.setMetaAnon(named.meta.position) - ) - List(dsn.patch) + val regDFC = dfc.setMeta(named.meta) + lazy val varDFC = if (regUse) regDFC.setName(s"${named.getName}_din") else regDFC + lazy val plantedNewVar = named.asValAny.dfType.<>(VAR)(using varDFC) + if (varUse) plantedNewVar // touch to trigger the lazy val + lazy val plantedNewReg = named.asValAny.dfType.<>(VAR.REG)(using regDFC) + if (regUse) plantedNewReg // touch to trigger the lazy val + val anonValue = named match + case Ident(_) => anonValueIR.asValAny + case _ => plantMember(anonValueIR).asValAny + named match + case _: DFConditional.Header => // do nothing + case _ => + if (varUse && regUse) + plantedNewVar := anonValue + plantedNewReg.din := plantedNewVar + else if (varUse) + plantedNewVar := anonValue + else if (regUse) + plantedNewReg.din := anonValue + val varPatchOpt = + if (varUse) + Some(named -> + Patch.Replace( + dsn.plantedNewVar.asIR, + Patch.Replace.Config.ChangeRefOnly, + Patch.Replace.RefFilter.OfMembers(readDeps.vars.asInstanceOf[Set[DFMember]]) + )) + else None + val regPatchOpt = + if (regUse) + Some(named -> + Patch.Replace( + dsn.plantedNewReg.asIR, + Patch.Replace.Config.ChangeRefOnly, + Patch.Replace.RefFilter.OfMembers(readDeps.regs.asInstanceOf[Set[DFMember]]) + )) + else None + // final named value handling according to the specialized use-cases + val finalizePatches = named match + // only ident values are removed completely, along with their references + case Ident(_) => Some(named -> Patch.Remove()) + case ch: DFConditional.Header => + val headerVar = if (varUse) dsn.plantedNewVar.asIR else dsn.plantedNewReg.asIR + // replacing all the references of header as a conditional header + val headerPatch = ch -> Patch.Replace( + anonValueIR, + Patch.Replace.Config.ChangeRefOnly, + WhenHeader + ) + // named header removal while preserving the + ch -> Patch.Remove(true) :: + // setting the conditional blocks to reference the new anonymous header + headerPatch :: + // patching chains + ch.patchChains(headerVar) + // remove the member, but preserve the references + case _ => Some(named -> Patch.Remove(isMoved = true)) + Iterable( + Some(dsn.patch), + varPatchOpt, + regPatchOpt, + finalizePatches + ).flatten } .toList designDB.patch(patchList) end transform end ExplicitNamedVars -//This stage turns all named values to variables that get assigned. -//As a result, conditional expressions (if/match) are converted to statements. extension [T: HasDB](t: T) def explicitNamedVars(using CompilerOptions): DB = StageRunner.run(ExplicitNamedVars)(t.db) diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala index d22d7347a..a2f88836a 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala @@ -157,4 +157,80 @@ class ExplicitNamedVarsSpec extends StageSpec: |""".stripMargin ) } + + test("RT process block named values") { + class ID extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + process: + def S0: Step = + val v = x + y.din := v + NextStep + val vr = x + def S1: Step = + y.din := vr + 1 + NextStep + val vrc = (if (x > 5) x else x + 1) + def S2: Step = + y.din := vrc + NextStep + val vm = x + y.din := vm + def S3: Step = + y.din := vm + NextStep + val vrcm = (if (x > 5) x else x + 1) + y.din := vrcm + def S4: Step = + y.din := vrcm + NextStep + end ID + val id = (new ID).explicitNamedVars + assertCodeString( + id, + """|class ID extends RTDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | process: + | def S0: Step = + | val v = SInt(16) <> VAR + | v := x + | y.din := v + | NextStep + | end S0 + | val vr = SInt(16) <> VAR.REG + | vr.din := x + | def S1: Step = + | y.din := vr + sd"16'1" + | NextStep + | end S1 + | val vrc = SInt(16) <> VAR.REG + | if (x > sd"16'5") vrc.din := x + | else vrc.din := x + sd"16'1" + | def S2: Step = + | y.din := vrc + | NextStep + | end S2 + | val vm_din = SInt(16) <> VAR + | val vm = SInt(16) <> VAR.REG + | vm_din := x + | vm.din := vm_din + | y.din := vm_din + | def S3: Step = + | y.din := vm + | NextStep + | end S3 + | val vrcm_din = SInt(16) <> VAR + | val vrcm = SInt(16) <> VAR.REG + | if (x > sd"16'5") vrcm_din := x + | else vrcm_din := x + sd"16'1" + | y.din := vrcm_din + | def S4: Step = + | y.din := vrcm + | NextStep + | end S4 + |end ID""".stripMargin + ) + } end ExplicitNamedVarsSpec diff --git a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala index 9cfe474fd..8daf89857 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala @@ -51,6 +51,10 @@ object Patch: final case class Inside(block: DFOwner) extends RefFilter: def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = refs.collect { case r: DFRef.TwoWayAny if r.originMember.isInsideOwner(block) => r } + // Only references to the given members are replaced + final case class OfMembers(members: Set[DFMember]) extends RefFilter: + def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = + refs.collect { case r: DFRef.TwoWayAny if members.contains(r.originMember) => r } end RefFilter end Replace final case class Add private[patching] (db: DB, config: Add.Config) extends Patch: @@ -172,6 +176,16 @@ extension (db: DB) .foldLeft(ReplacementContext.fromRefTable(refTable)) { case (rc, (origMember, Patch.Replace(repMember, config, refFilter))) if (origMember != repMember) => + // TODO: consider using this code in the future + // Include refs from any Add for this member so refs shared with added members (e.g. when + // the added block reuses the same member) are not purged and lost from the ref table. + // val addRefsForSameMember = + // patchList.collect { + // case (m, Patch.Add(db, _)) if m == origMember => db.members.flatMap(_.getRefs) + // }.flatten.toList + // val keepRefs = (repMember.getRefs ++ addRefsForSameMember).distinct + // val ret = + // rc.replaceMember(origMember, repMember, config, refFilter, keepRefs) val ret = rc.replaceMember(origMember, repMember, config, refFilter, repMember.getRefs) // patchDebug { @@ -191,7 +205,7 @@ extension (db: DB) case _ => db.refTable // updating the patched DB reference table members with the newest members kept by the replacement context val updatedPatchRefTable = rc.getUpdatedRefTable(fixedGlobalRefTable) - val keepRefList = db.members.flatMap(_.getRefs) + lazy val keepRefList = db.members.flatMap(_.getRefs) val repRT = config match case Patch.Add.Config.ReplaceWithMemberN(n, repConfig, refFilter) => val repMember = db.members(n + 1) // At index 0 we have the Top. We don't want that. @@ -209,23 +223,19 @@ extension (db: DB) Patch.Replace.RefFilter.All, keepRefList ) case _ => rc - // patchDebug { - // println("repRT.refTable:") - // println(repRT.refTable.mkString("\n")) - // } - // patchDebug { - // println("dbPatched.refTable:") - // println(dbPatched.refTable.mkString("\n")) - // } - // patchDebug { - // println("updatedPatchRefTable:") - // println(updatedPatchRefTable.mkString("\n")) - // } + // patchDebug { + // println("repRT.refTable:") + // println(repRT.refTable.mkString("\n")) + // } + // patchDebug { + // println("updatedPatchRefTable:") + // println(updatedPatchRefTable.mkString("\n")) + // } val ret = repRT.copy(refTable = repRT.refTable ++ updatedPatchRefTable) - // patchDebug { - // println("rc.refTable:") - // println(ret.refTable.mkString("\n")) - // } + // patchDebug { + // println("rc.refTable:") + // println(ret.refTable.mkString("\n")) + // } ret // skip over empty move case (rc, (origMember, Patch.Move(Nil, _, config))) => rc @@ -360,16 +370,20 @@ extension (db: DB) tbl + (m -> Patch.Add(add.db, Patch.Add.Config.ReplaceWithFirst())) // add followed by a replacement is allowed via a tandem patch execution case (add: Patch.Add, replace: Patch.Replace) if add.config == Patch.Add.Config.After => - tbl + (m -> Patch.Add( - add.db.copy(add.db.members.head :: replace.updatedMember :: add.db.members.drop(1)), - Patch.Add.Config.ReplaceWithFirst() - )) + tbl + + (m -> Patch.Add( + add.db.copy(add.db.members.head :: replace.updatedMember :: + add.db.members.drop(1)), + Patch.Add.Config.ReplaceWithFirst() + )) // replacement followed by an add via a tandem patch execution case (replace: Patch.Replace, add: Patch.Add) if add.config == Patch.Add.Config.After => - tbl + (m -> Patch.Add( - add.db.copy(add.db.members.head :: replace.updatedMember :: add.db.members.drop(1)), - Patch.Add.Config.ReplaceWithFirst() - )) + tbl + + (m -> Patch.Add( + add.db.copy(add.db.members.head :: replace.updatedMember :: + add.db.members.drop(1)), + Patch.Add.Config.ReplaceWithFirst() + )) // allow the same member to be removed more than once by getting rid of the redundant removals case (Patch.Remove(isMovedL), Patch.Remove(isMovedR)) => tbl + (m -> Patch.Remove(isMovedL || isMovedR)) diff --git a/core/src/main/scala/dfhdl/compiler/patching/ReplacementContext.scala b/core/src/main/scala/dfhdl/compiler/patching/ReplacementContext.scala index 0e8c902e4..8c3d6a269 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/ReplacementContext.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/ReplacementContext.scala @@ -99,11 +99,14 @@ private final case class ReplacementContext( def removeMember(origMember: DFMember): ReplacementContext = // Total references to be removed are: - // * refs from memberTable - references from other members to this member + // * refs from memberTable that still point to origMember in refTable (references from other + // members to this member; may have been reassigned by a prior Replace(ChangeRefOnly)) // * refs from origMember - references from this member to other members // Together these cover both directions of two-way references + val refsToOrigThatStillPointHere = + memberTable.getOrElse(origMember, Set()).filter(r => refTable.get(r).contains(origMember)) val purgedRefs = - memberTable.getOrElse(origMember, Set()) ++ origMember.getRefs + origMember.ownerRef + refsToOrigThatStillPointHere ++ origMember.getRefs + origMember.ownerRef val (updatedTypeRefRepeats, purgedRefsWithoutRepeatedTypeRefs) = getUpdatedTypeRefCount(purgedRefs) val updatedMemberTable = purgedRefsWithoutRepeatedTypeRefs.foldLeft(memberTable) { From d20b5c995bf7f5a04baa955ce9709db7cbf5af96 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 17 Feb 2026 09:12:20 +0200 Subject: [PATCH 018/115] add shortFilePath to MetaContext --- internals/src/main/scala/dfhdl/internals/MetaContext.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/internals/src/main/scala/dfhdl/internals/MetaContext.scala b/internals/src/main/scala/dfhdl/internals/MetaContext.scala index 24214e574..ad984d17a 100644 --- a/internals/src/main/scala/dfhdl/internals/MetaContext.scala +++ b/internals/src/main/scala/dfhdl/internals/MetaContext.scala @@ -9,6 +9,7 @@ final case class Position( lineEnd: Int, columnEnd: Int ) derives CanEqual: + def shortFilePath: String = file.split("\\\\").last override def toString: String = s"$file:$lineStart:$columnStart - $lineEnd:$columnEnd" def fileUnixPath: String = file.replaceAll("\\\\", "/") def isUnknown: Boolean = this == Position.unknown From 8e5527c43a8e47def57da2f1728608c2cc4f550a Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 18 Feb 2026 11:14:47 +0200 Subject: [PATCH 019/115] fix comment --- .../main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala index 4375b701a..1254db55c 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala @@ -164,7 +164,7 @@ case object ExplicitNamedVars extends Stage: Patch.Replace.Config.ChangeRefOnly, WhenHeader ) - // named header removal while preserving the + // named header removal while preserving the references ch -> Patch.Remove(true) :: // setting the conditional blocks to reference the new anonymous header headerPatch :: From e3dcf3c96e4c2d41352fa67eb4625d7692dae64c Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 19 Feb 2026 11:43:13 +0200 Subject: [PATCH 020/115] Add initial step definition for RT processes without leading steps, waits, or loops. This update introduces a new rule that adds an empty step definition (S_0) for processes that do not start with a step, wait statement, or while loop. --- .../dfhdl/compiler/stages/DropRTWaits.scala | 45 +++++++++++++- .../scala/StagesSpec/DropRTWaitsSpec.scala | 59 ++++++++++++------- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala index 304ec2ff4..794ad7008 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala @@ -57,6 +57,24 @@ import scala.collection.mutable * where N is the step number. * 5. While loops with nested waits: waits inside while loops become step definitions with nested naming * (S_0_0, S_0_1, etc.), and the while loop itself becomes a step definition (S_0) + * 6. If the process does not start with a step, a wait statement or while loop, we add an empty step definition for the first + * step (with a NextStep return value) before the first member. Internally, there could be anonymous value + * members before the step/while/wait members, but these are ignored for the check of what the first member is. + * For example: + * ```scala + * process: + * x.din := 0 + * end process + * ``` + * will be transformed to: + * ```scala + * process: + * def S_0: Step = + * NextStep + * end S_0 + * x.din := 0 + * end process + * ``` */ //format: on case object DropRTWaits extends Stage: @@ -97,10 +115,35 @@ case object DropRTWaits extends Stage: def getStepName(): String = stepNumberNest.view.reverse.mkString("S_", "_", "") + // Rule 6: If process does not start with step, wait, or while, add empty S_0 before first member + val needsInitialStep = + pbMembers + .dropWhile { + case v: DFVal => v.isAnonymous + case _ => false + }.headOption match + case None => true + case Some(_: Wait) => false + case Some(wb: DFLoop.DFWhileBlock) => wb.isCombinational + case Some(_: StepBlock) => false + case _ => true + + val initialStepPatches = if needsInitialStep then + val stepName = getStepName() + nextStepBlock() + val dsn = new MetaDesign(pb, Patch.Add.Config.InsideFirst): + import dfhdl.core.StepBlock + val step = StepBlock.forced(using dfc.setName(stepName)) + dfc.enterOwner(step) + NextStep + dfc.exitOwner() + List(dsn.patch) + else Nil + // transforming the process block members. // all members except the while loops can be an exit member, and need to be handled with `checkAndExitStepBlock`. // waits and while loops are handled specially. - pbMembers.flatMap { + initialStepPatches ++ pbMembers.flatMap { // transform a wait statement into a step block (assuming the wait is a single cycle wait, due to previous stages) case wait: Wait => val stepName = getStepName() diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala index 9ffb58f1d..a99cbb85f 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala @@ -13,7 +13,9 @@ class DropRTWaitsSpec extends StageSpec(): top, """|class Foo extends RTDesign: | process: - | + | def S_0: Step = + | NextStep + | end S_0 |end Foo""".stripMargin ) } @@ -31,6 +33,9 @@ class DropRTWaitsSpec extends StageSpec(): | val i = Bit <> IN | val x = Bit <> OUT.REG | process: + | def S_0: Step = + | NextStep + | end S_0 | x.din := i |end Foo""".stripMargin ) @@ -72,10 +77,13 @@ class DropRTWaitsSpec extends StageSpec(): | val i = Bit <> IN | val x = Bit <> OUT.REG | process: - | x.din := i | def S_0: Step = | NextStep | end S_0 + | x.din := i + | def S_1: Step = + | NextStep + | end S_1 |end Foo""".stripMargin ) } @@ -85,6 +93,7 @@ class DropRTWaitsSpec extends StageSpec(): val x = Bit <> OUT.REG process: x.din := 1 + x.din := i 1.cy.wait x.din := !x 1.cy.wait @@ -100,22 +109,26 @@ class DropRTWaitsSpec extends StageSpec(): | val i = Bit <> IN | val x = Bit <> OUT.REG | process: - | x.din := 1 | def S_0: Step = | NextStep | end S_0 - | x.din := !x + | x.din := 1 + | x.din := i | def S_1: Step = | NextStep | end S_1 - | x.din := 0 + | x.din := !x | def S_2: Step = | NextStep | end S_2 - | x.din := !x + | x.din := 0 | def S_3: Step = | NextStep | end S_3 + | x.din := !x + | def S_4: Step = + | NextStep + | end S_4 |end Foo""".stripMargin ) } @@ -344,27 +357,30 @@ class DropRTWaitsSpec extends StageSpec(): """|class Foo extends RTDesign: | val x = Bit <> OUT.REG init 0 | process: + | def S_0: Step = + | NextStep + | end S_0 | if (x) | x.din := !x - | def S_0: Step = - | NextStep - | end S_0 - | else - | x.din := !x | def S_1: Step = | NextStep | end S_1 + | else + | x.din := !x | def S_2: Step = | NextStep | end S_2 | def S_3: Step = | NextStep | end S_3 + | def S_4: Step = + | NextStep + | end S_4 | end if | x.din := !x - | def S_4: Step = + | def S_5: Step = | NextStep - | end S_4 + | end S_5 |end Foo""".stripMargin ) } @@ -393,32 +409,35 @@ class DropRTWaitsSpec extends StageSpec(): | val x = Bit <> OUT.REG init 0 | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | process: + | def S_0: Step = + | NextStep + | end S_0 | if (x) | x.din := !x - | def S_0: Step = + | def S_1: Step = | if (waitCnt1 != d"8'149") | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep | end if - | end S_0 + | end S_1 | waitCnt1.din := d"8'0" | else | x.din := !x - | def S_1: Step = - | NextStep - | end S_1 | def S_2: Step = | NextStep | end S_2 | def S_3: Step = | NextStep | end S_3 + | def S_4: Step = + | NextStep + | end S_4 | end if | x.din := !x - | def S_4: Step = + | def S_5: Step = | NextStep - | end S_4 + | end S_5 |end Foo""".stripMargin ) } From 5290ba1f4ce1af4802fb58d57c2ce2ff98ec7dda Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 20 Mar 2026 04:15:14 +0200 Subject: [PATCH 021/115] fix rt-process while loop internal step block detection --- .../StagesSpec/PrintCodeStringSpec.scala | 27 +++++++++++++++++++ .../src/main/scala/plugin/LoopFSMPhase.scala | 2 ++ 2 files changed, 29 insertions(+) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index 40da45252..1883c6ee3 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -1663,4 +1663,31 @@ class PrintCodeStringSpec extends StageSpec: |end Foo""".stripMargin ) } + + test("named rt-process loops") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG init 0 + process: + val MyWhile = while (x) + def GoGo: Step = + NextStep + end GoGo + x.din := !x + end MyWhile + end Foo + val top = (new Foo).getCodeString + assertNoDiff( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG init 0 + | process: + | val MyWhile = while (x) + | def GoGo: Step = + | NextStep + | end GoGo + | x.din := !x + | end while + |end Foo""".stripMargin + ) + } end PrintCodeStringSpec diff --git a/plugin/src/main/scala/plugin/LoopFSMPhase.scala b/plugin/src/main/scala/plugin/LoopFSMPhase.scala index 857da3870..2967b2ee3 100644 --- a/plugin/src/main/scala/plugin/LoopFSMPhase.scala +++ b/plugin/src/main/scala/plugin/LoopFSMPhase.scala @@ -89,6 +89,8 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: case None, Return, Loop, OnEntryExitFallThrough def processStatCheck(tree: Tree, returnCheck: CheckType)(using Context): Unit = tree match + case vd: ValDef if vd.tpt.tpe =:= defn.UnitType => + processStatCheck(vd.rhs, returnCheck) case Block(stats, expr) => returnCheck match case CheckType.OnEntryExitFallThrough => From eaed3c795cc49b46c0ce52bae30674056e768a2d Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 20 Mar 2026 05:24:54 +0200 Subject: [PATCH 022/115] fix rt-process for loop internal step blocks and introduce `PosKey` concept in compiler plugin --- plugin/src/main/scala/plugin/CommonPhase.scala | 14 +++++++++++--- .../main/scala/plugin/CustomControlPhase.scala | 15 ++++++++------- plugin/src/main/scala/plugin/LoopFSMPhase.scala | 10 +++++----- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/plugin/src/main/scala/plugin/CommonPhase.scala b/plugin/src/main/scala/plugin/CommonPhase.scala index 2255f8647..f121d004b 100755 --- a/plugin/src/main/scala/plugin/CommonPhase.scala +++ b/plugin/src/main/scala/plugin/CommonPhase.scala @@ -311,11 +311,19 @@ abstract class CommonPhase extends PluginPhase: case _ => None end extension + type PosKey = String + extension (sym: Symbol)(using Context) + def posKey: PosKey = sym.srcPos.show + extension (tree: Tree)(using Context) + def posKey: PosKey = tree.srcPos.show + extension (srcPos: util.SrcPos)(using Context) def show: String = - val pos = srcPos.startPos - val endPos = srcPos.endPos - s"${pos.source.path}:${pos.line}:${pos.column}-${endPos.line}:${endPos.column}" + if (srcPos.span == util.Spans.NoSpan) "NoSpan" + else + val pos = srcPos.startPos + val endPos = srcPos.endPos + s"${pos.source.path}:${pos.line}:${pos.column}-${endPos.line}:${endPos.column}" extension (tree: Apply)(using Context) def replaceArg(fromArg: Tree, toArg: Tree): Apply = diff --git a/plugin/src/main/scala/plugin/CustomControlPhase.scala b/plugin/src/main/scala/plugin/CustomControlPhase.scala index 9b1515ff9..060f35c34 100644 --- a/plugin/src/main/scala/plugin/CustomControlPhase.scala +++ b/plugin/src/main/scala/plugin/CustomControlPhase.scala @@ -38,8 +38,8 @@ class CustomControlPhase(setting: Setting) extends CommonPhase: // override val debugFilter: String => Boolean = _.contains("Playground.scala") override val runsAfter = Set(transform.Pickler.name) override val runsBefore = Set("MetaContextGen") - val ignoreIfs = mutable.Set.empty[String] - val replaceIfs = mutable.Set.empty[String] + val ignoreIfs = mutable.Set.empty[PosKey] + val replaceIfs = mutable.Set.empty[PosKey] var fromBooleanSym: Symbol = uninitialized var toFunc1Sym: Symbol = uninitialized var fromBranchesSym: Symbol = uninitialized @@ -99,16 +99,16 @@ class CustomControlPhase(setting: Setting) extends CommonPhase: case _ => false @tailrec private def ignoreElseIfRecur(tree: If)(using Context): Unit = - ignoreIfs += tree.srcPos.show + ignoreIfs += tree.posKey tree.elsep match case tree: If => ignoreElseIfRecur(tree) case _ => // done override def prepareForIf(tree: If)(using Context): Context = - if (!ignoreIfs.contains(tree.srcPos.show) && isHackedIfRecur(tree)) + if (!ignoreIfs.contains(tree.posKey) && isHackedIfRecur(tree)) tree.elsep match case tree: If => ignoreElseIfRecur(tree) case _ => // do nothing - replaceIfs += tree.srcPos.show + replaceIfs += tree.posKey ctx private def transformIfCond(condTree: Tree, dfcTree: Tree)(using @@ -167,7 +167,7 @@ class CustomControlPhase(setting: Setting) extends CommonPhase: case _ => false override def transformIf(tree: If)(using Context): Tree = - if (replaceIfs.contains(tree.srcPos.show)) + if (replaceIfs.contains(tree.posKey)) // debug("=======================") val dfcTree = dfcStack.head val combinedTpe = tree.tpe.widen @@ -780,7 +780,8 @@ class CustomControlPhase(setting: Setting) extends CommonPhase: } if (idxHigh != -1) report.error( - s"""Cannot compare a value of ${selectorWidth} bits width (LHS) to a value of ${selectorWidth - idxHigh - 1} bits width (RHS). + s"""Cannot compare a value of ${selectorWidth} bits width (LHS) to a value of ${selectorWidth - + idxHigh - 1} bits width (RHS). |An explicit conversion must be applied.""".stripMargin, patternTree.srcPos ) diff --git a/plugin/src/main/scala/plugin/LoopFSMPhase.scala b/plugin/src/main/scala/plugin/LoopFSMPhase.scala index 2967b2ee3..3762d1183 100644 --- a/plugin/src/main/scala/plugin/LoopFSMPhase.scala +++ b/plugin/src/main/scala/plugin/LoopFSMPhase.scala @@ -40,7 +40,7 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: var pluginOnEntryExitFallThroughSym: Symbol = uninitialized var waitSym: Symbol = uninitialized var dfcStack: List[Tree] = Nil - val processStepDefs = mutable.LinkedHashMap.empty[Symbol, DefDef] + val processStepDefs = mutable.LinkedHashMap.empty[PosKey, DefDef] override val runsAfter = Set(transform.Pickler.name) override val runsBefore = Set("CustomControl") @@ -54,7 +54,7 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: override def transformDefDef(tree: DefDef)(using Context): Tree = val updatedDefDef = if (tree.symbol == processAnonDefSym) - val registeredSteps = processStepDefs.view.map { (sym, dd) => + val registeredSteps = processStepDefs.valuesIterator.map { dd => ref(registerStepSym) .appliedTo(dd.genMeta) .appliedTo(dfcStack.head, ref(processScopeCtxSym)) @@ -172,7 +172,7 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: // checking process defs syntax and caching the process def symbols stepDefs.foreach { case dd @ DefDef(_, Nil, retTypeTree, _) if retTypeTree.tpe =:= stepType => - processStepDefs += (dd.symbol -> dd) + processStepDefs += (dd.symbol.posKey -> dd) case dd => report.error( "Unexpected register-transfer (RT) process `def` syntax. Must be `def xyz: Step = ...`", @@ -327,7 +327,7 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: else false object Goto: def unapply(tree: Ident)(using Context): Boolean = - processStepDefs.contains(tree.symbol) + processStepDefs.contains(tree.symbol.posKey) object Wait: def unapply(tree: Apply)(using Context): Boolean = tree.tpe.typeSymbol.fullName.toString == "dfhdl.core.Wait$package$.Wait" @@ -366,7 +366,7 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: override def transformStats(trees: List[Tree])(using Context): List[Tree] = trees.map { - case dd: DefDef if processStepDefs.contains(dd.symbol) => + case dd: DefDef if processStepDefs.contains(dd.symbol.posKey) => ref(addStepSym) .appliedTo(Literal(Constant(dd.name.toString))) .appliedTo(dd.rhs.changeOwner(dd.symbol, ctx.owner)) From cd63116525b9e760a8676c97c2c058166247e1df Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sat, 21 Mar 2026 01:38:22 +0200 Subject: [PATCH 023/115] fix: missing for loop naming fix: proper printing of loop ending --- .../dfhdl/compiler/printing/DFOwnerPrinter.scala | 6 ++++-- .../scala/StagesSpec/PrintCodeStringSpec.scala | 14 +++++++++++++- core/src/main/scala/dfhdl/core/DFFor.scala | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala index e831737c8..d77bc913a 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala @@ -371,10 +371,11 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: |${csFALL_THROUGH} |${csDFOwnerBody(forBlock)}""" val named = forBlock.meta.nameOpt.map(n => s"val $n = ").getOrElse("") + val endName = forBlock.meta.nameOpt.map(n => s"end $n").getOrElse("end for") //format: off sn"""|${named}for (${forBlock.iteratorRef.refCodeString} <- ${printer.csDFRange(forBlock.rangeRef.get)}) |${body.hindent} - |end for""" + |$endName""" //format: on def csDFWhileBlock(whileBlock: DFLoop.DFWhileBlock): String = val csCOMB_LOOP = if (whileBlock.isCombinational) "COMB_LOOP" else "" @@ -384,9 +385,10 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: |${csFALL_THROUGH} |${csDFOwnerBody(whileBlock)}""" val named = whileBlock.meta.nameOpt.map(n => s"val $n = ").getOrElse("") + val endName = whileBlock.meta.nameOpt.map(n => s"end $n").getOrElse("end while") sn"""|${named}while (${whileBlock.guardRef.refCodeString}) |${body.hindent} - |end while""" + |$endName""" def csDomainBlock(domain: DomainBlock): String = val body = csDFOwnerBody(domain) val named = domain.meta.nameOpt.map(n => s"val $n = ").getOrElse("") diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index 1883c6ee3..9ad23d177 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -1674,6 +1674,12 @@ class PrintCodeStringSpec extends StageSpec: end GoGo x.din := !x end MyWhile + val MyFor = for (i <- 0 until 10) + def GoGo2: Step = + NextStep + end GoGo2 + x.din := !x + end MyFor end Foo val top = (new Foo).getCodeString assertNoDiff( @@ -1686,7 +1692,13 @@ class PrintCodeStringSpec extends StageSpec: | NextStep | end GoGo | x.din := !x - | end while + | end MyWhile + | val MyFor = for (i <- 0 until 10) + | def GoGo2: Step = + | NextStep + | end GoGo2 + | x.din := !x + | end MyFor |end Foo""".stripMargin ) } diff --git a/core/src/main/scala/dfhdl/core/DFFor.scala b/core/src/main/scala/dfhdl/core/DFFor.scala index 7effa7c86..7e05834d3 100644 --- a/core/src/main/scala/dfhdl/core/DFFor.scala +++ b/core/src/main/scala/dfhdl/core/DFFor.scala @@ -26,7 +26,7 @@ object DFFor: )(using DFC): Unit = val iter = DFVal.Dcl.iterator(using dfc.setMeta(iterMeta)) dfc.mutableDB.DesignContext.addLoopIter(iterMeta, iter) - val block = Block(iter, range)(using dfc.setMetaAnon(forPos)) + val block = Block(iter, range)(using dfc.setMeta(position = forPos)) dfc.enterOwner(block) guards.foreach { guard => val guardVal = guard() From 834264a08838a1e4c201b6a7cde74d02d73cfaf0 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sat, 21 Mar 2026 01:40:10 +0200 Subject: [PATCH 024/115] Enhance step naming in DropRTWaits stage to support explicit naming for wait statements and while blocks. Introduce nested step naming conventions and update related tests for various scenarios including basic and complex named steps. --- .../dfhdl/compiler/stages/DropRTWaits.scala | 139 +++++++++++++--- .../scala/StagesSpec/DropRTWaitsSpec.scala | 157 ++++++++++++++++++ 2 files changed, 271 insertions(+), 25 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala index 794ad7008..5bbd9982b 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala @@ -33,12 +33,12 @@ import scala.collection.mutable * 3. While loop `while (condition) { body }` is replaced with: * ```scala * def S_N: Step = - * if (condition) { + * if (condition) * body * ThisStep - * } else { + * else * NextStep - * } + * end if * end S_N * ``` * where N is the step number. @@ -46,12 +46,12 @@ import scala.collection.mutable * ```scala * def S_N: Step = * def fallThrough = !condition - * if (condition) { + * if (condition) * body * ThisStep - * } else { + * else * NextStep - * } + * end if * end S_N * ``` * where N is the step number. @@ -75,6 +75,75 @@ import scala.collection.mutable * x.din := 0 * end process * ``` + * 7. The user can define explicit naming by setting a value name for a wait statement or a while block, + * or by defining a step block. Nested steps are named by appending the parent step name and an underscore. + * Unnamed steps are enumerated with the parent step name and an underscore. The enumeration starts from 0, + * and increments while counting the named steps as well. + * For example: + * ```scala + * process: + * def MyStep: Step = + * 1.cy.wait //no name, so enumerate as MyStep_0 + * val MyWait = 1.cy.wait //named, so use as MyStep_MyWait + * 1.cy.wait //no name, so enumerate as MyStep_2 + * def Internal: Step = //nested step, so change name to MyStep_Internal + * NextStep + * end Internal + * NextStep + * end MyStep + * x.din := y + * val MyStepB = 1.cy.wait //named, so use as MyStepB + * x.din := 1 + * 1.cy.wait //no name, so enumerate as MyStep_2 + * val MyWhile = while (y) //named, so use as MyWhile + * x.din := 1 + * def GoGo: Step = //nested step, so change name to MyWhile_GoGo + * NextStep + * end GoGo + * end MyWhile + * x.din := 1 + * end process + * ``` + * will be transformed to: + * ```scala + * process: + * def MyStep: Step = + * def MyStep_0: Step = + * NextStep + * end MyStep_0 + * def MyStep_MyWait: Step = + * NextStep + * end MyStep_MyWait + * def MyStep_2: Step = + * NextStep + * end MyStep_2 + * def MyStep_Internal: Step = + * NextStep + * end MyStep_Internal + * NextStep + * end MyStep + * x.din := y + * def MyStepB: Step = + * NextStep + * end MyStepB + * x.din := 1 + * def S_2: Step = + * NextStep + * end S_2 + * def MyWhile: Step = + * if (y) + * x.din := 1 + * def MyWhile_GoGo: Step = + * NextStep + * end MyWhile_GoGo + * ThisStep + * else + * NextStep + * end if + * end MyWhile + * x.din := 1 + * end process + * ``` */ //format: on case object DropRTWaits extends Stage: @@ -87,33 +156,41 @@ case object DropRTWaits extends Stage: // each process block has its own step enumeration case pb: ProcessBlock if pb.isInRTDomain => val pbMembers = pb.members(MemberView.Flattened) - // the nested step number is stored in a stack, the head of the list is the current step number - var stepNumberNest = List(0) + // Rule 7: step scope (prefix, counter). + case class StepScope(prefix: String, counter: Int) + var stepNameStack = List(StepScope("", 0)) // for nested while loops, we need to keep track of the exit members. // an exit member may have multiple patches that need to be applied in the LIFO order they are stacked. var exitMemberPatches = mutable.Map.empty[DFMember, List[Patch]] - // the step number is incremented by 1 + // increment the counter for the current (top) step scope def nextStepBlock(): Unit = - stepNumberNest = (stepNumberNest.head + 1) :: stepNumberNest.tail - // entering a step block starts a deeper nesting level and memoizes an exit member patch that - // needs to be applied - def enterStepBlock(patch: (DFMember, Patch)): Unit = + val h = stepNameStack.head + stepNameStack = StepScope(h.prefix, h.counter + 1) :: stepNameStack.tail + // entering a step block (e.g. while body) starts a deeper nesting level and memoizes an exit member patch + def enterStepBlock(stepMember: DFMember, exitMember: DFMember, patch: Option[Patch]): Unit = // stacking the patches for the exit member - exitMemberPatches.get(patch._1) match - case Some(patches) => exitMemberPatches += patch._1 -> (patch._2 :: patches) - case None => exitMemberPatches += patch._1 -> List(patch._2) - // starting a new step block with a new step number - stepNumberNest = 0 :: stepNumberNest + exitMemberPatches.get(exitMember) match + case Some(patches) => exitMemberPatches += exitMember -> (patch.toList ++ patches) + case None => exitMemberPatches += exitMember -> patch.toList + stepNameStack = StepScope(getStepName(stepMember), 0) :: stepNameStack // checking if the step block has an exit member and returning the patches that need to be applied. def checkAndExitStepBlock(lastMember: DFMember): List[(DFMember, Patch)] = exitMemberPatches.get(lastMember) match case Some(patches) => - stepNumberNest = stepNumberNest.tail + stepNameStack = stepNameStack.tail nextStepBlock() patches.map(lastMember -> _) case None => Nil - def getStepName(): String = - stepNumberNest.view.reverse.mkString("S_", "_", "") + + def getStepName(stepMember: DFMember): String = + val prefix = stepNameStack.head.prefix + stepMember.meta.nameOpt match + case Some(name) => + if (prefix.isEmpty) name + else s"${prefix}_${name}" + case None => + if (prefix.isEmpty) s"S_${stepNameStack.head.counter}" + else s"${prefix}_${stepNameStack.head.counter}" // Rule 6: If process does not start with step, wait, or while, add empty S_0 before first member val needsInitialStep = @@ -129,7 +206,7 @@ case object DropRTWaits extends Stage: case _ => true val initialStepPatches = if needsInitialStep then - val stepName = getStepName() + val stepName = "S_0" nextStepBlock() val dsn = new MetaDesign(pb, Patch.Add.Config.InsideFirst): import dfhdl.core.StepBlock @@ -146,7 +223,7 @@ case object DropRTWaits extends Stage: initialStepPatches ++ pbMembers.flatMap { // transform a wait statement into a step block (assuming the wait is a single cycle wait, due to previous stages) case wait: Wait => - val stepName = getStepName() + val stepName = getStepName(wait) nextStepBlock() val dsn = new MetaDesign( wait, @@ -160,7 +237,7 @@ case object DropRTWaits extends Stage: dsn.patch :: checkAndExitStepBlock(wait) // transform a while loop into a step block. case wb: DFLoop.DFWhileBlock if !wb.isCombinational => - val stepName = getStepName() + val stepName = getStepName(wb) // the last member of the while loop is the exit member. val lastLoopMember = wb.getVeryLastMember.get // creating the if part of the while loop step block. @@ -195,8 +272,20 @@ case object DropRTWaits extends Stage: dfc.enterOwner(elseBlock) NextStep dfc.exitOwner() - enterStepBlock(elseDsn.patch) + enterStepBlock(wb, lastLoopMember, Some(elseDsn.patch._2)) Some(stepAndIfDsn.patch) + case stepBlock: StepBlock => + val stepName = getStepName(stepBlock) + val lastStepBlockMember = stepBlock.getVeryLastMember.get + enterStepBlock(stepBlock, lastStepBlockMember, None) + if (stepBlock.getName != stepName) + Some( + stepBlock -> Patch.Replace( + stepBlock.setName(stepName), + Patch.Replace.Config.FullReplacement + ) + ) + else None case member => checkAndExitStepBlock(member) } }.flatten.toList diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala index a99cbb85f..7493142eb 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala @@ -441,4 +441,161 @@ class DropRTWaitsSpec extends StageSpec(): |end Foo""".stripMargin ) } + test("basic named steps") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG init 0 + process: + x.din := 1 + def MyStep: Step = + NextStep + end MyStep + 1.cy.wait + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG init 0 + | process: + | def S_0: Step = + | NextStep + | end S_0 + | x.din := 1 + | def MyStep: Step = + | NextStep + | end MyStep + | def S_2: Step = + | NextStep + | end S_2 + |end Foo""".stripMargin + ) + } + test("basic named steps with nested steps") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG init 0 + process: + def MyStep: Step = + 1.cy.wait + def Internal: Step = + NextStep + end Internal + 1.cy.wait + NextStep + end MyStep + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG init 0 + | process: + | def MyStep: Step = + | def MyStep_0: Step = + | NextStep + | end MyStep_0 + | def MyStep_Internal: Step = + | NextStep + | end MyStep_Internal + | def MyStep_2: Step = + | NextStep + | end MyStep_2 + | NextStep + | end MyStep + |end Foo""".stripMargin + ) + } + test("complex named steps with nested steps, loops, and waits") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG init 0 + process: + def MyStep: Step = + 1.cy.wait + def Internal: Step = + def Deeper: Step = + NextStep + end Deeper + 1.cy.wait + NextStep + end Internal + 1.cy.wait + NextStep + end MyStep + x.din := !x + def MyStepB: Step = + def InternalB: Step = + NextStep + end InternalB + 1.cy.wait + val MyWait = 1.cy.wait + 1.cy.wait + NextStep + end MyStepB + x.din := !x + val MyWhile = while (x) + x.din := !x + def GoGo: Step = + NextStep + end GoGo + 1.cy.wait + end MyWhile + x.din := !x + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG init 0 + | process: + | def MyStep: Step = + | def MyStep_0: Step = + | NextStep + | end MyStep_0 + | def MyStep_Internal: Step = + | def MyStep_Internal_Deeper: Step = + | NextStep + | end MyStep_Internal_Deeper + | def MyStep_Internal_1: Step = + | NextStep + | end MyStep_Internal_1 + | NextStep + | end MyStep_Internal + | def MyStep_2: Step = + | NextStep + | end MyStep_2 + | NextStep + | end MyStep + | x.din := !x + | def MyStepB: Step = + | def MyStepB_InternalB: Step = + | NextStep + | end MyStepB_InternalB + | def MyStepB_1: Step = + | NextStep + | end MyStepB_1 + | def MyStepB_MyWait: Step = + | NextStep + | end MyStepB_MyWait + | def MyStepB_3: Step = + | NextStep + | end MyStepB_3 + | NextStep + | end MyStepB + | x.din := !x + | def MyWhile: Step = + | if (x) + | x.din := !x + | def MyWhile_GoGo: Step = + | NextStep + | end MyWhile_GoGo + | def MyWhile_1: Step = + | NextStep + | end MyWhile_1 + | ThisStep + | else NextStep + | end if + | end MyWhile + | x.din := !x + |end Foo""".stripMargin + ) + } end DropRTWaitsSpec From feb4016cba7b7c9726cec35e2989bc7016d27585 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sat, 21 Mar 2026 02:09:32 +0200 Subject: [PATCH 025/115] enhance FSMs so that step names can be repeated in different scopes --- .../scala/StagesSpec/PrintCodeStringSpec.scala | 8 ++++---- core/src/main/scala/dfhdl/core/Step.scala | 13 ++++++++----- .../src/main/scala/plugin/LoopFSMPhase.scala | 18 +++++++++--------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index 9ad23d177..6be08d000 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -1675,9 +1675,9 @@ class PrintCodeStringSpec extends StageSpec: x.din := !x end MyWhile val MyFor = for (i <- 0 until 10) - def GoGo2: Step = + def GoGo: Step = NextStep - end GoGo2 + end GoGo x.din := !x end MyFor end Foo @@ -1694,9 +1694,9 @@ class PrintCodeStringSpec extends StageSpec: | x.din := !x | end MyWhile | val MyFor = for (i <- 0 until 10) - | def GoGo2: Step = + | def GoGo: Step = | NextStep - | end GoGo2 + | end GoGo | x.din := !x | end MyFor |end Foo""".stripMargin diff --git a/core/src/main/scala/dfhdl/core/Step.scala b/core/src/main/scala/dfhdl/core/Step.scala index b63a4e6e2..0844a5c7b 100644 --- a/core/src/main/scala/dfhdl/core/Step.scala +++ b/core/src/main/scala/dfhdl/core/Step.scala @@ -56,15 +56,18 @@ object Step extends Step: scope: DFC.Scope.Process ): Unit = val step = StepBlock(using dfc.setMeta(stepMeta)) - scope.stepCache += (stepMeta.name -> step.asIR) + // a unique step key is generated by combining the step's name with its starting line number. + // This handles cases where step names are reused in different internal scopes, such as within loops or nested steps. + val stepKey = s"${stepMeta.name}_${stepMeta.position.lineStart}" + scope.stepCache += (stepKey -> step.asIR) // this is called by the compiler plugin and replaces the step's `def`. this will add the // step to the context, update its reference to point to the proper owner, and finally run // the step's body with the step as its owner. - def pluginAddStep(stepName: String)( + def pluginAddStep(stepKey: String)( run: => Unit )(using dfc: DFC, scope: DFC.Scope.Process): Unit = - val stepIR = scope.stepCache(stepName) + val stepIR = scope.stepCache(stepKey) stepIR.addMember dfc.mutableDB.newRefFor(stepIR.ownerRef, dfc.owner.asIR) dfc.enterOwner(stepIR.asFE) @@ -96,9 +99,9 @@ object Step extends Step: // this is called by the compiler plugin and replaces references (calls) to the step `def`. // for the process this is considered as a goto statement. - def pluginGotoStep(nextStepName: String)(using dfc: DFC, scope: DFC.Scope.Process): Unit = + def pluginGotoStep(nextStepKey: String)(using dfc: DFC, scope: DFC.Scope.Process): Unit = import dfc.getSet - val nextStep = scope.stepCache(nextStepName) + val nextStep = scope.stepCache(nextStepKey) val member: ir.Goto = ir.Goto( nextStep.refTW[ir.Goto], dfc.owner.ref, diff --git a/plugin/src/main/scala/plugin/LoopFSMPhase.scala b/plugin/src/main/scala/plugin/LoopFSMPhase.scala index 3762d1183..7c2b49d65 100644 --- a/plugin/src/main/scala/plugin/LoopFSMPhase.scala +++ b/plugin/src/main/scala/plugin/LoopFSMPhase.scala @@ -291,13 +291,6 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: tree match case ProcessForever(scopeCtx, block) => processStatCheck(block, returnCheck = CheckType.None) - processStepDefs.view.groupBy(_._2.name.toString).foreach { case (name, defs) => - if (defs.size > 1) - report.error( - s"Process step `def`s must be unique. Found multiple `def $name: Step = ...`s.", - defs.head._2.srcPos - ) - } case Apply(Select(This(_), wait), args) if wait.toString == "wait" => // DFHDL/Java wait if (!tree.tpe.isContextualMethod) // Java's wait wouldn't be contextual report.error( @@ -335,7 +328,7 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: tree match case Goto() => ref(gotoStepSym) - .appliedTo(Literal(Constant(tree.name.toString))) + .appliedTo(Literal(Constant(getStepKey(tree)))) .appliedTo(dfcStack.head, ref(processScopeCtxSym)) case _ => tree @@ -364,11 +357,18 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: case _ => tree + // this is used to get a unique key for the step, + // because the step name may be repeated when declared internally in another step or loop. + // the line position makes sure we get a unique key for the step. + private def getStepKey(tree: DefDef | Ident)(using Context): String = + val sym = tree.symbol + s"${sym.name}_${sym.srcPos.startPos.line + 1}" + override def transformStats(trees: List[Tree])(using Context): List[Tree] = trees.map { case dd: DefDef if processStepDefs.contains(dd.symbol.posKey) => ref(addStepSym) - .appliedTo(Literal(Constant(dd.name.toString))) + .appliedTo(Literal(Constant(getStepKey(dd)))) .appliedTo(dd.rhs.changeOwner(dd.symbol, ctx.owner)) .appliedTo(dfcStack.head, ref(processScopeCtxSym)) case dd @ (OnEntryDef() | OnExitDef() | FallThroughDef()) => From e8fd87784f0431e608fd6beb1dd3361b25c7c83a Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sat, 21 Mar 2026 02:51:46 +0200 Subject: [PATCH 026/115] Refine nested step naming conventions in DropRTWaits stage to prevent duplication of parent step names. Update related tests to reflect changes in internal step definitions and ensure consistent naming behavior across various scenarios. --- .../scala/dfhdl/compiler/stages/DropRTWaits.scala | 10 +++++++++- .../src/test/scala/StagesSpec/DropRTWaitsSpec.scala | 11 ++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala index 5bbd9982b..03d6c796d 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala @@ -76,7 +76,8 @@ import scala.collection.mutable * end process * ``` * 7. The user can define explicit naming by setting a value name for a wait statement or a while block, - * or by defining a step block. Nested steps are named by appending the parent step name and an underscore. + * or by defining a step block. Nested steps are named by appending the parent step name and an underscore, + * unless the nested step already includes the parent step name with an underscore. * Unnamed steps are enumerated with the parent step name and an underscore. The enumeration starts from 0, * and increments while counting the named steps as well. * For example: @@ -89,6 +90,9 @@ import scala.collection.mutable * def Internal: Step = //nested step, so change name to MyStep_Internal * NextStep * end Internal + * def MyStep_Internal2: Step = //nested step, but already includes the parent step name with an underscore, + * NextStep //so use as MyStep_Internal2 + * end MyStep_Internal2 * NextStep * end MyStep * x.din := y @@ -120,6 +124,9 @@ import scala.collection.mutable * def MyStep_Internal: Step = * NextStep * end MyStep_Internal + * def MyStep_Internal2: Step = + * NextStep + * end MyStep_Internal2 * NextStep * end MyStep * x.din := y @@ -187,6 +194,7 @@ case object DropRTWaits extends Stage: stepMember.meta.nameOpt match case Some(name) => if (prefix.isEmpty) name + else if (name.startsWith(prefix + "_")) name else s"${prefix}_${name}" case None => if (prefix.isEmpty) s"S_${stepNameStack.head.counter}" diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala index 7493142eb..3aceb164d 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala @@ -522,9 +522,9 @@ class DropRTWaitsSpec extends StageSpec(): end MyStep x.din := !x def MyStepB: Step = - def InternalB: Step = + def MyStepB_Internal: Step = NextStep - end InternalB + end MyStepB_Internal 1.cy.wait val MyWait = 1.cy.wait 1.cy.wait @@ -540,7 +540,8 @@ class DropRTWaitsSpec extends StageSpec(): end MyWhile x.din := !x end Foo - val top = (new Foo).dropRTWaits + // run dropRTWaits twice to test the nested step name handling (nothing should change after the first run) + val top = (new Foo).dropRTWaits.dropRTWaits assertCodeString( top, """|class Foo extends RTDesign: @@ -566,9 +567,9 @@ class DropRTWaitsSpec extends StageSpec(): | end MyStep | x.din := !x | def MyStepB: Step = - | def MyStepB_InternalB: Step = + | def MyStepB_Internal: Step = | NextStep - | end MyStepB_InternalB + | end MyStepB_Internal | def MyStepB_1: Step = | NextStep | end MyStepB_1 From f7baa5fd6bc309b87f12af8fe16ce2c6b0af52da Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 22 Mar 2026 01:25:04 +0200 Subject: [PATCH 027/115] first claude instructions and skills --- .claude/commands/ir-reference.md | 967 +++++++++++++++++++++++++++++++ .claude/commands/new-stage.md | 753 ++++++++++++++++++++++++ CLAUDE.md | 112 ++++ 3 files changed, 1832 insertions(+) create mode 100644 .claude/commands/ir-reference.md create mode 100644 .claude/commands/new-stage.md create mode 100644 CLAUDE.md diff --git a/.claude/commands/ir-reference.md b/.claude/commands/ir-reference.md new file mode 100644 index 000000000..23df768a4 --- /dev/null +++ b/.claude/commands/ir-reference.md @@ -0,0 +1,967 @@ +# DFHDL IR Reference + +> **For contributors working with the DFHDL compiler internals.** +> This skill is version-controlled alongside the codebase — keep it updated when IR types or analysis helpers change. + +Complete reference for the IR (intermediate representation) layer at +`compiler/ir/src/main/scala/dfhdl/compiler/ir/` and the analysis helpers at +`compiler/ir/src/main/scala/dfhdl/compiler/analysis/`. + +This is the data model that every stage `transform` method pattern-matches on and patches. +See `/new-stage` for how to write a stage that uses this reference. + +--- + +## Top-level sealed hierarchy + +``` +DFMember (sealed) +├── DFMember.Empty — sentinel / placeholder (no owner, no meta) +├── DFVal — any value in the design +│ ├── DFVal.Const — literal constant +│ ├── DFVal.Dcl — port / variable / const declaration +│ ├── DFVal.Func — computed expression / operator +│ ├── DFVal.Alias — alias / cast / partial selection +│ │ ├── DFVal.Alias.AsIs — type cast (.as(T) / .actual) +│ │ ├── DFVal.Alias.History — prev / pipe (.prev, .reg) +│ │ ├── DFVal.Alias.ApplyRange — bit-range slice (x(hi, lo)) +│ │ ├── DFVal.Alias.ApplyIdx — vector / bits indexing (x(i)) +│ │ └── DFVal.Alias.SelectField — struct field (x.fieldName) +│ ├── DFVal.DesignParam — design-level parameter reference +│ ├── DFVal.PortByNameSelect — port selected by string path +│ └── DFVal.Special — NOTHING | OPEN | CLK_FREQ +├── Statement — executable statements +│ ├── DFNet — assignment / connection +│ ├── Wait — process wait +│ ├── TextOut — print / assert / finish +│ └── Goto — FSM step jump +├── DFBlock — containers for child members +│ ├── DFDesignBlock — module / design definition +│ ├── DomainBlock — clock-domain grouping +│ ├── ProcessBlock — always / process block +│ ├── StepBlock — FSM step +│ ├── DFConditional.Block — if / match clause +│ │ ├── DFIfElseBlock +│ │ └── DFCaseBlock +│ └── DFLoop.Block — loop body +│ ├── DFForBlock +│ └── DFWhileBlock +├── DFConditional.Header — if / match header expression +│ ├── DFIfHeader +│ └── DFMatchHeader +├── DFInterfaceOwner — interface abstraction +└── DFRange — for-loop range +``` + +Every `DFMember` carries three common fields: + +| Field | Type | Purpose | +|---|---|---| +| `ownerRef` | `DFOwner.Ref` | Points to the enclosing owner/block | +| `meta` | `Meta` | Name, source position, doc, annotations | +| `tags` | `DFTags` | Extensible tag map | + +--- + +## DFMember — common API + +```scala +// Navigation (all require using MemberGetSet) +m.getOwner // immediate owner (throws if global) +m.getOwnerBlock // nearest enclosing DFBlock +m.getOwnerDesign // nearest enclosing DFDesignBlock +m.getOwnerDomain // nearest DFDomainOwner (design, domainBlock) +m.getOwnerProcessBlock // nearest ProcessBlock +m.getOwnerStepBlock // nearest StepBlock +m.getThisOrOwnerDesign // this if DFDesignBlock, else getOwnerDesign +m.getThisOrOwnerDomain // this if DFDomainOwner, else getOwnerDomain +m.isMemberOf(owner) // direct child of owner? +m.isInsideOwner(owner) // anywhere inside owner at any depth? +m.isSameOwnerDesignAs(that) // same immediate design? +m.getOwnerChain // List[DFBlock] from root to direct owner + +// Domain checks (extension, compiler.ir package) +m.getDomainType // DomainType of nearest domain owner +m.isInDFDomain // true iff DF domain +m.isInRTDomain // true iff RT domain +m.isInEDDomain // true iff ED domain +m.isInProcess // true iff inside a ProcessBlock + +// Tag helpers (extension) +m.getTagOf[MyTag] // Option[MyTag] +m.hasTagOf[MyTag] // Boolean + +// Meta helpers (on DFMember.Named) +m.isAnonymous // meta.nameOpt.isEmpty +m.getName // resolved name string +m.getFullName // fully-qualified owner.owner.name +m.getRelativeName(callOwner) // shortest unambiguous name from callOwner + +// References +m.getRefs // List[DFRef.TwoWayAny] — all two-way refs in this member +m.copyWithNewRefs // deep copy with fresh reference IDs (using RefGen) +``` + +--- + +## DFVal — value subtypes + +### DFVal trait (common) + +```scala +dfVal.dfType // DFType +dfVal.width // Int (delegates to dfType) +dfVal.isGlobal // true when ownerRef points to DFMember.Empty +dfVal.isAnonymous // true when meta.nameOpt.isEmpty +dfVal.isFullyAnonymous // true when this and all its arg deps are anonymous +dfVal.isConst // true when getConstData.nonEmpty +dfVal.getConstData // Option[Any] — constant-fold result (cached) +dfVal.isSimilarTo(that) // semantic equivalence (type + data, ignoring identity) +dfVal.updateDFType(t) // create copy with new DFType +``` + +**Sub-trait markers:** +- `DFVal.CanBeExpr` — can appear as an expression (Const, Func, Alias, DesignParam, Special) +- `DFVal.CanBeGlobal` — can live at top-level scope (Const, Func, Alias.Partial) + +**Extension methods on any DFVal:** + +```scala +dfVal.isPort // Dcl with IN/OUT/INOUT modifier +dfVal.isPortIn // Dcl with IN modifier +dfVal.isPortOut // Dcl with OUT modifier +dfVal.isVar // Dcl with VAR modifier +dfVal.isReg // Dcl with REG special modifier +dfVal.isOpen // DFVal.Special(OPEN) +dfVal.isDesignParam // DFVal.DesignParam instance +dfVal.dealias // follow alias chain → Option[DFVal.Dcl | DFVal.Special] +dfVal.departial // strip partial selections → (DFVal, Range) +dfVal.departialDcl // strip partial selections → Option[(DFVal.Dcl, Range)] +dfVal.stripPortSel // PortByNameSelect → underlying Dcl, else identity +dfVal.isBubble // contains a don't-care / bubble constant +``` + +--- + +### DFVal.Const + +```scala +final case class DFVal.Const( + dfType: DFType, + data: Any, // actual value; cast to dfType.Data + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +Extends `CanBeExpr`, `CanBeGlobal`. +`data` is typed as `dfType.Data` — e.g. `Option[BigInt]` for `DFUInt`, `(BitVector, BitVector)` for `DFBits`. + +--- + +### DFVal.Dcl + +```scala +final case class DFVal.Dcl( + dfType: DFType, + modifier: DFVal.Modifier, // dir + special + initRefList: List[Dcl.InitRef], + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +// companion +type Dcl.InitRef = DFRef.TwoWay[DFVal, Dcl] +``` + +**`DFVal.Modifier`:** + +```scala +final case class Modifier(dir: Modifier.Dir, special: Modifier.Special) +enum Dir: VAR, IN, OUT, INOUT +enum Special: Ordinary, REG, SHARED + +mod.isPort // IN | OUT | INOUT +mod.isReg // special == REG +mod.isShared // special == SHARED +``` + +**Extension methods on `Dcl`:** + +```scala +dcl.initList // List[DFVal] — resolved init values +dcl.isClkDcl // dfType is DFOpaque(kind = Clk) +dcl.isRstDcl // dfType is DFOpaque(kind = Rst) +dcl.hasNonBubbleInit // initRefList non-empty and first element is not bubble +``` + +--- + +### DFVal.Func + +```scala +final case class DFVal.Func( + dfType: DFType, + op: Func.Op, + args: List[DFVal.Ref], // type DFVal.Ref = DFRef.TwoWay[DFVal, DFMember] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +Extends `CanBeExpr`, `CanBeGlobal`. + +**`Func.Op` enum** (all operators): +``` ++ - * / === =!= < > <= >= & | ^ % ++ +>> << ** ror rol reverse repeat +unary_- unary_~ unary_! +rising falling +clog2 max min abs sel +InitFile(format, path) +``` + +`Func.Op.associativeSet` = `{+, -, *, &, |, ^, ++, max, min}` + +--- + +### DFVal.Alias subtypes + +All aliases share: +```scala +val relValRef: DFRef.TwoWay[DFVal, Alias] // the value being aliased +``` + +Two sub-traits: +- `Alias.Consumer` — `relValRef: ConsumerRef` — consumes the source entirely (History) +- `Alias.Partial` — `relValRef: PartialRef` — partial view of source; propagates mutability (AsIs, ApplyRange, ApplyIdx, SelectField) + +#### DFVal.Alias.AsIs +```scala +final case class DFVal.Alias.AsIs( + dfType: DFType, // target type (may differ from relVal.dfType for casts) + relValRef: PartialRef, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` +Used for: `.as(T)` type casts, `.actual` on opaques, `IdentTag`-tagged identity aliases. + +Tag-based extractors: +```scala +Ident(underlying) // AsIs tagged IdentTag → underlying DFVal +Bind(underlying) // Alias tagged BindTag → underlying DFVal +OpaqueActual(relVal) // AsIs where relVal.dfType is DFOpaque and alias.dfType == actualType +AsOpaque(relVal) // AsIs where alias.dfType is DFOpaque and relVal.dfType == actualType +``` + +#### DFVal.Alias.History +```scala +final case class DFVal.Alias.History( + dfType: DFType, + relValRef: ConsumerRef, + step: Int, + op: History.Op, // State | Pipe + initRefOption: Option[History.InitRef], + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum History.Op: State, Pipe // State = .prev in DF / .reg in RT; Pipe = DF pipe constraint + +history.initOption // Option[DFVal] +history.hasNonBubbleInit // Boolean +``` + +#### DFVal.Alias.ApplyRange +```scala +final case class DFVal.Alias.ApplyRange( + dfType: DFType, + relValRef: PartialRef, + idxHighRef: IntParamRef, // high bit index (inclusive) + idxLowRef: IntParamRef, // low bit index (inclusive) + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +applyRange.elementWidth // width of each element in the range +``` + +#### DFVal.Alias.ApplyIdx +```scala +final case class DFVal.Alias.ApplyIdx( + dfType: DFType, + relValRef: PartialRef, + relIdx: DFVal.Ref, // the index value + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +// Companion extractor for compile-time constant indices: +DFVal.Alias.ApplyIdx.ConstIdx(idx: Int) // unapply on DFVal.Const → Option[Int] +``` + +#### DFVal.Alias.SelectField +```scala +final case class DFVal.Alias.SelectField( + dfType: DFType, + relValRef: PartialRef, + fieldName: String, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +--- + +### DFVal.DesignParam +```scala +final case class DFVal.DesignParam( + dfType: DFType, + dfValRef: DesignParam.Ref, // → the actual parameter value + defaultRef: DesignParam.DefaultRef, // → default value or DFMember.Empty + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +type DesignParam.Ref = DFRef.TwoWay[DFVal, DesignParam] +type DesignParam.DefaultRef = DFRef.TwoWay[DFVal | DFMember.Empty, DesignParam] +``` + +--- + +### DFVal.Special +```scala +final case class DFVal.Special( + dfType: DFType, + kind: Special.Kind, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum Special.Kind: NOTHING, OPEN, CLK_FREQ +``` + +--- + +### DFVal.PortByNameSelect +```scala +final case class DFVal.PortByNameSelect( + dfType: DFType, + designInstRef: PortByNameSelect.Ref, // → DFDesignInst + portNamePath: String, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +type PortByNameSelect.Ref = DFRef.TwoWay[DFDesignInst, PortByNameSelect] + +portByNameSelect.getPortDcl // resolve to actual DFVal.Dcl (via DB.portsByName) + +// Extractor: +DFVal.PortByNameSelect.Of(dcl) // unapply → Option[DFVal.Dcl] +``` + +--- + +## Statement subtypes + +### DFNet (assignment / connection) + +```scala +final case class DFNet( + lhsRef: DFNet.Ref, // DFRef.TwoWay[DFVal | DFInterfaceOwner, DFNet] + op: DFNet.Op, + rhsRef: DFNet.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum DFNet.Op: Assignment, NBAssignment, Connection, ViaConnection, LazyConnection +``` + +**Extension methods:** +```scala +net.isAssignment // Assignment | NBAssignment +net.isConnection // Connection | ViaConnection | LazyConnection +net.isViaConnection +net.isLazyConnection +``` + +**Pattern extractors** (most useful in stages): +```scala +DFNet.Assignment(toVal, fromVal) // Assignment or NBAssignment; toVal and fromVal are DFVal +DFNet.BAssignment(toVal, fromVal) // blocking only (op == Assignment) +DFNet.NBAssignment(toVal, fromVal) // non-blocking only (op == NBAssignment) +DFNet.Connection(toVal, fromVal, swapped) + // toVal: DFVal.Dcl | DFVal.Special | DFInterfaceOwner + // fromVal: DFVal | DFInterfaceOwner + // swapped: Boolean — true if lhs/rhs were physically reversed +``` + +--- + +### Wait +```scala +final case class Wait( + triggerRef: Wait.TriggerRef, // DFRef.TwoWay[DFVal, Wait] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +--- + +### TextOut +```scala +final case class TextOut( + op: TextOut.Op, + msgParts: List[String], // literal string segments + msgArgs: List[DFVal.Ref], // interpolated value arguments + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum TextOut.Op: + case Print, Println, Debug, Finish + case Report(severity: Severity) + case Assert(assertionRef: AssertionRef, severity: Severity) +enum TextOut.Severity: Info, Warning, Error, Fatal +``` + +--- + +### Goto +```scala +final case class Goto( + stepRef: Goto.Ref, // DFRef.TwoWay[StepBlock | ThisStep | NextStep | FirstStep, Goto] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +// Special step marker singletons: +case object ThisStep extends DFMember.Empty +case object NextStep extends DFMember.Empty +case object FirstStep extends DFMember.Empty +``` + +--- + +## DFBlock subtypes + +### DFDesignBlock (also aliased as `DFDesignInst`) +```scala +final case class DFDesignBlock( + domainType: DomainType, + dclMeta: Meta, // declaration-site meta (class name, position, …) + instMode: DFDesignBlock.InstMode, + ownerRef: DFOwner.Ref, + meta: Meta, // instance-site meta (val name, position) + tags: DFTags +) +enum InstMode: Normal, Def, Simulation +enum InstMode.BlackBox: NA, Files(path), Library(libName, nameSpace), VendorIP(vendor, typeName) +``` + +**Extension methods:** +```scala +design.isDuplicate // tagged DuplicateTag (multiple identical instantiations) +design.isBlackBox // instMode is BlackBox +design.isVendorIPBlackbox +design.inSimulation // instMode is Simulation +design.isTop // no ownerRef pointing to another design +design.dclName // design class name (from dclMeta) +design.getCommonDesignWith(other) // nearest common ancestor DFDesignBlock +``` + +**Companion extractors:** +```scala +DFDesignBlock.Top() // matches the top-level design (isTop == true) +``` + +--- + +### DomainBlock +```scala +final case class DomainBlock( + domainType: DomainType, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +--- + +### ProcessBlock +```scala +final case class ProcessBlock( + sensitivity: ProcessBlock.Sensitivity, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +sealed trait ProcessBlock.Sensitivity +case object ProcessBlock.Sensitivity.All // process(all) +final case class ProcessBlock.Sensitivity.List(refs: List[DFVal.Ref]) // process(x, y) +``` + +--- + +### StepBlock +```scala +final case class StepBlock( + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +// Extension methods: +stepBlock.isOnEntry // getName == "onEntry" +stepBlock.isOnExit // getName == "onExit" +stepBlock.isFallThrough // getName == "fallThrough" +stepBlock.isRegular // none of the above +``` + +--- + +### DFConditional + +#### DFMatchHeader +```scala +final case class DFMatchHeader( + dfType: DFType, + selectorRef: DFVal.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +#### DFCaseBlock +```scala +final case class DFCaseBlock( + pattern: DFCaseBlock.Pattern, + guardRef: Block.GuardRef, // optional boolean guard + prevBlockOrHeaderRef: DFCaseBlock.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +**`DFCaseBlock.Pattern` hierarchy:** +```scala +sealed trait Pattern +case object CatchAll // case _ +final case class Singleton(valueRef: DFVal.Ref) // case 42 +final case class Alternative(list: List[Pattern]) // case 1 | 2 | 3 +final case class Struct(name: String, fieldPatterns: List[Pattern])// case MyStruct(...) +final case class Bind(ref: Bind.Ref, pattern: Pattern) // case x @ pattern +final case class NamedArg(name: String, pattern: Pattern) // named arg +final case class BindSI(op, parts, refs) // string-interpolation bind +``` + +#### DFIfHeader +```scala +final case class DFIfHeader( + dfType: DFType, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +#### DFIfElseBlock +```scala +final case class DFIfElseBlock( + guardRef: Block.GuardRef, // Some → if/else-if guard; None → else + prevBlockOrHeaderRef: DFIfElseBlock.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +--- + +### DFLoop + +#### DFForBlock +```scala +final case class DFForBlock( + iteratorRef: DFForBlock.IteratorRef, // DFRef.TwoWay[DFVal.Dcl, DFForBlock] + rangeRef: DFForBlock.RangeRef, // DFRef.TwoWay[DFRange, DFForBlock] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +#### DFWhileBlock +```scala +final case class DFWhileBlock( + guardRef: DFWhileBlock.GuardRef, // DFRef.TwoWay[DFVal, DFWhileBlock] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +#### DFLoop.Block extension methods +```scala +loop.isCombinational // tagged CombinationalTag +loop.isFallThrough // tagged FallThroughTag +``` + +--- + +### DFRange +```scala +final case class DFRange( + startRef: DFRange.Ref, // DFRef.TwoWay[DFVal, DFRange] + endRef: DFRange.Ref, + op: DFRange.Op, + stepRef: DFRange.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum DFRange.Op: Until, To +``` + +--- + +## DFType hierarchy + +``` +DFType (sealed) +├── DFBoolOrBit (sealed) +│ ├── DFBool — boolean, width = 1 +│ └── DFBit — single hardware bit, width = 1 +├── DFBits(widthParamRef) — bit vector +├── DFDecimal(signed, widthParamRef, fractionWidth, nativeType) +│ ├── DFUInt(w) — unsigned integer +│ ├── DFSInt(w) — signed integer +│ └── DFInt32 — standard 32-bit signed (nativeType = Int32) +├── DFEnum(name, widthParam, entries: ListMap[String, BigInt]) +├── DFDouble — 64-bit floating point +├── DFString — unbounded string +├── DFVector(cellType, cellDimParamRefs) — ordered collection +├── DFStruct(name, fieldMap: ListMap[String, DFType]) +├── DFOpaque(name, kind, id, actualType) +├── DFUnit — unit / void +├── DFNothing — bottom type +├── DFTime — physical time quantity +├── DFFreq — physical frequency quantity +└── DFNumber — dimensionless literal number +``` + +**Type marker traits:** +- `ComposedDFType` — has inner types (Vector, Struct, Opaque) +- `NamedDFType` — has a `name: String` (Struct, Opaque, Enum) + +**Common DFType API:** +```scala +dfType.width // Int (requires MemberGetSet for parameterised widths) +dfType.getRefs // List[DFRef.TypeRef] +dfType.copyWithNewRefs // using RefGen +dfType.isSimilarTo(that) // structural equivalence ignoring identity +``` + +**DFVector helpers:** +```scala +vector.length // Int +vector.cellType // DFType of each element +``` + +**DFStruct helpers:** +```scala +struct.fieldMap // ListMap[String, DFType] (ordered) +struct.fieldIndex(name) // Int — field index +struct.fieldRelBitLow(name) // Int — bit offset of field within struct +``` + +**DFOpaque.Kind:** +```scala +sealed trait Kind +case object General extends Kind +sealed trait Magnet extends Kind +case object Clk extends Magnet +case object Rst extends Magnet +case object Magnet extends Magnet // generic magnet + +opaque.isMagnet // kind.isInstanceOf[Magnet] +``` + +**`IntParamRef`** — used for widths and indices in DFBits, DFDecimal, DFVector, ApplyRange: +```scala +opaque type IntParamRef = DFRef.TypeRef | Int +paramRef.getInt // resolve to Int (using MemberGetSet) +paramRef.isInt // true if literal +paramRef.isRef // true if parameterised +``` + +--- + +## DomainType + +```scala +enum DomainType: + case DF // dataflow (timing-agnostic) + case RT(cfg: RTDomainCfg) // register-transfer + case ED // event-driven (Verilog/VHDL semantics) +``` + +### RTDomainCfg +```scala +enum RTDomainCfg: + case Derived // inherit from context + case Related(relatedDomainRef: RTDomainCfg.RelatedDomainRef) + case Explicit(name: String, clkCfg: ClkCfg, rstCfg: RstCfg) +``` + +### ClkCfg.Explicit +```scala +final case class ClkCfg.Explicit( + edge: ClkCfg.Edge, // Rising | Falling + rate: RateNumber, + portName: String, + inclusionPolicy: ClkRstInclusionPolicy +) +enum ClkCfg.Edge: Rising, Falling +``` + +### RstCfg.Explicit +```scala +final case class RstCfg.Explicit( + mode: RstCfg.Mode, // Async | Sync + active: RstCfg.Active, // Low | High + portName: String, + inclusionPolicy: ClkRstInclusionPolicy +) +enum RstCfg.Mode: Async, Sync +enum RstCfg.Active: Low, High +enum ClkRstInclusionPolicy: AsNeeded, AlwaysAtTop +``` + +--- + +## Meta + +```scala +final case class Meta( + nameOpt: Option[String], + position: Position, + docOpt: Option[String], + annotations: List[HWAnnotation] +) +meta.isAnonymous // nameOpt.isEmpty +meta.name // generated name if anonymous, else nameOpt.get +meta.anonymize() // copy with nameOpt = None +meta.setName(name) +``` + +--- + +## DFTags + +```scala +opaque type DFTags = Map[String, DFTag] +tags.tag[CT <: DFTag](t) // add tag, returns new DFTags +tags.removeTagOf[CT] // remove tag by type +tags.getTagOf[CT] // Option[CT] +tags.hasTagOf[CT] // Boolean +tags.++(that) // merge two DFTags +DFTags.empty +``` + +**Built-in tags:** +```scala +case object DuplicateTag // design has multiple identical instantiations +case object IteratorTag // Dcl is a for-loop iterator variable +case object IdentTag // Alias.AsIs is a pure identity (named alias of itself) +case object BindTag // Alias is a pattern-match bind variable +case object CombinationalTag // loop/block is combinational (no cycles) +case object FallThroughTag // loop/block falls through to next step +case class DefaultRTDomainCfgTag(cfg: RTDomainCfg.Explicit) +case object ExtendTag +case object TruncateTag +case class DFHDLVersionTag(version: String) +``` + +--- + +## Reference types + +```scala +sealed trait DFRef[+M <: DFMember]: + val grpId: (Int, Int) // group (position hash, counter) + val id: Int + def get(using MemberGetSet): M + def getOption(using MemberGetSet): Option[M] + def copyAsNewRef(using RefGen): this.type + +type DFRefAny = DFRef[DFMember] +``` + +**Subtypes:** +- `DFRef.OneWay[M]` — unidirectional (e.g. `ownerRef`) +- `DFRef.TwoWay[M, O]` — bidirectional; `O` is the member that owns this ref (enables reverse lookup) +- `DFRef.TypeRef` — used for `IntParamRef` (width/index parameters) + +**Pattern extractor** (very common in stages): +```scala +DFRef(member) // matches any DFRef and extracts the resolved member +// Example: +case DFNet(DFRef(lhs: DFVal), _, DFRef(rhs: DFVal), _, _, _) => ... +``` + +### RefGen + +```scala +class RefGen private (magnetID, grpId, lastId): + def genOneWay[M](): DFRef.OneWay[M] + def genTwoWay[M, O](): DFRef.TwoWay[M, O] + def genTypeRef(): DFRef.TypeRef + def getGrpId: (Int, Int) + def setGrpId(id: (Int, Int)): Unit + +RefGen.initial // fresh RefGen (grpId = (0,0)) +RefGen.fromGetSet // RefGen seeded from the current DB's existing IDs +``` + +--- + +## DB — the design database + +```scala +final case class DB( + members: List[DFMember], // ordered flat list (top-design first) + refTable: Map[DFRefAny, DFMember], + globalTags: DFTags, + srcFiles: List[SourceFile] +) +``` + +**Key computed properties:** +```scala +db.top // DFDesignBlock — first member, always the root design +db.topIOs // List[DFVal.Dcl] — ports of the top design +db.getSet // MemberGetSet (immutable) +db.memberTable // Map[DFMember, Set[DFRefAny]] — member → refs pointing to it +db.originRefTable // Map[DFRef.TwoWayAny, DFMember] — ref → member that owns it +db.originMemberTable // Map[DFMember, Set[DFMember]] — member → members referencing it +db.ownerMemberList // List[(DFOwner, List[DFMember])] — members grouped by owner +db.membersNoGlobals // members excluding global values +db.membersGlobals // global CanBeGlobal values only +db.inSimulation // top has no ports (simulation context) +db.inBuild // top has a device constraint tag +``` + +**Patching:** +```scala +db.patch(patches: List[(DFMember, Patch)]): DB +``` + +### MemberGetSet + +```scala +trait MemberGetSet: + val isMutable: Boolean + def designDB: DB + def apply[M <: DFMember, M0 <: M](ref: DFRef[M]): M0 // resolve ref → member + def getOption[M <: DFMember, M0 <: M](ref: DFRef[M]): Option[M0] + def getOrigin(ref: DFRef.TwoWayAny): DFMember // reverse lookup + def set[M <: DFMember](orig: M)(f: M => M): M // update member + def replace[M <: DFMember](orig: M)(updated: M): M + def remove[M <: DFMember](member: M): M +``` + +`db.getSet` is immutable (`isMutable = false`). In stages, it is passed as `given` automatically: +```scala +def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = ... +``` + +--- + +## Analysis extractors (dfhdl.compiler.analysis.*) + +Import with `import dfhdl.compiler.analysis.*` (already in scope via the standard stage imports). +All require `using MemberGetSet`. + +### DFVal extractors + +| Extractor | Matches | Extracts | +|---|---|---| +| `DclVar()` | `DFVal.Dcl` with `modifier.dir == VAR` | Boolean (use in condition) | +| `DclConst()` | Any `DFVal.CanBeExpr` that is named and constant | Boolean | +| `DclPort()` | `DFVal.Dcl` with IN/OUT/INOUT | Boolean | +| `DclIn()` | `DFVal.Dcl` with IN | Boolean | +| `DclOut()` | `DFVal.Dcl` with OUT | Boolean | +| `IteratorDcl()` | `DFVal.Dcl` tagged `IteratorTag` | Boolean | +| `Ident(underlying)` | `DFVal.Alias.AsIs` tagged `IdentTag` | `DFVal` | +| `Bind(underlying)` | `DFVal.Alias` tagged `BindTag` | `DFVal` | +| `OpaqueActual(relVal)` | `AsIs` unwrapping an opaque | `DFVal` | +| `AsOpaque(relVal)` | `AsIs` casting to an opaque | `DFVal` | +| `ClkEdge(sig, edge)` | `DFVal.Func` with `rising`/`falling` op | `(DFVal, ClkCfg.Edge)` | +| `RstActive(sig, active)` | Reset condition expression | `(DFVal, RstCfg.Active)` | +| `BlockRamVar()` | `DFVal.Dcl` VAR of DFVector with only index accesses | Boolean | +| `DefaultOfDesignParam(dp)` | A value used as default of a DesignParam | `DFVal.DesignParam` | + +### DFVal extension methods (analysis) + +```scala +dfVal.getReadDeps // Set[DFValReadDep] — things that read this value +dfVal.getPartialAliases // Set[DFVal.Alias.Partial] — aliases of this value +dfVal.getConnectionTo // Option[DFNet] — single connection driving this value +dfVal.getConnectionsFrom // Set[DFNet] — connections driven from this value +dfVal.getAssignmentsTo // Set[DFVal] — values assigned to this +dfVal.getAssignmentsFrom // Set[DFVal] — values assigned from this +dfVal.getPortsByNameSelectors // List[DFVal.PortByNameSelect] (ports only) +dfVal.isReferencedByAnyDcl // Boolean +dfVal.isConstVAR // VAR never assigned/connected +dfVal.isAllowedMultipleReferences // Boolean +dfVal.isPartialNetDest // Boolean — is a partial assignment/connection target +dfVal.flatName // String — name derived from structure if anonymous +dfVal.suggestName // Option[String] — inferred name from context +dfVal.collectRelMembers(includeOrig) // List[DFVal] — this + all anonymous arg deps +``` + +**Reverse member lookup (any DFMember):** +```scala +member.originMembers // Set[DFMember] — members whose refs point to this +member.originMembersNoTypeRef // Set[DFMember] — same, excluding TypeRef references +member.consumesCycles // Boolean — true for Wait, StepBlock, Goto, non-comb loops +``` + +### ComposedDFTypeReplacement + +For recursive type transformation across Struct/Vector/Opaque nesting: + +```scala +class ComposedDFTypeReplacement[H]( + preCheck: DFType => Option[H], + updateFunc: PartialFunction[(DFType, H), DFType] +)(using MemberGetSet): + def unapply(dfType: DFType): Option[DFType] +``` + +- `preCheck` — return `Some(helper)` to trigger replacement at this node; `None` to skip (but still recurse into composed children) +- `updateFunc` — given `(composedOrOriginal DFType, helper)` → produce the replacement type +- Recurses automatically into `DFStruct.fieldMap`, `DFVector.cellType`, `DFOpaque.actualType` + +```scala +// Example: replace all DFOpaque types with their actualType +object DropOpaques extends ComposedDFTypeReplacement( + preCheck = { case dt: DFOpaque => Some(()); case _ => None }, + updateFunc = { case (dt: DFOpaque, _) => dt.actualType } +) +// Usage in pattern match: +case dfVal @ ComposedOpaqueDFValReplacement(updatedDFVal) => + dfVal -> Patch.Replace(updatedDFVal, Patch.Replace.Config.FullReplacement) +``` diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md new file mode 100644 index 000000000..53a599d80 --- /dev/null +++ b/.claude/commands/new-stage.md @@ -0,0 +1,753 @@ +# DFHDL Compiler Stage Creation Guide + +> **For contributors adding new compiler transformation stages to DFHDL.** +> This skill is version-controlled alongside the codebase — keep it updated when stage infrastructure changes. + +You are helping create a new DFHDL compiler transformation stage. Use the complete reference below to produce correct, idiomatic code. + +--- + +## Architecture Overview + +A **stage** is a single transformation pass over the design IR (`DB`). Stages are chained by the `StageRunner`, which resolves `dependencies` recursively and re-runs any `nullified` stages when needed. + +``` +Design (Scala frontend) → DB → Stage1 → Stage2 → ... → StagedN → backend printer +``` + +The `DB` is an **immutable snapshot**: each stage receives the current `DB`, computes a patch list, and returns a new `DB`. + +--- + +## Required Stage Properties + +Every stage **must** satisfy two invariants. Violating either causes subtle, hard-to-debug compiler bugs. + +### 1. Determinism — same input → same output, every time + +Given the same input `DB`, a stage must always produce bit-for-bit the same output `DB`, regardless of how many times the overall compilation is run. + +**Common causes of non-determinism to avoid:** +- Iterating over `Set`, `Map`, or any unordered collection to build the patch list — iteration order is not guaranteed. Always convert to a sorted or ordered structure first, or derive order from `designDB.members` (which is a `List` and is ordered). +- Using `hashCode`-based identity anywhere in the transformation logic. +- Relying on mutable external state (counters, caches, `var`s outside the `transform` call). + +**Rule:** Build patch lists by iterating `designDB.members` (ordered) or `designDB.blockMemberList` (ordered). Never collect from a `Set` or `HashMap` directly. + +### 2. Idempotency — `f(f(x)) == f(x)` + +Applying a stage to its own output must yield the same output again. In other words, if the transformation function is `f`, then for every input DB `x`: + +``` +f(f(x)) == f(x) +``` + +This is a **design guideline**, not something that needs to be formally proved. The intent is that a stage should recognize when the IR is already in the form it produces and leave it unchanged. A stage that keeps mutating an already-transformed DB is a sign its pattern matches are too broad. + +**How to achieve idempotency in practice:** +- **Match on the source form only.** Pattern-match on the exact IR shape that the stage is responsible for transforming. The transformed shape should not match the same pattern, so a second run produces an empty patch list. +- **Structural self-consistency.** After `f(x)` is applied, the resulting DB should contain no members that satisfy the stage's match predicates. + +--- + +## Core Infrastructure + +### `Stage` trait +```scala +// compiler/stages/src/main/scala/dfhdl/compiler/stages/Stage.scala +trait Stage extends Product, Serializable, HasTypeName derives CanEqual: + final lazy val depSet: Set[Stage] = dependencies.toSet + def dependencies: List[Stage] // prerequisite stages, run first + def nullifies: Set[Stage] // stages that must re-run after this one + def runCondition(using CompilerOptions): Boolean = true // skip stage when false + def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB // the transformation +``` + +### Special stage subtypes +```scala +sealed trait SpecialControlStage extends Stage // skips sanity checks in trace logging + +trait NoCheckStage extends SpecialControlStage // use when stage intentionally leaves dangling anon refs + +abstract class BundleStage(deps: Stage*) extends NoCheckStage: // no-op, used purely for dependency ordering + def transform(db: DB)(...): DB = db +``` + +### `HasDB` — accepts DB, Design, StagedDesign, or CompiledDesign as input +```scala +trait HasDB[T]: + def apply(t: T): DB +extension [T: HasDB](t: T) def db: DB = summon[HasDB[T]](t) +``` + +### `StageRunner` — recursive dependency-aware executor +```scala +object StageRunner: + def run(stage: Stage)(designDB: DB)(using CompilerOptions, PrinterOptions): DB +``` +- Traverses `dependencies` depth-first before running a stage. +- Removes `nullified` stages from the "done" set so they re-run if needed later. +- Automatically runs `sanityCheck` after each non-`NoCheckStage`. +- At `TRACE` log level, prints the code string after each stage that changed the DB. + +--- + +## IR Data Model + +### `DB` — the design database (immutable) +```scala +// compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +case class DB( + members: List[DFMember], // ordered flat list of all IR members + refTable: Map[DFRefAny, DFMember], // reference → member resolution + globalTags: Map[...], + srcFiles: List[...], +): + def getSet: MemberGetSet // member-lookup context (needed as `given`) + def top: DFDesignBlock // root design block + def memberTable: Map[DFMember, ...] // reverse lookup + def patch(patches: List[(DFMember, Patch)]): DB +``` + +### `DFMember` hierarchy +``` +DFMember (sealed) +├── DFVal ─ any value in the design +│ ├── DFVal.Dcl ─ port/var/const declaration +│ ├── DFVal.Func ─ operation / function call +│ ├── DFVal.Alias ─ alias / cast / selection +│ │ ├── DFVal.Alias.AsIs ─ .as(T) cast +│ │ ├── DFVal.Alias.ApplyIdx ─ vector indexing +│ │ └── DFVal.Alias.SelectField ─ struct field +│ └── DFVal.Const ─ literal constant +├── Statement ─ assignment, connection, net +├── DFBlock ─ container for members +│ ├── DFDesignBlock ─ module / design definition +│ ├── ProcessBlock ─ always/process block +│ ├── StepBlock ─ RT step (FSM state) +│ └── DFConditional.Block ─ if/match/while clause +├── DFConditional.Header ─ if/match/while header +├── DFInterfaceOwner +└── DFRange +``` + +### Useful extractor patterns (already in scope via `dfhdl.compiler.analysis.*`) +```scala +DclVar() // matches DFVal.Dcl that is a variable +DclConst() // matches DFVal.Dcl that is a constant +IteratorDcl() // matches for-loop iterator declarations +DclBind() // matches pattern-match bind declarations +Ident(underlying) // matches named alias that just wraps another value +``` + +### Navigation helpers on `DFMember` +```scala +m.getOwner // immediate parent member +m.getOwnerBlock // nearest enclosing block +m.getOwnerDesign // nearest enclosing design block +m.getOwnerDomain // nearest domain-bearing block +m.isAnonymous // true if the value has no user-visible name +m.originMembers // members that reference this member +``` + +### Domain checks +```scala +m.isInDFDomain // dataflow domain +m.isInRTDomain // register-transfer domain +m.isInProcess // inside a process block +``` + +--- + +## Patch System + +All mutations are expressed as a **patch list**: `List[(DFMember, Patch)]`. Apply them at the end: + +```scala +designDB.patch(patchList) +``` + +### Patch types + +| Patch | Effect | +|---|---| +| `Patch.Replace(newMember, cfg)` | Replace or re-reference a member | +| `Patch.Move(member, cfg)` | Move a member to a different position | +| `Patch.Add(dsn, cfg)` | Insert new members constructed via `MetaDesign` | +| `Patch.Remove(isMoved)` | Remove a member; `isMoved=true` preserves references | +| `Patch.ChangeRef(from, to)` | Redirect a single reference | + +### `Patch.Replace` configs + +```scala +Patch.Replace.Config.FullReplacement // replaces the member in the member list +Patch.Replace.Config.ChangeRefOnly // only updates references, keeps original in list +Patch.Replace.Config.ChangeRefAndRemove // updates refs AND removes original from list +``` + +Optionally scope reference changes with a `RefFilter`: +```scala +Patch.Replace.RefFilter.OfMembers(memberSet) // only refs from specific members +// or implement custom RefFilter: +object MyFilter extends Patch.Replace.RefFilter: + def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = refs.filter(...) +``` + +### `Patch.Move` configs +```scala +Patch.Move.Config.Before // insert before the anchor member +Patch.Move.Config.After +Patch.Move.Config.InsideFirst // insert as first child of a block +Patch.Move.Config.InsideLast +``` + +### `Patch.Add` via `MetaDesign` +Use `MetaDesign` when you need to construct new IR members using the DFHDL frontend DSL: + +```scala +val dsn = new MetaDesign( + anchorMember, // where to insert + Patch.Add.Config.Before // or After, InsideFirst, InsideLast + // or ReplaceWithFirst/ReplaceWithLast(replCfg) +): + // write DFHDL frontend code here — ports, vars, assignments, etc. + val newVar = someType.<>(VAR) + newVar := existingVal.asValAny + +dsn.patch // returns a single (DFMember, Patch) entry for the patch list +``` + +`ReplaceWithLast` / `ReplaceWithFirst` also replace the anchor with the last/first generated member: +```scala +Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove) +``` + +See the **MetaDesign Deep Dive** section below for the full mechanics. + +--- + +## DFC — The Implicit Design Context + +`DFC` (`dfhdl.core.DFC`) is the implicit context that flows through every frontend DSL operation. It carries the state needed to register new IR members in the right place during elaboration. + +```scala +// core/src/main/scala/dfhdl/core/DFC.scala +final case class DFC( + nameOpt: Option[String], // name the next member will receive + position: Position, // source-file position for meta/errors + docOpt: Option[String], // doc-comment + annotations: List[HWAnnotation], // active hardware annotations + mutableDB: MutableDB, // the live database being built + refGen: ir.RefGen, // reference-ID generator + tags: ir.DFTags, // member-level tags + elaborationOptionsContr: () => ElaborationOptions +) extends MetaContext +``` + +Every `Design` class provides its own `DFC` as a `given`: + +```scala +trait HasDFC: + lazy val dfc: DFC + protected given DFC = dfc // makes dfc available implicitly in the design body +``` + +All DSL operations (declaring ports, calling `:=`, etc.) require `using DFC`. The compiler plugin +injects DFC into the right places automatically — you rarely summon it explicitly. + +### Key DFC methods used in stages + +```scala +dfc.setMeta(meta) // copy DFC with updated name/position/doc/annotations +dfc.setName("foo") // copy DFC with a specific name for the next member +dfc.anonymize // copy DFC with nameOpt = None (anonymous member) +dfc.setMeta(m.meta) // copy DFC mirroring another member's metadata +dfc.enterOwner(owner) // push a new owner onto the ownership stack +dfc.exitOwner() // pop the owner stack +dfc.owner // current owner (top of stack) +dfc.inMetaProgramming // true when inside a MetaDesign +dfc.mutableDB // access the MutableDB directly +dfc.getSet // implicit MemberGetSet backed by mutableDB +``` + +### DFCG — the "global" variant + +`DFCG` is an opaque subtype of `DFC`. It is auto-synthesised when no explicit `DFC` is in scope +(i.e., in global/top-level Scala scope outside any design body). Operations that only need a name +and position (e.g., `==` for constant comparison) accept `using DFCG` instead of `using DFC`. + +--- + +## MutableDB vs DB + +### DB — immutable snapshot + +```scala +// compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +final case class DB( + members: List[DFMember], + refTable: Map[DFRefAny, DFMember], + globalTags: DFTags, + srcFiles: List[SourceFile] +) +``` + +- A **frozen, serialisable** record of the whole design. +- The `MemberGetSet` derived from it is **read-only** (`isMutable = false`). +- Every stage receives a `DB` and returns a new `DB`; it never mutates the one it received. +- Used by: stage `transform` methods, printers, analysis passes. + +### MutableDB — live workspace during elaboration + +```scala +// core/src/main/scala/dfhdl/core/MutableDB.scala +final class MutableDB() +``` + +- A **mutable** collection of `MemberEntry` records, reference tables, and ownership state. +- Lives inside `DFC`; every frontend DSL call that registers a member touches it. +- The `MemberGetSet` derived from it is **read-write** (`isMutable = true`). +- Converted to an immutable `DB` snapshot via `.immutable` (called internally by `design.getDB`). +- Used by: `Design` subclasses during Scala elaboration, `MetaDesign` during stage patching. + +| | `DB` | `MutableDB` | +|---|---|---| +| Mutability | Immutable case class | Mutable class | +| Phase | Post-elaboration (compilation) | During elaboration | +| `getSet.isMutable` | `false` | `true` | +| Member changes | None | `addMember`, `plantMember`, `newRefFor` | +| Ownership context | Static | OwnershipContext stack (enter/exit) | +| Serialisable | Yes (upickle) | No | +| Conversion | — | `.immutable` → `DB` | + +### Key MutableDB operations (used inside `MetaDesign`) + +```scala +mutableDB.addMember(member) // register a member under the current owner +mutableDB.plantMember(owner, member) // register member, forcing a specific owner +mutableDB.newRefFor(ref, member) // update what an existing reference points to +mutableDB.injectMetaGetSet(getSet) // allow meta-design members to reference the parent DB +mutableDB.OwnershipContext.enter(owner) // push owner +mutableDB.OwnershipContext.exit() // pop owner +mutableDB.immutable // snapshot → DB +``` + +--- + +## MetaDesign — Deep Dive + +`MetaDesign` (`core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala`) is the bridge +between the frontend DSL and stage patching. It lets you write ordinary DFHDL code (ports, vars, +assignments) inside a stage and have those members injected into the DB at a precise location. + +### Signature + +```scala +abstract class MetaDesign[+D <: DomainType]( + positionMember: ir.DFMember, // anchor: where to inject relative to + addCfg: Patch.Add.Config, + domainType: D = DomainType.DF +)(using + getSet: ir.MemberGetSet, // parent DB access (injected automatically by the stage) + refGen: ir.RefGen // shared reference generator +) extends Design with reflect.Selectable +``` + +`MetaDesign` extends `Design`, so you can write any DFHDL DSL code in its body. It is **ephemeral** +— the MetaDesign block itself is never present in the final DB; only the members created inside it +survive. + +### How the DFC is set up + +```scala +final override protected def __dfc: DFC = + DFC.emptyNoEO.copy(refGen = refGen, position = positionMember.meta.position) +``` + +The MetaDesign starts with a minimal DFC that: +- Reuses the **same `refGen`** as the calling stage (ensures generated reference IDs are globally + unique and don't clash with existing members). +- Copies the **source position** of the anchor member (so synthesised members appear to originate + at the right place in the source). + +After construction, the MetaDesign calls: +```scala +dfc.mutableDB.injectMetaGetSet(getSet) // lets members inside reference the parent DB +``` +This is what makes it possible to reference existing IR members (e.g., `existingVal.asValAny`) +from within a MetaDesign body. + +### Where members are injected + +```scala +lazy val injectedOwner: ir.DFOwner = addCfg match + case InsideFirst | InsideLast => + positionMember.asInstanceOf[ir.DFOwner] // positionMember must be a block + case _ if globalInjection => getSet.designDB.top + case _ => positionMember.getOwner // same owner as anchor +``` + +For `Before` / `After` / `ReplaceWith*`, members are created inside the **same owner as the anchor +member** (a sibling). For `InsideFirst` / `InsideLast`, members are created **inside** the anchor +(the anchor must be a block/design). + +### The `.patch` property + +```scala +lazy val patch = positionMember -> Patch.Add(this, addCfg) +``` + +This is a `(DFMember, Patch)` pair ready to be added to a stage's patch list. When the DB applies +this patch it: +1. Extracts the members created inside the MetaDesign. +2. Removes the MetaDesign block itself. +3. Inserts the members at the correct position relative to `positionMember` according to `addCfg`. + +### Planting existing IR members + +Sometimes you want to move or clone members that **already exist in the DB** into the MetaDesign +context: + +```scala +// Inject a single existing IR member, forcing it under the current owner +plantMember(existingIRMember) + +// Inject a set of members, re-owning those that belonged to baseOwner +plantMembers(baseOwner, memberIterable) + +// Deep-clone a list of members (new refs), re-mapping internal references +val cloneMap = plantClonedMembers(baseOwner, memberList) +// cloneMap: Map[original -> clone] for further reference remapping +``` + +### Temporarily switching owner during construction + +```scala +applyBlock(someOwner): + // members created here belong to someOwner, not the current owner + val v = SomeType <> VAR +``` + +### Converting IR members to core types inside MetaDesign + +Because MetaDesign extends Design, the following exports are always available inside its body: + +```scala +existingIRVal.asValAny // ir.DFVal → core DFValAny (wrap without adding to DB) +existingCoreVal.asIR // core DFVal → ir.DFVal (unwrap) +plantMember(irMember) // add an existing IR member to this MetaDesign's DB +``` + +--- + +## IR Layer vs Core Layer + +DFHDL uses a strict two-layer architecture: + +| | IR layer (`dfhdl.compiler.ir`) | Core layer (`dfhdl.core`) | +|---|---|---| +| Purpose | Immutable AST, serialisable | Live DSL objects during elaboration | +| DFVal | `ir.DFVal` — sealed trait + case-class subtypes | `core.DFVal[T, M]` — opaque wrapper around `ir.DFVal` | +| DFType | `ir.DFType` — sealed ADT (DFBits, DFStruct, …) | Extension methods on `ir.DFType` via `.asFE[T]` | +| Design block | `ir.DFDesignBlock` — immutable case class | `core.Design` — live abstract class with DFC | +| Mutability | Immutable; updates return new instances (`.copy`) | Mutable via `MutableDB` | +| Lifetime | Persists in `DB` across all stages | Ephemeral; discarded after `design.getDB` | +| Serialisable | Yes | No | + +### `ir.DFVal` — the IR representation + +```scala +// compiler/ir — sealed trait with case-class subtypes +sealed trait DFVal extends DFMember.Named: + val dfType: DFType +// Subtypes: +// DFVal.Const — literal constant with data +// DFVal.Dcl — port / var / const declaration +// DFVal.Func — computed expression / operator +// DFVal.Alias.AsIs — type cast (.as(T)) +// DFVal.Alias.ApplyIdx — vector indexing +// DFVal.Alias.SelectField — struct field selection +// DFVal.Alias.History — .prev(n) +// DFVal.Alias.ApplyRange — bit-range slice +// DFVal.DesignParam — design parameter reference +// DFVal.Special — NOTHING, OPEN, CLK_FREQ +``` + +Every IR member is a **pure data record** — a case class with no behaviour beyond `.copy()`. +When a stage needs to change a member, it creates a modified copy and patches it in. + +### `core.DFVal[T, M]` — the core (frontend) representation + +```scala +// core/src/main/scala/dfhdl/core/DFVal.scala +into final class DFVal[+T <: DFTypeAny, +M <: ModifierAny](val irValue: ir.DFVal | DFError) + extends DFMember[ir.DFVal] with Selectable +``` + +- An **opaque value class** — zero runtime overhead, just wraps `ir.DFVal`. +- `T` is the DFHDL type (e.g., `DFBits[8]`); `M` is the modifier (IN, OUT, VAR, CONST, …). +- All DSL operations (arithmetic, assignments, `:=`, `<>`) are extension methods on `DFVal`. +- Requires `using DFC` to register the resulting IR members in the `MutableDB`. +- The `| DFError` union allows deferred error reporting without exceptions. + +### Key conversion methods + +```scala +// IR → Core (wrapping) +irDFVal.asValAny // ir.DFVal → core.DFValAny +irDFVal.asVal[MyDFType, MyModifier] // ir.DFVal → typed core.DFVal[T, M] +irDFVal.asFE[MyCoreDFType] // general ir member → core frontend type + +// Core → IR (unwrapping) +coreDFVal.asIR // core.DFVal[T,M] → ir.DFVal (throws on DFError) + +// Registering in DB +irMember.addMember // (using DFC) adds IR member to the current MutableDB +``` + +### Why the split matters for stage authors + +Stage `transform` methods work **entirely in the IR layer**: they receive `ir.DB`, pattern-match +on `ir.DFMember` subtypes, and return a patched `ir.DB`. No `DFC` is required. + +`MetaDesign` is the **only place** where you cross from IR into Core: inside a MetaDesign body you +write Core DSL code (which needs `DFC`), and `.patch` converts the result back to a +`(ir.DFMember, Patch)` pair that the stage can use. + +The conversion idiom you will see most often in stage code: +```scala +// cross from IR into core to reference an existing member inside a MetaDesign +existingIRVal.asValAny // wrap for DSL use — does NOT add to DB +plantMember(existingIRVal) // wrap AND register under the MetaDesign owner + +// cross from core back to IR to build a Patch +dsn.plantedNewVar.asIR // get the IR DFVal that was created inside the MetaDesign +``` + +--- + +## Transformation Patterns + +> **IR member hierarchy, field definitions, and pattern-match extractors → see `/ir-reference`.** + +### Pattern 1 — Simple member replacement (most common) +Collect patch entries for every matching member, then apply: + +```scala +def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = + val patchList: List[(DFMember, Patch)] = + designDB.members.view.flatMap { + case m @ SomePattern(...) => + Some(m -> Patch.Replace(m.copy(...), Patch.Replace.Config.FullReplacement)) + case _ => None + }.toList + designDB.patch(patchList) +``` + +### Pattern 2 — Move members (e.g. hoisting declarations) +```scala +designDB.members.view + .collect { case m @ DclVar() => m } + .flatMap { dcl => + dcl.getOwnerBlock match + case cb: DFConditional.Block => + Some(cb.getTopConditionalHeader -> Patch.Move(dcl, Patch.Move.Config.Before)) + case _ => None + }.toList +``` + +### Pattern 3 — Construct new members with `MetaDesign` +```scala +designDB.members.view.flatMap { + case target @ MatchPattern() => + val dsn = new MetaDesign( + target, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove) + ): + val reg = someType.<>(VAR.REG) + reg.din := target.asValAny + Some(dsn.patch) + case _ => None +}.toList +``` + +### Pattern 4 — Type replacement (for opaque/struct type changes) +```scala +object MyDFTypeReplacement extends ComposedDFTypeReplacement( + preCheck = { case dt: DFOpaque if pred(dt) => Some(()); case _ => None }, + updateFunc = { case (dt: DFOpaque, _) => dt.actualType } +) +// Then match on values and call dfVal.updateDFType(newType) +``` + +### Pattern 5 — Backend-conditional logic +```scala +def transform(designDB: DB)(using MemberGetSet, co: CompilerOptions): DB = + val isVHDL = co.backend.isVHDL + val isVerilogOld = co.backend match + case be: dfhdl.backends.verilog => + be.dialect match + case VerilogDialect.v95 | VerilogDialect.v2001 => true + case _ => false + case _ => false + ... +``` + +### Pattern 6 — Recursive application (until stable) +Use when removing one member may expose additional members to remove: + +```scala +def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = + val patchList = designDB.members.flatMap { ... } + if (patchList.isEmpty) designDB + else + val newDB = designDB.patch(patchList) + transform(newDB)(using newDB.getSet) +``` + +### Pattern 7 — Multi-phase transformation +Apply phase 1, create a new `MemberGetSet`, then apply phase 2: + +```scala +val phase1DB = designDB.patch(phase1Patches) +locally { + given MemberGetSet = phase1DB.getSet + val phase2Patches = phase1DB.members.flatMap { ... } + phase1DB.patch(phase2Patches) +} +``` + +### Pattern 8 — `runCondition` for conditional stages +```scala +override def runCondition(using co: CompilerOptions): Boolean = + co.dropUserOpaques || co.backend match + case be: dfhdl.backends.verilog => be.dialect == VerilogDialect.v95 + case _ => false +``` + +--- + +## Boilerplate Template + +### Stage file: `compiler/stages/src/main/scala/dfhdl/compiler/stages/MyStage.scala` + +```scala +package dfhdl.compiler.stages + +import dfhdl.compiler.analysis.* +import dfhdl.compiler.ir.* +import dfhdl.compiler.patching.* +import dfhdl.options.CompilerOptions + +/** One-line description of what this stage does and why it exists. */ +case object MyStage extends Stage: + def dependencies: List[Stage] = List(SomePriorStage) // run these first + def nullifies: Set[Stage] = Set(SomeInvalidatedStage) + + def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = + val patchList: List[(DFMember, Patch)] = + designDB.members.view.flatMap { + case m @ TargetPattern(...) => + Some(m -> Patch.Replace(m.copy(/* changes */), Patch.Replace.Config.FullReplacement)) + case _ => None + }.toList + designDB.patch(patchList) +end MyStage + +// Convenience extension — follows the naming convention of all other stages +extension [T: HasDB](t: T) + def myStage(using CompilerOptions): DB = + StageRunner.run(MyStage)(t.db) +``` + +--- + +## Test File Template + +### Test file: `compiler/stages/src/test/scala/StagesSpec/MyStageSpec.scala` + +```scala +package StagesSpec + +import dfhdl.* +import dfhdl.compiler.stages.myStage +// scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} + +class MyStageSpec extends StageSpec: + + test("basic transformation") { + class Top extends EDDesign: // or RTDesign / DFDesign + val x = UInt(8) <> IN + val y = UInt(8) <> OUT + process(all): + y :== x + val result = (new Top).myStage + assertCodeString( + result, + """|class Top extends EDDesign: + | val x = UInt(8) <> IN + | val y = UInt(8) <> OUT + | process(all): + | y :== x + |end Top + |""".stripMargin + ) + } + + test("backend-specific behaviour under VHDL") { + given options.CompilerOptions.Backend = backends.vhdl.v93 + class Top extends EDDesign: + ... + val result = (new Top).myStage + assertCodeString(result, "...") + } + +end MyStageSpec +``` + +### `StageSpec` reference +```scala +// compiler/stages/src/test/scala/StageSpec.scala +abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) + extends FunSuite, NoTopAnnotIsRequired: + inline def assertCodeString(db: DB, cs: String): Unit = + import db.getSet + if (stageCreatesUnrefAnons) db.dropUnreferencedAnons.sanityCheck + else db.sanityCheck + assertNoDiff(DefaultPrinter.csDB, cs) +``` + +- Pass `stageCreatesUnrefAnons = true` when your stage intentionally leaves unreferenced anonymous members (e.g. it extends `NoCheckStage`). `assertCodeString` will then call `dropUnreferencedAnons` automatically before checking. +- `assertNoDiff` from munit gives a clear character-level diff on mismatch. +- The expected code string must reproduce DFHDL source syntax exactly (including `end DesignName` terminators, blank lines between designs, and pipe-aligned fields when using the scalafmt alignment hint at the top of the file). + +--- + +## Common Mistakes to Avoid + +1. **Iterating over `Set` or `Map` to build the patch list** — order is undefined, producing non-deterministic output. Always iterate `designDB.members` (a `List`) to drive patch collection. +2. **Overly broad pattern matches** — if a match can fire on already-transformed IR, the stage is not idempotent (`f(f(x)) ≠ f(x)`). Match on the exact source shape so that the output IR no longer satisfies the predicate. +3. **Forgetting `end transform` / `end MyStage`** — scalafmt's `insertEndMarkerMinLines = 15` rule requires end markers on blocks ≥ 15 lines. +2. **Missing `given RefGen = RefGen.fromGetSet`** inside `transform` when using `MetaDesign` with fresh reference generation. +3. **Mutating `getSet`** — always create a new `MemberGetSet` via `newDB.getSet` after patching before iterating again. +4. **Patch order matters** — patches are applied in list order; a `Move` before a `Replace` on the same member may conflict. +5. **Not extending `NoCheckStage`** when your stage produces unreferenced anonymous members as intermediate output — the automatic sanity check will fail. +6. **Nullifying too aggressively** — only nullify a stage if your transformation invalidates its invariant. Unnecessary nullification forces redundant re-runs. +7. **Confusing `ChangeRefOnly` vs `FullReplacement`** — `ChangeRefOnly` keeps the old member in the member list (useful when another stage still expects it); `FullReplacement` swaps it in place. + +--- + +## Checklist for a New Stage + +- [ ] Stage `object`/`class` extends `Stage` (or `NoCheckStage` / `BundleStage` if appropriate) +- [ ] `dependencies` lists every stage that must run first +- [ ] `nullifies` lists every stage whose invariant this transformation breaks +- [ ] `runCondition` added if the stage should be skipped for some backends/options +- [ ] `transform` builds `patchList` and calls `designDB.patch(patchList)` +- [ ] Patch list is derived from `designDB.members` (ordered `List`) — never from an unordered `Set` or `Map` **[determinism]** +- [ ] Pattern matches target the *source* form only; the transformed IR should not re-match the same predicate, so `f(f(x)) == f(x)` **[idempotency]** +- [ ] Convenience `extension` method added at the bottom of the file +- [ ] Test file in `StagesSpec/` extends `StageSpec` +- [ ] At least one "basic" test and one "edge case" or "backend-specific" test +- [ ] `assertCodeString` expected strings verified manually or via a first-run snapshot +- [ ] `sbt test` passes (or `sbt quickTestSetup; test` for faster iteration via `lib/Playground.scala`) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..0e0a6ea45 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,112 @@ +# DFHDL — Claude Code Guide + +> **For contributors and Claude Code users working on the DFHDL project.** +> This file is version-controlled — keep it updated as the project structure evolves. +> Skills for deeper topics live in [.claude/commands/](.claude/commands/). + +## Project Overview + +**DFHDL (DFiant HDL)** is a dataflow hardware description language embedded as a Scala 3 library. It provides timing-agnostic and device-agnostic hardware design with three levels of abstraction: + +- **Dataflow (DF)**: Timing-agnostic, uses dataflow firing rules +- **Register-Transfer (RT)**: Equivalent to Chisel/Amaranth +- **Event-Driven (ED)**: Equivalent to Verilog/VHDL + +Outputs: Verilog, SystemVerilog, VHDL. + +## Build System + +**Tool**: SBT 1.12.2 — **Scala**: 3.8.1 (nightly resolver enabled) + +```bash +sbt compile # compile all subprojects +sbt test # run all unit tests +sbt testApps # run simulation/app tests (requires OSS CAD tools) +sbt quickTestSetup # limit test scope to lib/Playground.scala only (fast iteration) +sbt clearSandbox # delete sandbox/ directory +sbt docExamplesRefUpdate # copy generated HDL from sandbox/ to lib/src/test/resources/ref/ +``` + +## Subproject Structure + +Dependencies flow left to right: + +``` +internals → plugin → compiler_ir → core → compiler_stages → lib → platforms + → ips +``` + +| Subproject | SBT name | Directory | Purpose | +|---|---|---|---| +| internals | `internals` | `internals/` | Core utilities: BitVector, MetaContext, DiskCache, etc. | +| plugin | `plugin` | `plugin/` | Scala 3 compiler plugin (9 phases) | +| compiler_ir | `compiler_ir` | `compiler/ir/` | IR/AST data structures, type system | +| core | `core` | `core/` | HDL language abstractions (DFVal, DFType, Design) | +| compiler_stages | `compiler_stages` | `compiler/stages/` | 50+ transformation stages for code generation | +| lib | `lib` | `lib/` | Standard library: arithmetic, memory, ALU, crypto | +| platforms | `platforms` | `platforms/` | FPGA board wrappers (Apache 2.0 licensed) | +| ips | `ips` | `ips/` | IP cores library | + +## Compiler Plugin Phases + +Located in `plugin/src/main/scala/plugin/`: + +1. `PreTyperPhase` — pre-typing transformations +2. `TopAnnotPhase` — top-level annotation processing +3. `MetaContextPlacerPhase` — places meta-context markers +4. `LoopFSMPhase` — loop-to-FSM transformations +5. `CustomControlPhase` — custom control flow +6. `DesignDefsPhase` — design definition processing +7. `MetaContextDelegatePhase` — meta-context delegation +8. `MetaContextGenPhase` — meta-context code generation +9. `OnCreateEventsPhase` — on-create event handling + +The plugin is applied to `core`, `compiler_stages`, `lib`, `platforms`, and `ips` via `pluginUseSettings` / `pluginTestUseSettings`. + +## Testing + +**Framework**: munit 1.2.2 + +- **Stage tests**: `compiler/stages/src/test/scala/StagesSpec/` — tests each compiler stage +- **Doc example tests**: `lib/src/test/scala/docExamples/` — validates documentation examples +- **Arithmetic tests**: `lib/src/test/scala/ArithSpec/` +- **AES tests**: `lib/src/test/scala/AES/` +- **Base class**: `DesignSpec` — provides `assertCodeString()` and `assertElaborationErrors()` +- **Playground**: `lib/src/test/scala/Playground.scala` — used for quick local iteration via `quickTestSetup` + +Generated HDL reference files live in `lib/src/test/resources/ref/`. Update them with `sbt docExamplesRefUpdate` after intentional output changes. + +`testApps` auto-detects installed simulation tools (ghdl, nvc, verilator, iverilog, questa, vivado) and runs the AES cipher simulation against all available tool/dialect combinations. + +## Code Conventions + +- **Formatting**: scalafmt 3.10.6, max 100 columns, Scala 3 dialect + - Optional braces removed (`removeOptionalBraces = oldSyntaxToo`) + - End markers inserted for blocks ≥ 15 lines + - Run `scalafmt` before committing +- **Compiler flags**: `-language:strictEquality`, `-unchecked`, `-feature`, `-preview`, `-deprecation` +- **Implicit conversions**: only enabled in `internals` and `compiler_ir` via `implicitConversionSettings` +- **Naming**: `DF`-prefixed types (e.g., `DFVal`, `DFType`), `DFC` for context; stage names follow `Drop*`, `Add*`, `Connect*`, `Break*` patterns +- **Package root**: `dfhdl.*` + +## Key Files + +| File | Purpose | +|---|---| +| `build.sbt` | Multi-project build definition | +| `project/DFHDLCommands.scala` | Custom SBT commands | +| `.scalafmt.conf` | Code formatting rules | +| `mkdocs.yml` | Documentation site config | +| `sandbox/` | Generated output during tests/apps (gitignored, cleared by `clearSandbox`) | +| `lib/src/test/resources/ref/` | Reference HDL output snapshots for regression tests | + +## External Simulation Tools (for `testApps`) + +CI installs these via OSS CAD Suite: +- **Verilog**: verilator, iverilog (sv2005 skipped for iverilog), questa, vivado +- **VHDL**: ghdl, nvc, questa, vivado (v2008 skipped for vivado) + +## Licenses + +- Main library (`internals`, `plugin`, `compiler_ir`, `core`, `compiler_stages`, `lib`, `ips`): **LGPL v3.0** +- `platforms/`: **Apache 2.0** From 07af0ec86f0948561e5922a794ed55069c5480cc Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 22 Mar 2026 18:41:14 +0200 Subject: [PATCH 028/115] introduce FlattenStepBlock stage add missing documentation --- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 11 + .../dfhdl/compiler/stages/DropRTWaits.scala | 4 + .../compiler/stages/FlattenStepBlocks.scala | 386 +++++++++++++++ .../StagesSpec/FlattenStepBlocksSpec.scala | 466 ++++++++++++++++++ .../dfhdl/compiler/patching/MetaDesign.scala | 26 + .../scala/dfhdl/compiler/patching/Patch.scala | 45 +- core/src/main/scala/dfhdl/core/DFC.scala | 4 + 7 files changed, 936 insertions(+), 6 deletions(-) create mode 100644 compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala create mode 100644 compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala 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 2a64a2946..ed29b178e 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -1255,6 +1255,17 @@ object DB: def fromJsonString(json: String): DB = read[DB](json) end DB +/** Controls how `owner.members(view)` traverses the ownership tree. + * - `Folded`: returns only the direct children of the owner (members whose `getOwner == owner`). + * - `Flattened`: returns all descendants recursively. For a `DFDesignBlock` owner this returns + * every member in the design (via `designMemberTable`). For other owners it recurses into + * nested blocks but does NOT cross `DFDesignBlock` boundaries — sub-designs appear as a + * single opaque entry. + * + * Use `Folded` when you only need to process immediate children (e.g. steps at one nesting level). + * Use `Flattened` when you need to inspect or collect all descendants (e.g. all `Goto` members + * anywhere inside a `ProcessBlock`, or gathering every member to move together with a block). + */ enum MemberView derives CanEqual: case Folded, Flattened diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala index 03d6c796d..b8aa60d32 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala @@ -282,6 +282,10 @@ case object DropRTWaits extends Stage: dfc.exitOwner() enterStepBlock(wb, lastLoopMember, Some(elseDsn.patch._2)) Some(stepAndIfDsn.patch) + // onEntry/onExit/fallThrough blocks must NOT be renamed: DropRTProcess identifies them + // by exact names "onEntry"/"onExit"/"fallThrough" via isOnEntry/isOnExit/isFallThrough. + // They also must NOT affect the step-name counter of their enclosing scope. + case stepBlock: StepBlock if !stepBlock.isRegular => None case stepBlock: StepBlock => val stepName = getStepName(stepBlock) val lastStepBlockMember = stepBlock.getVeryLastMember.get diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala new file mode 100644 index 000000000..87214c2a6 --- /dev/null +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala @@ -0,0 +1,386 @@ +package dfhdl.compiler.stages + +import dfhdl.compiler.analysis.* +import dfhdl.compiler.ir.* +import dfhdl.compiler.patching.* +import dfhdl.options.CompilerOptions +import dfhdl.internals.* +import scala.annotation.tailrec +//format: off +/** This stage flattens the nested StepBlock hierarchy produced by [[DropRTWaits]] so that every + * StepBlock in an RT process becomes a direct child of the enclosing ProcessBlock, and resolves + * every relative `Goto` reference (`NextStep`, `ThisStep`, `FirstStep`) to an explicit reference + * to the concrete target StepBlock. After this stage, every `Goto` in every RT process carries + * an explicit `StepBlock` reference — no relative form remains. + * + * == Background == + * + * After [[DropRTWaits]], waits and while-loops inside an RT process have been converted into + * StepBlocks. When the original source contained nested wait/while constructs (e.g. a while loop + * with waits inside it, or a user-defined step block that contains further step blocks), the + * resulting IR reflects that nesting: some StepBlocks own other StepBlocks as children. + * Each step's control-flow terminus is a `Goto` member whose `stepRef` points to one of: + * - A concrete `StepBlock` — an explicit jump to that state (already resolved). + * - `Goto.NextStep` — advance to the "next" state in the sequential order (relative). + * - `Goto.ThisStep` — loop back to the current state (relative; used by while-loop steps). + * - `Goto.FirstStep` — jump to the first state of the process (relative). + * + * The three relative forms must all be resolved to explicit `StepBlock` references before + * [[DropRTProcess]] can generate the FSM. `NextStep` additionally requires hierarchy context + * that is destroyed by flattening, so all three are resolved here. + * + * == Transformation Rules == + * + * 1. Relative `NextStep` in the last step of a process (in DFS pre-order) wraps around to the + * first step. In all other steps `NextStep` advances to the immediately following step: + * ```scala + * // input + * process: + * def S0: Step = + * y.din := 0 + * NextStep + * end S0 + * def S1: Step = + * y.din := 1 + * NextStep + * end S1 + * // output + * process: + * def S0: Step = + * y.din := 0 + * S1 + * end S0 + * def S1: Step = + * y.din := 1 + * S0 + * end S1 + * ``` + * 2. `ThisStep` resolves to the enclosing step; `FirstStep` resolves to the first step of the + * process (DFS pre-order head): + * ```scala + * // input — S_0 loops to itself; S_1 jumps back to S_0 + * process: + * def S_0: Step = + * if (i) ThisStep else NextStep + * end S_0 + * def S_1: Step = + * if (i) FirstStep else NextStep + * end S_1 + * def S_2: Step = NextStep + * end S_2 + * // output + * process: + * def S_0: Step = + * if (i) S_0 else S_1 + * end S_0 + * def S_1: Step = + * if (i) S_0 else S_2 + * end S_1 + * def S_2: Step = S_0 + * end S_2 + * ``` + * 3. Non-step statements that appear between consecutive steps at any nesting level are relocated + * into the body of the immediately preceding step (before its terminal `NextStep` goto). + * They are placed at the end of the deepest last-child step, so they execute just before + * control leaves that sub-tree: + * ```scala + * // input + * process: + * def S_0: Step = NextStep + * end S_0 + * x.din := i // inter-step statement + * def S_1: Step = NextStep + * end S_1 + * // output — x.din := i moved into S_0 before its goto + * process: + * def S_0: Step = + * x.din := i + * S_1 + * end S_0 + * def S_1: Step = S_0 + * end S_1 + * ``` + * 4. Nested StepBlocks (a step that directly contains another step) are lifted one level at a + * time until all steps are direct children of the ProcessBlock. The parent step's `NextStep` + * is replaced by a goto to the first child step; the last child's `NextStep` becomes the + * former parent's `NextStep` target: + * ```scala + * // input + * process: + * def MyStep: Step = + * def MyStep_0: Step = NextStep + * end MyStep_0 + * NextStep + * end MyStep + * // output + * process: + * def MyStep: Step = MyStep_0 + * end MyStep + * def MyStep_0: Step = MyStep + * end MyStep_0 + * ``` + * 5. A StepBlock nested directly inside a conditional branch is extracted to ProcessBlock level. + * A goto to that step replaces it in the branch; the "consumed Goto" that immediately followed + * it in the branch (encoding what happens when the step sequence ends) is removed and its + * target becomes the extracted step's terminal goto: + * ```scala + * // input + * process: + * def S_0: Step = + * if (i) + * def S_0_0: Step = NextStep + * end S_0_0 + * ThisStep // consumed goto: S_0_0's next is S_0 + * else + * NextStep // else branch: S_0's next is S_1 + * end if + * end S_0 + * def S_1: Step = NextStep + * end S_1 + * // output + * process: + * def S_0: Step = + * if (i) S_0_0 + * else S_1 + * end S_0 + * def S_0_0: Step = S_0 // NextStep of S_0_0 resolved via consumed ThisStep -> S_0 + * end S_0_0 + * def S_1: Step = S_0 + * end S_1 + * ``` + * + * == Implementation Phases == + * + * The stage applies four sequential `db.patch()` calls to avoid patch conflicts: + * + * - **Phase 0** (inter-step relocation): moves trailing statements before the `NextStep` Goto of + * `deepestLastChild(stepI)` — processed inner-first so Move patches concatenate correctly. + * - **Phase 1** (conditional extraction): uses the Phase-0 DB so relocated statements travel with + * the extracted step. Inserts a goto at the branch site, removes the consumed Goto, moves step + * and all descendants to ProcessBlock level. + * - **Phase 2** (structural flattening): one level per `@tailrec` pass — each pass moves direct + * nested children (with full `Flattened` descendants) up one level. + * - **Phase 3** (goto resolution): `ChangeRef` patches computed from the *original* DB, so + * `nextStepMap` and `conditionalStepMap` remain correct regardless of structural changes made + * in Phases 0–2. + */ +//format: on +case object FlattenStepBlocks extends Stage: + def dependencies: List[Stage] = List(DropRTWaits) + def nullifies: Set[Stage] = Set() + + def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = + given RefGen = RefGen.fromGetSet + // Phase 3 ChangeRef patches are computed from the original DB. + val gotoPatchList = designDB.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectGotoPatches(pb) + case _ => Nil + }.toList + // Phase 0: inter-step relocation (Step 5 inter-step + Step 6) + val db0 = designDB.patch( + designDB.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectInterStepPatches(pb) + case _ => Nil + }.toList + ) + // Phase 1: conditional branch extraction (uses db0 for updated member structure) + val db1 = locally { + given MemberGetSet = db0.getSet + db0.patch( + db0.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectConditionalExtractionPatches(pb) + case _ => Nil + }.toList + ) + } + // Phase 2: structural flattening, one level at a time (uses db1, applied repeatedly) + val db2 = flattenRepeatedly(db1) + // Phase 3: Goto ChangeRef + db2.patch(gotoPatchList) + end transform + + // Repeatedly flatten one nesting level of StepBlocks until all are direct pb children. + @tailrec private def flattenRepeatedly(db: DB)(using RefGen): DB = + given MemberGetSet = db.getSet + val patches = db.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectFlattenPatchesOneLevel(pb) + case _ => Nil + }.toList + if patches.isEmpty then db + else flattenRepeatedly(db.patch(patches)) + + // --- Shared helpers --- + + private def collectDirectFlatSteps(owner: DFOwner)(using MemberGetSet): List[StepBlock] = + owner.members(MemberView.Folded).flatMap { + case sb: StepBlock if sb.isRegular => sb :: collectDirectFlatSteps(sb) + case _ => Nil + } + + private def findConsumedGoto(s: StepBlock)(using MemberGetSet): (DFConditional.Block, Goto) = + val cb = s.getOwner.asInstanceOf[DFConditional.Block] + val cbMembers = cb.members(MemberView.Folded) + val sIdx = cbMembers.indexOf(s) + val consumedGoto = cbMembers.drop(sIdx + 1).collectFirst { case g: Goto => g }.get + (cb, consumedGoto) + + private def deepestLastChild(step: StepBlock)(using MemberGetSet): StepBlock = + step.members(MemberView.Folded) + .collect { case sb: StepBlock if sb.isRegular => sb } + .lastOption match + case None => step + case Some(last) => deepestLastChild(last) + + private def findNextStepGoto(step: StepBlock)(using MemberGetSet): Option[Goto] = + step.members(MemberView.Flattened).collectFirst { + case g: Goto if g.stepRef.get == Goto.NextStep => g + } + + // --- Phase 3: Goto ChangeRef patches (computed from original DB) --- + + private def collectGotoPatches(pb: ProcessBlock)(using MemberGetSet): List[(DFMember, Patch)] = + val flatSteps = collectDirectFlatSteps(pb) + if flatSteps.isEmpty then return Nil + val nextStepMap = (flatSteps lazyZip (flatSteps.tail :+ flatSteps.head)).toMap + val firstStep = flatSteps.head + val conditionalBranchSteps = pb.members(MemberView.Flattened).collect { + case sb: StepBlock if sb.isRegular && sb.getOwner.isInstanceOf[DFConditional.Block] => sb + } + val consumedGotos = conditionalBranchSteps.map(findConsumedGoto(_)._2).toSet + val conditionalStepMap = conditionalBranchSteps.map { s => + val (_, consumedGoto) = findConsumedGoto(s) + val target: StepBlock = consumedGoto.stepRef.get match + case sb: StepBlock => sb + case Goto.ThisStep => consumedGoto.getOwnerStepBlock + case Goto.NextStep => nextStepMap(consumedGoto.getOwnerStepBlock) + case Goto.FirstStep => firstStep + s -> target + }.toMap + pb.members(MemberView.Flattened) + .collect { case g: Goto if !consumedGotos.contains(g) => g } + .flatMap { g => + g.stepRef.get match + case _: StepBlock => None + case Goto.ThisStep => + Some(g -> Patch.ChangeRef(_.asInstanceOf[Goto].stepRef, g.getOwnerStepBlock)) + case Goto.FirstStep => + Some(g -> Patch.ChangeRef(_.asInstanceOf[Goto].stepRef, firstStep)) + case Goto.NextStep => + val owningStep = g.getOwnerStepBlock + val target = conditionalStepMap.getOrElse(owningStep, nextStepMap(owningStep)) + Some(g -> Patch.ChangeRef(_.asInstanceOf[Goto].stepRef, target)) + } + end collectGotoPatches + + // --- Phase 0: Inter-step relocation patches --- + + private def collectInterStepPatches( + pb: ProcessBlock + )(using MemberGetSet): List[(DFMember, Patch)] = + val flatSteps = collectDirectFlatSteps(pb) + if flatSteps.isEmpty then return Nil + // Step 5 inter-step: relocate statements in conditional branches into the step's body + val conditionalBranchSteps = pb.members(MemberView.Flattened).collect { + case sb: StepBlock if sb.isRegular && sb.getOwner.isInstanceOf[DFConditional.Block] => sb + } + val step5InterStep = conditionalBranchSteps.flatMap { s => + val (cb, consumedGoto) = findConsumedGoto(s) + val cbMembers = cb.members(MemberView.Folded) + val sIdx = cbMembers.indexOf(s) + val consumedGotoIdx = cbMembers.indexOf(consumedGoto) + val interStepStmts = cbMembers.slice(sIdx + 1, consumedGotoIdx) + .filterNot(m => m.isInstanceOf[StepBlock] || m.isInstanceOf[Goto]) + val targetStep = deepestLastChild(s) + findNextStepGoto(targetStep).toList.flatMap { nextStepGoto => + interStepStmts.map { stmt => + nextStepGoto -> Patch.Move(List(stmt), stmt.getOwner, Patch.Move.Config.Before) + } + } + } + // Step 6: relocate inter-step statements at each nesting level (inner-first for correct order) + def collectOwners(owner: DFOwner): List[DFOwner] = + owner.members(MemberView.Folded) + .collect { case sb: StepBlock if sb.isRegular => sb } + .flatMap(collectOwners) :+ owner + val step6 = collectOwners(pb).flatMap { owner => + val directMembers = owner.members(MemberView.Folded) + val directSteps = directMembers.collect { case sb: StepBlock if sb.isRegular => sb } + if directSteps.isEmpty then Nil + else + directSteps.zipWithIndex.flatMap { (step, idx) => + val stepPos = directMembers.indexOf(step) + val nextPos = + if idx + 1 < directSteps.length then directMembers.indexOf(directSteps(idx + 1)) + else directMembers.length + val stmtsToMove = directMembers.slice(stepPos + 1, nextPos).filterNot { + case _: StepBlock => true + case _: Goto => true + case _ => false + } + if stmtsToMove.isEmpty then Nil + else + val targetStep = deepestLastChild(step) + findNextStepGoto(targetStep).toList.flatMap { nextStepGoto => + stmtsToMove.map { stmt => + nextStepGoto -> Patch.Move(List(stmt), stmt.getOwner, Patch.Move.Config.Before) + } + } + } + } + step5InterStep ++ step6 + end collectInterStepPatches + + // --- Phase 1: Conditional branch extraction (uses db0's member structure) --- + + private def collectConditionalExtractionPatches( + pb: ProcessBlock + )(using MemberGetSet, RefGen): List[(DFMember, Patch)] = + val flatSteps = collectDirectFlatSteps(pb) + if flatSteps.isEmpty then return Nil + val conditionalBranchSteps = pb.members(MemberView.Flattened).collect { + case sb: StepBlock if sb.isRegular && sb.getOwner.isInstanceOf[DFConditional.Block] => sb + } + conditionalBranchSteps.flatMap { s => + val (cb, consumedGoto) = findConsumedGoto(s) + // Insert an explicit Goto to s at s's former position in the branch + val dsn = new MetaDesign(s, Patch.Add.Config.Before): + import dfhdl.core.* + Goto(s.refTW[Goto], dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags).addMember + // Remove the consumed goto + val removeConsumedGoto: (DFMember, Patch) = consumedGoto -> Patch.Remove() + // Move s and ALL its descendants to after the parent step at pb level. + // Including descendants ensures the flat member list maintains valid ownership ordering. + val parentStep = cb.getOwnerStepBlock + val allMembersToMove = s :: s.members(MemberView.Flattened) + val movePatch: (DFMember, Patch) = + parentStep -> Patch.Move(allMembersToMove, cb, Patch.Move.Config.After) + List(dsn.patch, removeConsumedGoto, movePatch) + } + end collectConditionalExtractionPatches + + // --- Phase 2: One-level structural flattening --- + + private def collectFlattenPatchesOneLevel( + pb: ProcessBlock + )(using MemberGetSet): List[(DFMember, Patch)] = + // For each direct pb-child step, lift its immediate nested StepBlock children one level up. + // Each lift moves the child and ALL its descendants so the flat member list stays valid. + // Multiple levels require repeated application (see flattenRepeatedly). + pb.members(MemberView.Folded).flatMap { + case topAncestor: StepBlock if topAncestor.isRegular => + topAncestor.members(MemberView.Folded).flatMap { + case child: StepBlock if child.isRegular => + val allMembersToMove = child :: child.members(MemberView.Flattened) + List( + topAncestor -> Patch.Move(allMembersToMove, child.getOwner, Patch.Move.Config.After) + ) + case _ => Nil + } + case _ => Nil + } + end collectFlattenPatchesOneLevel +end FlattenStepBlocks + +extension [T: HasDB](t: T) + def flattenStepBlocks(using CompilerOptions): DB = + StageRunner.run(FlattenStepBlocks)(t.db) diff --git a/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala b/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala new file mode 100644 index 000000000..9d1107026 --- /dev/null +++ b/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala @@ -0,0 +1,466 @@ +package StagesSpec + +import dfhdl.* +import dfhdl.compiler.stages.flattenStepBlocks +// scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} + +class FlattenStepBlocksSpec extends StageSpec(): + + test("single flat step") { + class Foo extends RTDesign: + process: + def S_0: Step = + NextStep + end S_0 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | process: + | def S_0: Step = + | S_0 + | end S_0 + |end Foo""".stripMargin + ) + } + + test("two flat steps") { + class Foo extends RTDesign: + val y = Bit <> OUT.REG init 0 + process: + def S0: Step = + y.din := 0 + NextStep + end S0 + def S1: Step = + y.din := 1 + NextStep + end S1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val y = Bit <> OUT.REG init 0 + | process: + | def S0: Step = + | y.din := 0 + | S1 + | end S0 + | def S1: Step = + | y.din := 1 + | S0 + | end S1 + |end Foo""".stripMargin + ) + } + + test("two flat steps with inter-step statement") { + class Foo extends RTDesign: + val i = Bit <> IN + val x = Bit <> OUT.REG + process: + def S_0: Step = + NextStep + end S_0 + x.din := i + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | val x = Bit <> OUT.REG + | process: + | def S_0: Step = + | x.din := i + | S_1 + | end S_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("one level of nesting") { + class Foo extends RTDesign: + process: + def MyStep: Step = + def MyStep_0: Step = + NextStep + end MyStep_0 + NextStep + end MyStep + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | process: + | def MyStep: Step = + | MyStep_0 + | end MyStep + | def MyStep_0: Step = + | MyStep + | end MyStep_0 + |end Foo""".stripMargin + ) + } + + test("three flat steps with inter-step statements") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + val y = Bit <> OUT.REG + process: + def S_0: Step = + NextStep + end S_0 + x.din := 0 + def S_1: Step = + NextStep + end S_1 + y.din := 1 + def S_2: Step = + NextStep + end S_2 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | val y = Bit <> OUT.REG + | process: + | def S_0: Step = + | x.din := 0 + | S_1 + | end S_0 + | def S_1: Step = + | y.din := 1 + | S_2 + | end S_1 + | def S_2: Step = + | S_0 + | end S_2 + |end Foo""".stripMargin + ) + } + + test("nested siblings with inter-step statements") { + class Foo extends RTDesign: + val a = Int <> OUT.REG + val b = Int <> OUT.REG + process: + def S_0: Step = + a.din := 1 + def S_0_0: Step = + NextStep + end S_0_0 + b.din := 2 + def S_0_1: Step = + NextStep + end S_0_1 + NextStep + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val a = Int <> OUT.REG + | val b = Int <> OUT.REG + | process: + | def S_0: Step = + | a.din := 1 + | S_0_0 + | end S_0 + | def S_0_0: Step = + | b.din := 2 + | S_0_1 + | end S_0_0 + | def S_0_1: Step = + | S_1 + | end S_0_1 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("two levels of nesting with inner-to-outer statement relocation") { + class Foo extends RTDesign: + val a = Int <> OUT.REG + val b = Int <> OUT.REG + val c = Int <> OUT.REG + process: + def S_0: Step = + def S_0_0: Step = + a.din := 1 + def S_0_0_0: Step = + NextStep + end S_0_0_0 + b.din := 2 + NextStep + end S_0_0 + c.din := 3 + NextStep + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val a = Int <> OUT.REG + | val b = Int <> OUT.REG + | val c = Int <> OUT.REG + | process: + | def S_0: Step = + | S_0_0 + | end S_0 + | def S_0_0: Step = + | a.din := 1 + | S_0_0_0 + | end S_0_0 + | def S_0_0_0: Step = + | b.din := 2 + | c.din := 3 + | S_1 + | end S_0_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("ThisStep and FirstStep resolution") { + class Foo extends RTDesign: + val i = Bit <> IN + process: + def S_0: Step = + if (i) + ThisStep + else + NextStep + end if + end S_0 + def S_1: Step = + if (i) + FirstStep + else + NextStep + end if + end S_1 + def S_2: Step = + NextStep + end S_2 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | process: + | def S_0: Step = + | if (i) S_0 + | else S_1 + | end S_0 + | def S_1: Step = + | if (i) S_0 + | else S_2 + | end S_1 + | def S_2: Step = + | S_0 + | end S_2 + |end Foo""".stripMargin + ) + } + + test("step nested inside conditional branch") { + class Foo extends RTDesign: + val i = Bit <> IN + process: + def S_0: Step = + if (i) + def S_0_0: Step = + NextStep + end S_0_0 + ThisStep + else + NextStep + end if + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | process: + | def S_0: Step = + | if (i) S_0_0 + | else S_1 + | end S_0 + | def S_0_0: Step = + | S_0 + | end S_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("step nested inside conditional branch with inter-step statement") { + class Foo extends RTDesign: + val i = Bit <> IN + val x = Bit <> OUT.REG + process: + def S_0: Step = + if (i) + def S_0_0: Step = + NextStep + end S_0_0 + x.din := 1 + ThisStep + else + NextStep + end if + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | val x = Bit <> OUT.REG + | process: + | def S_0: Step = + | if (i) S_0_0 + | else S_1 + | end S_0 + | def S_0_0: Step = + | x.din := 1 + | S_0 + | end S_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("onEntry and onExit blocks move with their parent step during flattening") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + process: + def S_0: Step = + def onEntry = + x.din := 1 + end onEntry + def S_0_0: Step = + def onExit = + x.din := 0 + end onExit + NextStep + end S_0_0 + NextStep + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | process: + | def S_0: Step = + | def onEntry: Unit = + | x.din := 1 + | end onEntry + | S_0_0 + | end S_0 + | def S_0_0: Step = + | def onExit: Unit = + | x.din := 0 + | end onExit + | S_1 + | end S_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("multiple inter-step statements from nested and conditional scopes collected before single NextStep") { + class Foo extends RTDesign: + val a = Int <> OUT.REG + val b = Int <> OUT.REG + val c = Int <> OUT.REG + val i = Bit <> IN + process: + def S_0: Step = + def S_0_0: Step = + if (i) + a.din := 1 + else + a.din := 0 + end if + NextStep + end S_0_0 + b.din := 2 + NextStep + end S_0 + c.din := 3 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val a = Int <> OUT.REG + | val b = Int <> OUT.REG + | val c = Int <> OUT.REG + | val i = Bit <> IN + | process: + | def S_0: Step = + | S_0_0 + | end S_0 + | def S_0_0: Step = + | if (i) a.din := 1 + | else a.din := 0 + | b.din := 2 + | c.din := 3 + | S_1 + | end S_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + +end FlattenStepBlocksSpec diff --git a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala index 0fea68954..cc4ac036e 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala @@ -6,6 +6,32 @@ import scala.annotation.unchecked.uncheckedVariance import dfhdl.compiler.ir.DFDesignBlock type MetaDesignAny = MetaDesign[DomainType] + +/** A base class for synthesising new IR members inside a compiler stage and injecting them into + * the DB at a precise position relative to an anchor member. + * + * Extend this class anonymously inside a stage's `transform` method to write ordinary DFHDL DSL + * code (ports, vars, assignments, etc.). The members created in the body are extracted and + * inserted into the DB when `dsn.patch` is applied; the MetaDesign block itself is discarded. + * + * === Constructing raw IR members inside the body === + * + * To build an IR member directly (e.g. `ir.Goto`) rather than through the DSL, add + * `import dfhdl.core.*` at the top of the anonymous body. This brings `refTW` and `addMember` + * into scope. Use `dfc.ownerOrEmptyRef` to obtain the owner ref — do not use `dfc.owner.ref` + * as that extension method may conflict with other imports: + * {{{ + * val dsn = new MetaDesign(anchor, Patch.Add.Config.Before): + * import dfhdl.core.* + * ir.Goto(existingStep.refTW[ir.Goto], dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags).addMember + * }}} + * + * === Ownership === + * + * For `Before` / `After` / `ReplaceWith*` configs, newly created members are placed as siblings + * of the anchor (same owner). For `InsideFirst` / `InsideLast`, members are placed as direct + * children of the anchor (which must be a `DFOwner`). + */ abstract class MetaDesign[+D <: DomainType]( positionMember: ir.DFMember, addCfg: AddCfg, diff --git a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala index 8daf89857..b473984aa 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala @@ -105,8 +105,35 @@ object Patch: case object Via extends Config end Config end Add - // movedMembers: members to move - // origOwner: the original owner of the top members + /** Moves `movedMembers` to a new position in the flat member list relative to the anchor member. + * + * `origOwner` is the original direct owner of the top-level moved members. During ownership + * resolution, only members in `movedMembers` whose current owner equals `origOwner` have their + * `ownerRef` updated to the new owner (determined by the anchor and config). Members owned by + * nested sub-blocks are not re-owned — their `ownerRef` is assumed to already point to the + * correct intermediate owner. + * + * '''Descendants are NOT moved automatically.''' Only the explicitly listed `movedMembers` are + * repositioned in the flat list. Moving a `DFOwner` (e.g. a `StepBlock`) without its children + * breaks the pre-order DFS ownership invariant. Always include all descendants: + * {{{ + * val all = block :: block.members(MemberView.Flattened) + * anchor -> Patch.Move(all, block.getOwner, Patch.Move.Config.After) + * }}} + * + * '''`DFOwner` anchor + `After`/`InsideLast` redirect.''' When the anchor is a `DFOwner`, + * physical placement is redirected to `anchor.getVeryLastMember` (deepest recursive last + * descendant). Ownership resolution still uses the original anchor, so `newOwner = + * anchor.getOwnerBlock`. + * + * '''Concatenation.''' Multiple `Move` patches targeting the same anchor and config are merged + * in patchList order into a single Move before being applied to the member list. + * + * '''Conflict detection.''' This patch internally generates `Patch.Remove` for every member in + * `movedMembers`. If any of those members also appear in a separate patch in the same patchList + * (e.g. as the anchor of another `Move.Before`), an `IllegalArgumentException` is thrown. + * Resolve by splitting conflicting patches into sequential `db.patch()` calls. + */ final case class Move(movedMembers: List[DFMember], origOwner: DFOwner, config: Move.Config) extends Patch: override def toString(): String = @@ -123,13 +150,19 @@ object Patch: sealed trait Config extends Product with Serializable derives CanEqual: def ==(addConfig: Add.Config): Boolean = addConfig == this object Config: - // moves members before the patched member + // Moves members before the anchor member. The anchor must not be a DFOwner when + // members to be moved using Before are also referenced by other patches (e.g., as movedMembers + // in another Move), as the internal Remove generated here would conflict. case object Before extends Config - // moves members after the patched member + // Moves members after the anchor member. + // When the anchor is a DFOwner, placement is redirected to anchor.getVeryLastMember. + // Ownership update: newOwner = anchor.getOwnerBlock (NOT anchor itself). case object After extends Config - // moves members inside the given block, at the beginning + // Moves members inside the given block, at the beginning (after the block header). + // The anchor must be a DFOwner; members become direct children of the anchor. case object InsideFirst extends Config - // moves members inside the given block, at the end + // Moves members inside the given block, at the end. + // The anchor must be a DFOwner; redirects to anchor.getVeryLastMember for placement. case object InsideLast extends Config end Move diff --git a/core/src/main/scala/dfhdl/core/DFC.scala b/core/src/main/scala/dfhdl/core/DFC.scala index b2faa467b..baf8f12db 100644 --- a/core/src/main/scala/dfhdl/core/DFC.scala +++ b/core/src/main/scala/dfhdl/core/DFC.scala @@ -65,6 +65,10 @@ final case class DFC( def lateConstruction: Boolean = mutableDB.OwnershipContext.lateConstruction def ownerOption: Option[DFOwnerAny] = mutableDB.OwnershipContext.ownerOption.map(_.asFE) + // Returns the IR ref for the current owner, or a ref to DFMember.Empty when there is no + // owner in the context. Prefer this over `dfc.owner.ref` when constructing raw IR members + // (e.g. ir.Goto) inside a MetaDesign body: the `ref` extension method requires + // `import dfhdl.core.*` in scope, which can conflict with other `dfhdl.core` imports. def ownerOrEmptyRef: ir.DFOwner.Ref = ownerOption.map(_.asIR.ref(using this)).getOrElse(ir.DFMember.Empty.ref(using this)) def setName(name: String): this.type = From d3d7532c1ca30c7749fd0b0847908226328123b4 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 22 Mar 2026 18:41:31 +0200 Subject: [PATCH 029/115] updates to new-stage skill --- .claude/commands/new-stage.md | 166 ++++++++++++++++++++++++++++++++-- 1 file changed, 156 insertions(+), 10 deletions(-) diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index 53a599d80..6999a7998 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -150,6 +150,21 @@ m.isAnonymous // true if the value has no user-visible name m.originMembers // members that reference this member ``` +### `MemberView.Folded` vs `MemberView.Flattened` + +```scala +owner.members(MemberView.Folded) // direct children only: m.getOwner == owner +owner.members(MemberView.Flattened) // all descendants recursively +``` + +Use `Folded` when processing only immediate children (e.g. iterating steps at one nesting level). +Use `Flattened` when you need to inspect or collect all descendants (e.g. finding all `Goto` +members anywhere inside a `ProcessBlock`, or collecting all members to move with a block). + +A member's **direct owner** is the block for which `m.getOwner == block`. `Flattened` includes +members owned by nested blocks too, which can be confusing: a `Goto` inside a `StepBlock` inside +a `ProcessBlock` appears in `pb.members(Flattened)` but NOT in `pb.members(Folded)`. + ### Domain checks ```scala m.isInDFDomain // dataflow domain @@ -201,6 +216,43 @@ Patch.Move.Config.InsideFirst // insert as first child of a block Patch.Move.Config.InsideLast ``` +### `Patch.Move` semantics — critical details + +**Descendants are NOT moved automatically.** `Patch.Move(List(member), origOwner, cfg)` only +moves the listed members. If `member` is a `DFOwner` (e.g. a `StepBlock`), its children remain +at their original positions in the flat member list. Since the flat member list must be a valid +pre-order DFS traversal (every member's owner must appear before it), moving a block without its +children violates the ownership invariant and causes `sanityCheck` to fail. + +**Always include descendants when moving a block:** +```scala +val allMembersToMove = block :: block.members(MemberView.Flattened) +anchor -> Patch.Move(allMembersToMove, block.getOwner, Patch.Move.Config.After) +``` + +**Anchor redirect for `DFOwner` + `Config.After`.** When the anchor is a `DFOwner` and config +is `After`, the patch system redirects the physical insertion point to +`anchor.getVeryLastMember` (deepest recursive last descendant). However, the ownership update +uses the **original anchor** to determine the new owner: `newOwner = origAnchor.getOwnerBlock`. +This means after the move, the moved members' `ownerRef`s point to `anchor.getOwnerBlock`, +not to `anchor` itself. + +**Multiple Move patches on the same anchor + config are concatenated** in patchList order. +If you add several `anchor -> Patch.Move(...)` entries for the same `(anchor, Config.After)`, +all the member lists are merged and inserted together after the anchor (or its redirect target). + +**Conflict: a member cannot appear in two patches simultaneously.** If `Patch.Move` lists a +member AND that same member appears in a separate `Patch.Remove` (or another `Patch.Move`), +the DB throws `IllegalArgumentException: Received two different patches for the same member`. +Note that `Patch.Move` internally generates `Patch.Remove` for each listed member. Common +triggers: +- Using a `Goto` as a `Move.Before` anchor (so the Move internally removes it on re-insertion) + while the same `Goto` is also in another Move's members list. +- Moving a parent block with all descendants AND separately moving one of those descendants. + +The fix is always **multi-phase patching**: split conflicting patches into sequential +`db.patch()` calls so each phase operates on a clean, conflict-free DB. + ### `Patch.Add` via `MetaDesign` Use `MetaDesign` when you need to construct new IR members using the DFHDL frontend DSL: @@ -265,11 +317,33 @@ dfc.setMeta(m.meta) // copy DFC mirroring another member's metadata dfc.enterOwner(owner) // push a new owner onto the ownership stack dfc.exitOwner() // pop the owner stack dfc.owner // current owner (top of stack) +dfc.ownerOrEmptyRef // ir.DFOwner.Ref for the current owner — use this inside MetaDesign + // when constructing raw IR members (avoids `import dfhdl.core.*` ref conflicts) dfc.inMetaProgramming // true when inside a MetaDesign dfc.mutableDB // access the MutableDB directly dfc.getSet // implicit MemberGetSet backed by mutableDB ``` +### Constructing raw IR members inside `MetaDesign` + +When you need to build an IR member directly (rather than through the DSL) inside a MetaDesign +body — e.g. creating a `Goto` that references an existing `StepBlock` — use this idiom: + +```scala +val dsn = new MetaDesign(anchorMember, Patch.Add.Config.Before): + import dfhdl.core.* // brings refTW, addMember, and IR type helpers into scope + ir.Goto(existingStep.refTW[ir.Goto], dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags).addMember +``` + +Two important details: +- `import dfhdl.core.*` (not `import dfhdl.compiler.ir.*`) is needed inside MetaDesign for + `refTW` and `addMember`. Do **not** write `ir.Goto` with a qualified prefix inside the body + when `dfhdl.compiler.ir.*` is already imported at the file level — use the unqualified `Goto` + or the explicit `ir.Goto` form consistently, but be aware that `import dfhdl.core.*` re-exports + what you need. In practice: inside MetaDesign, use `import dfhdl.core.*` and unqualified names. +- Use `dfc.ownerOrEmptyRef` (a direct method on `DFC`) rather than `dfc.owner.ref`. The latter + requires extension methods that may conflict with `import dfhdl.core.*`. + ### DFCG — the "global" variant `DFCG` is an opaque subtype of `DFC`. It is auto-synthesised when no explicit `DFC` is in scope @@ -625,6 +699,49 @@ override def runCondition(using co: CompilerOptions): Boolean = case _ => false ``` +### Pattern 9 — One-level-at-a-time repeated patching (`@tailrec`) + +Use when a transformation must be applied iteratively, processing one nesting level per pass +(e.g. flattening a hierarchy depth-by-depth). This avoids duplicate entries that would arise +if you tried to simultaneously move a parent block (with its descendant as part of the moved +list) AND separately move that same descendant. + +```scala +import scala.annotation.tailrec + +@tailrec private def flattenRepeatedly(db: DB)(using RefGen): DB = + given MemberGetSet = db.getSet + val patches = db.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectOneLevelPatches(pb) + case _ => Nil + }.toList + if patches.isEmpty then db + else flattenRepeatedly(db.patch(patches)) + +private def collectOneLevelPatches(pb: ProcessBlock)(using MemberGetSet): List[(DFMember, Patch)] = + // Only look at direct children of pb-level steps; lift each one level up. + // After one pass, previously-nested blocks are now direct pb children, + // and their children become the candidates for the next pass. + pb.members(MemberView.Folded).flatMap { + case parent: StepBlock if parent.isRegular => + parent.members(MemberView.Folded).flatMap { + case child: StepBlock if child.isRegular => + val allMembersToMove = child :: child.members(MemberView.Flattened) + List(parent -> Patch.Move(allMembersToMove, child.getOwner, Patch.Move.Config.After)) + case _ => Nil + } + case _ => Nil + } +``` + +Key invariants: +- Include ALL descendants in `allMembersToMove` to preserve the flat-list ownership ordering. +- Process only **direct children** per pass — do not recurse into grandchildren in the same pass. +- The `@tailrec` loop terminates when `collectOneLevelPatches` returns an empty list (all blocks + are already direct children of the `ProcessBlock`). +- `given MemberGetSet = db.getSet` must be re-established at the top of each recursive call so + navigation helpers see the updated member structure. + --- ## Boilerplate Template @@ -639,7 +756,11 @@ import dfhdl.compiler.ir.* import dfhdl.compiler.patching.* import dfhdl.options.CompilerOptions -/** One-line description of what this stage does and why it exists. */ +/** + * Describe what this stage does, why it exists, input/output IR shapes, and ordering/resolution + * rules if applicable. Do NOT include Idempotency or Determinism sections — those are + * implementation invariants enforced by the infrastructure, not user-facing documentation. + */ case object MyStage extends Stage: def dependencies: List[Stage] = List(SomePriorStage) // run these first def nullifies: Set[Stage] = Set(SomeInvalidatedStage) @@ -662,6 +783,14 @@ extension [T: HasDB](t: T) --- +## Test Authoring Rules + +**Tests must be self-contained.** Each test should only exercise the stage under test. Do not write input designs that rely on a prior stage to produce the IR shape that the current stage expects — write that IR shape directly using the DFHDL DSL. + +For example, a stage that operates on `StepBlock`s should use explicit `def MyStep: Step = …` syntax in the test design rather than `1.cy.wait` or `while (…)` constructs, because those are transformed into `StepBlock`s by `DropRTWaits`, not by the stage under test. Letting `DropRTWaits` silently run as a dependency makes a failing test ambiguous: it is unclear whether the bug is in the stage under test or in the dependency. + +The same principle applies to any other prior-stage construct: if `DropFoo` normally feeds into `AddBar`, the `AddBarSpec` tests should express the output of `DropFoo` directly, without triggering `DropFoo`. + ## Test File Template ### Test file: `compiler/stages/src/test/scala/StagesSpec/MyStageSpec.scala` @@ -725,15 +854,32 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) ## Common Mistakes to Avoid -1. **Iterating over `Set` or `Map` to build the patch list** — order is undefined, producing non-deterministic output. Always iterate `designDB.members` (a `List`) to drive patch collection. -2. **Overly broad pattern matches** — if a match can fire on already-transformed IR, the stage is not idempotent (`f(f(x)) ≠ f(x)`). Match on the exact source shape so that the output IR no longer satisfies the predicate. -3. **Forgetting `end transform` / `end MyStage`** — scalafmt's `insertEndMarkerMinLines = 15` rule requires end markers on blocks ≥ 15 lines. -2. **Missing `given RefGen = RefGen.fromGetSet`** inside `transform` when using `MetaDesign` with fresh reference generation. -3. **Mutating `getSet`** — always create a new `MemberGetSet` via `newDB.getSet` after patching before iterating again. -4. **Patch order matters** — patches are applied in list order; a `Move` before a `Replace` on the same member may conflict. -5. **Not extending `NoCheckStage`** when your stage produces unreferenced anonymous members as intermediate output — the automatic sanity check will fail. -6. **Nullifying too aggressively** — only nullify a stage if your transformation invalidates its invariant. Unnecessary nullification forces redundant re-runs. -7. **Confusing `ChangeRefOnly` vs `FullReplacement`** — `ChangeRefOnly` keeps the old member in the member list (useful when another stage still expects it); `FullReplacement` swaps it in place. +1. **Writing `end process` in code examples** — Scala does not support `end` markers on method + calls, so `process` blocks have no closing `end process`. Only named definitions (`def`, `class`, + `object`, `val`, etc.) accept end markers. StepBlock prints as `def Name: Step = … end Name`; + ProcessBlock prints as `process(sensitivity):\n body` with no end marker. +2. **Iterating over `Set` or `Map` to build the patch list** — order is undefined, producing non-deterministic output. Always iterate `designDB.members` (a `List`) to drive patch collection. +3. **Overly broad pattern matches** — if a match can fire on already-transformed IR, the stage is not idempotent (`f(f(x)) ≠ f(x)`). Match on the exact source shape so that the output IR no longer satisfies the predicate. +4. **Forgetting `end transform` / `end MyStage`** — scalafmt's `insertEndMarkerMinLines = 15` rule requires end markers on blocks ≥ 15 lines. +5. **Missing `given RefGen = RefGen.fromGetSet`** inside `transform` when using `MetaDesign` with fresh reference generation. +6. **Mutating `getSet`** — always create a new `MemberGetSet` via `newDB.getSet` after patching before iterating again. +7. **Patch order matters** — patches are applied in list order; a `Move` before a `Replace` on the same member may conflict. +8. **Not extending `NoCheckStage`** when your stage produces unreferenced anonymous members as intermediate output — the automatic sanity check will fail. +9. **Nullifying too aggressively** — only nullify a stage if your transformation invalidates its invariant. Unnecessary nullification forces redundant re-runs. +10. **Confusing `ChangeRefOnly` vs `FullReplacement`** — `ChangeRefOnly` keeps the old member in the member list (useful when another stage still expects it); `FullReplacement` swaps it in place. +11. **Moving a `DFOwner` without its descendants** — `Patch.Move` only moves the listed members. + Moving a block but leaving its children at their original positions violates the ownership + invariant (pre-order DFS ordering). Always include `block :: block.members(MemberView.Flattened)` + in the moved-members list. If you need to move multiple nesting levels, use the one-level-at-a-time + `@tailrec` pattern (Pattern 9) to avoid duplicating descendants across multiple Move entries. +12. **Conflicting patches on the same member** — `Patch.Move` internally generates `Patch.Remove` + for each moved member. If that same member also appears in another patch in the same list, you + get `IllegalArgumentException: Received two different patches for the same member`. The most + common cause is using a `Goto` as a `Move.Before` anchor while that same `Goto` also appears + in another Move's members list. Fix by splitting into sequential `db.patch()` calls (multi-phase). +13. **Using `dfc.owner.ref` inside MetaDesign** — `import dfhdl.core.*` introduces extension + methods that conflict with `.ref`. Use `dfc.ownerOrEmptyRef` instead to obtain the owner's + `ir.DFOwner.Ref` when constructing raw IR members. --- From 448f1aa3f608b55ef282121cf995ad2c878adde0 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 22 Mar 2026 18:49:50 +0200 Subject: [PATCH 030/115] update DropRTProcess to depend on FlattenStepBlocks and the tests to not use relative goto statements --- .../dfhdl/compiler/stages/DropRTProcess.scala | 9 ++--- .../scala/StagesSpec/DropRTProcessSpec.scala | 37 ++++++++++++------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala index c38cbbb92..150660d2f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala @@ -15,7 +15,7 @@ import dfhdl.core.{DFValAny, DFOwnerAny, asValAny, DFC} */ //format: on case object DropRTProcess extends Stage: - def dependencies: List[Stage] = List() // DropRTWaits + def dependencies: List[Stage] = List(FlattenStepBlocks) def nullifies: Set[Stage] = Set(DFHDLUniqueNames) def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = @@ -74,11 +74,8 @@ case object DropRTProcess extends Stage: ): import dfhdl.core.{DFIf, DFUnit, DFBool} val currentStepBlock = g.getOwnerStepBlock - val nextStepBlock: StepBlock = g.stepRef.get match - case stepBlock: StepBlock => stepBlock - case Goto.ThisStep => currentStepBlock - case Goto.NextStep => nextBlocks(currentStepBlock) - case Goto.FirstStep => stateBlocks.head + // the stage `FlattenStepBlocks` guarantees that the goto always references a regular StepBlock + val nextStepBlock: StepBlock = g.stepRef.get.asInstanceOf[StepBlock] def setState(nextStepBlock: StepBlock): Unit = stateReg.din.:=(enumEntry(entries(nextStepBlock.getName)))(using dfc.setMeta(g.meta) diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala index 249d60a94..adbe184a4 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala @@ -11,15 +11,15 @@ class DropRTProcessSpec extends StageSpec(): val my_fsm = process: def S0: Step = y.din := 0 - if (x) NextStep else S0 + if (x) S1 else S0 def S1: Step = def onEntry = y.din := 1 - if (x) S2 else FirstStep + if (x) S2 else S0 def S2: Step = def onExit = y.din := 0 - if (x) ThisStep else FirstStep + if (x) S2 else S0 end Foo val top = (new Foo).dropRTProcess assertCodeString( @@ -98,20 +98,31 @@ class DropRTProcessSpec extends StageSpec(): |end Foo""".stripMargin ) } - test("process with no steps") { + test("process with a single step") { class Foo extends RTDesign: val x = Bit <> IN val y = Bit <> OUT.REG init 0 process: - y.din := 0 + def S0: Step = + y.din := 0 + S0 + end S0 end Foo val top = (new Foo).dropRTProcess assertCodeString( top, """|class Foo extends RTDesign: + | enum State(val value: UInt[1] <> CONST) extends Encoded.Manual(1): + | case S0 extends State(d"1'0") + | | val x = Bit <> IN | val y = Bit <> OUT.REG init 0 - | y.din := 0 + | val state = State <> VAR.REG init State.S0 + | state match + | case State.S0 => + | y.din := 0 + | state.din := State.S0 + | end match |end Foo""".stripMargin ) } @@ -122,22 +133,22 @@ class DropRTProcessSpec extends StageSpec(): process: def S0: Step = y.din := 0 - NextStep + S1 def S1: Step = def fallThrough = x def onEntry = y.din := 1 - NextStep + S2 def S2: Step = def fallThrough = !x def onEntry = y.din := !y - NextStep + S3 def S3: Step = def fallThrough = x ^ x.reg(1, init = 0) def onEntry = y.din := y ^ y.reg - NextStep + S4 def S4: Step = y.din := 0 if (x) S2 else S0 @@ -208,17 +219,17 @@ class DropRTProcessSpec extends StageSpec(): def fallThrough = x def onEntry = y.din := y - NextStep + S1 def S1: Step = def fallThrough = !x def onEntry = y.din := !y - NextStep + S2 def S2: Step = def fallThrough = x ^ x.reg(1, init = 0) def onEntry = y.din := y ^ y.reg - NextStep + S0 end Foo val top = (new Foo).dropRTProcess assertCodeString( From 55c0d6613d2487cc0ec4366c25b4e38ef27af88b Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 22 Mar 2026 20:10:44 +0200 Subject: [PATCH 031/115] add documentation for DropRTProcess and fix the width of the enumeration register --- .../dfhdl/compiler/stages/DropRTProcess.scala | 172 +++++++++++++++++- .../scala/StagesSpec/DropRTProcessSpec.scala | 37 ++++ 2 files changed, 207 insertions(+), 2 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala index 150660d2f..681c59046 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala @@ -11,7 +11,175 @@ import scala.collection.immutable.ListMap import dfhdl.core.DFType.asFE import dfhdl.core.{DFValAny, DFOwnerAny, asValAny, DFC} //format: off -/** This stage drops RT processes by creating an explicit enumerated state machines +/** This stage drops RT processes by creating an explicit enumerated state machine. + * + * Each RT `process` block containing `StepBlock` definitions (produced by the preceding + * `FlattenStepBlocks` stage) is replaced by: + * - An `enum` type whose entries correspond 1-to-1 with the step names. + * - A `VAR.REG` state register of that enum type, initialised to the first step. + * - A `match` expression that dispatches on the current state value and contains one + * case per step. + * + * All `Goto` members are already explicit `StepBlock` references at this point + * (relative forms — `NextStep`, `ThisStep`, `FirstStep` — were resolved by + * `FlattenStepBlocks`). Each `Goto` is replaced by an assignment to `stateReg.din`. + * + * ==Rules== + * + * ===Rule 1: Basic FSM transformation=== + * Each step becomes a match case; each `Goto` inside it becomes `stateReg.din := EnumEntry`. + * {{{ + * // Before + * process: + * def S0: Step = + * y.din := 0 + * if (x) S1 else S0 + * def S1: Step = + * y.din := 1 + * if (x) S2 else S0 + * def S2: Step = + * y.din := 0 + * if (x) S2 else S0 + * + * // After + * enum State(...) extends Encoded.Manual(2): + * case S0 extends State(d"2'0") + * case S1 extends State(d"2'1") + * case S2 extends State(d"2'2") + * val state = State <> VAR.REG init State.S0 + * state match + * case State.S0 => + * y.din := 0 + * if (x) state.din := State.S1 + * else state.din := State.S0 + * case State.S1 => + * y.din := 1 + * if (x) state.din := State.S2 + * else state.din := State.S0 + * case State.S2 => + * y.din := 0 + * if (x) state.din := State.S2 + * else state.din := State.S0 + * end match + * }}} + * + * ===Rule 2: `onEntry` block inlining=== + * If a step `S` has an `onEntry` child block, its body is inlined at every site that + * transitions *into* `S` from a *different* step (i.e., wherever a `Goto` targets `S` + * and the source step differs from `S`), immediately before the `stateReg.din` assignment. + * The `onEntry` block itself is removed from the case body. Self-transitions (`S -> S`) + * do NOT trigger `onEntry`. + * {{{ + * // Before + * def S0: Step = + * if (x) S1 else S0 + * def S1: Step = + * def onEntry = + * y.din := 1 // run only when entering S1 from a different step + * if (x) S1 else S0 // self-transition: onEntry NOT fired + * + * // After + * case State.S0 => + * if (x) + * y.din := 1 // S1.onEntry inlined (S0 -> S1: different step) + * state.din := State.S1 + * else state.din := State.S0 + * case State.S1 => + * if (x) state.din := State.S1 // S1 -> S1: onEntry NOT inlined + * else state.din := State.S0 + * }}} + * + * ===Rule 3: `onExit` block inlining=== + * If a step `S` has an `onExit` child block, its body is inlined at every site that + * transitions *out of* `S` to a *different* step, immediately before any `onEntry` + * inlining and the `stateReg.din` assignment. Self-transitions do not trigger `onExit`. + * {{{ + * // Before + * def S2: Step = + * def onExit = + * y.din := 0 // run whenever we leave S2 to another step + * if (x) S2 else S0 + * + * // After + * case State.S2 => + * if (x) state.din := State.S2 // self-loop: onExit NOT triggered + * else + * y.din := 0 // S2.onExit inlined here (transition to S0) + * state.din := State.S0 + * end if + * }}} + * + * ===Rule 4: `fallThrough` block — conditional cascading=== + * If a step `S` has a `fallThrough` child block, it defines a condition under which + * control *immediately falls through* to the next step in the same clock cycle (without + * waiting). When transitioning into `S`: + * 1. Inline `S.onEntry` (if any) and assign `stateReg.din := S`. + * 2. Emit a conditional `if (fallThroughCondition)` block. + * 3. Inside that block, recursively apply steps 1–2 for the next step (the one `S` + * itself eventually transitions to via its own `Goto`). + * The `fallThrough` condition represents the *exit* predicate — when true, control passes + * to the subsequent step without registering in `S`. + * {{{ + * // Before + * def S0: Step = + * y.din := 0 + * S1 + * def S1: Step = + * def fallThrough = x // if x, skip straight to S2 + * def onEntry = y.din := 1 + * S2 + * def S2: Step = + * def fallThrough = !x // if !x, skip straight to S3 + * def onEntry = y.din := !y + * S3 + * + * // After (case for S0) + * case State.S0 => + * y.din := 0 + * y.din := 1 // S1.onEntry + * state.din := State.S1 + * if (x) // S1.fallThrough: skip to S2 + * y.din := !y // S2.onEntry + * state.din := State.S2 + * if (!x) // S2.fallThrough: skip to S3 + * ... + * end if + * end if + * }}} + * + * ===Rule 5: Circular fall-through protection=== + * The recursive `fallThrough` chain stops as soon as the next step to handle equals the + * step that contains the original `Goto`. This prevents infinite expansion when the fall- + * through cycle loops back to the current case. + * {{{ + * // Before + * def S0: Step = + * def fallThrough = x + * def onEntry = y.din := y + * S1 + * def S1: Step = + * def fallThrough = !x + * def onEntry = y.din := !y + * S2 + * def S2: Step = + * def fallThrough = x ^ x.reg(1, init = 0) + * def onEntry = y.din := y ^ y.reg + * S0 + * + * // After (case for S2) + * case State.S2 => + * y.din := y // S0.onEntry + * state.din := State.S0 + * if (x) // S0.fallThrough: cascade to S1 + * y.din := !y // S1.onEntry + * state.din := State.S1 + * if (!x) // S1.fallThrough: cascade to S2 + * y.din := y ^ y.reg // S2.onEntry + * state.din := State.S2 + * // S2.fallThrough would cascade to S0, but S0 == currentStep => stop + * end if + * end if + * }}} */ //format: on case object DropRTProcess extends Stage: @@ -37,7 +205,7 @@ case object DropRTProcess extends Stage: val entries = ListMap.from(stateBlocks.view.zipWithIndex.map { case (sb, idx) => sb.getName -> BigInt(idx) }) - val stateEnumIR = DFEnum(enumName, stateBlocks.length.bitsWidth(false), entries) + val stateEnumIR = DFEnum(enumName, (stateBlocks.length - 1).bitsWidth(false), entries) type StateEnum = dfhdl.core.DFEnum[dfhdl.core.DFEncoding] val stateEnumFE = stateEnumIR.asFE[StateEnum] def enumEntry(value: BigInt)(using DFC) = dfhdl.core.DFVal.Const(stateEnumFE, Some(value)) diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala index adbe184a4..9a5abf749 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala @@ -54,6 +54,43 @@ class DropRTProcessSpec extends StageSpec(): |end Foo""".stripMargin ) } + test("onEntry is not fired on self-transition") { + class Foo extends RTDesign: + val x = Bit <> IN + val y = Bit <> OUT.REG init 0 + process: + def S0: Step = + if (x) S1 else S0 + def S1: Step = + def onEntry = + y.din := 1 + if (x) S1 else S0 + end Foo + val top = (new Foo).dropRTProcess + assertCodeString( + top, + """|class Foo extends RTDesign: + | enum State(val value: UInt[1] <> CONST) extends Encoded.Manual(1): + | case S0 extends State(d"1'0") + | case S1 extends State(d"1'1") + | + | val x = Bit <> IN + | val y = Bit <> OUT.REG init 0 + | val state = State <> VAR.REG init State.S0 + | state match + | case State.S0 => + | if (x) + | y.din := 1 + | state.din := State.S1 + | else state.din := State.S0 + | end if + | case State.S1 => + | if (x) state.din := State.S1 + | else state.din := State.S0 + | end match + |end Foo""".stripMargin + ) + } test("unnamed FSM steps") { class Foo extends RTDesign: val x = Bit <> IN From f28da139d57f5ffd4a8f40e574d97c7f4bcfa340 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 22 Mar 2026 20:57:47 +0200 Subject: [PATCH 032/115] better support while running through sbtn --- core/src/main/scala/dfhdl/options/OnError.scala | 4 ++-- .../src/main/scala/dfhdl/internals/helpers.scala | 12 ++++++++---- lib/src/main/scala/dfhdl/app/DFApp.scala | 4 ++-- lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala | 9 ++++++--- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/dfhdl/options/OnError.scala b/core/src/main/scala/dfhdl/options/OnError.scala index c022736f5..12e7ef5b0 100644 --- a/core/src/main/scala/dfhdl/options/OnError.scala +++ b/core/src/main/scala/dfhdl/options/OnError.scala @@ -1,7 +1,7 @@ package dfhdl.options -import dfhdl.internals.{sbtShellIsRunning, sbtTestIsRunning} +import dfhdl.internals.{sbtShellIsRunning, sbtTestIsRunning, sbtnIsRunning} enum OnError derives CanEqual: case Exit, Exception object OnError: - given OnError = if (sbtShellIsRunning || sbtTestIsRunning) Exception else Exit + given OnError = if (sbtShellIsRunning || sbtnIsRunning || sbtTestIsRunning) Exception else Exit diff --git a/internals/src/main/scala/dfhdl/internals/helpers.scala b/internals/src/main/scala/dfhdl/internals/helpers.scala index 1a533ff53..c68a0c70b 100644 --- a/internals/src/main/scala/dfhdl/internals/helpers.scala +++ b/internals/src/main/scala/dfhdl/internals/helpers.scala @@ -343,10 +343,11 @@ extension (str: String) def forceWindowsToLinuxPath: String = str.replaceAll("""\\""", "/") def simplePattenToRegex: Regex = val updatedPattern = - "^" + str - .replace(".", "\\.") // Escape dots to match literal dots - .replace("*", ".*") // Replace * with .* - .replace("?", ".") // Replace ? with . + "^" + + str + .replace(".", "\\.") // Escape dots to match literal dots + .replace("*", ".*") // Replace * with .* + .replace("?", ".") // Replace ? with . + "$" updatedPattern.r end extension @@ -387,6 +388,9 @@ lazy val getShellCommand: Option[String] = end match end getShellCommand +lazy val sbtnIsRunning: Boolean = + sbtIsRunning && getShellCommand.exists(cmd => cmd.endsWith("--server")) + lazy val sbtShellIsRunning: Boolean = getShellCommand.exists(cmd => cmd.endsWith("xsbt.boot.Boot") || cmd.endsWith("sbt-launch.jar")) diff --git a/lib/src/main/scala/dfhdl/app/DFApp.scala b/lib/src/main/scala/dfhdl/app/DFApp.scala index 5046a2bbb..38543f61b 100644 --- a/lib/src/main/scala/dfhdl/app/DFApp.scala +++ b/lib/src/main/scala/dfhdl/app/DFApp.scala @@ -6,7 +6,7 @@ import wvlet.log.{Logger, LogFormatter} import scala.collection.mutable import dfhdl.options.* import org.rogach.scallop.* -import dfhdl.internals.sbtShellIsRunning +import dfhdl.internals.{sbtShellIsRunning, sbtnIsRunning} import scala.util.chaining.scalaUtilChainingOps import java.time.Instant import dfhdl.compiler.stages.{StagedDesign, CompiledDesign} @@ -400,7 +400,7 @@ trait DFApp: ) parsedCommandLine.getExitCodeOption match case Some(code) => - if (!sbtShellIsRunning) sys.exit(code) + if (!sbtShellIsRunning && !sbtnIsRunning) sys.exit(code) case None => given CanEqual[ScallopConfBase, ScallopConfBase] = CanEqual.derived // update app options from command line diff --git a/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala b/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala index 4faf67413..7943fef06 100644 --- a/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala +++ b/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala @@ -241,11 +241,14 @@ class ParsedCommandLine( end HelpMode private def usageText(options: String): String = - import dfhdl.internals.{sbtIsRunning, scala_cliIsRunning, sbtShellIsRunning} + import dfhdl.internals.{sbtIsRunning, scala_cliIsRunning, sbtShellIsRunning, sbtnIsRunning} if (scala_cliIsRunning) s"scala run . -M $topScalaPath -- $options" else if (sbtIsRunning) - if (sbtShellIsRunning && !scastieIsRunning) s"runMain $topScalaPath $options" - else s"""sbt "runMain $topScalaPath $options"""" + if ((sbtShellIsRunning || sbtnIsRunning) && !scastieIsRunning) + s"runMain $topScalaPath $options" + else + val sbtCommand = if (sbtnIsRunning) "sbtn" else "sbt" + s"""$sbtCommand "runMain $topScalaPath $options"""" else s" $options" banner(s"""Design Name: $designName\nUsage: ${usageText("[design-args] [options]")} """) From f9e52af516fa2861b898ef52a3e51775bbba8d10 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 22 Mar 2026 21:11:31 +0200 Subject: [PATCH 033/115] add DropRTProcess to dependencies in ToED stage --- compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 530bb9664..1574777c1 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala @@ -11,7 +11,7 @@ import scala.annotation.tailrec import scala.collection.mutable case object ToED extends Stage: def dependencies: List[Stage] = - List(DropUnreferencedAnons, ToRT, NameRegAliases, ExplicitNamedVars, AddClkRst, + List(DropUnreferencedAnons, ToRT, DropRTProcess, NameRegAliases, ExplicitNamedVars, AddClkRst, SimpleOrderMembers) def nullifies: Set[Stage] = Set(DropUnreferencedAnons) def transform(designDB: DB)(using getSet: MemberGetSet, co: CompilerOptions): DB = From ebb6cec6f45a1da71206b39a66bebab2f71c2490 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 22 Mar 2026 21:19:15 +0200 Subject: [PATCH 034/115] enhance documentation for new stage creation with lessons learned and update guidelines --- .claude/commands/new-stage.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index 6999a7998..4ee10940d 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -2,6 +2,8 @@ > **For contributors adding new compiler transformation stages to DFHDL.** > This skill is version-controlled alongside the codebase — keep it updated when stage infrastructure changes. +> After completing a stage, **update this file** with any general lessons learned (new patterns, API +> behaviours, pitfalls). See the "Keeping This Skill Up to Date" section at the bottom for guidance. You are helping create a new DFHDL compiler transformation stage. Use the complete reference below to produce correct, idiomatic code. @@ -897,3 +899,32 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) - [ ] At least one "basic" test and one "edge case" or "backend-specific" test - [ ] `assertCodeString` expected strings verified manually or via a first-run snapshot - [ ] `sbt test` passes (or `sbt quickTestSetup; test` for faster iteration via `lib/Playground.scala`) +- [ ] **Update this skill** with any general lessons learned (see below) + +--- + +## Keeping This Skill Up to Date + +While creating a new stage, you will often discover things that are not yet documented here: +new IR API behaviours, patch interaction subtleties, MetaDesign patterns, common pitfalls, etc. + +**After completing a stage, review what you learned and update this file** when the lesson is +general enough to help with future stage creation. Specifically: + +- **New IR / API behaviour** → add to the relevant section (IR Data Model, Patch System, MetaDesign). +- **New pitfall or gotcha** → add a numbered entry to "Common Mistakes to Avoid". +- **New reusable transformation pattern** → add a numbered "Pattern N" entry under "Transformation Patterns". +- **Corrected or outdated information** → update the existing section in place. + +Do **not** add stage-specific implementation details here. Only document things that are likely +to recur in other stages. The rule of thumb: *would a future contributor hit this same issue if +they didn't already know about it?* If yes, document it. + +Also update **stage-adjacent source files** when you discover missing or incorrect API documentation: +- `Patch.scala` — document `Patch.Move` / `Patch.Replace` / `Patch.Add` semantics +- `DB.scala` — document `MemberView.Folded` / `Flattened` +- `MetaDesign.scala` — document ownership and `plantMember` / `plantClonedMembers` +- `DFC.scala` — document DFC helpers used in MetaDesign + +This skill and the source files it references are the authoritative guide for future stage authors. +Keep them accurate. From c72f2db5420a6e89f26c961fede8bc5ea1b618b7 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 00:54:26 +0200 Subject: [PATCH 035/115] add instructions for creating or modifying compiler stages in Claude guide --- CLAUDE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 0e0a6ea45..0eabac6c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,6 +106,10 @@ CI installs these via OSS CAD Suite: - **Verilog**: verilator, iverilog (sv2005 skipped for iverilog), questa, vivado - **VHDL**: ghdl, nvc, questa, vivado (v2008 skipped for vivado) +## Claude Instructions + +- When asked to **create a new compiler stage** or **modify an existing compiler stage**, always invoke the `/new-stage` skill before doing any work. + ## Licenses - Main library (`internals`, `plugin`, `compiler_ir`, `core`, `compiler_stages`, `lib`, `ips`): **LGPL v3.0** From c0cc28e5cf828c3ae338198a4b4e43ffe951db99 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 03:17:16 +0200 Subject: [PATCH 036/115] update DropLocalDcls documenation --- .../dfhdl/compiler/stages/DropLocalDcls.scala | 76 ++++++++++++++++++- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropLocalDcls.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropLocalDcls.scala index 982856b52..17638d2d2 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropLocalDcls.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropLocalDcls.scala @@ -5,11 +5,79 @@ import dfhdl.compiler.ir.* import dfhdl.compiler.patching.* import dfhdl.options.CompilerOptions -/** This stage moves the local vars or named constants (at the conditional/process block level) to - * either the design level (in Verilog) or its owner level (in VHDL), where the owner can be a - * design or a process. Verilog does not support declarations inside an always block, so they must - * be moved to the design level. VHDL does support declarations at the process level. +//format: off +/** This stage moves local variable and constant declarations out of their lexical scope to a + * position where the target language supports them. It also inserts reset-to-init assignments for + * local variables with initialization values inside RT process blocks. + * + * ==Context== + * + * Verilog `always` blocks (process blocks) do not support variable declarations — all declarations + * must appear at the design (module) level. VHDL `process` blocks DO support variable + * declarations, so only conditional-block-level declarations need lifting in VHDL. + * + * ==Rules== + * + * ===Rule 1: Declarations inside conditional blocks=== + * Any local variable (`VAR`) or non-global named constant (`CONST`) that is declared inside a + * conditional block (`if`, `match`, or `while`) is moved to before the top-level conditional + * header that contains it: + * - For non-VHDL backends: if the top-level conditional is itself inside a process block, the + * declaration is moved to before the process block (design level). + * - For VHDL: the declaration is moved to just before the top-level conditional header, staying + * inside the process block if one exists. + * {{{ + * // Before — zz declared inside an if inside a process + * class ID extends EDDesign: + * process(all): + * if (x > 5) + * val zz = SInt(16) <> VAR + * ... + * + * // After (Verilog) — moved to design level, before the process + * class ID extends EDDesign: + * val zz = SInt(16) <> VAR + * process(all): + * if (x > 5) + * ... + * + * // After (VHDL) — moved to just before the if, inside the process + * class ID extends EDDesign: + * process(all): + * val zz = SInt(16) <> VAR + * if (x > 5) + * ... + * }}} + * + * ===Rule 2: Declarations directly inside process blocks=== + * A local variable declared directly inside a process block (not inside a conditional): + * - For non-VHDL backends: moved to before the process block (design level). + * - For VHDL: moved to the top of the process block (before any statements), because VHDL + * requires all variable declarations to precede statements in a process. + * {{{ + * // Before + * class ID extends EDDesign: + * process(all): + * stmt1 + * val zz = SInt(16) <> VAR + * stmt2 + * + * // After (Verilog) — moved to design level + * class ID extends EDDesign: + * val zz = SInt(16) <> VAR + * process(all): + * stmt1 + * stmt2 + * + * // After (VHDL) — moved to top of process + * class ID extends EDDesign: + * process(all): + * val zz = SInt(16) <> VAR + * stmt1 + * stmt2 + * }}} */ +//format: on case object DropLocalDcls extends Stage: override def dependencies: List[Stage] = List(ExplicitNamedVars) override def nullifies: Set[Stage] = Set() From e3c944f8923839bc0ed0e89791943c4c76cbb2b3 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 03:21:10 +0200 Subject: [PATCH 037/115] add ExplicitNamedVars and DropLocalDcls to dependencies in FlattenStepBlocks stage --- .../dfhdl/compiler/stages/FlattenStepBlocks.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala index 87214c2a6..417868a57 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala @@ -166,7 +166,7 @@ import scala.annotation.tailrec */ //format: on case object FlattenStepBlocks extends Stage: - def dependencies: List[Stage] = List(DropRTWaits) + def dependencies: List[Stage] = List(DropRTWaits, ExplicitNamedVars, DropLocalDcls) def nullifies: Set[Stage] = Set() def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = @@ -174,13 +174,13 @@ case object FlattenStepBlocks extends Stage: // Phase 3 ChangeRef patches are computed from the original DB. val gotoPatchList = designDB.members.view.flatMap { case pb: ProcessBlock if pb.isInRTDomain => collectGotoPatches(pb) - case _ => Nil + case _ => Nil }.toList // Phase 0: inter-step relocation (Step 5 inter-step + Step 6) val db0 = designDB.patch( designDB.members.view.flatMap { case pb: ProcessBlock if pb.isInRTDomain => collectInterStepPatches(pb) - case _ => Nil + case _ => Nil }.toList ) // Phase 1: conditional branch extraction (uses db0 for updated member structure) @@ -189,7 +189,7 @@ case object FlattenStepBlocks extends Stage: db0.patch( db0.members.view.flatMap { case pb: ProcessBlock if pb.isInRTDomain => collectConditionalExtractionPatches(pb) - case _ => Nil + case _ => Nil }.toList ) } @@ -204,7 +204,7 @@ case object FlattenStepBlocks extends Stage: given MemberGetSet = db.getSet val patches = db.members.view.flatMap { case pb: ProcessBlock if pb.isInRTDomain => collectFlattenPatchesOneLevel(pb) - case _ => Nil + case _ => Nil }.toList if patches.isEmpty then db else flattenRepeatedly(db.patch(patches)) @@ -260,7 +260,7 @@ case object FlattenStepBlocks extends Stage: .collect { case g: Goto if !consumedGotos.contains(g) => g } .flatMap { g => g.stepRef.get match - case _: StepBlock => None + case _: StepBlock => None case Goto.ThisStep => Some(g -> Patch.ChangeRef(_.asInstanceOf[Goto].stepRef, g.getOwnerStepBlock)) case Goto.FirstStep => @@ -326,6 +326,7 @@ case object FlattenStepBlocks extends Stage: } } } + end if } step5InterStep ++ step6 end collectInterStepPatches From 2635739a79f63bb9ba4d2ba5b30075d8e4cc1963 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 03:21:31 +0200 Subject: [PATCH 038/115] enhance SimplifyRTOps with detailed transformation rules and add tests for RT for loop conversions --- .../dfhdl/compiler/stages/SimplifyRTOps.scala | 170 ++++++++++++++++-- .../scala/StagesSpec/SimplifyRTOpsSpec.scala | 119 ++++++++++-- 2 files changed, 264 insertions(+), 25 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala index 393954faf..66a0b7a97 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala @@ -8,19 +8,103 @@ import dfhdl.internals.* import DFVal.Func.Op as FuncOp import scala.collection.mutable //format: off -/** This stage transforms: - * - wait statements into while loops with a cycle wait. For example: - * waitWhile(cond) -> while(cond) 1.cy.wait - * - rising/falling edge operations into reg alias detection operations. For example: - * i.rising -> !i.reg(1, init = 1) && i - * i.falling -> i.reg(1, init = 0) && !i - * - non-zero/one cycle wait statements to while loops with a counter. For example (under 50Mhz clock): - * 50000000.cy.wait -> while(waitCnt != 50000000) 1.cy.wait +/** This stage simplifies RT-domain operations into lower-level constructs that downstream stages + * can process. It handles four kinds of transformations: rising/falling edge predicates, boolean + * wait statements, cycle-count wait statements, and for loops. + * + * ==Rule 1: Rising/falling edge simplification== + * + * `rising` and `falling` predicates on a `Bit` signal are replaced with register-based edge + * detection expressions: + * {{{ + * // Before + * i.rising + * i.falling + * + * // After + * (!i.reg(1, init = 1)) && i + * i.reg(1, init = 0) && (!i) + * }}} + * + * ==Rule 2: Boolean wait → while loop== + * + * `waitWhile(cond)` / `waitUntil(cond)` statements (including those with rising/falling edge + * triggers) are replaced with `while` loops. If the wait has a name, the generated while loop + * takes that name: + * {{{ + * // Before + * waitWhile(i) + * val MyWait = waitUntil(i) + * waitUntil(i.falling) + * + * // After + * while (i) + * end while + * val MyWait = while (!i) + * end MyWait + * while ((!i.reg(1, init = 0)) || i) + * end while + * }}} + * + * ==Rule 3: Cycle-count wait → while loop with counter== + * + * A wait with a cycle count (`N.cy.wait`) is replaced by a `VAR.REG` counter assigned to zero + * before the loop and a `while` loop that counts up to `N-1`. A single-cycle wait (`1.cy.wait`) + * is left unchanged (no loop needed). If the wait has a name (`val N = X.cy.wait`), the counter + * register is prefixed with `N_`: + * {{{ + * // Before + * x.din := 1 + * 50000000.cy.wait + * val MyWait = waitParam.cy.wait + * + * // After + * x.din := 1 + * val waitCnt = UInt(26) <> VAR.REG + * waitCnt.din := d"26'0" + * while (waitCnt != d"26'49999999") + * waitCnt.din := waitCnt + d"26'1" + * end while + * val MyWait_waitCnt = UInt(26) <> VAR.REG + * MyWait_waitCnt.din := d"26'0" + * val MyWait = while (MyWait_waitCnt != (waitParam - d"26'1")) + * MyWait_waitCnt.din := MyWait_waitCnt + d"26'1" + * end MyWait + * }}} + * + * ==Rule 4: For loop → while loop with iterator== + * + * `for` loops in RT processes are replaced by a `Int <> VAR.REG` iterator assigned to the range + * start before the loop, and a `while` loop whose body ends with the iterator increment. The + * comparison operator is `<` for `until` (exclusive) and `<=` for `to` (inclusive); for negative + * steps the operators are `>` and `>=` respectively. For loops tagged as `COMB_LOOP` or outside + * the RT domain are left unchanged. + * + * Iterator naming: + * - anonymous `for (i <- ...)`: iterator register keeps the original name (`i`) + * - named `val Name = for (i <- ...)`: iterator register is named `Name_i` + * {{{ + * // Before + * x.din := 1 + * for (i <- 0 until 4) + * x.din := 0 + * x.din := 1 + * + * // After + * x.din := 1 + * val i = Int <> VAR.REG + * i.din := 0 + * while (i < 4) + * x.din := 0 + * i.din := i + 1 + * end while + * x.din := 1 + * }}} */ //format: on case object SimplifyRTOps extends Stage: def dependencies: List[Stage] = List(DropTimedRTWaits) - def nullifies: Set[Stage] = Set(DropUnreferencedAnons, DFHDLUniqueNames) + def nullifies: Set[Stage] = Set(DropUnreferencedAnons, DFHDLUniqueNames, DropLocalDcls) def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = given RefGen = RefGen.fromGetSet @@ -29,7 +113,7 @@ case object SimplifyRTOps extends Stage: case _: Wait => true case _ => false } - val patchList = designDB.members.view.collect { + val patchList = designDB.members.view.flatMap { case trigger @ DFVal.Func( _, op @ (FuncOp.rising | FuncOp.falling), @@ -94,8 +178,11 @@ case object SimplifyRTOps extends Stage: // the bug originates from a conversion from UInt[1] to the iterType. It could be that the // methods under core.DFVal.Alias are not working as intended because of meta-programming. val initZero = dfhdl.core.DFVal.Const(iterType, Some(BigInt(0))) - val waitCnt = - iterType.<>(VAR.REG).initForced(List(initZero))(using dfc.setName("waitCnt")) + val waitCntName = waitMember.meta.nameOpt match + case Some(waitName) => s"${waitName}_waitCnt" + case None => "waitCnt" + val waitCnt = iterType.<>(VAR.REG)(using dfc.setName(waitCntName)) + waitCnt.din := initZero // the upper bound for the while loop count val upperBound = cyclesVal match // the upper bound is reduced to a simpler form when the number of cycles is a constant anonymous value @@ -112,7 +199,64 @@ case object SimplifyRTOps extends Stage: Some(dsn.patch) else None end if - }.flatten.toList + + // replace RT for loops with while loops + iterator VAR.REG + increment at end of body + case forBlock: DFLoop.DFForBlock if forBlock.isInRTDomain && !forBlock.isCombinational => + val iteratorDcl = forBlock.iteratorRef.get + val range = forBlock.rangeRef.get + val startBigInt: BigInt = range.startRef.get match + case DFVal.Const(data = Some(v: BigInt)) => v + case _ => BigInt(0) + val stepBigInt: BigInt = range.stepRef.get match + case DFVal.Const(data = Some(v: BigInt)) => v + case _ => BigInt(1) + val endValIR = range.endRef.get + val rangeOp = range.op + val iterName = forBlock.meta.nameOpt match + case None => iteratorDcl.getName + case Some(name) => s"${name}_${iteratorDcl.getName}" + val forBodyMembers = forBlock.members(MemberView.Folded) + // M1: create iterator VAR.REG + guard + whileBlock, replacing forBlock. + // ReplaceWithLast(ChangeRefAndRemove) makes whileBlock (last M1 member) replace forBlock, + // redirecting ALL refs to forBlock (including body members' ownerRefs) to whileBlock. + val m1 = new MetaDesign( + forBlock, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove), + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) + ): + val startConst = dfhdl.core.DFVal.Const(dfhdl.core.DFInt32, Some(startBigInt)) + val newIterDcl = dfhdl.core.DFInt32.<>(VAR.REG)(using dfc.setName(iterName)) + newIterDcl.din := startConst + val endVal = endValIR.asValOf[dfhdl.core.DFInt32] + val guard = (rangeOp, stepBigInt.signum) match + case (DFRange.Op.Until, s) if s >= 0 => newIterDcl < endVal + case (DFRange.Op.To, s) if s >= 0 => newIterDcl <= endVal + case (DFRange.Op.Until, _) => newIterDcl > endVal + case (DFRange.Op.To, _) => newIterDcl >= endVal + val whileBlock = dfhdl.core.DFWhile.Block(guard)(using dfc.setMeta(forBlock.meta)) + dfc.enterOwner(whileBlock) + dfc.exitOwner() + val newIterDclIR = m1.newIterDcl.asIR + val iterDclPatch = + iteratorDcl -> Patch.Replace(newIterDclIR, Patch.Replace.Config.ChangeRefAndRemove) + if forBodyMembers.nonEmpty then + // M2: create the increment (newIterDcl.din := newIterDcl + step) after the last body member. + // M2's injectedOwner = forBodyMembers.last.getOwner = forBlock (in original DB). + // After M1's ref redirect (forBlock → whileBlock), M2's members' ownerRefs are also + // redirected to whileBlock, placing the increment correctly as the last while-body statement. + val m2 = new MetaDesign( + forBodyMembers.last, + Patch.Add.Config.After, + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) + ): + val stepConst = dfhdl.core.DFVal.Const(dfhdl.core.DFInt32, Some(stepBigInt)) + m1.newIterDcl.din := m1.newIterDcl + stepConst + List(m1.patch, iterDclPatch, m2.patch) + else List(m1.patch, iterDclPatch) + end if + + case _ => None + }.toList designDB.patch(patchList) end transform diff --git a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala index 228b9bc41..dddd0d66d 100644 --- a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala @@ -62,7 +62,7 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): x.din := i.rising x.din := i.falling waitUntil(i) - waitUntil(i.falling) + val MyWait = waitUntil(i.falling) waitUntil(i.rising) x.din := 0 end Foo @@ -78,8 +78,8 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): | x.din := i.reg(1, init = 0) && (!i) | while (!i) | end while - | while ((!i.reg(1, init = 0)) || i) - | end while + | val MyWait = while ((!i.reg(1, init = 0)) || i) + | end MyWait | while (i.reg(1, init = 1) || (!i)) | end while | x.din := 0 @@ -96,7 +96,7 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): x.din := 1 waitWhile(i) x.din := 0 - waitWhile(j) + val MyWait = waitWhile(j) x.din := 1 end Foo val top = (new Foo).simplifyRTOps @@ -111,8 +111,8 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): | while (i) | end while | x.din := 0 - | while (j) - | end while + | val MyWait = while (j) + | end MyWait | x.din := 1 |end Foo""".stripMargin ) @@ -126,7 +126,7 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): x.din := 1 50000000.cy.wait x.din := 0 - waitParam.cy.wait + val MyWait = waitParam.cy.wait 1.cy.wait end Foo val top = (new Foo).simplifyRTOps @@ -137,17 +137,112 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): | val waitParam: UInt[26] <> CONST = d"26'50000000" | process: | x.din := 1 - | val waitCnt = UInt(26) <> VAR.REG init d"26'0" + | val waitCnt = UInt(26) <> VAR.REG + | waitCnt.din := d"26'0" | while (waitCnt != d"26'49999999") | waitCnt.din := waitCnt + d"26'1" | end while | x.din := 0 - | val waitCnt = UInt(26) <> VAR.REG init d"26'0" - | while (waitCnt != (waitParam - d"26'1")) - | waitCnt.din := waitCnt + d"26'1" - | end while + | val MyWait_waitCnt = UInt(26) <> VAR.REG + | MyWait_waitCnt.din := d"26'0" + | val MyWait = while (MyWait_waitCnt != (waitParam - d"26'1")) + | MyWait_waitCnt.din := MyWait_waitCnt + d"26'1" + | end MyWait | 1.cy.wait |end Foo""".stripMargin ) } + test("RT for loop with until is converted to while loop") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + process: + x.din := 1 + for (i <- 0 until 4) + x.din := 0 + x.din := 1 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | process: + | x.din := 1 + | val i = Int <> VAR.REG + | i.din := 0 + | while (i < 4) + | x.din := 0 + | i.din := i + 1 + | end while + | x.din := 1 + |end Foo""".stripMargin + ) + } + + test("RT for loop with to is converted to while loop with <= guard") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + process: + val MyFor = for (i <- 0 to 3) + x.din := 0 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | process: + | val MyFor_i = Int <> VAR.REG + | MyFor_i.din := 0 + | val MyFor = while (MyFor_i <= 3) + | x.din := 0 + | MyFor_i.din := MyFor_i + 1 + | end MyFor + |end Foo""".stripMargin + ) + } + + test("RT for loop with explicit step is converted to while loop with step increment") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + process: + for (i <- 0 until 8 by 2) + x.din := 0 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | process: + | val i = Int <> VAR.REG + | i.din := 0 + | while (i < 8) + | x.din := 0 + | i.din := i + 2 + | end while + |end Foo""".stripMargin + ) + } + + test("ED domain for loop is untouched") { + class Foo extends EDDesign: + val x = Bit <> OUT + process: + for (i <- 0 until 4) + x :== 0 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends EDDesign: + | val x = Bit <> OUT + | process: + | for (i <- 0 until 4) + | x :== 0 + | end for + |end Foo""".stripMargin + ) + } + end SimplifyRTOpsSpec From e5a9d82efbeda22b5fb07f66c30bee1516a3dca9 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 03:21:52 +0200 Subject: [PATCH 039/115] enhance new-stage documentation with additional transformation patterns and ScalaDoc guidelines --- .claude/commands/new-stage.md | 117 ++++++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 4 deletions(-) diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index 4ee10940d..d272f8703 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -744,12 +744,81 @@ Key invariants: - `given MemberGetSet = db.getSet` must be re-established at the top of each recursive call so navigation helpers see the updated member structure. +### Pattern 10 — Replace a DFOwner while preserving its children + +Use when you want to swap one owner block for another (e.g. `DFForBlock` → `DFWhileBlock`) but +keep all body members in place. `ReplaceWithLast(ChangeRefAndRemove)` redirects **all** refs to +the old owner — including every child member's `ownerRef` — to the new owner (the last member +synthesised in the MetaDesign). Body members naturally remain in the flat list at their correct +positions, now re-parented to the new block. No `plantMembers` is needed. + +```scala +case forBlock: DFLoop.DFForBlock if forBlock.isInRTDomain => + val m1 = new MetaDesign( + forBlock, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove), + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) + ): + // preamble members (e.g. iterator VAR.REG, guard expression) ... + val whileBlock = dfhdl.core.DFWhile.Block(guard)(using dfc.setMeta(forBlock.meta)) + dfc.enterOwner(whileBlock) // required so whileBlock is a proper owner in the DB + dfc.exitOwner() + // capture M1 members for use in further patches + val newIterDclIR = m1.newIterDcl.asIR + List(m1.patch, ...) +``` + +The `dfc.enterOwner` / `dfc.exitOwner` pair is required for the new owner (here `whileBlock`) to +be registered in the DB's ownership context so that child member navigation works correctly. + +### Pattern 11 — Two-MetaDesign pattern: append members at end of a re-owned body + +Use when you need to inject members **after the last body member** of a block that is being +replaced by Pattern 10. Anchor M2 at `bodyMembers.last` with `After`. M2's `injectedOwner` is +`bodyMembers.last.getOwner = oldBlock`. When M1's `ChangeRefAndRemove` redirects refs to the old +block → new block, M2's members' `ownerRef`s are also redirected, placing them correctly as the +last statements inside the new block. + +```scala +val forBodyMembers = forBlock.members(MemberView.Folded) +// M1: replace forBlock with whileBlock (Pattern 10) ... +if forBodyMembers.nonEmpty then + val m2 = new MetaDesign( + forBodyMembers.last, + Patch.Add.Config.After, + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) + ): + // members here are owned by forBodyMembers.last.getOwner = forBlock + // after M1's ChangeRefAndRemove, their ownerRefs are redirected to whileBlock + m1.newIterDcl.din := m1.newIterDcl + stepConst // cross-MetaDesign ref is fine + List(m1.patch, iterDclPatch, m2.patch) +else List(m1.patch, iterDclPatch) +``` + +**Cross-MetaDesign references**: Members declared with `val` in M1 body are accessible as +`m1.memberName` (MetaDesign extends `reflect.Selectable`). They can be used inside M2's body +via closure; the symbolic refs resolve correctly in the final DB because M1's members are present +after all patches are applied. + +**Returning multiple patches per case**: use `.flatMap { ... }` (not `.collect { ... }.flatten`) +and return `List(...)` from the case; return `None` from the catch-all `case _ =>`. Both `Option` +and `List` are `IterableOnce`, so `flatMap` handles them uniformly. + --- ## Boilerplate Template ### Stage file: `compiler/stages/src/main/scala/dfhdl/compiler/stages/MyStage.scala` +### Stage ScalaDoc guidelines + +- Wrap the ScalaDoc comment with `//format: off` / `//format: on` (scalafmt reformats `{{{ }}}` blocks and `==Headings==` if left unguarded). +- Use `==Rule N: Title==` ScalaDoc sections for each distinct transformation rule. +- Use `{{{ }}}` code blocks (not ` ```scala ` fences) for before/after examples. +- Each code block should start with `// Before` and `// After` comment lines. +- Before/after examples should show the construct in context (e.g. with a preceding statement), not as the first and only statement in a process — this makes the placement of generated members unambiguous. +- Do NOT include Idempotency or Determinism sections — those are implementation invariants, not user-facing documentation. + ```scala package dfhdl.compiler.stages @@ -758,11 +827,24 @@ import dfhdl.compiler.ir.* import dfhdl.compiler.patching.* import dfhdl.options.CompilerOptions -/** - * Describe what this stage does, why it exists, input/output IR shapes, and ordering/resolution - * rules if applicable. Do NOT include Idempotency or Determinism sections — those are - * implementation invariants enforced by the infrastructure, not user-facing documentation. +//format: off +/** Brief description of what this stage does and which IR shapes it transforms. + * + * ==Rule 1: Title of first transformation== + * + * Prose explanation of the rule. + * {{{ + * // Before + * + * + * // After + * + * }}} + * + * ==Rule 2: Title of second transformation== + * ... */ +//format: on case object MyStage extends Stage: def dependencies: List[Stage] = List(SomePriorStage) // run these first def nullifies: Set[Stage] = Set(SomeInvalidatedStage) @@ -882,6 +964,33 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) 13. **Using `dfc.owner.ref` inside MetaDesign** — `import dfhdl.core.*` introduces extension methods that conflict with `.ref`. Use `dfc.ownerOrEmptyRef` instead to obtain the owner's `ir.DFOwner.Ref` when constructing raw IR members. +14. **`DFBlock` subtypes lack `isAnonymous` / `getName`** — these helpers are extension methods on + `DFMember.Named` (value types), not on blocks. For `DFForBlock`, `DFWhileBlock`, and similar, + access the name via `block.meta.nameOpt` directly: + ```scala + val iterName = forBlock.meta.nameOpt match + case None => iteratorDcl.getName + case Some(name) => s"${name}_${iteratorDcl.getName}" + ``` +15. **Case-class extractor `DFForBlock()` requires all fields** — writing `case x @ DFLoop.DFForBlock()` + fails with "wrong number of argument patterns" because `DFForBlock` has multiple fields. Use a + type pattern instead: `case x: DFLoop.DFForBlock`. The same applies to other multi-field IR + case classes that have no dedicated single-argument unapply. + +--- + +## API Notes + +### `dfhdl.core.DFInt32` + +Available as both a type alias and a value (`ir.DFInt32.asFE[DFInt32]`). Use it inside MetaDesign +bodies to create 32-bit signed integer variables: + +```scala +dfhdl.core.DFInt32.<>(VAR.REG).initForced(List(initConst))(using dfc.setName("i")) +``` + +This mirrors the `iterType.<>(VAR.REG)` pattern used for UInt variables. --- From ef36f1ca4a948f4fa4d497554f4a614e167c2be6 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 03:49:38 +0200 Subject: [PATCH 040/115] enhance FlattenStepBlocks with improved handling of inter-step statements in conditional branches and add corresponding tests --- .../compiler/stages/FlattenStepBlocks.scala | 63 ++++++++++++++----- .../StagesSpec/FlattenStepBlocksSpec.scala | 60 +++++++++++++++++- 2 files changed, 107 insertions(+), 16 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala index 417868a57..0ac86454a 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala @@ -224,6 +224,13 @@ case object FlattenStepBlocks extends Stage: val consumedGoto = cbMembers.drop(sIdx + 1).collectFirst { case g: Goto => g }.get (cb, consumedGoto) + // Returns the next regular StepBlock sibling inside the same conditional branch, if any. + private def findNextStepInBranch(s: StepBlock)(using MemberGetSet): Option[StepBlock] = + val cb = s.getOwner.asInstanceOf[DFConditional.Block] + val cbMembers = cb.members(MemberView.Folded) + val sIdx = cbMembers.indexOf(s) + cbMembers.drop(sIdx + 1).collectFirst { case sb: StepBlock if sb.isRegular => sb } + private def deepestLastChild(step: StepBlock)(using MemberGetSet): StepBlock = step.members(MemberView.Folded) .collect { case sb: StepBlock if sb.isRegular => sb } @@ -232,8 +239,11 @@ case object FlattenStepBlocks extends Stage: case Some(last) => deepestLastChild(last) private def findNextStepGoto(step: StepBlock)(using MemberGetSet): Option[Goto] = + // Restrict to gotos whose enclosing StepBlock is `step` itself, not a nested step. + // Without this guard, pre-order DFS would find a nested step's Goto(NextStep) first, + // causing inter-step statements to be incorrectly moved into an inner step's body. step.members(MemberView.Flattened).collectFirst { - case g: Goto if g.stepRef.get == Goto.NextStep => g + case g: Goto if g.stepRef.get == Goto.NextStep && g.getOwnerStepBlock == step => g } // --- Phase 3: Goto ChangeRef patches (computed from original DB) --- @@ -248,12 +258,16 @@ case object FlattenStepBlocks extends Stage: } val consumedGotos = conditionalBranchSteps.map(findConsumedGoto(_)._2).toSet val conditionalStepMap = conditionalBranchSteps.map { s => - val (_, consumedGoto) = findConsumedGoto(s) - val target: StepBlock = consumedGoto.stepRef.get match - case sb: StepBlock => sb - case Goto.ThisStep => consumedGoto.getOwnerStepBlock - case Goto.NextStep => nextStepMap(consumedGoto.getOwnerStepBlock) - case Goto.FirstStep => firstStep + // Non-last steps in a branch target the next step in the branch directly. + // Only the last step uses the branch-terminal consumed goto to find its target. + val target: StepBlock = findNextStepInBranch(s).getOrElse { + val (_, consumedGoto) = findConsumedGoto(s) + consumedGoto.stepRef.get match + case sb: StepBlock => sb + case Goto.ThisStep => consumedGoto.getOwnerStepBlock + case Goto.NextStep => nextStepMap(consumedGoto.getOwnerStepBlock) + case Goto.FirstStep => firstStep + } s -> target }.toMap pb.members(MemberView.Flattened) @@ -288,7 +302,13 @@ case object FlattenStepBlocks extends Stage: val cbMembers = cb.members(MemberView.Folded) val sIdx = cbMembers.indexOf(s) val consumedGotoIdx = cbMembers.indexOf(consumedGoto) - val interStepStmts = cbMembers.slice(sIdx + 1, consumedGotoIdx) + // When multiple steps share the same branch, only collect statements up to the next + // step (not all the way to the consumed goto). Otherwise the same statements would be + // collected for every step that precedes them, producing duplicate Move patches. + val upperIdx = findNextStepInBranch(s) + .map(cbMembers.indexOf) + .getOrElse(consumedGotoIdx) + val interStepStmts = cbMembers.slice(sIdx + 1, upperIdx) .filterNot(m => m.isInstanceOf[StepBlock] || m.isInstanceOf[Goto]) val targetStep = deepestLastChild(s) findNextStepGoto(targetStep).toList.flatMap { nextStepGoto => @@ -343,19 +363,32 @@ case object FlattenStepBlocks extends Stage: } conditionalBranchSteps.flatMap { s => val (cb, consumedGoto) = findConsumedGoto(s) - // Insert an explicit Goto to s at s's former position in the branch - val dsn = new MetaDesign(s, Patch.Add.Config.Before): - import dfhdl.core.* - Goto(s.refTW[Goto], dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags).addMember - // Remove the consumed goto - val removeConsumedGoto: (DFMember, Patch) = consumedGoto -> Patch.Remove() + val cbMembers = cb.members(MemberView.Folded) + val sIdx = cbMembers.indexOf(s) + val isFirstStepInBranch = !cbMembers.take(sIdx).exists(_.isInstanceOf[StepBlock]) + val isLastStepInBranch = findNextStepInBranch(s).isEmpty + // Insert an explicit Goto to s at s's former position in the branch only for the first + // step. Subsequent steps in the same branch are reached via the preceding step's goto. + val dsnPatchOpt: Option[(DFMember, Patch)] = + if isFirstStepInBranch then + Some( + new MetaDesign(s, Patch.Add.Config.Before): + import dfhdl.core.* + Goto(s.refTW[Goto], dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags).addMember + .patch + ) + else None + // Remove the consumed goto only for the last step so it is not removed twice when + // multiple steps share the same branch-terminal goto. + val removeConsumedGotoOpt: Option[(DFMember, Patch)] = + if isLastStepInBranch then Some(consumedGoto -> Patch.Remove()) else None // Move s and ALL its descendants to after the parent step at pb level. // Including descendants ensures the flat member list maintains valid ownership ordering. val parentStep = cb.getOwnerStepBlock val allMembersToMove = s :: s.members(MemberView.Flattened) val movePatch: (DFMember, Patch) = parentStep -> Patch.Move(allMembersToMove, cb, Patch.Move.Config.After) - List(dsn.patch, removeConsumedGoto, movePatch) + dsnPatchOpt.toList ++ List(movePatch) ++ removeConsumedGotoOpt } end collectConditionalExtractionPatches diff --git a/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala b/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala index 9d1107026..0e342996c 100644 --- a/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala @@ -413,7 +413,9 @@ class FlattenStepBlocksSpec extends StageSpec(): ) } - test("multiple inter-step statements from nested and conditional scopes collected before single NextStep") { + test( + "multiple inter-step statements from nested and conditional scopes collected before single NextStep" + ) { class Foo extends RTDesign: val a = Int <> OUT.REG val b = Int <> OUT.REG @@ -462,5 +464,61 @@ class FlattenStepBlocksSpec extends StageSpec(): |end Foo""".stripMargin ) } + test("multiple steps nested inside the same conditional branch with inter-step statements") { + class Foo extends RTDesign: + val i = Int <> VAR.REG + process: + def S_0: Step = + NextStep + end S_0 + i.din := 0 + def S_1: Step = + if (i < 3) + println(s"Hello") + def S_1_0: Step = + NextStep + end S_1_0 + println(s"World") + def S_1_1: Step = + NextStep + end S_1_1 + println(s"!") + i.din := i + 1 + ThisStep + else NextStep + end if + end S_1 + finish() + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Int <> VAR.REG + | process: + | def S_0: Step = + | i.din := 0 + | S_1 + | end S_0 + | def S_1: Step = + | if (i < 3) + | println(s"Hello") + | S_1_0 + | else + | finish() + | S_0 + | end S_1 + | def S_1_0: Step = + | println(s"World") + | S_1_1 + | end S_1_0 + | def S_1_1: Step = + | println(s"!") + | i.din := i + 1 + | S_1 + | end S_1_1 + |end Foo""".stripMargin + ) + } end FlattenStepBlocksSpec From 89eeadc691dfe436f5ddba5730475a01bdec94ae Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 03:49:49 +0200 Subject: [PATCH 041/115] enhance new-stage documentation to clarify contributions for both adding and modifying compiler transformation stages --- .claude/commands/new-stage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index d272f8703..dcd663ae6 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -1,11 +1,11 @@ # DFHDL Compiler Stage Creation Guide -> **For contributors adding new compiler transformation stages to DFHDL.** +> **For contributors adding or modifying compiler transformation stages in DFHDL.** > This skill is version-controlled alongside the codebase — keep it updated when stage infrastructure changes. > After completing a stage, **update this file** with any general lessons learned (new patterns, API > behaviours, pitfalls). See the "Keeping This Skill Up to Date" section at the bottom for guidance. -You are helping create a new DFHDL compiler transformation stage. Use the complete reference below to produce correct, idiomatic code. +You are helping create or modify a DFHDL compiler transformation stage. Use the complete reference below to produce correct, idiomatic code. --- From 17e54d99dd1126aab62aba30daa10998289084f1 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 04:00:43 +0200 Subject: [PATCH 042/115] enhance DropRTWaits to handle empty while loops and add corresponding test case --- .../dfhdl/compiler/stages/DropRTWaits.scala | 95 ++++++++++++------- .../scala/StagesSpec/DropRTWaitsSpec.scala | 16 ++++ 2 files changed, 75 insertions(+), 36 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala index b8aa60d32..b340a2f2c 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala @@ -246,42 +246,65 @@ case object DropRTWaits extends Stage: // transform a while loop into a step block. case wb: DFLoop.DFWhileBlock if !wb.isCombinational => val stepName = getStepName(wb) - // the last member of the while loop is the exit member. - val lastLoopMember = wb.getVeryLastMember.get - // creating the if part of the while loop step block. - val stepAndIfDsn = new MetaDesign( - wb, - Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement) - ): - import dfhdl.core.{StepBlock, DFIf, DFBool, DFUnit} - val step = StepBlock.forced(using dfc.setName(stepName)) - dfc.enterOwner(step) - val wbGuard = wb.guardRef.get - if (wb.isFallThrough) - val fallThrough = StepBlock.forced(using dfc.setName("fallThrough")) - dfc.enterOwner(fallThrough) - val clonedCond = !wbGuard.cloneAnonValueAndDepsHere.asValOf[DFBool] - dfhdl.core.DFVal.Alias.AsIs.ident(clonedCond)(using dfc.anonymize) - dfc.exitOwner() - end if - val cond = wbGuard.asValOf[DFBool] - val ifBlock = DFIf.Block(Some(cond), DFIf.Header(DFUnit)) - dfc.exitOwner() - // creating the else part of the while loop step block, to be applied when the while loop exits. - val elseDsn = new MetaDesign( - lastLoopMember, - Patch.Add.Config.After - ): - import dfhdl.core.DFIf - dfc.enterOwner(stepAndIfDsn.ifBlock) - ThisStep - dfc.exitOwner() - val elseBlock = DFIf.Block(None, stepAndIfDsn.ifBlock) - dfc.enterOwner(elseBlock) - NextStep - dfc.exitOwner() - enterStepBlock(wb, lastLoopMember, Some(elseDsn.patch._2)) - Some(stepAndIfDsn.patch) + wb.getVeryLastMember match + // Empty loop body: generate the complete step+if/else structure in one shot. + case None => + nextStepBlock() + val dsn = new MetaDesign( + wb, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement) + ): + import dfhdl.core.{StepBlock, DFIf, DFBool, DFUnit} + val step = StepBlock.forced(using dfc.setName(stepName)) + dfc.enterOwner(step) + val wbGuard = wb.guardRef.get + val cond = wbGuard.asValOf[DFBool] + val ifBlock = DFIf.Block(Some(cond), DFIf.Header(DFUnit)) + dfc.exitOwner() + dfc.enterOwner(ifBlock) + ThisStep + dfc.exitOwner() + val elseBlock = DFIf.Block(None, ifBlock) + dfc.enterOwner(elseBlock) + NextStep + dfc.exitOwner() + Some(dsn.patch) + // Non-empty loop body: the last member of the while loop is the exit member. + case Some(lastLoopMember) => + // creating the if part of the while loop step block. + val stepAndIfDsn = new MetaDesign( + wb, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement) + ): + import dfhdl.core.{StepBlock, DFIf, DFBool, DFUnit} + val step = StepBlock.forced(using dfc.setName(stepName)) + dfc.enterOwner(step) + val wbGuard = wb.guardRef.get + if (wb.isFallThrough) + val fallThrough = StepBlock.forced(using dfc.setName("fallThrough")) + dfc.enterOwner(fallThrough) + val clonedCond = !wbGuard.cloneAnonValueAndDepsHere.asValOf[DFBool] + dfhdl.core.DFVal.Alias.AsIs.ident(clonedCond)(using dfc.anonymize) + dfc.exitOwner() + end if + val cond = wbGuard.asValOf[DFBool] + val ifBlock = DFIf.Block(Some(cond), DFIf.Header(DFUnit)) + dfc.exitOwner() + // creating the else part of the while loop step block, to be applied when the while loop exits. + val elseDsn = new MetaDesign( + lastLoopMember, + Patch.Add.Config.After + ): + import dfhdl.core.DFIf + dfc.enterOwner(stepAndIfDsn.ifBlock) + ThisStep + dfc.exitOwner() + val elseBlock = DFIf.Block(None, stepAndIfDsn.ifBlock) + dfc.enterOwner(elseBlock) + NextStep + dfc.exitOwner() + enterStepBlock(wb, lastLoopMember, Some(elseDsn.patch._2)) + Some(stepAndIfDsn.patch) // onEntry/onExit/fallThrough blocks must NOT be renamed: DropRTProcess identifies them // by exact names "onEntry"/"onExit"/"fallThrough" via isOnEntry/isOnExit/isFallThrough. // They also must NOT affect the step-name counter of their enclosing scope. diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala index 3aceb164d..9111b616f 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala @@ -599,4 +599,20 @@ class DropRTWaitsSpec extends StageSpec(): |end Foo""".stripMargin ) } + test("empty loop") { + class Foo extends RTDesign: + process: + while (true) {} + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | process: + | def S_0: Step = + | if (true) ThisStep + | else NextStep + | end S_0 + |end Foo""".stripMargin + ) + } end DropRTWaitsSpec From 0064ba28da41622d02da8e06a8d9b83c76b930c5 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 04:40:13 +0200 Subject: [PATCH 043/115] enhance SanityCheck to validate ownership consistency in conditional block chains --- .../scala/dfhdl/compiler/stages/SanityCheck.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala index a38f79e9f..864ea8adc 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala @@ -41,6 +41,18 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: case m: DFDesignBlock if !m.isTop => if (!refTable.contains(m.ownerRef)) reportViolation(s"Missing owner ref for the member: $m") + // check that all blocks in a conditional chain share the same owner + case cb: DFConditional.Block => + cb.prevBlockOrHeaderRef.get match + case prevBlock: DFConditional.Block if prevBlock.getOwner != cb.getOwner => + reportViolation( + s"""|Conditional block chain has mismatched owners. + |Block: $cb + |Owner: ${cb.getOwner} + |Prev block: $prevBlock + |Prev owner: ${prevBlock.getOwner}""".stripMargin + ) + case _ => // check by-name selectors case pbns: DFVal.PortByNameSelect => val design = pbns.designInstRef.get From 7ed981d7c36c06a552a52a44292cc53a195519d1 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 04:40:46 +0200 Subject: [PATCH 044/115] enhance DropRTWaits to maintain ownership structure in if/else blocks and update tests to remove unnecessary end if statements --- .../dfhdl/compiler/stages/DropRTWaits.scala | 16 ++++++++++++++-- .../test/scala/StagesSpec/DropRTWaitsSpec.scala | 7 ------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala index b340a2f2c..70de538a2 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala @@ -260,7 +260,11 @@ case object DropRTWaits extends Stage: val wbGuard = wb.guardRef.get val cond = wbGuard.asValOf[DFBool] val ifBlock = DFIf.Block(Some(cond), DFIf.Header(DFUnit)) - dfc.exitOwner() + // Stay inside `step` while building the if/else structure so that both + // the if-true-branch and the else-branch are owned by `step`, not by the + // enclosing ProcessBlock. (Previously `dfc.exitOwner()` was called here, + // which caused elseBlock and its Goto(NextStep) to be created at process + // level, breaking getOwnerStepBlock in FlattenStepBlocks.) dfc.enterOwner(ifBlock) ThisStep dfc.exitOwner() @@ -268,6 +272,7 @@ case object DropRTWaits extends Stage: dfc.enterOwner(elseBlock) NextStep dfc.exitOwner() + dfc.exitOwner() Some(dsn.patch) // Non-empty loop body: the last member of the while loop is the exit member. case Some(lastLoopMember) => @@ -299,17 +304,24 @@ case object DropRTWaits extends Stage: dfc.enterOwner(stepAndIfDsn.ifBlock) ThisStep dfc.exitOwner() + // Enter `step` explicitly so that elseBlock is owned by `step` (sibling of + // ifBlock), not by `ifBlock`. Without this, FullReplacement(wb → ifBlock) + // would redirect elseBlock's ownerRef to ifBlock, nesting it inside the + // if-true branch instead of at the same level. + dfc.enterOwner(stepAndIfDsn.step) val elseBlock = DFIf.Block(None, stepAndIfDsn.ifBlock) dfc.enterOwner(elseBlock) NextStep dfc.exitOwner() + dfc.exitOwner() enterStepBlock(wb, lastLoopMember, Some(elseDsn.patch._2)) Some(stepAndIfDsn.patch) + end match // onEntry/onExit/fallThrough blocks must NOT be renamed: DropRTProcess identifies them // by exact names "onEntry"/"onExit"/"fallThrough" via isOnEntry/isOnExit/isFallThrough. // They also must NOT affect the step-name counter of their enclosing scope. case stepBlock: StepBlock if !stepBlock.isRegular => None - case stepBlock: StepBlock => + case stepBlock: StepBlock => val stepName = getStepName(stepBlock) val lastStepBlockMember = stepBlock.getVeryLastMember.get enterStepBlock(stepBlock, lastStepBlockMember, None) diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala index 9111b616f..7a93455c7 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala @@ -154,7 +154,6 @@ class DropRTWaitsSpec extends StageSpec(): | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep - | end if | end S_0 | waitCnt1.din := d"8'0" | x.din := !x @@ -230,7 +229,6 @@ class DropRTWaitsSpec extends StageSpec(): | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep - | end if | end S_0 | waitCnt1.din := d"8'0" | x.din := !x @@ -239,7 +237,6 @@ class DropRTWaitsSpec extends StageSpec(): | waitCnt2.din := waitCnt2 + d"8'1" | ThisStep | else NextStep - | end if | end S_1 | waitCnt2.din := d"8'0" | x.din := 1 @@ -278,7 +275,6 @@ class DropRTWaitsSpec extends StageSpec(): | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep - | end if | end S_0 | waitCnt1.din := d"8'0" | x.din := !x @@ -287,7 +283,6 @@ class DropRTWaitsSpec extends StageSpec(): | waitCnt2.din := waitCnt2 + d"8'1" | ThisStep | else NextStep - | end if | end S_1 | waitCnt2.din := d"8'0" | x.din := 1 @@ -323,7 +318,6 @@ class DropRTWaitsSpec extends StageSpec(): | waitCnt2.din := waitCnt2 + d"8'1" | ThisStep | else NextStep - | end if | end S_0_0 | waitCnt2.din := d"8'0" | x.din := !x @@ -419,7 +413,6 @@ class DropRTWaitsSpec extends StageSpec(): | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep - | end if | end S_1 | waitCnt1.din := d"8'0" | else From f7926836bddb26a7fbe83cd8633aa6d6f0a9a4b3 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 05:18:24 +0200 Subject: [PATCH 045/115] enhance SimplifyRTOps to refine for loop transformation rules and add tests for untouched cases --- .../dfhdl/compiler/stages/SimplifyRTOps.scala | 13 +++--- .../scala/StagesSpec/SimplifyRTOpsSpec.scala | 42 +++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala index 66a0b7a97..67b9ebdff 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala @@ -74,11 +74,11 @@ import scala.collection.mutable * * ==Rule 4: For loop → while loop with iterator== * - * `for` loops in RT processes are replaced by a `Int <> VAR.REG` iterator assigned to the range - * start before the loop, and a `while` loop whose body ends with the iterator increment. The - * comparison operator is `<` for `until` (exclusive) and `<=` for `to` (inclusive); for negative - * steps the operators are `>` and `>=` respectively. For loops tagged as `COMB_LOOP` or outside - * the RT domain are left unchanged. + * `for` loops that are inside an RT process are replaced by a `Int <> VAR.REG` iterator assigned + * to the range start before the loop, and a `while` loop whose body ends with the iterator + * increment. The comparison operator is `<` for `until` (exclusive) and `<=` for `to` (inclusive); + * for negative steps the operators are `>` and `>=` respectively. For loops tagged as `COMB_LOOP`, + * outside the RT domain, or outside a process block are left unchanged. * * Iterator naming: * - anonymous `for (i <- ...)`: iterator register keeps the original name (`i`) @@ -201,7 +201,8 @@ case object SimplifyRTOps extends Stage: end if // replace RT for loops with while loops + iterator VAR.REG + increment at end of body - case forBlock: DFLoop.DFForBlock if forBlock.isInRTDomain && !forBlock.isCombinational => + case forBlock: DFLoop.DFForBlock + if forBlock.isInRTDomain && !forBlock.isCombinational && forBlock.isInProcess => val iteratorDcl = forBlock.iteratorRef.get val range = forBlock.rangeRef.get val startBigInt: BigInt = range.startRef.get match diff --git a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala index dddd0d66d..323a601e9 100644 --- a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala @@ -225,6 +225,48 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): ) } + test("RT combinational for loop is untouched") { + class Foo extends RTDesign: + val x = Bits(4) <> OUT.REG + process: + x.din := all(0) + for (i <- 0 until 4) + COMB_LOOP + x(i).din := 1 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bits(4) <> OUT.REG + | process: + | x.din := h"0" + | for (i <- 0 until 4) + | COMB_LOOP + | x(i).din := 1 + | end for + |end Foo""".stripMargin + ) + } + + test("RT for loop outside a process is untouched") { + class Foo(val WIDTH: Int <> CONST = 4) extends RTDesign: + val r = Bits(WIDTH) <> OUT.REG init all(0) + for (i <- 0 until WIDTH) + r(i).din := 1 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo(val WIDTH: Int <> CONST = 4) extends RTDesign: + | val r = Bits(WIDTH) <> OUT.REG init b"0".repeat(WIDTH) + | for (i <- 0 until WIDTH) + | r(i).din := 1 + | end for + |end Foo""".stripMargin + ) + } + test("ED domain for loop is untouched") { class Foo extends EDDesign: val x = Bit <> OUT From a6aa29ec7c851c95fea3fd8230711c1323e0fee3 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 06:06:12 +0200 Subject: [PATCH 046/115] enhance DropUnreferencedAnons to remove unreferenced DFRange instances and update SanityCheck to report violations for anonymous ranges --- .../main/scala/dfhdl/compiler/stages/DropUnreferenced.scala | 3 ++- .../src/main/scala/dfhdl/compiler/stages/SanityCheck.scala | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropUnreferenced.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropUnreferenced.scala index 74171eaad..b8b42388a 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropUnreferenced.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropUnreferenced.scala @@ -25,7 +25,8 @@ case object DropUnreferencedAnons extends Stage, NoCheckStage: case Ident(_) => None case m: DFVal if m.isAnonymous && m.originMembers.isEmpty => Some(m -> Patch.Remove()) - case _ => None + case m: DFRange if m.originMembers.isEmpty => Some(m -> Patch.Remove()) + case _ => None } if (patchList.isEmpty) designDB else diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala index 864ea8adc..e15633837 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala @@ -102,6 +102,11 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: s"""|An anonymous value has no references. |Referenced value: $dfVal""".stripMargin ) + case range: DFRange if !skipAnonRefCheck && range.originMembers.isEmpty => + reportViolation( + s"""|An anonymous range has no references. + |Referenced range: $range""".stripMargin + ) case _ => end match } From d8a5ae2bdcc7cee77b5a5c0020bff8be37a7dda9 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 23 Mar 2026 06:23:58 +0200 Subject: [PATCH 047/115] fix caching of relative step goto calls --- .../src/main/scala/dfhdl/compiler/ir/DFMember.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index a34594d7f..0500efac1 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -108,7 +108,7 @@ end DFMember object DFMember: given ReadWriter[DFMember] = ReadWriter.merge( - summon[ReadWriter[DFMember.Empty.type]], + summon[ReadWriter[DFMember.Empty]], summon[ReadWriter[DFVal]], summon[ReadWriter[Statement]], summon[ReadWriter[DFInterfaceOwner]], @@ -161,6 +161,12 @@ object DFMember: lazy val getRefs: List[DFRef.TwoWayAny] = Nil def copyWithNewRefs(using RefGen): this.type = this case object Empty extends Empty: + given ReadWriter[Empty] = ReadWriter.merge( + summon[ReadWriter[Empty.type]], + summon[ReadWriter[Goto.ThisStep.type]], + summon[ReadWriter[Goto.NextStep.type]], + summon[ReadWriter[Goto.FirstStep.type]] + ) given ReadWriter[Empty.type] = macroRW sealed trait Named extends DFMember: @@ -1112,8 +1118,11 @@ end Goto object Goto: case object ThisStep extends DFMember.Empty + given ReadWriter[ThisStep.type] = macroRW case object NextStep extends DFMember.Empty + given ReadWriter[NextStep.type] = macroRW case object FirstStep extends DFMember.Empty + given ReadWriter[FirstStep.type] = macroRW type Ref = DFRef.TwoWay[StepBlock | ThisStep.type | NextStep.type | FirstStep.type, Goto] sealed trait DFOwner extends DFMember: From d845529150379fb9c6830907d0a8b069332ec47b Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 25 Mar 2026 05:11:44 +0200 Subject: [PATCH 048/115] wip params --- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 17 +----- .../scala/dfhdl/compiler/ir/DFMember.scala | 58 +++++++------------ .../main/scala/dfhdl/compiler/ir/DFRef.scala | 11 ++++ .../dfhdl/compiler/ir/HasRefCompare.scala | 11 ++++ core/src/main/scala/dfhdl/core/DFVal.scala | 5 +- core/src/main/scala/dfhdl/core/Design.scala | 11 ++-- .../src/main/scala/dfhdl/core/MutableDB.scala | 17 +++++- .../main/scala/dfhdl/core/r__For_Plugin.scala | 4 ++ .../dfhdl/tools/toolsCore/QuartusPrime.scala | 4 +- .../scala/dfhdl/tools/toolsCore/Vivado.scala | 4 +- .../scala/plugin/MetaContextPlacerPhase.scala | 20 ++++++- 11 files changed, 98 insertions(+), 64 deletions(-) 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 ed29b178e..cca11e561 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -1032,9 +1032,6 @@ final case class DB( membersNoGlobals.view.drop(1).flatMap { case _: PortByNameSelect => None case m => - val isDesignParam = m match - case _: DFVal.DesignParam => true - case _ => false m.getRefs.view.map(_.get).flatMap { // global values are ok to be referenced case dfVal: DFVal.CanBeGlobal if dfVal.isGlobal => None @@ -1050,17 +1047,9 @@ final case class DB( case refMember: DFDesignBlock => if (m.isMemberOf(refMember)) None else Some(refMember) - case refMember => - m match - // design parameters are expected to reference values from their parent design - // or from the same design for default parameter values - case dp: DFVal.DesignParam => - if (refMember.isSameOwnerDesignAs(dp) && dp.defaultRef.get == refMember) None - else if (m.isOneLevelBelow(refMember)) None - else Some(refMember) - // the rest must be in the same design - case _ if !refMember.isSameOwnerDesignAs(m) => Some(refMember) - case _ => None + // the rest must be in the same design + case refMember if !refMember.isSameOwnerDesignAs(m) => Some(refMember) + case _ => None }.map(m -> _) }.toList val errorMessages = problemReferences.map { (from, to) => diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index 0500efac1..21e47cfa0 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -223,8 +223,8 @@ sealed trait DFVal extends DFMember.Named: case DFVal.Alias.AsIs(dfType = dfType, relValRef = DFRef(relVal)) if dfType == relVal.dfType => stripAsIsAndDesignParam(relVal) - case DFVal.DesignParam(dfValRef = DFRef(dfVal)) => - stripAsIsAndDesignParam(dfVal) + case dp: DFVal.DesignParam => + stripAsIsAndDesignParam(dp.dfValRef.get) case _ => dfVal // TODO: maybe we need a better way to check equivalent expressions, with symbolic algebra comparison? // with such comparison, it is possible to simplify expressions at least in the common cases. @@ -451,13 +451,13 @@ object DFVal: final case class DesignParam( dfType: DFType, - dfValRef: DesignParam.Ref, defaultRef: DesignParam.DefaultRef, ownerRef: DFOwner.Ref, meta: Meta, tags: DFTags ) extends CanBeExpr derives ReadWriter: assert(!this.isAnonymous, "Design parameters cannot be anonymous.") + def dfValRef(using MemberGetSet): DFDesignBlock.ParamRef = getOwnerDesign.paramMap(getName) protected def protIsFullyAnonymous(using MemberGetSet): Boolean = false protected def protGetConstData(using MemberGetSet): Option[Any] = dfValRef.get.getConstData @@ -472,25 +472,21 @@ object DFVal: case _ => false protected[ir] def protIsSimilarTo(that: CanBeExpr)(using MemberGetSet): Boolean = that match - case that: DesignParam => - this.dfType.isSimilarTo(that.dfType) && - this.dfValRef.get.isSimilarTo(that.dfValRef.get) - case _ => false + case that: DesignParam => this.dfType.isSimilarTo(that.dfType) + case _ => false protected def setMeta(meta: Meta): this.type = copy(meta = meta).asInstanceOf[this.type] protected def setTags(tags: DFTags): this.type = copy(tags = tags).asInstanceOf[this.type] lazy val getRefs: List[DFRef.TwoWayAny] = - dfValRef :: defaultRef :: dfType.getRefs ++ meta.getRefs + defaultRef :: dfType.getRefs ++ meta.getRefs def updateDFType(dfType: DFType): this.type = copy(dfType = dfType).asInstanceOf[this.type] def copyWithNewRefs(using RefGen): this.type = copy( meta = meta.copyWithNewRefs, dfType = dfType.copyWithNewRefs, ownerRef = ownerRef.copyAsNewRef, - dfValRef = dfValRef.copyAsNewRef, defaultRef = defaultRef.copyAsNewRef ).asInstanceOf[this.type] end DesignParam object DesignParam: - type Ref = DFRef.TwoWay[DFVal, DesignParam] type DefaultRef = DFRef.TwoWay[DFVal | DFMember.Empty, DesignParam] final case class Special( @@ -544,11 +540,8 @@ object DFVal: protected def protGetConstData(using MemberGetSet): Option[Any] = None protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: Dcl => - val sameInit = - if (this.initRefList.length == that.initRefList.length) - this.initRefList.lazyZip(that.initRefList).forall(_ =~ _) - else false - this.dfType =~ that.dfType && this.modifier == that.modifier && sameInit && + this.dfType =~ that.dfType && this.modifier == that.modifier && + this.initRefList =~ that.initRefList && this.meta =~ that.meta && this.tags =~ that.tags case _ => false def initList(using MemberGetSet): List[DFVal] = initRefList.map(_.get) @@ -594,10 +587,7 @@ object DFVal: else Some(calcFuncData(dfType, op, argTypes, argData)) protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: Func => - this.dfType =~ that.dfType && this.op == that.op && - (this.args - .lazyZip(that.args) - .forall((l, r) => l =~ r)) && + this.dfType =~ that.dfType && this.op == that.op && this.args =~ that.args && this.meta =~ that.meta && this.tags =~ that.tags case _ => false // TODO: consider algebraic equivalence be added here @@ -1206,7 +1196,7 @@ object ProcessBlock: def copyWithNewRefs(using RefGen): this.type = this final case class List(refs: scala.List[DFVal.Ref]) extends Sensitivity: protected def `prot_=~`(that: Sensitivity)(using MemberGetSet): Boolean = that match - case that: List => this.refs.lazyZip(that.refs).forall(_ =~ _) + case that: List => this.refs =~ that.refs case _ => false lazy val getRefs: scala.List[DFRef.TwoWayAny] = refs def copyWithNewRefs(using RefGen): this.type = @@ -1305,9 +1295,8 @@ object DFConditional: final case class Alternative(list: List[Pattern]) extends Pattern: protected def `prot_=~`(that: Pattern)(using MemberGetSet): Boolean = that match - case that: Alternative => - this.list.lazyZip(that.list).forall(_ =~ _) - case _ => false + case that: Alternative => this.list =~ that.list + case _ => false lazy val getRefs: List[DFRef.TwoWayAny] = list.flatMap(_.getRefs) def copyWithNewRefs(using RefGen): this.type = copy( list.map(_.copyWithNewRefs) @@ -1315,12 +1304,8 @@ object DFConditional: final case class Struct(name: String, fieldPatterns: List[Pattern]) extends Pattern: protected def `prot_=~`(that: Pattern)(using MemberGetSet): Boolean = that match - case that: Struct => - this.name == that.name && - this.fieldPatterns - .lazyZip(that.fieldPatterns) - .forall(_ =~ _) - case _ => false + case that: Struct => this.name == that.name && this.fieldPatterns =~ that.fieldPatterns + case _ => false lazy val getRefs: List[DFRef.TwoWayAny] = fieldPatterns.flatMap(_.getRefs) def copyWithNewRefs(using RefGen): this.type = copy( fieldPatterns = fieldPatterns.map(_.copyWithNewRefs) @@ -1356,10 +1341,7 @@ object DFConditional: protected def `prot_=~`(that: Pattern)(using MemberGetSet): Boolean = that match case that: BindSI => - this.op == that.op && this.parts == that.parts && - this.refs - .lazyZip(that.refs) - .forall(_ =~ _) + this.op == that.op && this.parts == that.parts && this.refs =~ that.refs case _ => false lazy val getRefs: List[DFRef.TwoWayAny] = refs def copyWithNewRefs(using RefGen): this.type = copy( @@ -1485,6 +1467,7 @@ final case class DFDesignBlock( domainType: DomainType, dclMeta: Meta, instMode: DFDesignBlock.InstMode, + paramMap: ListMap[String, DFDesignBlock.ParamRef], ownerRef: DFOwner.Ref, meta: Meta, tags: DFTags @@ -1496,11 +1479,13 @@ final case class DFDesignBlock( this.domainType =~ that.domainType && this.dclMeta =~ that.dclMeta && this.instMode == that.instMode && + this.paramMap =~ that.paramMap && this.meta =~ that.meta && this.tags =~ that.tags case _ => false protected def setMeta(meta: Meta): this.type = copy(meta = meta).asInstanceOf[this.type] protected def setTags(tags: DFTags): this.type = copy(tags = tags).asInstanceOf[this.type] - lazy val getRefs: List[DFRef.TwoWayAny] = domainType.getRefs ++ dclMeta.getRefs + lazy val getRefs: List[DFRef.TwoWayAny] = + domainType.getRefs ++ dclMeta.getRefs ++ paramMap.values.toList /** Whether this design is considered to be a device's top-level design. THIS MAY NOT BE THE TOP * DESIGN, for example if the design is in a simulation. A design is considered to be a device @@ -1515,11 +1500,13 @@ final case class DFDesignBlock( meta = meta.copyWithNewRefs, dclMeta = dclMeta.copyWithNewRefs, domainType = domainType.copyWithNewRefs, + paramMap = paramMap.map((k, v) => k -> v.copyAsNewRef), ownerRef = ownerRef.copyAsNewRef ).asInstanceOf[this.type] end DFDesignBlock object DFDesignBlock: + type ParamRef = DFRef.TwoWay[DFVal, DFDesignBlock] import InstMode.BlackBox.Source enum InstMode derives CanEqual, ReadWriter: case Normal, Def, Simulation @@ -1700,8 +1687,7 @@ final case class TextOut( ) extends Statement: protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: TextOut => - this.op =~ that.op && this.msgParts == that.msgParts && - this.msgArgs.lazyZip(that.msgArgs).forall(_ =~ _) && + this.op =~ that.op && this.msgParts == that.msgParts && this.msgArgs =~ that.msgArgs && this.meta =~ that.meta && this.tags =~ that.tags case _ => false protected def setMeta(meta: Meta): this.type = copy(meta = meta).asInstanceOf[this.type] diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala index 091438f1b..1f92dbaf7 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala @@ -3,6 +3,7 @@ import scala.annotation.unchecked.uncheckedVariance import dfhdl.internals.hashString import upickle.default.* import scala.collection.mutable +import scala.collection.immutable.ListMap type DFRefAny = DFRef[DFMember] sealed trait DFRef[+M <: DFMember] extends Product, Serializable derives CanEqual: @@ -40,6 +41,16 @@ object DFRef: override def copyAsNewRef(using refGen: RefGen): this.type = refGen.genTypeRef.asInstanceOf[this.type] + extension (list: List[DFRefAny]) + def =~(that: List[DFRefAny])(using MemberGetSet): Boolean = + list.length == that.length && list.lazyZip(that).forall(_ =~ _) + + extension (list: ListMap[String, DFRefAny]) + def =~(that: ListMap[String, DFRefAny])(using MemberGetSet): Boolean = + list.size == that.size && list.lazyZip(that).forall { + case ((k1, v1), (k2, v2)) => k1 == k2 && v1 =~ v2 + } + extension (ref: DFRefAny) def isTypeRef: Boolean = ref match case ref: TypeRef => true diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/HasRefCompare.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/HasRefCompare.scala index 5dfff9183..3750a2d02 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/HasRefCompare.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/HasRefCompare.scala @@ -1,4 +1,5 @@ package dfhdl.compiler.ir +import scala.collection.immutable.ListMap trait HasRefCompare[T <: HasRefCompare[T]]: private var cachedCompare: Option[(T, Boolean)] = None @@ -12,3 +13,13 @@ trait HasRefCompare[T <: HasRefCompare[T]]: protected def `prot_=~`(that: T)(using MemberGetSet): Boolean lazy val getRefs: List[DFRef.TwoWayAny] def copyWithNewRefs(using RefGen): this.type + +object HasRefCompare: + extension [T <: HasRefCompare[T]](list: List[T]) + def =~(that: List[T])(using MemberGetSet): Boolean = + list.length == that.length && list.lazyZip(that).forall(_ =~ _) + extension [T <: HasRefCompare[T]](list: ListMap[String, T]) + def =~(that: ListMap[String, T])(using MemberGetSet): Boolean = + list.size == that.size && list.lazyZip(that).forall { + case ((k1, v1), (k2, v2)) => k1 == k2 && v1 =~ v2 + } diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index a2c30e686..1455480be 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -649,7 +649,6 @@ object DFVal extends DFValLP: val alias: ir.DFVal.DesignParam = ir.DFVal.DesignParam( dfVal.asIR.dfType.dropUnreachableRefs, - dfVal.asIR.refTW[ir.DFVal.DesignParam](knownReachable = true), default.map(_.asIR.refTW[ir.DFVal.DesignParam]) .getOrElse(ir.DFMember.Empty.refTW[ir.DFVal.DesignParam]), dfc.ownerOrEmptyRef, @@ -1869,8 +1868,8 @@ extension (dfVal: ir.DFVal) else dfVal match // design parameter, so recurse on the referenced value - case ir.DFVal.DesignParam(dfValRef = ir.DFRef(of)) => - of.cloneUnreachable + case dp: ir.DFVal.DesignParam => + dp.dfValRef.get.cloneUnreachable // named constant, so clone under a new name within relation to the current design case _ => val newMeta = diff --git a/core/src/main/scala/dfhdl/core/Design.scala b/core/src/main/scala/dfhdl/core/Design.scala index 2127c2bff..8d5e45c1f 100644 --- a/core/src/main/scala/dfhdl/core/Design.scala +++ b/core/src/main/scala/dfhdl/core/Design.scala @@ -152,11 +152,14 @@ object Design: type Block = DFOwner[ir.DFDesignBlock] object Block: def apply(domain: ir.DomainType, dclMeta: ir.Meta, instMode: InstMode)(using DFC): Block = - ir.DFDesignBlock( - domain, dclMeta, instMode, dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags + val paramMap = ListMap.from( + dfc.mutableDB.DesignContext.getDesignParamValueMap.view.mapValues( + _.asIR.refTW[ir.DFDesignBlock] + ) ) - .addMember - .asFE + ir.DFDesignBlock( + domain, dclMeta, instMode, paramMap, dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags + ).addMember.asFE end apply end Block extension [D <: Design](dsn: D) diff --git a/core/src/main/scala/dfhdl/core/MutableDB.scala b/core/src/main/scala/dfhdl/core/MutableDB.scala index 4bc6dc3c4..b77524161 100644 --- a/core/src/main/scala/dfhdl/core/MutableDB.scala +++ b/core/src/main/scala/dfhdl/core/MutableDB.scala @@ -28,6 +28,7 @@ import dfhdl.compiler.analysis.filterPublicMembers import scala.reflect.{ClassTag, classTag} import collection.mutable +import collection.immutable.ListMap private case class MemberEntry( irValue: DFMember, @@ -168,6 +169,17 @@ final class MutableDB(): var stack = List.empty[DesignContext] val designMembers = mutable.Map.empty[DFDesignBlock, List[DFMember]] val uniqueDesigns = mutable.Map.empty[String, List[List[DFDesignBlock]]] + + // for design parameters we save them via the plugin before the design is elaborated, and then + // construct the design block with the parameters referenced in its paramMap. + private var designParamValueMap = ListMap.empty[String, DFValAny] + def prepareDesignParamValues(paramNames: List[String], paramValues: List[DFValAny]): Unit = + designParamValueMap = ListMap.from(paramNames.lazyZip(paramValues)) + def getDesignParamValueMap: ListMap[String, DFValAny] = + val ret = designParamValueMap + designParamValueMap = ListMap.empty + ret + def startDesign(design: DFDesignBlock): Unit = stack = current :: stack current = new DesignContext @@ -373,8 +385,9 @@ final class MutableDB(): else resource.allSigConstraints } // merge the existing constraints with the new constraints - val updatedSigConstraints = - (existingSigConstraints ++ newSigConstraints).merge.consolidate(dcl.width) + val updatedSigConstraints = (existingSigConstraints ++ newSigConstraints).merge.consolidate( + dcl.width + ) // merge all other annotations val updatedAnnotations = updatedSigConstraints ++ otherAnnotations dcl -> dcl.copy(meta = dcl.meta.copy(annotations = updatedAnnotations)) diff --git a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala index 8919bdd3e..808a2d1d8 100644 --- a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala +++ b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala @@ -113,6 +113,10 @@ object r__For_Plugin: paramValue.asIR, DFVal.DesignParam(paramValue, default)(using dfc.setMeta(paramMeta)).asIR ).asValAny.asInstanceOf[V] + def prepareDesignParamValues(paramNames: List[String], paramValues: List[DFValAny])(using + DFC + ): Unit = + dfc.mutableDB.DesignContext.prepareDesignParamValues(paramNames, paramValues) @metaContextIgnore def designFromDefGetInput[V <: DFValAny](idx: Int)(using DFC): V = dfc.mutableDB.DesignContext.getDefInput(idx).asInstanceOf[V] diff --git a/lib/src/main/scala/dfhdl/tools/toolsCore/QuartusPrime.scala b/lib/src/main/scala/dfhdl/tools/toolsCore/QuartusPrime.scala index e2f409ad7..3cbe79ca6 100644 --- a/lib/src/main/scala/dfhdl/tools/toolsCore/QuartusPrime.scala +++ b/lib/src/main/scala/dfhdl/tools/toolsCore/QuartusPrime.scala @@ -325,11 +325,11 @@ class QuartusPrimeIPPrinter(using val members = qsysIP.members(MemberView.Folded) val ipVersion = members.collectFirst { case param: DFVal.DesignParam if param.getName == "version" => - " " + param.dfValRef.get.getConstData.get.asInstanceOf[Option[String]].get + " " + param.getConstData.get.asInstanceOf[Option[String]].get }.getOrElse("") val ipParams = members.collect { case param: DFVal.DesignParam if param.getName != "version" => - s"set_instance_parameter_value $ipInstanceName {${param.getName}} {${param.dfValRef.get.getConstData.get.asInstanceOf[Option[Any]].get}}" + s"set_instance_parameter_value $ipInstanceName {${param.getName}} {${param.getConstData.get.asInstanceOf[Option[Any]].get}}" }.mkString("\n") val ipExports = members.collect { case port @ DclPort() => diff --git a/lib/src/main/scala/dfhdl/tools/toolsCore/Vivado.scala b/lib/src/main/scala/dfhdl/tools/toolsCore/Vivado.scala index 86d4fdc2c..a4ead792c 100644 --- a/lib/src/main/scala/dfhdl/tools/toolsCore/Vivado.scala +++ b/lib/src/main/scala/dfhdl/tools/toolsCore/Vivado.scala @@ -331,7 +331,7 @@ class VivadoIPPrinter(using val members = vivadoIP.members(MemberView.Folded) val ipVersion = members.collectFirst { case param: DFVal.DesignParam if param.getName == "version" => - val version = param.dfValRef.get.getConstData.get.asInstanceOf[Option[String]].get + val version = param.getConstData.get.asInstanceOf[Option[String]].get if (version.nonEmpty) Some(" -version " + version) else None }.flatten.getOrElse("") @@ -339,7 +339,7 @@ class VivadoIPPrinter(using // Each DFVal.DesignParam except "version" becomes a CONFIG. {value} line val ipConfigParams = members.collect { case param: DFVal.DesignParam if param.getName != "version" => - val value = param.dfValRef.get.getConstData.get.asInstanceOf[Option[Any]].get + val value = param.getConstData.get.asInstanceOf[Option[Any]].get s"CONFIG.${param.getName} {$value}" } val ipConfigBlock = diff --git a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala index 72ffa9038..ef2cb5bb7 100644 --- a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala @@ -51,6 +51,7 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: var listMapEmptySym: TermSymbol = uninitialized var listMapSym: TermSymbol = uninitialized var dfhdlDFValIdentSym: TermSymbol = uninitialized + var prepareDesignParamValuesSym: TermSymbol = uninitialized val defaultParamMap = mutable.Map.empty[ClassSymbol, Map[Int, Tree]] override def prepareForTypeDef(tree: TypeDef)(using Context): Context = val sym = tree.symbol @@ -269,8 +270,23 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: val parent = nameArgs(tree) val od = dfcOverrideDef(cls, tree.srcPos) val cdef = ClassDefWithParents(cls, DefDef(constr), List(parent), List(od)) + val prepareStatOpt: Option[Tree] = + if (tpe <:< designTpe) then + val allParams = + tpe.typeSymbol.primaryConstructor.paramSymss.flatten.filter(_.isTerm) + val dfValPairs = allParams.lazyZip(valDefs.reverse).collect { + case (sym, vd: ValDef) if sym.info.dfValTpeOpt.nonEmpty => + (Literal(Constant(sym.name.toString)): Tree, ref(vd.symbol)) + }.toList + if (dfValPairs.nonEmpty) then + val names = mkList(dfValPairs.map(_._1), Some(defn.StringType)) + val values = mkList(dfValPairs.map(_._2)) + val dfc = dfcArgStack.headOption.getOrElse(ref(emptyNoEODFCSym)) + Some(ref(prepareDesignParamValuesSym).appliedTo(names, values).appliedTo(dfc)) + else None + else None Block( - valDefs.reverse :+ cdef, + valDefs.reverse ++ prepareStatOpt.toList :+ cdef, Typed(New(Ident(cdef.namedType)).select(constr).appliedToNone, TypeTree(tpe)) ) case _ => tree @@ -333,6 +349,8 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: listMapEmptySym = requiredMethod("scala.collection.immutable.ListMap.empty") listMapSym = requiredModule("scala.collection.immutable.ListMap") dfhdlDFValIdentSym = requiredMethod("dfhdl.core.r__For_Plugin.identVal") + prepareDesignParamValuesSym = + requiredMethod("dfhdl.core.r__For_Plugin.prepareDesignParamValues") dfcArgStack = Nil defaultParamMap.clear() ctx From 8c076f03e38196a54a6db518966c6f5216bf104d Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 25 Mar 2026 11:08:42 +0200 Subject: [PATCH 049/115] design parameters partially working after claude unique workarounds. saving as a commit for posterity, but will likely revert to a different architecture --- .../scala/plugin/MetaContextGenPhase.scala | 27 ++- .../scala/plugin/MetaContextPlacerPhase.scala | 159 +++++++++++------- 2 files changed, 115 insertions(+), 71 deletions(-) diff --git a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala index 98f4e1c28..9c866d96c 100755 --- a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala @@ -33,6 +33,7 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: var treeOwnerApplyMap = Map.empty[Apply, (MemberDef, util.SrcPos)] var treeOwnerApplyMapStack = List.empty[Map[Apply, (MemberDef, util.SrcPos)]] val treeOwnerOverrideMap = mutable.Map.empty[DefDef, (Tree, util.SrcPos)] + val corruptedDFCDefMap = mutable.Map.empty[DefDef, (Symbol, util.SrcPos)] val contextDefs = mutable.Map.empty[String, Tree] var clsStack = List.empty[TypeDef] var applyStack = List.empty[Apply] @@ -192,11 +193,13 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: case _ :+ (cls @ TypeDef(_, template: Template)) if cls.symbol.isAnonymousClass => template.parents.collectFirst { case p: Apply => template.body.collectFirst { - case dd: DefDef - if dd.symbol.is(Override) && dd.symbol.name.toString == "__dfc" && - dd.tpt.tpe.isMetaContext => - if (!treeOwnerOverrideMap.contains(dd)) - treeOwnerOverrideMap += (dd -> (EmptyTree, p.srcPos)) + case dd: DefDef if dd.symbol.name.toString == "__dfc" && dd.tpt.tpe.isMetaContext => + if (dd.symbol.is(Override)) + if (!treeOwnerOverrideMap.contains(dd)) + treeOwnerOverrideMap += (dd -> (EmptyTree, p.srcPos)) + else if (cls.symbol.typeRef <:< hasDFCTpe) + if (!corruptedDFCDefMap.contains(dd)) + corruptedDFCDefMap += (dd -> (cls.symbol, p.srcPos)) } } case _ => @@ -216,7 +219,18 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: cpy.DefDef(tree)(rhs = tree.rhs.setMeta(None, srcPos, None, Nil)) else tree case None => tree - else dropProxies(tree) + else + corruptedDFCDefMap.get(tree) match + case Some(ownerCls, srcPos) => + val newSym = newSymbol( + ownerCls, + "__dfc".toTermName, + Override | Protected | Method | Touched, + sym.info + ) + DefDef(newSym, tree.rhs.setMeta(None, srcPos, None, Nil)) + case None => + dropProxies(tree) else tree end transformDefDef @@ -512,6 +526,7 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: setMetaSym = metaContextCls.requiredMethod("setMeta") setMetaAnonSym = metaContextCls.requiredMethod("setMetaAnon") treeOwnerOverrideMap.clear() + corruptedDFCDefMap.clear() contextDefs.clear() ctx end prepareForUnit diff --git a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala index ef2cb5bb7..b00b1caba 100644 --- a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala @@ -206,34 +206,79 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: case _ => tree - private def dfcOverrideDef(owner: Symbol, treeSrcPos: util.SrcPos)(using Context): Tree = - val sym = - newSymbol(owner, "__dfc".toTermName, Override | Protected | Method | Touched, dfcTpe) + private def dfcGen(owner: Symbol, treeSrcPos: util.SrcPos)(using + Context + ): (tree: Tree, valDefOpt: Option[ValDef]) = // getting DFC context from the stack or need to generate an empty one // with elaboration options found in the @top annotation - val dfcArg = dfcArgStack.headOption.getOrElse { - owner.getAnnotation(topAnnotSym).map(a => dropProxies(a.tree)) match - // found top annotation - case Some(Apply(Apply(Apply(_, _), _), topElaborationOptionsTree :: _)) => - ref(emptyDFCSym).appliedTo(topElaborationOptionsTree) - // no top - case _ => - var currentOwner = owner.owner - while (currentOwner != NoSymbol && !(currentOwner.typeRef <:< noTopAnnotIsRequired)) - currentOwner = currentOwner.owner - // no top, but if has an owner that extends `NoTopAnnotIsRequired`, - // we generate new context with default elaboration options - if (currentOwner.typeRef <:< noTopAnnotIsRequired) ref(emptyNoEODFCSym) - else - report.error( - "Missing `@top` annotation for this design to be instantiated as a top-level design.", - treeSrcPos - ) - EmptyTree - } - DefDef(sym, dfcArg) + dfcArgStack.headOption match + case Some(dfcTree) => (dfcTree, None) + case None => + val value = owner.getAnnotation(topAnnotSym).map(a => dropProxies(a.tree)) match + // found top annotation + case Some(Apply(Apply(Apply(_, _), _), topElaborationOptionsTree :: _)) => + ref(emptyDFCSym).appliedTo(topElaborationOptionsTree) + // no top + case _ => + var currentOwner = owner.owner + while (currentOwner != NoSymbol && !(currentOwner.typeRef <:< noTopAnnotIsRequired)) + currentOwner = currentOwner.owner + // no top, but if has an owner that extends `NoTopAnnotIsRequired`, + // we generate new context with default elaboration options + if (currentOwner.typeRef <:< noTopAnnotIsRequired) ref(emptyNoEODFCSym) + else + report.error( + "Missing `@top` annotation for this design to be instantiated as a top-level design.", + treeSrcPos + ) + EmptyTree + val flags: FlagSet = if (ctx.owner.isConstructor) Private else EmptyFlags + val valDef = SyntheticValDef("dfcGen_plugin".toTermName, value, flags) + (ref(valDef.symbol), Some(valDef)) + end dfcGen + + private def dfcOverrideDef(owner: Symbol, dfcTree: Tree)(using Context): Tree = + val sym = + newSymbol(owner, "__dfc".toTermName, Override | Protected | Method | Touched, dfcTpe) + DefDef(sym, dfcTree) end dfcOverrideDef + private def prepareDesignParamValuesTree(tree: Tree, dfcTree: Tree)(using + Context + ): (valDefs: List[ValDef], parent: Tree, prepareTreeOpt: Option[Tree]) = + val tpe = tree.tpe + var valDefs: List[ValDef] = Nil + // naming the arguments before extending the tree as as parent because + // otherwise ownership and references need to change. + def nameArgs(tree: Tree): Tree = + tree match + case Apply(fun, args) => + val updatedArgs = args.map { a => + val uniqueName = NameKinds.UniqueName.fresh(s"arg_plugin".toTermName) + val valDef = SyntheticValDef(uniqueName, a) + valDefs = valDef :: valDefs + ref(valDef.symbol) + } + Apply(nameArgs(fun), updatedArgs) + case _ => tree + val parent = nameArgs(tree) + val prepareTreeOpt = + if (tpe <:< designTpe) then + val allParams = + tpe.typeSymbol.primaryConstructor.paramSymss.flatten.filter(_.isTerm) + val dfValPairs = allParams.lazyZip(valDefs.reverse).collect { + case (sym, vd: ValDef) if sym.info.dfValTpeOpt.nonEmpty => + (Literal(Constant(sym.name.toString)): Tree, ref(vd.symbol)) + }.toList + if (dfValPairs.nonEmpty) then + val names = mkList(dfValPairs.map(_._1), Some(defn.StringType)) + val values = mkList(dfValPairs.map(_._2)) + Some(ref(prepareDesignParamValuesSym).appliedTo(names, values).appliedTo(dfcTree)) + else None + else None + (valDefs, parent, prepareTreeOpt) + end prepareDesignParamValuesTree + override def transformApply(tree: Apply)(using Context): Tree = val tpe = tree.tpe tree match @@ -253,63 +298,47 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: ) cls.addAnnotations(tpe.typeSymbol.annotations) val constr = newConstructor(cls, Synthetic, Nil, Nil).entered - var valDefs: List[ValDef] = Nil - // naming the arguments before extending the tree as as parent because - // otherwise ownership and references need to change. - def nameArgs(tree: Tree): Tree = - tree match - case Apply(fun, args) => - val updatedArgs = args.map { a => - val uniqueName = NameKinds.UniqueName.fresh(s"arg_plugin".toTermName) - val valDef = SyntheticValDef(uniqueName, a) - valDefs = valDef :: valDefs - ref(valDef.symbol) - } - Apply(nameArgs(fun), updatedArgs) - case _ => tree - val parent = nameArgs(tree) - val od = dfcOverrideDef(cls, tree.srcPos) + val dfc = dfcGen(cls, tree.srcPos) + val od = dfcOverrideDef(cls, dfc.tree) + val origSym = tpe.typeSymbol + val (valDefs, parent, prepareOpt) = prepareDesignParamValuesTree(tree, dfc.tree) val cdef = ClassDefWithParents(cls, DefDef(constr), List(parent), List(od)) - val prepareStatOpt: Option[Tree] = - if (tpe <:< designTpe) then - val allParams = - tpe.typeSymbol.primaryConstructor.paramSymss.flatten.filter(_.isTerm) - val dfValPairs = allParams.lazyZip(valDefs.reverse).collect { - case (sym, vd: ValDef) if sym.info.dfValTpeOpt.nonEmpty => - (Literal(Constant(sym.name.toString)): Tree, ref(vd.symbol)) - }.toList - if (dfValPairs.nonEmpty) then - val names = mkList(dfValPairs.map(_._1), Some(defn.StringType)) - val values = mkList(dfValPairs.map(_._2)) - val dfc = dfcArgStack.headOption.getOrElse(ref(emptyNoEODFCSym)) - Some(ref(prepareDesignParamValuesSym).appliedTo(names, values).appliedTo(dfc)) - else None - else None Block( - valDefs.reverse ++ prepareStatOpt.toList :+ cdef, + valDefs.reverse ++ dfc.valDefOpt ++ prepareOpt :+ cdef, Typed(New(Ident(cdef.namedType)).select(constr).appliedToNone, TypeTree(tpe)) ) case _ => tree end match end transformApply - override def transformBlock(tree: Block)(using Context): tpd.Tree = - tree match + override def transformBlock(block: Block)(using Context): tpd.Tree = + block match case Block( List(td @ TypeDef(tn, template: Template)), - Typed(apply @ Apply(fun, _), _) - ) if tree.tpe.typeConstructor <:< hasDFCTpe => + Typed(apply: Apply, tpt) + ) if block.tpe.typeConstructor <:< hasDFCTpe => val hasDFCOverride = template.body.exists { case dd: DefDef if dd.name.toString == "__dfc" => true case _ => false } - if (hasDFCOverride) tree + if (hasDFCOverride) block else - val od = dfcOverrideDef(td.symbol, tree.srcPos) - val updatedTemplate = cpy.Template(template)(body = od :: template.body) + val dfc = dfcGen(td.symbol, block.srcPos) + val od = dfcOverrideDef(td.symbol, dfc.tree) + val (valDefs, parent, prepareOpt) = + prepareDesignParamValuesTree( + template.parents.head.changeOwner(template.constr.symbol, ctx.owner), + dfc.tree + ) + val updatedTemplate = + cpy.Template(template)(body = od :: template.body, parents = List(parent)) val updatedTypeDef = cpy.TypeDef(td)(rhs = updatedTemplate) - cpy.Block(tree)(stats = List(updatedTypeDef), expr = tree.expr) + Block( + valDefs.reverse ++ dfc.valDefOpt ++ prepareOpt :+ updatedTypeDef, + block.expr + ) + end if case _ => - tree + block // transform basic val x = y to val x = dfhdl.core.r__For_Plugin.identVal(y) if y is a DFVal override def transformValDef(tree: ValDef)(using Context): ValDef = object DFValIdent: From 6624d5f5328849338a9a8069540beb3c8f84a71e Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 26 Mar 2026 01:38:40 +0200 Subject: [PATCH 050/115] new runtime solution for design parameters. still some problematic stages --- .../scala/dfhdl/compiler/ir/DFMember.scala | 18 ++- .../dfhdl/compiler/stages/SanityCheck.scala | 1 + .../StagesSpec/PrintCodeStringSpec.scala | 6 +- core/src/main/scala/dfhdl/core/DFVal.scala | 4 +- core/src/main/scala/dfhdl/core/Design.scala | 14 ++ .../src/main/scala/dfhdl/core/MutableDB.scala | 16 +- .../main/scala/dfhdl/core/r__For_Plugin.scala | 1 + .../scala/plugin/MetaContextGenPhase.scala | 39 ++--- .../scala/plugin/MetaContextPlacerPhase.scala | 147 ++++++------------ 9 files changed, 108 insertions(+), 138 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index 21e47cfa0..644b862dc 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -224,7 +224,7 @@ sealed trait DFVal extends DFMember.Named: if dfType == relVal.dfType => stripAsIsAndDesignParam(relVal) case dp: DFVal.DesignParam => - stripAsIsAndDesignParam(dp.dfValRef.get) + stripAsIsAndDesignParam(dp.dfVal) case _ => dfVal // TODO: maybe we need a better way to check equivalent expressions, with symbolic algebra comparison? // with such comparison, it is possible to simplify expressions at least in the common cases. @@ -457,10 +457,20 @@ object DFVal: tags: DFTags ) extends CanBeExpr derives ReadWriter: assert(!this.isAnonymous, "Design parameters cannot be anonymous.") - def dfValRef(using MemberGetSet): DFDesignBlock.ParamRef = getOwnerDesign.paramMap(getName) + // the value will be cached during elaboration, because the reference via the design's paramMap + // will not be available until the design is fully elaborated. during initial contruction and mutation, + // the value will be cached in core.DFVal.DesignParam, and later cleared in core.Design + private var cachedVal: Option[DFVal] = None + protected[compiler] def dfValRef(using MemberGetSet): DFDesignBlock.ParamRef = + getOwnerDesign.paramMap(getName) + def dfVal(using MemberGetSet): DFVal = + if (getSet.isMutable) cachedVal.getOrElse(dfValRef.get) + else dfValRef.get + protected[dfhdl] def setCachedVal(dfVal: DFVal): Unit = + cachedVal = Some(dfVal) + protected[dfhdl] def clearCachedVal(): Unit = cachedVal = None protected def protIsFullyAnonymous(using MemberGetSet): Boolean = false - protected def protGetConstData(using MemberGetSet): Option[Any] = - dfValRef.get.getConstData + protected def protGetConstData(using MemberGetSet): Option[Any] = dfVal.getConstData protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: DesignParam => // design parameters are considered to be the same even if they are referencing diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala index e15633837..7fd8f26bf 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala @@ -156,6 +156,7 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: originMember match case originVal: DFVal if originVal.isGlobal => case _: DFVal.DesignParam => + case _: DFDesignBlock => // paramMap entries may reference global anon vals case _ => reportViolation( s"""|A global anonymous member is referenced by a non-global member. diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index 6be08d000..baed9341f 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -218,10 +218,10 @@ class PrintCodeStringSpec extends StageSpec: val id = (new IDExt(gp)).getCodeString assertNoDiff( id, - """|val gp: Bit <> CONST = 1 - |val gp2: Bit <> CONST = gp - |val i: SInt[16] <> CONST = sd"16'0" + """|val i: SInt[16] <> CONST = sd"16'0" |val i2: SInt[16] <> CONST = i + sd"16'5" + |val gp: Bit <> CONST = 1 + |val gp2: Bit <> CONST = gp | |class IDExt( | val dp: Bit <> CONST = gp2 && gp, diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index 1455480be..73ba959a3 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -642,6 +642,7 @@ object DFVal extends DFValLP: end Const object DesignParam: + // Note: in meta-programming, the user needs to manually set the Design's paramMap. def apply[T <: DFTypeAny]( dfVal: DFValOf[T], default: Option[DFValOf[T]] = None @@ -655,6 +656,7 @@ object DFVal extends DFValLP: dfc.getMeta, dfc.tags ) + if (!dfc.inMetaProgramming) alias.setCachedVal(dfVal.asIR) alias.addMember.asConstOf[T] end apply end DesignParam @@ -1869,7 +1871,7 @@ extension (dfVal: ir.DFVal) dfVal match // design parameter, so recurse on the referenced value case dp: ir.DFVal.DesignParam => - dp.dfValRef.get.cloneUnreachable + dp.dfVal.cloneUnreachable // named constant, so clone under a new name within relation to the current design case _ => val newMeta = diff --git a/core/src/main/scala/dfhdl/core/Design.scala b/core/src/main/scala/dfhdl/core/Design.scala index 8d5e45c1f..00438fa1c 100644 --- a/core/src/main/scala/dfhdl/core/Design.scala +++ b/core/src/main/scala/dfhdl/core/Design.scala @@ -54,6 +54,7 @@ trait Design extends Container, HasClsMetaArgs: final override def onCreateStartLate: Unit = hasStartedLate = true import dfc.getSet + Design.Block.updateWithParams(containedOwner.asIR) if (dfc.owner.asIR.getThisOrOwnerDesign.isDeviceTop) handleResourceConstraints() dfc.mutableDB.ResourceOwnershipContext.emptyTopResourceOwners() @@ -161,6 +162,19 @@ object Design: domain, dclMeta, instMode, paramMap, dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags ).addMember.asFE end apply + protected[core] def updateWithParams(designBlock: ir.DFDesignBlock)(using dfc: DFC): Unit = + import dfc.getSet + val paramMap = + ListMap.from( + dfc.mutableDB.DesignContext.current.getImmutableMemberList.view.collect { + case dp: ir.DFVal.DesignParam => + val dfVal = dp.dfVal + // invalidating the param cache value after design elaboration + dp.clearCachedVal() + dp.getName -> dfVal.refTW[ir.DFDesignBlock](knownReachable = true) + }.toMap + ) + getSet.replace(designBlock)(designBlock.copy(paramMap = paramMap)) end Block extension [D <: Design](dsn: D) def getDB: ir.DB = dsn.dfc.mutableDB.immutable diff --git a/core/src/main/scala/dfhdl/core/MutableDB.scala b/core/src/main/scala/dfhdl/core/MutableDB.scala index b77524161..152f3531a 100644 --- a/core/src/main/scala/dfhdl/core/MutableDB.scala +++ b/core/src/main/scala/dfhdl/core/MutableDB.scala @@ -190,10 +190,7 @@ final class MutableDB(): var isDuplicate = false def sameDesignAs(groupDesign: DFDesignBlock): Boolean = if (design.dclMeta == groupDesign.dclMeta) - val groupMembers = designMembers(groupDesign) - if (currentMembers.length == groupMembers.length) - (currentMembers lazyZip groupMembers).forall { case (l, r) => l =~ r } - else false + currentMembers =~ designMembers(groupDesign) else false uniqueDesigns.get(designType) match // this design type already exists and has at least one group @@ -224,10 +221,13 @@ final class MutableDB(): // so they are kept as duplicates in the design instances val publicMembers = currentMembers.filterPublicMembers designMembers += design -> publicMembers - val transferredRefs = publicMembers.view.flatMap(m => - (m.ownerRef -> currentRefTable(m.ownerRef)) :: - m.getRefs.map(r => r -> currentRefTable(r)) - ) + val transferredRefs = + // getting the design references to parameters + design.getRefs.map(r => r -> currentRefTable(r)) ++ + publicMembers.view.flatMap(m => + (m.ownerRef -> currentRefTable(m.ownerRef)) :: + m.getRefs.map(r => r -> currentRefTable(r)) + ) stack.head.refTable ++= transferredRefs else designMembers += design -> currentMembers diff --git a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala index 808a2d1d8..d113d315d 100644 --- a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala +++ b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala @@ -139,6 +139,7 @@ object r__For_Plugin: } val (isDuplicate, ret): (Boolean, V) = dfc.mutableDB.DesignContext.runFuncWithInputs(func, inputs) + Design.Block.updateWithParams(designBlock.asIR) def exitAndConnectInputs() = dfc.exitOwner() inputs.lazyZip(args).foreach { case (input, (arg, _)) => diff --git a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala index 9c866d96c..00d84d7c8 100755 --- a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala @@ -33,7 +33,6 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: var treeOwnerApplyMap = Map.empty[Apply, (MemberDef, util.SrcPos)] var treeOwnerApplyMapStack = List.empty[Map[Apply, (MemberDef, util.SrcPos)]] val treeOwnerOverrideMap = mutable.Map.empty[DefDef, (Tree, util.SrcPos)] - val corruptedDFCDefMap = mutable.Map.empty[DefDef, (Symbol, util.SrcPos)] val contextDefs = mutable.Map.empty[String, Tree] var clsStack = List.empty[TypeDef] var applyStack = List.empty[Apply] @@ -154,7 +153,8 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: val origApply = applyStack.head applyStack = applyStack.drop(1) if ( - fixedApply.tpe.isParameterless && !fixedApply.fun.symbol.ignoreMetaContext && !fixedApply.fun.symbol.forwardMetaContext + fixedApply.tpe.isParameterless && !fixedApply.fun.symbol.ignoreMetaContext && + !fixedApply.fun.symbol.forwardMetaContext ) fixedApply match // found a context argument @@ -193,13 +193,11 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: case _ :+ (cls @ TypeDef(_, template: Template)) if cls.symbol.isAnonymousClass => template.parents.collectFirst { case p: Apply => template.body.collectFirst { - case dd: DefDef if dd.symbol.name.toString == "__dfc" && dd.tpt.tpe.isMetaContext => - if (dd.symbol.is(Override)) - if (!treeOwnerOverrideMap.contains(dd)) - treeOwnerOverrideMap += (dd -> (EmptyTree, p.srcPos)) - else if (cls.symbol.typeRef <:< hasDFCTpe) - if (!corruptedDFCDefMap.contains(dd)) - corruptedDFCDefMap += (dd -> (cls.symbol, p.srcPos)) + case dd: DefDef + if dd.symbol.is(Override) && dd.symbol.name.toString == "__dfc" && + dd.tpt.tpe.isMetaContext => + if (!treeOwnerOverrideMap.contains(dd)) + treeOwnerOverrideMap += (dd -> (EmptyTree, p.srcPos)) } } case _ => @@ -219,18 +217,7 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: cpy.DefDef(tree)(rhs = tree.rhs.setMeta(None, srcPos, None, Nil)) else tree case None => tree - else - corruptedDFCDefMap.get(tree) match - case Some(ownerCls, srcPos) => - val newSym = newSymbol( - ownerCls, - "__dfc".toTermName, - Override | Protected | Method | Touched, - sym.info - ) - DefDef(newSym, tree.rhs.setMeta(None, srcPos, None, Nil)) - case None => - dropProxies(tree) + else dropProxies(tree) else tree end transformDefDef @@ -404,8 +391,10 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: tree match case Apply(Select(lhs, fun), List(rhs)) if (fun == nme.EQ || fun == nme.NE) && - (lhs.tpe <:< defn.IntType || lhs.tpe <:< defn.BooleanType || lhs.tpe <:< defn - .TupleTypeRef) => + (lhs.tpe <:< defn.IntType || lhs.tpe <:< defn.BooleanType || + lhs.tpe <:< + defn + .TupleTypeRef) => val rhsSym = rhs.tpe.dealias.typeSymbol if (rhsSym == dfValSym) report.error( @@ -413,7 +402,8 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: pos ) case Apply(Select(lhs, fun), List(Apply(Apply(Ident(hackName), _), _))) - if (fun == nme.ZOR || fun == nme.ZAND || fun == nme.XOR) && hackName.toString == "BooleanHack" => + if (fun == nme.ZOR || fun == nme.ZAND || fun == nme.XOR) && + hackName.toString == "BooleanHack" => report.error( s"Unsupported Scala Boolean primitive at the LHS of `$fun` with a DFHDL value.\nConsider switching positions of the arguments.", pos @@ -526,7 +516,6 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: setMetaSym = metaContextCls.requiredMethod("setMeta") setMetaAnonSym = metaContextCls.requiredMethod("setMetaAnon") treeOwnerOverrideMap.clear() - corruptedDFCDefMap.clear() contextDefs.clear() ctx end prepareForUnit diff --git a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala index b00b1caba..72ffa9038 100644 --- a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala @@ -51,7 +51,6 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: var listMapEmptySym: TermSymbol = uninitialized var listMapSym: TermSymbol = uninitialized var dfhdlDFValIdentSym: TermSymbol = uninitialized - var prepareDesignParamValuesSym: TermSymbol = uninitialized val defaultParamMap = mutable.Map.empty[ClassSymbol, Map[Int, Tree]] override def prepareForTypeDef(tree: TypeDef)(using Context): Context = val sym = tree.symbol @@ -206,79 +205,34 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: case _ => tree - private def dfcGen(owner: Symbol, treeSrcPos: util.SrcPos)(using - Context - ): (tree: Tree, valDefOpt: Option[ValDef]) = - // getting DFC context from the stack or need to generate an empty one - // with elaboration options found in the @top annotation - dfcArgStack.headOption match - case Some(dfcTree) => (dfcTree, None) - case None => - val value = owner.getAnnotation(topAnnotSym).map(a => dropProxies(a.tree)) match - // found top annotation - case Some(Apply(Apply(Apply(_, _), _), topElaborationOptionsTree :: _)) => - ref(emptyDFCSym).appliedTo(topElaborationOptionsTree) - // no top - case _ => - var currentOwner = owner.owner - while (currentOwner != NoSymbol && !(currentOwner.typeRef <:< noTopAnnotIsRequired)) - currentOwner = currentOwner.owner - // no top, but if has an owner that extends `NoTopAnnotIsRequired`, - // we generate new context with default elaboration options - if (currentOwner.typeRef <:< noTopAnnotIsRequired) ref(emptyNoEODFCSym) - else - report.error( - "Missing `@top` annotation for this design to be instantiated as a top-level design.", - treeSrcPos - ) - EmptyTree - val flags: FlagSet = if (ctx.owner.isConstructor) Private else EmptyFlags - val valDef = SyntheticValDef("dfcGen_plugin".toTermName, value, flags) - (ref(valDef.symbol), Some(valDef)) - end dfcGen - - private def dfcOverrideDef(owner: Symbol, dfcTree: Tree)(using Context): Tree = + private def dfcOverrideDef(owner: Symbol, treeSrcPos: util.SrcPos)(using Context): Tree = val sym = newSymbol(owner, "__dfc".toTermName, Override | Protected | Method | Touched, dfcTpe) - DefDef(sym, dfcTree) + // getting DFC context from the stack or need to generate an empty one + // with elaboration options found in the @top annotation + val dfcArg = dfcArgStack.headOption.getOrElse { + owner.getAnnotation(topAnnotSym).map(a => dropProxies(a.tree)) match + // found top annotation + case Some(Apply(Apply(Apply(_, _), _), topElaborationOptionsTree :: _)) => + ref(emptyDFCSym).appliedTo(topElaborationOptionsTree) + // no top + case _ => + var currentOwner = owner.owner + while (currentOwner != NoSymbol && !(currentOwner.typeRef <:< noTopAnnotIsRequired)) + currentOwner = currentOwner.owner + // no top, but if has an owner that extends `NoTopAnnotIsRequired`, + // we generate new context with default elaboration options + if (currentOwner.typeRef <:< noTopAnnotIsRequired) ref(emptyNoEODFCSym) + else + report.error( + "Missing `@top` annotation for this design to be instantiated as a top-level design.", + treeSrcPos + ) + EmptyTree + } + DefDef(sym, dfcArg) end dfcOverrideDef - private def prepareDesignParamValuesTree(tree: Tree, dfcTree: Tree)(using - Context - ): (valDefs: List[ValDef], parent: Tree, prepareTreeOpt: Option[Tree]) = - val tpe = tree.tpe - var valDefs: List[ValDef] = Nil - // naming the arguments before extending the tree as as parent because - // otherwise ownership and references need to change. - def nameArgs(tree: Tree): Tree = - tree match - case Apply(fun, args) => - val updatedArgs = args.map { a => - val uniqueName = NameKinds.UniqueName.fresh(s"arg_plugin".toTermName) - val valDef = SyntheticValDef(uniqueName, a) - valDefs = valDef :: valDefs - ref(valDef.symbol) - } - Apply(nameArgs(fun), updatedArgs) - case _ => tree - val parent = nameArgs(tree) - val prepareTreeOpt = - if (tpe <:< designTpe) then - val allParams = - tpe.typeSymbol.primaryConstructor.paramSymss.flatten.filter(_.isTerm) - val dfValPairs = allParams.lazyZip(valDefs.reverse).collect { - case (sym, vd: ValDef) if sym.info.dfValTpeOpt.nonEmpty => - (Literal(Constant(sym.name.toString)): Tree, ref(vd.symbol)) - }.toList - if (dfValPairs.nonEmpty) then - val names = mkList(dfValPairs.map(_._1), Some(defn.StringType)) - val values = mkList(dfValPairs.map(_._2)) - Some(ref(prepareDesignParamValuesSym).appliedTo(names, values).appliedTo(dfcTree)) - else None - else None - (valDefs, parent, prepareTreeOpt) - end prepareDesignParamValuesTree - override def transformApply(tree: Apply)(using Context): Tree = val tpe = tree.tpe tree match @@ -298,47 +252,48 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: ) cls.addAnnotations(tpe.typeSymbol.annotations) val constr = newConstructor(cls, Synthetic, Nil, Nil).entered - val dfc = dfcGen(cls, tree.srcPos) - val od = dfcOverrideDef(cls, dfc.tree) - val origSym = tpe.typeSymbol - val (valDefs, parent, prepareOpt) = prepareDesignParamValuesTree(tree, dfc.tree) + var valDefs: List[ValDef] = Nil + // naming the arguments before extending the tree as as parent because + // otherwise ownership and references need to change. + def nameArgs(tree: Tree): Tree = + tree match + case Apply(fun, args) => + val updatedArgs = args.map { a => + val uniqueName = NameKinds.UniqueName.fresh(s"arg_plugin".toTermName) + val valDef = SyntheticValDef(uniqueName, a) + valDefs = valDef :: valDefs + ref(valDef.symbol) + } + Apply(nameArgs(fun), updatedArgs) + case _ => tree + val parent = nameArgs(tree) + val od = dfcOverrideDef(cls, tree.srcPos) val cdef = ClassDefWithParents(cls, DefDef(constr), List(parent), List(od)) Block( - valDefs.reverse ++ dfc.valDefOpt ++ prepareOpt :+ cdef, + valDefs.reverse :+ cdef, Typed(New(Ident(cdef.namedType)).select(constr).appliedToNone, TypeTree(tpe)) ) case _ => tree end match end transformApply - override def transformBlock(block: Block)(using Context): tpd.Tree = - block match + override def transformBlock(tree: Block)(using Context): tpd.Tree = + tree match case Block( List(td @ TypeDef(tn, template: Template)), - Typed(apply: Apply, tpt) - ) if block.tpe.typeConstructor <:< hasDFCTpe => + Typed(apply @ Apply(fun, _), _) + ) if tree.tpe.typeConstructor <:< hasDFCTpe => val hasDFCOverride = template.body.exists { case dd: DefDef if dd.name.toString == "__dfc" => true case _ => false } - if (hasDFCOverride) block + if (hasDFCOverride) tree else - val dfc = dfcGen(td.symbol, block.srcPos) - val od = dfcOverrideDef(td.symbol, dfc.tree) - val (valDefs, parent, prepareOpt) = - prepareDesignParamValuesTree( - template.parents.head.changeOwner(template.constr.symbol, ctx.owner), - dfc.tree - ) - val updatedTemplate = - cpy.Template(template)(body = od :: template.body, parents = List(parent)) + val od = dfcOverrideDef(td.symbol, tree.srcPos) + val updatedTemplate = cpy.Template(template)(body = od :: template.body) val updatedTypeDef = cpy.TypeDef(td)(rhs = updatedTemplate) - Block( - valDefs.reverse ++ dfc.valDefOpt ++ prepareOpt :+ updatedTypeDef, - block.expr - ) - end if + cpy.Block(tree)(stats = List(updatedTypeDef), expr = tree.expr) case _ => - block + tree // transform basic val x = y to val x = dfhdl.core.r__For_Plugin.identVal(y) if y is a DFVal override def transformValDef(tree: ValDef)(using Context): ValDef = object DFValIdent: @@ -378,8 +333,6 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: listMapEmptySym = requiredMethod("scala.collection.immutable.ListMap.empty") listMapSym = requiredModule("scala.collection.immutable.ListMap") dfhdlDFValIdentSym = requiredMethod("dfhdl.core.r__For_Plugin.identVal") - prepareDesignParamValuesSym = - requiredMethod("dfhdl.core.r__For_Plugin.prepareDesignParamValues") dfcArgStack = Nil defaultParamMap.clear() ctx From 888127e4e1dbf4cbb5fee6e9f0ddf8fb1e8467ef Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 26 Mar 2026 01:58:27 +0200 Subject: [PATCH 051/115] refactor SanityCheck to improve violation reporting and hierarchy display --- .../main/scala/dfhdl/compiler/stages/SanityCheck.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala index 7fd8f26bf..ba6a463b5 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala @@ -156,8 +156,8 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: originMember match case originVal: DFVal if originVal.isGlobal => case _: DFVal.DesignParam => - case _: DFDesignBlock => // paramMap entries may reference global anon vals - case _ => + case _: DFDesignBlock => // paramMap entries may reference global anon vals + case _ => reportViolation( s"""|A global anonymous member is referenced by a non-global member. |Target member: ${targetVal} @@ -239,8 +239,10 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: s"The global member ${m.hashString}:\n$m\nHas reference $r pointing to a later member ${rm.hashString}:\n${rm}" ) case _ => + val hierarchy = + if (m.ownerRef.get == DFMember.Empty) "" else m.getOwnerNamed.getFullName println( - s"The member ${m.hashString}:\n$m\nIn hierarchy:\n${m.getOwnerNamed.getFullName}\nHas reference $r pointing to a later member ${rm.hashString}:\n${rm}" + s"The member ${m.hashString}:\n$m\nIn hierarchy:\n$hierarchy\nHas reference $r pointing to a later member ${rm.hashString}:\n${rm}" ) hasViolations = true require(!hasViolations, "Failed member order check!") From ace1e95accafd7a6aae2828fe48d755c45668100 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 26 Mar 2026 04:13:52 +0200 Subject: [PATCH 052/115] internal handling of patch global member references --- .../dfhdl/compiler/patching/MetaDesign.scala | 25 ++++++++----------- .../scala/dfhdl/compiler/patching/Patch.scala | 13 +++++++--- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala index cc4ac036e..d17e5a91d 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala @@ -7,30 +7,30 @@ import dfhdl.compiler.ir.DFDesignBlock type MetaDesignAny = MetaDesign[DomainType] -/** A base class for synthesising new IR members inside a compiler stage and injecting them into - * the DB at a precise position relative to an anchor member. +/** A base class for synthesising new IR members inside a compiler stage and injecting them into the + * DB at a precise position relative to an anchor member. * * Extend this class anonymously inside a stage's `transform` method to write ordinary DFHDL DSL * code (ports, vars, assignments, etc.). The members created in the body are extracted and * inserted into the DB when `dsn.patch` is applied; the MetaDesign block itself is discarded. * - * === Constructing raw IR members inside the body === + * ===Constructing raw IR members inside the body=== * * To build an IR member directly (e.g. `ir.Goto`) rather than through the DSL, add - * `import dfhdl.core.*` at the top of the anonymous body. This brings `refTW` and `addMember` - * into scope. Use `dfc.ownerOrEmptyRef` to obtain the owner ref — do not use `dfc.owner.ref` - * as that extension method may conflict with other imports: + * `import dfhdl.core.*` at the top of the anonymous body. This brings `refTW` and `addMember` into + * scope. Use `dfc.ownerOrEmptyRef` to obtain the owner ref — do not use `dfc.owner.ref` as that + * extension method may conflict with other imports: * {{{ * val dsn = new MetaDesign(anchor, Patch.Add.Config.Before): * import dfhdl.core.* * ir.Goto(existingStep.refTW[ir.Goto], dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags).addMember * }}} * - * === Ownership === + * ===Ownership=== * - * For `Before` / `After` / `ReplaceWith*` configs, newly created members are placed as siblings - * of the anchor (same owner). For `InsideFirst` / `InsideLast`, members are placed as direct - * children of the anchor (which must be a `DFOwner`). + * For `Before` / `After` / `ReplaceWith*` configs, newly created members are placed as siblings of + * the anchor (same owner). For `InsideFirst` / `InsideLast`, members are placed as direct children + * of the anchor (which must be a `DFOwner`). */ abstract class MetaDesign[+D <: DomainType]( positionMember: ir.DFMember, @@ -75,10 +75,7 @@ abstract class MetaDesign[+D <: DomainType]( final lazy val __domainType: ir.DomainType = ir.DomainType.DF final def plantMember[T <: ir.DFMember](member: T): T = - if (globalInjection) - dfc.mutableDB.plantMember(ir.DFMember.Empty, member) - else - dfc.mutableDB.plantMember(dfc.owner.asIR, member) + dfc.mutableDB.plantMember(dfc.owner.asIR, member) final def plantMembers(baseOwner: ir.DFOwner, members: Iterable[ir.DFMember]): Unit = members.foreach { m => val owner = m.getOwner diff --git a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala index b473984aa..e31364467 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala @@ -105,6 +105,7 @@ object Patch: case object Via extends Config end Config end Add + /** Moves `movedMembers` to a new position in the flat member list relative to the anchor member. * * `origOwner` is the original direct owner of the top-level moved members. During ownership @@ -164,6 +165,7 @@ object Patch: // Moves members inside the given block, at the end. // The anchor must be a DFOwner; redirects to anchor.getVeryLastMember for placement. case object InsideLast extends Config + end Config end Move final case class ChangeRef( @@ -229,13 +231,18 @@ extension (db: DB) case (rc, (origMember, Patch.Add(db, config))) => // if the original member is a global value, the all references pointing to the top design in the patch // db should point to an empty member for global placement - val fixedGlobalRefTable = origMember match - case dfVal: DFVal.CanBeGlobal if dfVal.isGlobal => + val globalPlacement = + (origMember, config) match + case (dfVal: DFVal.CanBeGlobal, _) if dfVal.isGlobal => true + case (design: DFDesignBlock, Patch.Add.Config.Before) if design.isTop => true + case _ => false + val fixedGlobalRefTable = + if (globalPlacement) db.refTable.map { case (ref, member) => if (member == db.top) (ref, DFMember.Empty) else (ref, member) } - case _ => db.refTable + else db.refTable // updating the patched DB reference table members with the newest members kept by the replacement context val updatedPatchRefTable = rc.getUpdatedRefTable(fixedGlobalRefTable) lazy val keepRefList = db.members.flatMap(_.getRefs) From e22c5248b2ef218969ad16412c2da731c439e4a3 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 26 Mar 2026 04:14:31 +0200 Subject: [PATCH 053/115] fix GlobalizePortVectorParams class name to have Spec --- .../test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala index 7dc350a90..e2a2f510b 100644 --- a/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala @@ -4,7 +4,7 @@ import dfhdl.* import dfhdl.compiler.stages.globalizePortVectorParams // scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} -class GlobalizePortVectorParams extends StageSpec(stageCreatesUnrefAnons = true): +class GlobalizePortVectorParamsSpec extends StageSpec(stageCreatesUnrefAnons = true): given options.CompilerOptions.Backend = backends.vhdl.v93 test("Various vector params are kept"): val width: Int <> CONST = 8 @@ -312,4 +312,4 @@ class GlobalizePortVectorParams extends StageSpec(stageCreatesUnrefAnons = true) | y3 <> id3.y |end IDTop""".stripMargin ) -end GlobalizePortVectorParams +end GlobalizePortVectorParamsSpec From f3a02501408e1d0ebcf9ad4ee42ad7915ee8a017 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 26 Mar 2026 04:37:46 +0200 Subject: [PATCH 054/115] wip GlobalizePortVectorParams --- .../stages/GlobalizePortVectorParams.scala | 52 +++++++++++++------ .../GlobalizePortVectorParamsSpec.scala | 13 +++-- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala index d12adc6e5..d0e815be7 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala @@ -8,7 +8,7 @@ import dfhdl.options.CompilerOptions import scala.collection.immutable.ListMap import scala.collection.mutable import dfhdl.compiler.stages.vhdl.VHDLDialect -import dfhdl.core.{DFTypeAny, asFE} +import dfhdl.core.{DFTypeAny, asFE, refTW} import dfhdl.compiler.ir.DFVal.PortByNameSelect /** This stage globalizes design parameters that set port vector lengths. This is needed only for @@ -157,7 +157,16 @@ case object GlobalizePortVectorParams extends Stage: ) // patches to change the duplicated design declaration names to a unique identifier according - // to theie instance names + // to their instance names. + // Only remove globalized params from paramMap; keep unglobalized ones intact. + val globalizedParamNamesPerDesign: Map[DFDesignBlock, Set[String]] = + designParams.view.collect { case dp: DFVal.DesignParam => dp } + .groupBy(_.getOwnerDesign) + .view.mapValues(_.map(_.getName).toSet).toMap + def prunedParamMap(design: DFDesignBlock): ListMap[String, DFDesignBlock.ParamRef] = + val toRemove = globalizedParamNamesPerDesign.getOrElse(design, Set.empty) + if (toRemove.isEmpty) design.paramMap + else design.paramMap.filterNot((name, _) => toRemove.contains(name)) val designPatches = dupDesignMap.view.flatMap { case (orig, dups) if dups.length >= 1 => (orig :: dups).map { design => @@ -165,11 +174,26 @@ case object GlobalizePortVectorParams extends Stage: design.dclMeta.setName( s"${design.dclName}_${design.getFullName.replaceAll("\\.", "_")}" ) - val updatedDesign = design.removeTagOf[DuplicateTag].copy(dclMeta = updatedDclMeta) + val updatedDesign = design.removeTagOf[DuplicateTag].copy( + dclMeta = updatedDclMeta, + paramMap = prunedParamMap(design) + ) design -> Patch.Replace(updatedDesign, Patch.Replace.Config.FullReplacement) } - case _ => None + case (orig, _) => + Some( + orig -> + Patch.Replace( + orig.copy(paramMap = prunedParamMap(orig)), + Patch.Replace.Config.FullReplacement + ) + ) }.toList + val paramReplacementMap = mutable.Map.empty[DFVal, DFVal] + def getUpdatedParamValue(param: DFVal.DesignParam): DFVal = + var dfVal = param.dfVal + while (paramReplacementMap.contains(dfVal)) dfVal = paramReplacementMap(dfVal) + dfVal // add global parameters before the design top val dsn = new MetaDesign(dupDesignDB.top, Patch.Add.Config.Before): // patches to replace with properly named parameter or just move the anonymous members @@ -177,21 +201,17 @@ case object GlobalizePortVectorParams extends Stage: // design parameters are transformed into global as-is named aliases case param: DFVal.DesignParam => val updatedMeta = param.meta.setName(param.getFullName.replaceAll("\\.", "_")) - val globalParam = - DFVal.Alias.AsIs( - param.dfType, - // TODO: the class tag here is incorrect (currently is DesignParam) and should be fixed or... - // do we really need the tags at all? - param.dfValRef.asInstanceOf[DFVal.Alias.PartialRef], - param.ownerRef, - updatedMeta, - param.tags - ) - plantMember(globalParam) + val updatedParamVal = getUpdatedParamValue(param) + val globalParam = dfhdl.core.DFVal.Alias.AsIs.forced( + param.dfType, + updatedParamVal + )(using dfc.setMeta(updatedMeta)) + paramReplacementMap += param -> globalParam param -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) - case m if !m.isAnonymous => + case m: DFVal if !m.isAnonymous => val globalParam = m.setName(m.getFullName.replaceAll("\\.", "_")) plantMember(globalParam) + paramReplacementMap += m -> globalParam m -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) case m => plantMember(m) diff --git a/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala index e2a2f510b..26164cdbb 100644 --- a/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala @@ -46,9 +46,13 @@ class GlobalizePortVectorParamsSpec extends StageSpec(stageCreatesUnrefAnons = t ) test("Various vector params are globalized, only ports are affected"): class Foo( - val width: Int <> CONST = 8, - val length: Int <> CONST = 10 + val keepThisParam: Int <> CONST = 4, + val width: Int <> CONST = 8, + val length: Int <> CONST = 10 ) extends RTDesign: + val x0 = Bits(keepThisParam) <> IN + val y0 = Bits(keepThisParam) <> OUT + y0 <> x0 val x1 = Bits(width) X length <> IN val y1 = Bits(width) X length <> OUT val v1 = Bits(width) X length <> VAR @@ -76,7 +80,10 @@ class GlobalizePortVectorParamsSpec extends StageSpec(stageCreatesUnrefAnons = t """|val Foo_length: Int <> CONST = 10 |val Foo_width: Int <> CONST = 8 | - |class Foo extends RTDesign: + |class Foo(val keepThisParam: Int <> CONST = 4) extends RTDesign: + | val x0 = Bits(keepThisParam) <> IN + | val y0 = Bits(keepThisParam) <> OUT + | y0 <> x0 | val x1 = Bits(Foo_width) X Foo_length <> IN | val y1 = Bits(Foo_width) X Foo_length <> OUT | val v1 = Bits(Foo_width) X Foo_length <> VAR From 1272244376a27ade62985e2a37542e579f1151e0 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 26 Mar 2026 05:23:18 +0200 Subject: [PATCH 055/115] GlobalizePortVectorParams working --- .../stages/GlobalizePortVectorParams.scala | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala index d0e815be7..887f5ca6b 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala @@ -77,21 +77,39 @@ case object GlobalizePortVectorParams extends Stage: if !(lhsType == rhsType) && lhsType.isSimilarTo(rhsType) => val lhsCnt = checkVector(lhsType) val rhsCnt = checkVector(rhsType) - if (lhsCnt < rhsCnt) vecTypeReplaceMap += lhsType -> rhsType + // when a PBNS connects to a non-PBNS, always unify to the child design's port type + // so that after globalization both sides share the same instance-specific globals + val lhsIsPbns = net.lhsRef.get.isInstanceOf[DFVal.PortByNameSelect] + val rhsIsPbns = net.rhsRef.get.isInstanceOf[DFVal.PortByNameSelect] + if (lhsIsPbns && !rhsIsPbns) vecTypeReplaceMap += rhsType -> lhsType + else if (rhsIsPbns && !lhsIsPbns) vecTypeReplaceMap += lhsType -> rhsType + else if (lhsCnt < rhsCnt) vecTypeReplaceMap += lhsType -> rhsType else if (lhsCnt > rhsCnt) vecTypeReplaceMap += rhsType -> lhsType case _ => } - val vecTypeReplacePatches = designDB.members.collect { + // Split vecTypeReplace into PBNS patches and non-PBNS patches. + // PBNS replacements introduce TypeRefs from port declarations (shared TypeRefs). + // If applied in the same patch batch as port replacements (which purge those same TypeRefs), + // the TypeRefs get removed before the PBNS can reference them. + // Apply PBNS replacements first so the shared TypeRefs have a higher repeat count. + val (vecTypePbnsPatches, vecTypeOtherPatches) = designDB.members.collect { case dfVal @ DFVector.Val(dfType) if vecTypeReplaceMap.contains(dfType) => dfVal -> Patch.Replace( dfVal.updateDFType(vecTypeReplaceMap(dfType)), Patch.Replace.Config.FullReplacement ) - } + }.partition((m, _) => m.isInstanceOf[DFVal.PortByNameSelect]) def movedMembers(namedParam: DFVal): List[DFVal] = - namedParam.collectRelMembers(true).filterNot(_.isGlobal) + namedParam match + // for DesignParams, also collect anonymous deps of the actual param value + // (from paramMap), since collectRelMembers only follows getRefs which + // does not include the paramMap value reference + case param: DFVal.DesignParam => + param.dfVal.collectRelMembers(false).filterNot(_.isGlobal) ++ List(param) + case _ => + namedParam.collectRelMembers(true).filterNot(_.isGlobal) - val addedGlobals = designParams.view.flatMap(movedMembers).toList + val addedGlobals = designParams.view.flatMap(movedMembers).toList.distinct val dupDesignSet = designParams.view.map(_.getOwnerDesign).toSet // origin -> duplicates design map val dupDesignMap = dupDesignSet.groupBy(_.dclName).values.map { grp => @@ -218,8 +236,11 @@ case object GlobalizePortVectorParams extends Stage: m -> Patch.Remove(isMoved = true) } // TODO: when combined to a single patch, there is a bug that prevents some members to get a global ownership + // Apply PBNS type patches first (they introduce shared TypeRefs), then other type patches + // (which may purge those same TypeRefs from the originals). dupDesignDB - .patch(dsn.patch :: dsn.replacePatches ++ vecTypeReplacePatches) + .patch(dsn.patch :: dsn.replacePatches ++ vecTypePbnsPatches) + .patch(vecTypeOtherPatches) .patch(designPatches) end transform end GlobalizePortVectorParams From dca953f36ad7e957a57de2f96997de016b5927a5 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 26 Mar 2026 22:58:45 +0200 Subject: [PATCH 056/115] fix ordering after design parameter change --- .../compiler/analysis/DFValAnalysis.scala | 11 ++++---- .../dfhdl/compiler/stages/OrderMembers.scala | 4 +-- .../scala/dfhdl/compiler/stages/ToED.scala | 27 ++++++++++--------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala index 20066762b..5e78db2b8 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala @@ -261,12 +261,13 @@ extension (dfVal: DFVal) .toSet ++ fromRefs case _ => fromRefs end getReadDeps - def isReferencedByAnyDcl(using MemberGetSet): Boolean = + def isReferencedByAnyDclOrDesign(using MemberGetSet): Boolean = dfVal.originMembers.view.exists { - case _: DFVal.Dcl => true - case DclConst() => true - case dfVal: DFVal => dfVal.isReferencedByAnyDcl - case _ => false + case _: DFVal.Dcl => true + case DclConst() => true + case _: DFDesignBlock => true + case dfVal: DFVal => dfVal.isReferencedByAnyDclOrDesign + case _ => false } @tailrec private def flatName(member: DFVal, suffix: String)(using MemberGetSet): String = diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/OrderMembers.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/OrderMembers.scala index b961f53b2..d69ade63b 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/OrderMembers.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/OrderMembers.scala @@ -38,8 +38,8 @@ object OrderMembers: // design parameters come second as they are dependent only on external // initialization or default values and everything else can depend on them case _: DFVal.DesignParam => 2 - // anonymous members that are referenced by declarations come third - case dfVal: DFVal if dfVal.isReferencedByAnyDcl => 3 + // anonymous members that are referenced by declarations or design instances come third + case dfVal: DFVal if dfVal.isReferencedByAnyDclOrDesign => 3 // fourth to come are constant declarations that may be referenced by ports case DclConst() => 4 // fifth are ports 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 1574777c1..e18df5e26 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala @@ -54,12 +54,12 @@ case object ToED extends Stage: case _ => } def collectFilter(member: DFMember): Boolean = member match - case IteratorDcl() => true - case _: DFVal.Dcl => false - case _: DFVal.DesignParam => false - case _: DFOwnerNamed => false - case dfVal: DFVal if dfVal.isReferencedByAnyDcl => false - case _ => true + case IteratorDcl() => true + case _: DFVal.Dcl => false + case _: DFVal.DesignParam => false + case _: DFOwnerNamed => false + case dfVal: DFVal if dfVal.isReferencedByAnyDclOrDesign => false + case _ => true def getProcessAllMembers(list: List[DFMember]): List[DFMember] = val processBlockAllMembersSet: Set[DFMember] = list.view.flatMap { @@ -160,11 +160,12 @@ case object ToED extends Stage: }.toList // create a combinational process if needed val hasProcessAll = - !domainIsPureSequential && (dclChangeList.nonEmpty || processBlockAllMembers.exists { - case net: DFNet => true - case ch: DFConditional.Header => true - case _ => false - }) + !domainIsPureSequential && + (dclChangeList.nonEmpty || processBlockAllMembers.exists { + case net: DFNet => true + case ch: DFConditional.Header => true + case _ => false + }) if (hasProcessAll) process(all) { val inVHDL = co.backend.isVHDL @@ -256,7 +257,9 @@ case object ToED extends Stage: block ) val hasSeqProcess = - clkCfg != None && (dclREGList.nonEmpty || processBlockAllMembers.nonEmpty && domainIsPureSequential) + clkCfg != None && + (dclREGList.nonEmpty || + processBlockAllMembers.nonEmpty && domainIsPureSequential) if (hasSeqProcess) if (rstCfg != None) From 7f74168b78ea0e44b13a9248ae2c86111627b663 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 27 Mar 2026 07:17:07 +0300 Subject: [PATCH 057/115] remove redundant named value in RT process elaboration test --- .../test/scala/ElaborationChecksSpec.scala | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/lib/src/test/scala/ElaborationChecksSpec.scala b/lib/src/test/scala/ElaborationChecksSpec.scala index eaf7e1df4..ecc699ddd 100644 --- a/lib/src/test/scala/ElaborationChecksSpec.scala +++ b/lib/src/test/scala/ElaborationChecksSpec.scala @@ -555,33 +555,4 @@ class ElaborationChecksSpec extends DesignSpec: |To Fix: |Make sure you connect the resource to the port with the correct direction.""".stripMargin ) - test("named values in RT process blocks are forbidden"): - object Test: - @top(false) class Top extends RTDesign: - val x = Bit <> IN - val p = Bits(8) <> IN - process: - val y = Bit <> VAR - val z = x ^ x - for (i <- 0 until 10) - println(i) - def MyStep: Step = - val w = !x - FirstStep - p match - case b"101${v: B[2]}010" => - println(v) - case _ => - end Top - end Test - import Test.* - assertElaborationErrors(Top())( - s"""|Elaboration errors found! - |Named DFHDL values are not allowed in RT process blocks. Found the following named values: - | y at ${currentFilePos}ElaborationChecksSpec.scala:564:19 - 564:29 - | z at ${currentFilePos}ElaborationChecksSpec.scala:565:19 - 565:24 - | w at ${currentFilePos}ElaborationChecksSpec.scala:569:21 - 569:23 - |To Fix: - |Use anonymous values instead.""".stripMargin - ) end ElaborationChecksSpec From 489061d50acd36f3344e1a84ff7327af1ba43e6e Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 27 Mar 2026 07:27:06 +0300 Subject: [PATCH 058/115] finish refactoring of design parameters. all tests green --- .../compiler/analysis/DFValAnalysis.scala | 2 +- .../scala/dfhdl/compiler/ir/DFMember.scala | 36 ++++++++++--------- .../compiler/printing/DFOwnerPrinter.scala | 26 +++++++------- .../compiler/printing/DFValPrinter.scala | 2 +- .../compiler/stages/DropDesignParamDeps.scala | 2 +- .../stages/GlobalizePortVectorParams.scala | 4 +-- .../compiler/stages/LocalToDesignParams.scala | 5 +-- .../stages/verilog/VerilogOwnerPrinter.scala | 14 +++++--- .../stages/verilog/VerilogValPrinter.scala | 5 +-- .../stages/vhdl/VHDLOwnerPrinter.scala | 14 +++++--- .../StagesSpec/DropDesignParamDepsSpec.scala | 5 +-- .../StagesSpec/LocalToDesignParamsSpec.scala | 5 +-- core/src/main/scala/dfhdl/core/DFVal.scala | 12 +++---- core/src/main/scala/dfhdl/core/Design.scala | 4 +-- .../main/scala/dfhdl/core/r__For_Plugin.scala | 8 ++--- 15 files changed, 77 insertions(+), 67 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala index 5e78db2b8..6a0a50d5c 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala @@ -65,7 +65,7 @@ object StrippedPortByNameSelect: object DefaultOfDesignParam: def unapply(dfVal: DFVal)(using MemberGetSet): Option[DFVal.DesignParam] = dfVal.originMembers.collectFirst { - case dp: DFVal.DesignParam if dp.defaultRef.get == dfVal => dp + case dp: DFVal.DesignParam if dp.defaultValRef.get == dfVal => dp } object OpaqueActual: diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index 644b862dc..00caee88b 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -224,7 +224,7 @@ sealed trait DFVal extends DFMember.Named: if dfType == relVal.dfType => stripAsIsAndDesignParam(relVal) case dp: DFVal.DesignParam => - stripAsIsAndDesignParam(dp.dfVal) + stripAsIsAndDesignParam(dp.appliedOrDefaultVal) case _ => dfVal // TODO: maybe we need a better way to check equivalent expressions, with symbolic algebra comparison? // with such comparison, it is possible to simplify expressions at least in the common cases. @@ -451,7 +451,7 @@ object DFVal: final case class DesignParam( dfType: DFType, - defaultRef: DesignParam.DefaultRef, + defaultValRef: DesignParam.DefaultValRef, ownerRef: DFOwner.Ref, meta: Meta, tags: DFTags @@ -460,24 +460,28 @@ object DFVal: // the value will be cached during elaboration, because the reference via the design's paramMap // will not be available until the design is fully elaborated. during initial contruction and mutation, // the value will be cached in core.DFVal.DesignParam, and later cleared in core.Design - private var cachedVal: Option[DFVal] = None - protected[compiler] def dfValRef(using MemberGetSet): DFDesignBlock.ParamRef = - getOwnerDesign.paramMap(getName) - def dfVal(using MemberGetSet): DFVal = - if (getSet.isMutable) cachedVal.getOrElse(dfValRef.get) - else dfValRef.get - protected[dfhdl] def setCachedVal(dfVal: DFVal): Unit = - cachedVal = Some(dfVal) - protected[dfhdl] def clearCachedVal(): Unit = cachedVal = None + private var cachedAppliedVal: Option[DFVal] = None + protected[compiler] def appliedValRefOpt(using MemberGetSet): Option[DFDesignBlock.ParamRef] = + getOwnerDesign.paramMap.get(getName) + def appliedValOpt(using MemberGetSet): Option[DFVal] = + if (getSet.isMutable) cachedAppliedVal.orElse(appliedValRefOpt.map(_.get)) + else appliedValRefOpt.map(_.get) + def appliedOrDefaultValRef(using MemberGetSet): DFVal.Ref = + appliedValRefOpt.getOrElse(defaultValRef.asInstanceOf[DFVal.Ref]) + def appliedOrDefaultVal(using MemberGetSet): DFVal = + appliedValOpt.getOrElse(defaultValRef.get.asInstanceOf[DFVal]) + protected[dfhdl] def setCachedAppliedVal(dfVal: DFVal): Unit = cachedAppliedVal = Some(dfVal) + protected[dfhdl] def clearCachedAppliedVal(): Unit = cachedAppliedVal = None protected def protIsFullyAnonymous(using MemberGetSet): Boolean = false - protected def protGetConstData(using MemberGetSet): Option[Any] = dfVal.getConstData + protected def protGetConstData(using MemberGetSet): Option[Any] = + appliedOrDefaultVal.getConstData protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: DesignParam => // design parameters are considered to be the same even if they are referencing // a different member (this should be quite common), because that member is // external to the design. however, different default value is considered to be a // different design parameter. - this.dfType =~ that.dfType && this.defaultRef =~ that.defaultRef && + this.dfType =~ that.dfType && this.defaultValRef =~ that.defaultValRef && this.meta =~ that.meta && this.tags =~ that.tags case _ => false protected[ir] def protIsSimilarTo(that: CanBeExpr)(using MemberGetSet): Boolean = @@ -487,17 +491,17 @@ object DFVal: protected def setMeta(meta: Meta): this.type = copy(meta = meta).asInstanceOf[this.type] protected def setTags(tags: DFTags): this.type = copy(tags = tags).asInstanceOf[this.type] lazy val getRefs: List[DFRef.TwoWayAny] = - defaultRef :: dfType.getRefs ++ meta.getRefs + defaultValRef :: dfType.getRefs ++ meta.getRefs def updateDFType(dfType: DFType): this.type = copy(dfType = dfType).asInstanceOf[this.type] def copyWithNewRefs(using RefGen): this.type = copy( meta = meta.copyWithNewRefs, dfType = dfType.copyWithNewRefs, ownerRef = ownerRef.copyAsNewRef, - defaultRef = defaultRef.copyAsNewRef + defaultValRef = defaultValRef.copyAsNewRef ).asInstanceOf[this.type] end DesignParam object DesignParam: - type DefaultRef = DFRef.TwoWay[DFVal | DFMember.Empty, DesignParam] + type DefaultValRef = DFRef.TwoWay[DFVal | DFMember.Empty, DesignParam] final case class Special( dfType: DFType, diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala index d77bc913a..08ded86d6 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala @@ -202,14 +202,20 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: s"def ${design.dclName}$designParamCS($defArgsCS)$retTypeCS =\n${bodyWithDcls.hindent}\nend ${design.dclName}" s"${printer.csAnnotations(design.dclMeta.annotations)}$dcl\n" end csDFDesignDefDcl + private def csDesignParamList(design: DFDesignBlock): List[String] = + design.members(MemberView.Folded).flatMap { + case param: DesignParam => + param.appliedValRefOpt match + case Some(ref) => Some(s"${param.getName} = ${ref.refCodeString}") + case None => None + case _ => None + } def csDFDesignDefInst(design: DFDesignBlock): String = val ports = design.members(MemberView.Folded).view.collect { case port @ DclIn() => val DFNet.Connection(_, from: DFVal, _) = port.getConnectionTo.get.runtimeChecked printer.csDFValRef(from, design.getOwner) }.mkString(", ") - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s"${param.getName} = ${param.dfValRef.refCodeString}" - } + val designParamList = csDesignParamList(design) val designParamCS = if (designParamList.length == 0) "" else if (designParamList.length == 1) designParamList.mkString("(", ", ", ")") @@ -219,9 +225,7 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: else s"val ${design.getName} = $dcl" end csDFDesignDefInst def csDFDesignBlockParamInst(design: DFDesignBlock): String = - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s"${param.getName} = ${param.dfValRef.refCodeString}" - } + val designParamList = csDesignParamList(design) if (designParamList.length <= 1) designParamList.mkString("(", ", ", ")") else "(" + designParamList.mkString("\n", ",\n", "\n").hindent(2) + ")" def csDFDesignBlockDcl(design: DFDesignBlock): String = @@ -247,11 +251,11 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: case _ => "EDDesign" val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => val defaultValue = - if (design.isTop) s" = ${param.dfValRef.refCodeString}" + if (design.isTop) s" = ${param.appliedOrDefaultValRef.refCodeString}" else - param.defaultRef.get match + param.defaultValRef.get match case DFMember.Empty => "" - case _ => s" = ${param.defaultRef.refCodeString}" + case _ => s" = ${param.defaultValRef.refCodeString}" s"val ${param.getName}${printer.csDFValConstType(param.dfType)}$defaultValue" } val designIsVendorIPBlackbox = design.isVendorIPBlackbox @@ -279,9 +283,7 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: end csDFDesignBlockDcl def csDFDesignBlockInst(design: DFDesignBlock): String = val body = csDFDesignLateBody(design) - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s"${param.getName} = ${param.dfValRef.refCodeString}" - } + val designParamList = csDesignParamList(design) val designParamCS = // for vendor IP blackbox, we define the parameters in the class extension instead of the // blackbox instantiation diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala index e8e988d4a..59681dc47 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala @@ -168,7 +168,7 @@ trait AbstractValPrinter extends AbstractPrinter: case dv: Const => csDFValConstExpr(dv) case dv: Func => csDFValFuncExpr(dv, typeCS) case dv: Alias => csDFValAliasExpr(dv) - case dv: DFVal.DesignParam => dv.dfValRef.refCodeString + case dv: DFVal.DesignParam => dv.appliedValRefOpt.get.refCodeString case dv: DFConditional.Header => printer.csDFConditional(dv) // case dv: Timer.IsActive => csTimerIsActive(dv) case dv: Special => diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDesignParamDeps.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDesignParamDeps.scala index 3fce6aa6e..a6bf77293 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDesignParamDeps.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDesignParamDeps.scala @@ -39,7 +39,7 @@ case object DropDesignParamDeps extends Stage: // Collect all design parameters that have dependencies on other design parameters designDB.members.foreach { case param: DFVal.DesignParam => - param.defaultRef.get match + param.defaultValRef.get match case default: DFVal => if (hasDesignParamDependency(default)) designParamDefaultsToInline.add(default) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala index 887f5ca6b..f51e6327b 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala @@ -105,7 +105,7 @@ case object GlobalizePortVectorParams extends Stage: // (from paramMap), since collectRelMembers only follows getRefs which // does not include the paramMap value reference case param: DFVal.DesignParam => - param.dfVal.collectRelMembers(false).filterNot(_.isGlobal) ++ List(param) + param.appliedOrDefaultVal.collectRelMembers(false).filterNot(_.isGlobal) ++ List(param) case _ => namedParam.collectRelMembers(true).filterNot(_.isGlobal) @@ -209,7 +209,7 @@ case object GlobalizePortVectorParams extends Stage: }.toList val paramReplacementMap = mutable.Map.empty[DFVal, DFVal] def getUpdatedParamValue(param: DFVal.DesignParam): DFVal = - var dfVal = param.dfVal + var dfVal = param.appliedOrDefaultVal while (paramReplacementMap.contains(dfVal)) dfVal = paramReplacementMap(dfVal) dfVal // add global parameters before the design top diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/LocalToDesignParams.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/LocalToDesignParams.scala index 099c5d6b6..a78d3555e 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/LocalToDesignParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/LocalToDesignParams.scala @@ -6,8 +6,9 @@ import dfhdl.compiler.patching.* import dfhdl.options.CompilerOptions import scala.collection.mutable -/** This stage converts local parameters that are used in IOs to be design parameters, since VHDL - * does not support local parameters for IO access. +/** This stage converts local parameters that are used in IOs to be design parameters with default + * values, since VHDL does not support local parameters for IO access. These kind of design + * parameters remain at their default (relative) values and are never directly applied. */ case object LocalToDesignParams extends Stage: override def runCondition(using co: CompilerOptions): Boolean = diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala index 31824d82a..db9163e19 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala @@ -105,9 +105,9 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: ) val designParamList = designMembers.collect { case param: DesignParam => val defaultValue = - if (design.isTop) s" = ${param.dfValRef.refCodeString}" + if (design.isTop) s" = ${param.appliedOrDefaultValRef.refCodeString}" else - param.defaultRef.get match + param.defaultValRef.get match case DFMember.Empty => // missing default values are supported if (noDefaultParamSupport) "" @@ -115,7 +115,7 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: // (different instances may have different constant data, but for default, // a single module description can have any valid data, just to satisfy the standard) else s" = ${printer.csConstData(param.dfType, param.getConstData.get)}" - case _ => s" = ${param.defaultRef.refCodeString}" + case _ => s" = ${param.defaultValRef.refCodeString}" val csType = printer.csDFType(param.dfType).emptyOr(_ + " ") val csTypeNoLogic = if (printer.supportLogicType) csType else csType.replace("logic ", "") s"parameter ${csTypeNoLogic}${param.getName}$defaultValue" @@ -162,8 +162,12 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: |""".stripMargin def csDFDesignBlockInst(design: DFDesignBlock): String = val body = csDFDesignLateBody(design) - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s".${param.getName} (${param.dfValRef.refCodeString})" + val designParamList = design.members(MemberView.Folded).flatMap { + case param: DesignParam => + param.appliedValRefOpt match + case Some(ref) => Some(s".${param.getName} (${ref.refCodeString})") + case None => None + case _ => None } val designParamCS = if (designParamList.isEmpty || design.isVendorIPBlackbox) "" diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala index 4f0221fb9..3e96f6893 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala @@ -34,7 +34,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: // using the constant data only happens in verilog.v95, since parameters are declared in // the body and must have defaults. case param: DesignParam => - param.defaultRef.get match + param.defaultValRef.get match case defaultVal: CanBeExpr if !param.getOwnerDesign.isTop => csDFValExpr(defaultVal) case _ => printer.csConstData(param.dfType, param.getConstData.get) case _ => csDFValExpr(dfVal) @@ -85,7 +85,8 @@ protected trait VerilogValPrinter extends AbstractValPrinter: val cellWidth = dfType.cellType.width val length = dfType.cellDimParamRefs.head.getInt val ret = for (i <- 0 until length) - yield s"${dfVal.getName}[$i] = ${initVal.getName}[${(length - i) * cellWidth - 1}:${(length - i) * cellWidth - cellWidth}];" + yield s"${dfVal.getName}[$i] = ${initVal.getName}[${(length - i) * cellWidth - + 1}:${(length - i) * cellWidth - cellWidth}];" ret.mkString("\n") case Func(op = Func.Op.++, args = args) => args.view.zipWithIndex diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala index 0440284c0..a4a0815d9 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala @@ -36,11 +36,11 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: .mkString(";\n") val designParamList = designMembers.collect { case param: DesignParam => val defaultValue = - if (design.isTop) s" := ${param.dfValRef.refCodeString}" + if (design.isTop) s" := ${param.appliedOrDefaultValRef.refCodeString}" else - param.defaultRef.get match + param.defaultValRef.get match case DFMember.Empty => "" - case _ => s" := ${param.defaultRef.refCodeString}" + case _ => s" := ${param.defaultValRef.refCodeString}" s"${param.getName} : ${printer.csDFType(param.dfType)}$defaultValue" } val genericBlock = @@ -177,8 +177,12 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: end csDFDesignBlockDcl def csDFDesignBlockInst(design: DFDesignBlock): String = val body = csDFDesignLateBody(design) - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s"${param.getName} => ${param.dfValRef.refCodeString}" + val designParamList = design.members(MemberView.Folded).flatMap { + case param: DesignParam => + param.appliedValRefOpt match + case Some(ref) => Some(s"${param.getName} => ${ref.refCodeString}") + case None => None + case _ => None } val designParamCS = if (designParamList.isEmpty || design.isVendorIPBlackbox) "" diff --git a/compiler/stages/src/test/scala/StagesSpec/DropDesignParamDepsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropDesignParamDepsSpec.scala index 91dd8708d..49b4db535 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropDesignParamDepsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropDesignParamDepsSpec.scala @@ -124,10 +124,7 @@ class DropDesignParamDepsSpec extends StageSpec: | val inner_depth: Int <> CONST = baseWidth + 1 | val x = Bits(baseWidth) <> IN | val y = Bits(baseWidth) <> OUT - | val inner = Inner( - | width = baseWidth, - | depth = inner_depth - | ) + | val inner = Inner(width = baseWidth) | inner.x <> x | y <> inner.y.resize(baseWidth) |end Outer diff --git a/compiler/stages/src/test/scala/StagesSpec/LocalToDesignParamsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/LocalToDesignParamsSpec.scala index cff228795..8f9657e2f 100644 --- a/compiler/stages/src/test/scala/StagesSpec/LocalToDesignParamsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/LocalToDesignParamsSpec.scala @@ -205,10 +205,7 @@ class LocalToDesignParamsSpec extends StageSpec: |class PrimeDiv5 extends RTDesign: | val primeDiv5_DIVIDEND_WIDTH: Int <> CONST = 5 + 1 | val dividend = UInt(6) <> VAR.REG init d"6'0" - | val primeDiv5 = PrimeDiv( - | primeDivisor = 5, - | DIVIDEND_WIDTH = primeDiv5_DIVIDEND_WIDTH - | ) + | val primeDiv5 = PrimeDiv(primeDivisor = 5) | primeDiv5.dividend <> dividend |end PrimeDiv5""".stripMargin ) diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index 73ba959a3..6fe9830cb 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -644,19 +644,19 @@ object DFVal extends DFValLP: object DesignParam: // Note: in meta-programming, the user needs to manually set the Design's paramMap. def apply[T <: DFTypeAny]( - dfVal: DFValOf[T], - default: Option[DFValOf[T]] = None + appliedVal: DFValOf[T], + defaultVal: Option[DFValOf[T]] = None )(using DFC): DFConstOf[T] = val alias: ir.DFVal.DesignParam = ir.DFVal.DesignParam( - dfVal.asIR.dfType.dropUnreachableRefs, - default.map(_.asIR.refTW[ir.DFVal.DesignParam]) + appliedVal.asIR.dfType.dropUnreachableRefs, + defaultVal.map(_.asIR.refTW[ir.DFVal.DesignParam]) .getOrElse(ir.DFMember.Empty.refTW[ir.DFVal.DesignParam]), dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags ) - if (!dfc.inMetaProgramming) alias.setCachedVal(dfVal.asIR) + if (!dfc.inMetaProgramming) alias.setCachedAppliedVal(appliedVal.asIR) alias.addMember.asConstOf[T] end apply end DesignParam @@ -1871,7 +1871,7 @@ extension (dfVal: ir.DFVal) dfVal match // design parameter, so recurse on the referenced value case dp: ir.DFVal.DesignParam => - dp.dfVal.cloneUnreachable + dp.appliedOrDefaultVal.cloneUnreachable // named constant, so clone under a new name within relation to the current design case _ => val newMeta = diff --git a/core/src/main/scala/dfhdl/core/Design.scala b/core/src/main/scala/dfhdl/core/Design.scala index 00438fa1c..1b167dda4 100644 --- a/core/src/main/scala/dfhdl/core/Design.scala +++ b/core/src/main/scala/dfhdl/core/Design.scala @@ -168,9 +168,9 @@ object Design: ListMap.from( dfc.mutableDB.DesignContext.current.getImmutableMemberList.view.collect { case dp: ir.DFVal.DesignParam => - val dfVal = dp.dfVal + val dfVal = dp.appliedValOpt.get // invalidating the param cache value after design elaboration - dp.clearCachedVal() + dp.clearCachedAppliedVal() dp.getName -> dfVal.refTW[ir.DFDesignBlock](knownReachable = true) }.toMap ) diff --git a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala index d113d315d..87cc92d12 100644 --- a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala +++ b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala @@ -104,14 +104,14 @@ object r__For_Plugin: Pattern.BindSI(op, parts, bindVals.map(_.asIR.refTW[DFConditional.DFCaseBlock])) @metaContextIgnore def genDesignParam[V <: DFValAny]( - paramValue: DFValAny, - default: Option[DFValAny], + appliedVal: DFValAny, + defaultVal: Option[DFValAny], paramMeta: ir.Meta )(using DFC): V = trydf: dfc.mutableDB.DesignContext.getReachableNamedValue( - paramValue.asIR, - DFVal.DesignParam(paramValue, default)(using dfc.setMeta(paramMeta)).asIR + appliedVal.asIR, + DFVal.DesignParam(appliedVal, defaultVal)(using dfc.setMeta(paramMeta)).asIR ).asValAny.asInstanceOf[V] def prepareDesignParamValues(paramNames: List[String], paramValues: List[DFValAny])(using DFC From ae5deb2707f2bdbdb441e94ad504cb4035b23b6f Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 27 Mar 2026 07:29:51 +0300 Subject: [PATCH 059/115] update skill after changed --- .claude/commands/ir-reference.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.claude/commands/ir-reference.md b/.claude/commands/ir-reference.md index 23df768a4..1e845c88b 100644 --- a/.claude/commands/ir-reference.md +++ b/.claude/commands/ir-reference.md @@ -321,17 +321,21 @@ final case class DFVal.Alias.SelectField( ### DFVal.DesignParam ```scala final case class DFVal.DesignParam( - dfType: DFType, - dfValRef: DesignParam.Ref, // → the actual parameter value - defaultRef: DesignParam.DefaultRef, // → default value or DFMember.Empty - ownerRef: DFOwner.Ref, - meta: Meta, - tags: DFTags + dfType: DFType, + defaultValRef: DesignParam.DefaultValRef, // → default value or DFMember.Empty + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags ) -type DesignParam.Ref = DFRef.TwoWay[DFVal, DesignParam] -type DesignParam.DefaultRef = DFRef.TwoWay[DFVal | DFMember.Empty, DesignParam] +type DesignParam.DefaultValRef = DFRef.TwoWay[DFVal | DFMember.Empty, DesignParam] ``` +Key accessor methods on `DesignParam`: +- `param.appliedValRefOpt` — `Option[DFDesignBlock.ParamRef]` — `Some` when the owner design has an applied value in its `paramMap`, `None` otherwise +- `param.appliedValOpt` — `Option[DFVal]` — resolved applied value (if any) +- `param.appliedOrDefaultValRef` — `DFVal.Ref` — applied ref if present, else `defaultValRef` +- `param.appliedOrDefaultVal` — `DFVal` — applied value if present, else default value + --- ### DFVal.Special @@ -923,7 +927,7 @@ dfVal.getConnectionsFrom // Set[DFNet] — connections driven from this va dfVal.getAssignmentsTo // Set[DFVal] — values assigned to this dfVal.getAssignmentsFrom // Set[DFVal] — values assigned from this dfVal.getPortsByNameSelectors // List[DFVal.PortByNameSelect] (ports only) -dfVal.isReferencedByAnyDcl // Boolean +dfVal.isReferencedByAnyDclOrDesign // Boolean — true if referenced by a Dcl, DclConst, or DFDesignBlock dfVal.isConstVAR // VAR never assigned/connected dfVal.isAllowedMultipleReferences // Boolean dfVal.isPartialNetDest // Boolean — is a partial assignment/connection target From 7d0d2424a32743a3921af2864bd4612b2bf8f6ee Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sat, 28 Mar 2026 22:57:33 +0300 Subject: [PATCH 060/115] enhance patching logic to support concatenation of additions with Before and After configurations --- .../scala/dfhdl/compiler/patching/Patch.scala | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala index e31364467..48deb1c06 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala @@ -388,6 +388,32 @@ extension (db: DB) Patch.Add(db2, Patch.Add.Config.After) ) => tbl + (m -> Patch.Add(db1 concat db2, config1)) + // concatenating additions with Before and After configurations, respectively + case ( + Patch.Add(db1, Patch.Add.Config.Before), + Patch.Add(db2, Patch.Add.Config.After) + ) => + val combinedDB = (db1 concat db2).copy(members = + db1.members ++ (m :: db2.members.drop(1)) + ) + val config = Patch.Add.Config.ReplaceWithMemberN( + db1.members.length - 1, + Patch.Replace.Config.FullReplacement + ) + tbl + (m -> Patch.Add(combinedDB, config)) + // concatenating additions with After and Before configurations, respectively + case ( + Patch.Add(db1, Patch.Add.Config.After), + Patch.Add(db2, Patch.Add.Config.Before) + ) => + val combinedDB = (db1 concat db2).copy(members = + (db1.members.head :: db2.members.drop(1)) ++ (m :: db1.members.drop(1)) + ) + val config = Patch.Add.Config.ReplaceWithMemberN( + db2.members.length - 1, + Patch.Replace.Config.FullReplacement + ) + tbl + (m -> Patch.Add(combinedDB, config)) // concatenating moves with the same configuration // (the patch table does not care about original owner, so we ignore it. // only the patchList that has all the move patches uses the original owners From f55e79121620c6118b6eef3a225af0a5e27c6866 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 29 Mar 2026 11:03:30 +0300 Subject: [PATCH 061/115] wip: better design deduplication --- .claude/commands/ir-reference.md | 2 +- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 95 +++-- .../scala/dfhdl/compiler/ir/DFMember.scala | 2 +- .../main/scala/dfhdl/compiler/ir/DFRef.scala | 8 + .../compiler/printing/DFOwnerPrinter.scala | 12 +- .../dfhdl/compiler/stages/ConnectUnused.scala | 16 +- .../stages/GlobalizePortVectorParams.scala | 387 +++++++++--------- .../dfhdl/compiler/stages/SanityCheck.scala | 14 +- .../dfhdl/compiler/stages/ViaConnection.scala | 4 +- .../stages/verilog/VerilogOwnerPrinter.scala | 10 +- .../stages/vhdl/VHDLOwnerPrinter.scala | 10 +- .../src/main/scala/dfhdl/core/MutableDB.scala | 41 +- 12 files changed, 331 insertions(+), 270 deletions(-) diff --git a/.claude/commands/ir-reference.md b/.claude/commands/ir-reference.md index 1e845c88b..110a6c59b 100644 --- a/.claude/commands/ir-reference.md +++ b/.claude/commands/ir-reference.md @@ -364,7 +364,7 @@ final case class DFVal.PortByNameSelect( ) type PortByNameSelect.Ref = DFRef.TwoWay[DFDesignInst, PortByNameSelect] -portByNameSelect.getPortDcl // resolve to actual DFVal.Dcl (via DB.portsByName) +portByNameSelect.getPortDcl // resolve to actual DFVal.Dcl (via DB.dupPortsByName) // Extractor: DFVal.PortByNameSelect.Of(dcl) // unapply → Option[DFVal.Dcl] 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 cca11e561..49f0e6ca6 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -43,13 +43,30 @@ final case class DB( // considered to be in build if not in simulation and has a device constraint lazy val inBuild: Boolean = !inSimulation && top.isDeviceTop - lazy val portsByName: Map[DFDesignInst, Map[String, DFVal.Dcl]] = - members.view + lazy val dupPortsByName: Map[DFDesignInst, ListMap[String, DFVal.Dcl]] = + val origMap = members.view .collect { case m: DFVal.Dcl if m.isPort => m } .groupBy(_.getOwnerDesign) .map { case (design, dcls) => - design -> dcls.map(m => m.getRelativeName(design) -> m).toMap + design -> ListMap.from(dcls.view.map(m => m.getRelativeName(design) -> m)) }.toMap + // Add entries for duplicate designs by copying origin Dcls with DuplicationRef owner. + // PBNS members carry the correct dfType for each duplicate's port (reflecting the + // actual instantiation parameters), so we use it to override the origin Dcl's dfType. + val pbnsByDesign = members.view + .collect { case m: DFVal.PortByNameSelect => m } + .groupBy(m => m.designInstRef.get) + .view.mapValues(_.map(m => m.portNamePath -> m.dfType).toMap).toMap + val dupEntries = dupDesignToOrigMap.map { (dupDesign, origDesign) => + val pbnsTypes = pbnsByDesign.getOrElse(dupDesign, Map.empty) + dupDesign -> ListMap.from(origMap(origDesign).view.map { (name, dcl) => + val dfType = pbnsTypes.getOrElse(name, dcl.dfType) + name -> dcl.copy(ownerRef = DFRef.DuplicationRef(dupDesign), dfType = dfType) + }) + } + // dupEntries only fills in missing entries (designs without real port members) + dupEntries ++ origMap + end dupPortsByName lazy val topIOs: List[DFVal.Dcl] = designMemberTable(top).collect { case dcl: DFVal.Dcl if dcl.isPort => dcl @@ -268,6 +285,37 @@ final case class DB( lazy val uniqueDesignMemberList: List[(DFDesignBlock, List[DFMember])] = designMemberList.filterNot(_._1.isDuplicate) + // maps each duplicated design to its origin (first non-duplicate design with the same dclName) + lazy val dupDesignToOrigMap: Map[DFDesignBlock, DFDesignBlock] = + val origByName = + uniqueDesignMemberList.map(_._1).groupBy(_.dclName).view.mapValues(_.head).toMap + designMemberList.collect { + case (design, _) if design.isDuplicate => design -> origByName(design.dclName) + }.toMap + + // maps each design (origin and duplicates) to its domain blocks. + // For duplicates, domain blocks are cloned from the origin with DuplicationRef owners. + // Nested domain blocks get DuplicationRef pointing to the duplicated parent block. + lazy val dupDesignDomainBlockMap: Map[DFDesignBlock, List[DomainBlock]] = + val origDomainBlocks: Map[DFDesignBlock, List[DomainBlock]] = + members.view + .collect { case db: DomainBlock => db } + .groupBy(_.getOwnerDesign) + .map { (design, blocks) => design -> blocks.toList } + .toMap + val dupDomainBlocks = dupDesignToOrigMap.map { (dupDesign, origDesign) => + val origToDupMap = mutable.Map.empty[DFDomainOwner, DFDomainOwner] + origToDupMap += origDesign -> dupDesign + dupDesign -> origDomainBlocks.getOrElse(origDesign, Nil).map { origBlock => + val dupOwner = origToDupMap(origBlock.getOwnerDomain) + val dupBlock = origBlock.copy(ownerRef = DFRef.DuplicationRef(dupOwner)) + origToDupMap += origBlock -> dupBlock + dupBlock + } + } + origDomainBlocks ++ dupDomainBlocks + end dupDesignDomainBlockMap + // holds a hash table that lists members of each owner block. The member list order is maintained. lazy val designMemberTable: Map[DFDesignBlock, List[DFMember]] = Map(designMemberList*) @@ -638,24 +686,25 @@ final case class DB( ) } // collect all ports that are not connected directly or implicitly as magnets - val danglingPorts = members.collect { - case p: DFVal.Dcl - if p.isPortIn && !p.isClkDcl && !p.isRstDcl && !connectionTable.contains(p) && - !p.getOwnerDesign.isTop && !magnetConnectionTable.contains(p) => - val ownerDesign = p.getOwnerDesign - s"""|DFiant HDL connectivity error! - |Position: ${ownerDesign.meta.position} - |Hierarchy: ${ownerDesign.getFullName} - |Message: Found a dangling (unconnected) input port `${p.getName}`.""".stripMargin - case p: DFVal.Dcl - if p.isPortOut && !p.getOwnerDesign.isDuplicate && !p.getOwnerDesign.isBlackBox && - !connectionTable.contains(p) && !assignmentsDclTable.contains(p) && - !magnetConnectionTable.contains(p) && !p.hasNonBubbleInit => - val ownerDesign = p.getOwnerDesign - s"""|DFiant HDL connectivity error! - |Position: ${p.meta.position} - |Hierarchy: ${ownerDesign.getFullName} - |Message: Found a dangling (unconnected/unassigned and uninitialized) output port `${p.getName}`.""".stripMargin + val danglingPorts = dupPortsByName.view.flatMap { (ownerDesign, ports) => + ports.collect { + case (_, p: DFVal.Dcl) + if p.isPortIn && !p.isClkDcl && !p.isRstDcl && !connectionTable.contains(p) && + !ownerDesign.isTop && !magnetConnectionTable.contains(p) => + s"""|DFiant HDL connectivity error! + |Position: ${ownerDesign.meta.position} + |Hierarchy: ${ownerDesign.getFullName} + |Message: Found a dangling (unconnected) input port `${p.getName}`.""".stripMargin + case (_, p: DFVal.Dcl) + if p.isPortOut && !ownerDesign.isDuplicate && !ownerDesign.isBlackBox && + !connectionTable.contains(p) && !assignmentsDclTable.contains(p) && + !magnetConnectionTable.contains(p) && !p.hasNonBubbleInit => + val ownerDesign = p.getOwnerDesign + s"""|DFiant HDL connectivity error! + |Position: ${p.meta.position} + |Hierarchy: ${ownerDesign.getFullName} + |Message: Found a dangling (unconnected/unassigned and uninitialized) output port `${p.getName}`.""".stripMargin + } } if (danglingPorts.nonEmpty) throw new IllegalArgumentException( @@ -1248,8 +1297,8 @@ end DB * - `Folded`: returns only the direct children of the owner (members whose `getOwner == owner`). * - `Flattened`: returns all descendants recursively. For a `DFDesignBlock` owner this returns * every member in the design (via `designMemberTable`). For other owners it recurses into - * nested blocks but does NOT cross `DFDesignBlock` boundaries — sub-designs appear as a - * single opaque entry. + * nested blocks but does NOT cross `DFDesignBlock` boundaries — sub-designs appear as a single + * opaque entry. * * Use `Folded` when you only need to process immediate children (e.g. steps at one nesting level). * Use `Flattened` when you need to inspect or collect all descendants (e.g. all `Goto` members diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index 00caee88b..1b40298bc 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -671,7 +671,7 @@ object DFVal: extension (portByNameSelect: PortByNameSelect) def getPortDcl(using MemberGetSet): DFVal.Dcl = val designInst = portByNameSelect.designInstRef.get - getSet.designDB.portsByName(designInst)(portByNameSelect.portNamePath) + getSet.designDB.dupPortsByName(designInst)(portByNameSelect.portNamePath) sealed trait Alias extends CanBeExpr: val relValRef: Alias.Ref diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala index 1f92dbaf7..bd2349b45 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala @@ -27,6 +27,12 @@ object DFRef: final case class Gen[M <: DFMember](grpId: (Int, Int), id: Int) extends OneWay[M] case object Empty extends OneWay[DFMember.Empty] with DFRef.Empty + final case class DuplicationRef(owner: DFOwnerNamed) extends OneWay[DFOwnerNamed]: + val grpId: (Int, Int) = (-1, -1) + val id: Int = -1 + override def get(using getSet: MemberGetSet): DFOwnerNamed = owner + override def getOption(using getSet: MemberGetSet): Option[DFOwnerNamed] = Some(owner) + sealed trait TwoWay[+M <: DFMember, +O <: DFMember] extends DFRef[M]: def copyAsNewRef(using refGen: RefGen): this.type = refGen.genTwoWay[M, O].asInstanceOf[this.type] @@ -66,6 +72,8 @@ object DFRef: case TypeRef(grpId, id) => s"TR_${grpId._1.toHexString}_${grpId._2.toHexString}_${id}" case TwoWay.Gen(grpId, id) => s"TW_${grpId._1.toHexString}_${grpId._2.toHexString}_${id}" case OneWay.Gen(grpId, id) => s"OW_${grpId._1.toHexString}_${grpId._2.toHexString}_${id}" + case _: DuplicationRef => + throw new IllegalArgumentException("DuplicationRef must never be serialized") , str => if str == "TWE" then TwoWay.Empty.asInstanceOf[T] diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala index 08ded86d6..8c2ad62d2 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala @@ -203,15 +203,11 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: s"${printer.csAnnotations(design.dclMeta.annotations)}$dcl\n" end csDFDesignDefDcl private def csDesignParamList(design: DFDesignBlock): List[String] = - design.members(MemberView.Folded).flatMap { - case param: DesignParam => - param.appliedValRefOpt match - case Some(ref) => Some(s"${param.getName} = ${ref.refCodeString}") - case None => None - case _ => None - } + design.paramMap.view.map { (name, ref) => + s"${name} = ${ref.refCodeString}" + }.toList def csDFDesignDefInst(design: DFDesignBlock): String = - val ports = design.members(MemberView.Folded).view.collect { case port @ DclIn() => + val ports = getSet.designDB.dupPortsByName(design).view.values.collect { case port @ DclIn() => val DFNet.Connection(_, from: DFVal, _) = port.getConnectionTo.get.runtimeChecked printer.csDFValRef(from, design.getOwner) }.mkString(", ") diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ConnectUnused.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ConnectUnused.scala index 3a522c939..d7f13de65 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ConnectUnused.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ConnectUnused.scala @@ -14,27 +14,23 @@ case object ConnectUnused extends Stage: def nullifies: Set[Stage] = Set() def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = given RefGen = RefGen.fromGetSet - val patchList: List[(DFMember, Patch)] = designDB.designMemberList.collect { - // Find all design instances (internal designs) - case (designInst: DFDesignBlock, members) if !designInst.isTop => + val patchList: List[(DFMember, Patch)] = designDB.dupPortsByName.view.collect { + // For design instances + case (designInst, ports) if !designInst.isTop => val designInstPatches = mutable.ListBuffer.empty[(DFMember, Patch)] - // Get all ports for this design instance - val ports = members.view.collect { - case p: DFVal.Dcl if p.isPort => p - } // Find ports annotated with @unused - val unusedPorts = ports.filter { port => + val unusedPorts = ports.view.values.filter { port => port.meta.annotations.exists { case _: annotation.Unused => true case _ => false } - } + }.toList // Create connections to OPEN for unused ports val dsn = new MetaDesign(designInst, Patch.Add.Config.After): for (unusedPort <- unusedPorts) do unusedPort.asDclAny <> OPEN dsn.patch - } + }.toList designDB.patch(patchList) end transform end ConnectUnused diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala index f51e6327b..186390e12 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala @@ -26,222 +26,207 @@ case object GlobalizePortVectorParams extends Stage: case _ => false def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = given RefGen = RefGen.fromGetSet - // to collect unique design parameters while maintaining order for consistent compilation and dependency - val designParams = mutable.LinkedHashSet.empty[DFVal] - // check ref - def checkRef(ref: DFRef.TwoWayAny): Int = - ref.get match - case dfVal: DFVal => - if (dfVal.isGlobal) 0 - else - val ret = dfVal.getRefs.map(checkRef).sum - if (dfVal.isAnonymous) ret - else - designParams += dfVal - ret + 1 - case _ => 0 - // check int param ref - def checkIntParamRef(intParamRef: IntParamRef): Int = - intParamRef.getRef.map(checkRef).getOrElse(0) - - // checking vector types - def checkVector(dfType: DFType): Int = dfType match - case dt: DFVector => - // checking vector dimensions for parameters we need to globalize - val dimParamCnt = dt.cellDimParamRefs.map(checkIntParamRef).sum - // checking vector cell type for composed dependency on parameters we need to globalize - val cellTypeParamCnt = dt.cellType match - case DFBits(widthParamRef) => checkIntParamRef(widthParamRef) - case DFUInt(widthParamRef) => checkIntParamRef(widthParamRef) - case DFSInt(widthParamRef) => checkIntParamRef(widthParamRef) - case dt: DFVector => checkVector(dt.cellType) - case _ => 0 - dimParamCnt + cellTypeParamCnt - case _ => 0 - val vecTypeReplaceMap = mutable.Map.empty[DFVector, DFVector] - // vector type extractor through reference and port by name select - object VectorNetNodeType: - def unapply(ref: DFRefAny): Option[DFVector] = ref.get match - case PortByNameSelect.Of(DFVector.Val(dfType)) => Some(dfType) - case DFVector.Val(dfType) => Some(dfType) - case _ => None - designDB.members.foreach { - // checking all ports - case dcl @ DclPort() => checkVector(dcl.dfType) - // checking all port by name selects that change their type - case pbns @ PortByNameSelect.Of(DFVector.Val(dclType)) if pbns.dfType != dclType => - vecTypeReplaceMap += pbns.dfType.asInstanceOf[DFVector] -> dclType - // checking all assignments/connections between vectors that are considered to be similar types, - // but are not exactly the same (e.g., two vectors types referencing a `(LEN + 1)` length value) - case net @ DFNet(lhsRef = VectorNetNodeType(lhsType), rhsRef = VectorNetNodeType(rhsType)) - if !(lhsType == rhsType) && lhsType.isSimilarTo(rhsType) => - val lhsCnt = checkVector(lhsType) - val rhsCnt = checkVector(rhsType) - // when a PBNS connects to a non-PBNS, always unify to the child design's port type - // so that after globalization both sides share the same instance-specific globals - val lhsIsPbns = net.lhsRef.get.isInstanceOf[DFVal.PortByNameSelect] - val rhsIsPbns = net.rhsRef.get.isInstanceOf[DFVal.PortByNameSelect] - if (lhsIsPbns && !rhsIsPbns) vecTypeReplaceMap += rhsType -> lhsType - else if (rhsIsPbns && !lhsIsPbns) vecTypeReplaceMap += lhsType -> rhsType - else if (lhsCnt < rhsCnt) vecTypeReplaceMap += lhsType -> rhsType - else if (lhsCnt > rhsCnt) vecTypeReplaceMap += rhsType -> lhsType - case _ => - } - // Split vecTypeReplace into PBNS patches and non-PBNS patches. - // PBNS replacements introduce TypeRefs from port declarations (shared TypeRefs). - // If applied in the same patch batch as port replacements (which purge those same TypeRefs), - // the TypeRefs get removed before the PBNS can reference them. - // Apply PBNS replacements first so the shared TypeRefs have a higher repeat count. - val (vecTypePbnsPatches, vecTypeOtherPatches) = designDB.members.collect { - case dfVal @ DFVector.Val(dfType) if vecTypeReplaceMap.contains(dfType) => - dfVal -> Patch.Replace( - dfVal.updateDFType(vecTypeReplaceMap(dfType)), - Patch.Replace.Config.FullReplacement - ) - }.partition((m, _) => m.isInstanceOf[DFVal.PortByNameSelect]) - def movedMembers(namedParam: DFVal): List[DFVal] = - namedParam match - // for DesignParams, also collect anonymous deps of the actual param value - // (from paramMap), since collectRelMembers only follows getRefs which - // does not include the paramMap value reference - case param: DFVal.DesignParam => - param.appliedOrDefaultVal.collectRelMembers(false).filterNot(_.isGlobal) ++ List(param) - case _ => - namedParam.collectRelMembers(true).filterNot(_.isGlobal) - - val addedGlobals = designParams.view.flatMap(movedMembers).toList.distinct - val dupDesignSet = designParams.view.map(_.getOwnerDesign).toSet - // origin -> duplicates design map - val dupDesignMap = dupDesignSet.groupBy(_.dclName).values.map { grp => - val (dups, orig) = grp.partition(_.isDuplicate) - assert(orig.size == 1) - orig.head -> dups.toList - }.toMap - val dupRefTable = mutable.Map.empty[DFRefAny, DFMember] - val dupDesignMembersMap = mutable.Map.empty[DFDesignBlock, List[DFMember]] - // go through all designs that require member duplication from their original design - dupDesignMap.foreach { (orig, dups) => - val origMembers = designDB.designMemberTable(orig) - dups.foreach { dup => - val dupPublicMembers = designDB.designMemberTable(dup) - // orig->dup replacement map memoization for reference mapping in `dupRefTable` - val origToDupMemberMap = mutable.Map.empty[DFMember, DFMember] - // add the designs to the replacement map - origToDupMemberMap += orig -> dup - // collect all public members (they are not necessarily at the top of the design) - val origPublicMembers = origMembers.filterPublicMembers - val origPublicMembersSet = origPublicMembers.toSet - assert(origPublicMembers.length == dupPublicMembers.length) - // adding public members to the orig->dup member replacement map - origPublicMembers.lazyZip(dupPublicMembers).foreach(origToDupMemberMap += _) - // if the replacement map has a member, get its mapped replacement, otherwise the member - // remains as is for reference change - def getReplacement(member: DFMember): DFMember = - origToDupMemberMap.getOrElse(member, member) - val dupMembers = origMembers.map { origMember => - // a public member already has an equivalent as a dup design member - if (origPublicMembersSet.contains(origMember)) origToDupMemberMap(origMember) - // a non-public member needs to be duplicated with new references to be mapped accordingly - else - // duplicate member + // First, reconstruct members for duplicate designs (which have no members in the DB). + // This is needed so the parameter analysis below can find design params for all designs. + val dupDesignDB = + val dupRefTable = mutable.Map.empty[DFRefAny, DFMember] + val dupDesignMembersMap = mutable.Map.empty[DFDesignBlock, List[DFMember]] + designDB.dupDesignToOrigMap.groupBy(_._2).foreach { (orig, dupMap) => + val origMembers = designDB.designMemberTable(orig) + dupMap.keys.foreach { dup => + val origToDupMemberMap = mutable.Map.empty[DFMember, DFMember] + origToDupMemberMap += orig -> dup + def getReplacement(member: DFMember): DFMember = + origToDupMemberMap.getOrElse(member, member) + val dupMembers = origMembers.map { origMember => val dupMember = origMember.copyWithNewRefs - // add to replacement map origToDupMemberMap += origMember -> dupMember - // add duplicated member owner reference dupRefTable += dupMember.ownerRef -> getReplacement(origMember.getOwner) - // add duplicated member relative references origMember.getRefs.lazyZip(dupMember.getRefs).foreach { (origRef, dupRef) => dupRefTable += dupRef -> getReplacement(origRef.get) } dupMember + } + dupDesignMembersMap += dup -> dupMembers } - dupDesignMembersMap += dup -> dupMembers } - } + def populateWithDupMembers(members: List[DFMember]): List[DFMember] = + members.flatMap { + case design: DFDesignBlock => + design :: populateWithDupMembers( + dupDesignMembersMap.getOrElse(design, designDB.designMemberTable(design)) + ) + case member => Some(member) + } + designDB.copy( + members = designDB.membersGlobals ++ populateWithDupMembers(List(designDB.top)), + refTable = designDB.refTable ++ dupRefTable + ) + end dupDesignDB + // Now run the analysis on the reconstructed DB with all design members present. + locally { + given MemberGetSet = dupDesignDB.getSet + // to collect unique design parameters while maintaining order for consistent compilation and dependency + val designParams = mutable.LinkedHashSet.empty[DFVal] + // check ref + def checkRef(ref: DFRef.TwoWayAny): Int = + ref.get match + case dfVal: DFVal => + if (dfVal.isGlobal) 0 + else + val ret = dfVal.getRefs.map(checkRef).sum + if (dfVal.isAnonymous) ret + else + designParams += dfVal + ret + 1 + case _ => 0 + // check int param ref + def checkIntParamRef(intParamRef: IntParamRef): Int = + intParamRef.getRef.map(checkRef).getOrElse(0) - // manually the duplicated members and then using patch for replacing - // the design parameters with global parameters - def populateWithDupMembers(members: List[DFMember]): List[DFMember] = - members.flatMap { - case design: DFDesignBlock => - design :: populateWithDupMembers( - dupDesignMembersMap.getOrElse(design, designDB.designMemberTable(design)) - ) - case member => Some(member) + // checking vector types + def checkVector(dfType: DFType): Int = dfType match + case dt: DFVector => + // checking vector dimensions for parameters we need to globalize + val dimParamCnt = dt.cellDimParamRefs.map(checkIntParamRef).sum + // checking vector cell type for composed dependency on parameters we need to globalize + val cellTypeParamCnt = dt.cellType match + case DFBits(widthParamRef) => checkIntParamRef(widthParamRef) + case DFUInt(widthParamRef) => checkIntParamRef(widthParamRef) + case DFSInt(widthParamRef) => checkIntParamRef(widthParamRef) + case dt: DFVector => checkVector(dt.cellType) + case _ => 0 + dimParamCnt + cellTypeParamCnt + case _ => 0 + val vecTypeReplaceMap = mutable.Map.empty[DFVector, DFVector] + // vector type extractor through reference and port by name select + object VectorNetNodeType: + def unapply(ref: DFRefAny): Option[DFVector] = ref.get match + case PortByNameSelect.Of(DFVector.Val(dfType)) => Some(dfType) + case DFVector.Val(dfType) => Some(dfType) + case _ => None + dupDesignDB.members.foreach { + // checking all ports + case dcl @ DclPort() => checkVector(dcl.dfType) + // checking all port by name selects that change their type + case pbns @ PortByNameSelect.Of(DFVector.Val(dclType)) if pbns.dfType != dclType => + vecTypeReplaceMap += pbns.dfType.asInstanceOf[DFVector] -> dclType + // checking all assignments/connections between vectors that are considered to be similar types, + // but are not exactly the same (e.g., two vectors types referencing a `(LEN + 1)` length value) + case net @ DFNet(lhsRef = VectorNetNodeType(lhsType), rhsRef = VectorNetNodeType(rhsType)) + if !(lhsType == rhsType) && lhsType.isSimilarTo(rhsType) => + val lhsCnt = checkVector(lhsType) + val rhsCnt = checkVector(rhsType) + // when a PBNS connects to a non-PBNS, always unify to the child design's port type + // so that after globalization both sides share the same instance-specific globals + val lhsIsPbns = net.lhsRef.get.isInstanceOf[DFVal.PortByNameSelect] + val rhsIsPbns = net.rhsRef.get.isInstanceOf[DFVal.PortByNameSelect] + if (lhsIsPbns && !rhsIsPbns) vecTypeReplaceMap += rhsType -> lhsType + else if (rhsIsPbns && !lhsIsPbns) vecTypeReplaceMap += lhsType -> rhsType + else if (lhsCnt < rhsCnt) vecTypeReplaceMap += lhsType -> rhsType + else if (lhsCnt > rhsCnt) vecTypeReplaceMap += rhsType -> lhsType + case _ => } - val dupDesignDB = designDB.copy( - members = designDB.membersGlobals ++ populateWithDupMembers(List(designDB.top)), - refTable = designDB.refTable ++ dupRefTable - ) + // Split vecTypeReplace into PBNS patches and non-PBNS patches. + // PBNS replacements introduce TypeRefs from port declarations (shared TypeRefs). + // If applied in the same patch batch as port replacements (which purge those same TypeRefs), + // the TypeRefs get removed before the PBNS can reference them. + // Apply PBNS replacements first so the shared TypeRefs have a higher repeat count. + val (vecTypePbnsPatches, vecTypeOtherPatches) = dupDesignDB.members.collect { + case dfVal @ DFVector.Val(dfType) if vecTypeReplaceMap.contains(dfType) => + dfVal -> Patch.Replace( + dfVal.updateDFType(vecTypeReplaceMap(dfType)), + Patch.Replace.Config.FullReplacement + ) + }.partition((m, _) => m.isInstanceOf[DFVal.PortByNameSelect]) + def movedMembers(namedParam: DFVal): List[DFVal] = + namedParam match + // for DesignParams, also collect anonymous deps of the actual param value + // (from paramMap), since collectRelMembers only follows getRefs which + // does not include the paramMap value reference + case param: DFVal.DesignParam => + param.appliedOrDefaultVal.collectRelMembers(false).filterNot(_.isGlobal) ++ List(param) + case _ => + namedParam.collectRelMembers(true).filterNot(_.isGlobal) - // patches to change the duplicated design declaration names to a unique identifier according - // to their instance names. - // Only remove globalized params from paramMap; keep unglobalized ones intact. - val globalizedParamNamesPerDesign: Map[DFDesignBlock, Set[String]] = - designParams.view.collect { case dp: DFVal.DesignParam => dp } - .groupBy(_.getOwnerDesign) - .view.mapValues(_.map(_.getName).toSet).toMap - def prunedParamMap(design: DFDesignBlock): ListMap[String, DFDesignBlock.ParamRef] = - val toRemove = globalizedParamNamesPerDesign.getOrElse(design, Set.empty) - if (toRemove.isEmpty) design.paramMap - else design.paramMap.filterNot((name, _) => toRemove.contains(name)) - val designPatches = dupDesignMap.view.flatMap { - case (orig, dups) if dups.length >= 1 => - (orig :: dups).map { design => - val updatedDclMeta = - design.dclMeta.setName( - s"${design.dclName}_${design.getFullName.replaceAll("\\.", "_")}" + val addedGlobals = designParams.view.flatMap(movedMembers).toList.distinct + val dupDesignSet = designParams.view.map(_.getOwnerDesign).toSet + // origin -> duplicates design map + val dupDesignMap = dupDesignSet.groupBy(_.dclName).values.map { grp => + val (dups, orig) = grp.partition(_.isDuplicate) + assert(orig.size == 1) + orig.head -> dups.toList + }.toMap + + // patches to change the duplicated design declaration names to a unique identifier according + // to their instance names. + // Only remove globalized params from paramMap; keep unglobalized ones intact. + val globalizedParamNamesPerDesign: Map[DFDesignBlock, Set[String]] = + designParams.view.collect { case dp: DFVal.DesignParam => dp } + .groupBy(_.getOwnerDesign) + .view.mapValues(_.map(_.getName).toSet).toMap + def prunedParamMap(design: DFDesignBlock): ListMap[String, DFDesignBlock.ParamRef] = + val toRemove = globalizedParamNamesPerDesign.getOrElse(design, Set.empty) + if (toRemove.isEmpty) design.paramMap + else design.paramMap.filterNot((name, _) => toRemove.contains(name)) + val designPatches = dupDesignMap.view.flatMap { + case (orig, dups) if dups.length >= 1 => + (orig :: dups).map { design => + val updatedDclMeta = + design.dclMeta.setName( + s"${design.dclName}_${design.getFullName.replaceAll("\\.", "_")}" + ) + val updatedDesign = design.removeTagOf[DuplicateTag].copy( + dclMeta = updatedDclMeta, + paramMap = prunedParamMap(design) ) - val updatedDesign = design.removeTagOf[DuplicateTag].copy( - dclMeta = updatedDclMeta, - paramMap = prunedParamMap(design) + design -> Patch.Replace(updatedDesign, Patch.Replace.Config.FullReplacement) + } + case (orig, _) => + Some( + orig -> + Patch.Replace( + orig.copy(paramMap = prunedParamMap(orig)), + Patch.Replace.Config.FullReplacement + ) ) - design -> Patch.Replace(updatedDesign, Patch.Replace.Config.FullReplacement) + }.toList + val paramReplacementMap = mutable.Map.empty[DFVal, DFVal] + def getUpdatedParamValue(param: DFVal.DesignParam): DFVal = + var dfVal = param.appliedOrDefaultVal + while (paramReplacementMap.contains(dfVal)) dfVal = paramReplacementMap(dfVal) + dfVal + // add global parameters before the design top + val dsn = new MetaDesign(dupDesignDB.top, Patch.Add.Config.Before): + // patches to replace with properly named parameter or just move the anonymous members + val replacePatches = addedGlobals.map { + // design parameters are transformed into global as-is named aliases + case param: DFVal.DesignParam => + val updatedMeta = param.meta.setName(param.getFullName.replaceAll("\\.", "_")) + val updatedParamVal = getUpdatedParamValue(param) + val globalParam = dfhdl.core.DFVal.Alias.AsIs.forced( + param.dfType, + updatedParamVal + )(using dfc.setMeta(updatedMeta)) + paramReplacementMap += param -> globalParam + param -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) + case m: DFVal if !m.isAnonymous => + val globalParam = m.setName(m.getFullName.replaceAll("\\.", "_")) + plantMember(globalParam) + paramReplacementMap += m -> globalParam + m -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) + case m => + plantMember(m) + m -> Patch.Remove(isMoved = true) } - case (orig, _) => - Some( - orig -> - Patch.Replace( - orig.copy(paramMap = prunedParamMap(orig)), - Patch.Replace.Config.FullReplacement - ) - ) - }.toList - val paramReplacementMap = mutable.Map.empty[DFVal, DFVal] - def getUpdatedParamValue(param: DFVal.DesignParam): DFVal = - var dfVal = param.appliedOrDefaultVal - while (paramReplacementMap.contains(dfVal)) dfVal = paramReplacementMap(dfVal) - dfVal - // add global parameters before the design top - val dsn = new MetaDesign(dupDesignDB.top, Patch.Add.Config.Before): - // patches to replace with properly named parameter or just move the anonymous members - val replacePatches = addedGlobals.map { - // design parameters are transformed into global as-is named aliases - case param: DFVal.DesignParam => - val updatedMeta = param.meta.setName(param.getFullName.replaceAll("\\.", "_")) - val updatedParamVal = getUpdatedParamValue(param) - val globalParam = dfhdl.core.DFVal.Alias.AsIs.forced( - param.dfType, - updatedParamVal - )(using dfc.setMeta(updatedMeta)) - paramReplacementMap += param -> globalParam - param -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) - case m: DFVal if !m.isAnonymous => - val globalParam = m.setName(m.getFullName.replaceAll("\\.", "_")) - plantMember(globalParam) - paramReplacementMap += m -> globalParam - m -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) - case m => - plantMember(m) - m -> Patch.Remove(isMoved = true) - } - // TODO: when combined to a single patch, there is a bug that prevents some members to get a global ownership - // Apply PBNS type patches first (they introduce shared TypeRefs), then other type patches - // (which may purge those same TypeRefs from the originals). - dupDesignDB - .patch(dsn.patch :: dsn.replacePatches ++ vecTypePbnsPatches) - .patch(vecTypeOtherPatches) - .patch(designPatches) + // TODO: when combined to a single patch, there is a bug that prevents some members to get a global ownership + // Apply PBNS type patches first (they introduce shared TypeRefs), then other type patches + // (which may purge those same TypeRefs from the originals). + dupDesignDB + .patch(dsn.patch :: dsn.replacePatches ++ vecTypePbnsPatches) + .patch(vecTypeOtherPatches) + .patch(designPatches) + } end transform end GlobalizePortVectorParams diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala index ba6a463b5..a6994dfd5 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala @@ -57,7 +57,7 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: case pbns: DFVal.PortByNameSelect => val design = pbns.designInstRef.get // check port existence - getSet.designDB.portsByName(design).get(pbns.portNamePath) match + getSet.designDB.dupPortsByName(design).get(pbns.portNamePath) match case None => reportViolation( s"Missing port ${pbns.portNamePath} for by-name port selection: ${pbns}" @@ -165,6 +165,18 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: ) case _ => } + // check that no DuplicationRef exists in refTable + refTable.foreach { (ref, _) => + if (ref.isInstanceOf[DFRef.DuplicationRef]) + reportViolation(s"DuplicationRef found in refTable: $ref") + } + // check that no member has a DuplicationRef ownerRef + getSet.designDB.members.foreach { member => + if (member.ownerRef.isInstanceOf[DFRef.DuplicationRef]) + reportViolation( + s"Member with DuplicationRef ownerRef found in members: $member" + ) + } require(!hasViolations, "Failed reference check!") end refCheck private def memberExistenceCheck()(using MemberGetSet): Unit = diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ViaConnection.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ViaConnection.scala index a2bc18ad0..78f24cc94 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ViaConnection.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ViaConnection.scala @@ -15,7 +15,8 @@ case object ViaConnection extends Stage: case (ib, members) if !ib.isTop => // getting only ports that are not already connected to variables val (ports, nets): (List[DFVal.Dcl], List[DFNet]) = - members.foldRight((List.empty[DFVal.Dcl], List.empty[DFNet])) { + val ports = designDB.dupPortsByName(ib).values + ports.foldRight((List.empty[DFVal.Dcl], List.empty[DFNet])) { case (p @ DclOut(), (ports, nets)) => val conns = p.getConnectionsFrom conns.headOption match @@ -43,6 +44,7 @@ case object ViaConnection extends Stage: case _ => (p :: ports, nets) case (_, x) => x } + end val extension (port: DFVal.Dcl) // set reachable type parameters for the selected ports by setting the MetaDesign context's diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala index db9163e19..22fd55f62 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala @@ -162,13 +162,9 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: |""".stripMargin def csDFDesignBlockInst(design: DFDesignBlock): String = val body = csDFDesignLateBody(design) - val designParamList = design.members(MemberView.Folded).flatMap { - case param: DesignParam => - param.appliedValRefOpt match - case Some(ref) => Some(s".${param.getName} (${ref.refCodeString})") - case None => None - case _ => None - } + val designParamList = design.paramMap.view.map { (name, ref) => + s".${name} (${ref.refCodeString})" + }.toList val designParamCS = if (designParamList.isEmpty || design.isVendorIPBlackbox) "" else " #(" + designParamList.mkString("\n", ",\n", "\n").hindent(1) + ")" diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala index a4a0815d9..aad9c18f7 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala @@ -177,13 +177,9 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: end csDFDesignBlockDcl def csDFDesignBlockInst(design: DFDesignBlock): String = val body = csDFDesignLateBody(design) - val designParamList = design.members(MemberView.Folded).flatMap { - case param: DesignParam => - param.appliedValRefOpt match - case Some(ref) => Some(s"${param.getName} => ${ref.refCodeString}") - case None => None - case _ => None - } + val designParamList = design.paramMap.view.map { (name, ref) => + s"${name} => ${ref.refCodeString}" + }.toList val designParamCS = if (designParamList.isEmpty || design.isVendorIPBlackbox) "" else " generic map (" + designParamList.mkString("\n", ",\n", "\n").hindent(1) + ")" diff --git a/core/src/main/scala/dfhdl/core/MutableDB.scala b/core/src/main/scala/dfhdl/core/MutableDB.scala index 152f3531a..5e95d8bbf 100644 --- a/core/src/main/scala/dfhdl/core/MutableDB.scala +++ b/core/src/main/scala/dfhdl/core/MutableDB.scala @@ -210,15 +210,16 @@ final class MutableDB(): // first time encountering this design type, so add the first group case None => uniqueDesigns += designType -> List(List(design)) end match - // generally, if this design is a duplicate we want to add only the by-name members and their - // owner references. however, if current design context is known to be a duplicate (as a result - // of a `hw.pure` annotation), then we can skip this extra step since the design context is - // already minimized to the named members. + // If this design is a duplicate, we retain only the public members (ports, design + // parameters, domain blocks, and their dependencies) during elaboration, because + // user code may still reference them (e.g., connecting to a port requires the Dcl + // before a PortByNameSelect is created). These public members are later removed + // during immutable DB creation (see `immutable`), where ports are resolved + // on-demand via DuplicationRef in `DB.dupPortsByName`. + // If the current design context is already known to be a duplicate (as a result + // of a `hw.pure` annotation), then we can skip this extra step since the design + // context is already minimized to the named members. if (isDuplicate && !current.isDuplicate) - // public members are ports, design design parameters, and - // design domains. for design parameters we also get dependencies. - // all these members are interacted with outside the design, - // so they are kept as duplicates in the design instances val publicMembers = currentMembers.filterPublicMembers designMembers += design -> publicMembers val transferredRefs = @@ -581,6 +582,7 @@ final class MutableDB(): case (ref: DFRef.TypeRef, m) => usedTypeRefs.contains(ref) case _ => true }.toMap + val duplicateDesignSet = mutable.Set.empty[DFDesignBlock] val duplicateDesignRepMap = DesignContext.uniqueDesigns.view.flatMap { case (designType, groupList) => groupList.view.reverse.zipWithIndex.flatMap { @@ -594,7 +596,9 @@ final class MutableDB(): if (first) first = false design.tags - else design.tags.tag(DuplicateTag) + else + duplicateDesignSet += design + design.tags.tag(DuplicateTag) design -> design.copy( dclMeta = design.dclMeta.copy(nameOpt = Some(updatedDclName)), tags = tags @@ -622,7 +626,24 @@ final class MutableDB(): case dcl: DFVal.Dcl => constrainedDcls.getOrElse(dcl, dcl) case m => m } - (members.map(finalFixFunc), fixedRefTable.view.mapValues(finalFixFunc).toMap) + // Remove all remaining public members (ports, domain blocks, and their + // dependencies) from duplicate designs. During elaboration these were kept + // so user code could reference them, but in the immutable DB they are no + // longer needed. Ports for duplicate designs are resolved on-demand via + // DuplicationRef in `DB.dupPortsByName`. + val redundantRefs = mutable.Set.empty[DFRefAny] + val finalMembers = members.flatMap { + case m: DFVal if m.isGlobal => Some(finalFixFunc(m)) + case m: (DomainBlock | DFVal) if duplicateDesignSet.contains(m.getOwnerDesign) => + redundantRefs += m.ownerRef + redundantRefs ++= m.getRefs + None + case m => Some(finalFixFunc(m)) + } + val finalRefTable = fixedRefTable.view.flatMap { case (ref, member) => + if (redundantRefs.contains(ref)) None else Some(ref -> finalFixFunc(member)) + }.toMap + (finalMembers, finalRefTable) val membersNoGlobalCtx = members.map { case m: DFVal.CanBeGlobal => m.copyWithoutGlobalCtx case m => m From 16f454a4fbe72a2a496e2f5088d0734b3d7d903a Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 29 Mar 2026 11:38:16 +0300 Subject: [PATCH 062/115] fix last stage errors after better design deduplication. still have elaboration check errors. --- .../compiler/printing/DFOwnerPrinter.scala | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala index 8c2ad62d2..f810e66d9 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala @@ -26,14 +26,20 @@ trait AbstractOwnerPrinter extends AbstractPrinter: // a def design that is anonymous may not be referenced later, // so we need to check if it has an output port that is referenced later case design: DFDesignBlock if design.instMode == InstMode.Def && design.isAnonymous => - design.members(MemberView.Folded).view.reverse.collectFirst { case port @ DclOut() => + // For duplicate designs, ports may not be in the members list but are + // available via dupPortsByName (with DuplicationRef owners). + // For DuplicationRef-backed ports, we check the PBNS read deps instead. + val ports = getSet.designDB.dupPortsByName(design).view.values.collect { + case port @ DclOut() => port + } + val hasOutput = ports.lastOption.map(port => // no dependencies means the output is not read (referenced later), // so we need to print now - port.getReadDeps.isEmpty - } - // no output port means a Unit return that cannot be referenced, - // so we need to print it now - .getOrElse(true) + port.getPortsByNameSelectors.forall(_.getReadDeps.isEmpty) + ) + // no output port means a Unit return that cannot be referenced, + // so we need to print it now + hasOutput.getOrElse(true) // named members case m: DFMember.Named if !m.isAnonymous => true // excluding late (via) connections From 70723b73ec47b3d2885934140966d69ce0de3c94 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 30 Mar 2026 00:53:01 +0300 Subject: [PATCH 063/115] wip --- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 141 ++++++++++++------ .../scala/dfhdl/compiler/ir/DFMember.scala | 6 + .../main/scala/dfhdl/compiler/ir/DFRef.scala | 5 +- 3 files changed, 102 insertions(+), 50 deletions(-) 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 49f0e6ca6..aa3b1c7ea 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -43,31 +43,6 @@ final case class DB( // considered to be in build if not in simulation and has a device constraint lazy val inBuild: Boolean = !inSimulation && top.isDeviceTop - lazy val dupPortsByName: Map[DFDesignInst, ListMap[String, DFVal.Dcl]] = - val origMap = members.view - .collect { case m: DFVal.Dcl if m.isPort => m } - .groupBy(_.getOwnerDesign) - .map { case (design, dcls) => - design -> ListMap.from(dcls.view.map(m => m.getRelativeName(design) -> m)) - }.toMap - // Add entries for duplicate designs by copying origin Dcls with DuplicationRef owner. - // PBNS members carry the correct dfType for each duplicate's port (reflecting the - // actual instantiation parameters), so we use it to override the origin Dcl's dfType. - val pbnsByDesign = members.view - .collect { case m: DFVal.PortByNameSelect => m } - .groupBy(m => m.designInstRef.get) - .view.mapValues(_.map(m => m.portNamePath -> m.dfType).toMap).toMap - val dupEntries = dupDesignToOrigMap.map { (dupDesign, origDesign) => - val pbnsTypes = pbnsByDesign.getOrElse(dupDesign, Map.empty) - dupDesign -> ListMap.from(origMap(origDesign).view.map { (name, dcl) => - val dfType = pbnsTypes.getOrElse(name, dcl.dfType) - name -> dcl.copy(ownerRef = DFRef.DuplicationRef(dupDesign), dfType = dfType) - }) - } - // dupEntries only fills in missing entries (designs without real port members) - dupEntries ++ origMap - end dupPortsByName - lazy val topIOs: List[DFVal.Dcl] = designMemberTable(top).collect { case dcl: DFVal.Dcl if dcl.isPort => dcl } @@ -281,6 +256,10 @@ final case class DB( List(top -> List()) ).reverse + // holds a hash table that lists members of each owner block. The member list order is maintained. + lazy val designMemberTable: Map[DFDesignBlock, List[DFMember]] = + Map(designMemberList*) + // holds the topological order of unique design block dependency lazy val uniqueDesignMemberList: List[(DFDesignBlock, List[DFMember])] = designMemberList.filterNot(_._1.isDuplicate) @@ -293,32 +272,98 @@ final case class DB( case (design, _) if design.isDuplicate => design -> origByName(design.dclName) }.toMap - // maps each design (origin and duplicates) to its domain blocks. - // For duplicates, domain blocks are cloned from the origin with DuplicationRef owners. + // Maps each DomainBlock to a copy with DuplicationRef as ownerRef. // Nested domain blocks get DuplicationRef pointing to the duplicated parent block. - lazy val dupDesignDomainBlockMap: Map[DFDesignBlock, List[DomainBlock]] = - val origDomainBlocks: Map[DFDesignBlock, List[DomainBlock]] = - members.view - .collect { case db: DomainBlock => db } - .groupBy(_.getOwnerDesign) - .map { (design, blocks) => design -> blocks.toList } - .toMap - val dupDomainBlocks = dupDesignToOrigMap.map { (dupDesign, origDesign) => - val origToDupMap = mutable.Map.empty[DFDomainOwner, DFDomainOwner] - origToDupMap += origDesign -> dupDesign - dupDesign -> origDomainBlocks.getOrElse(origDesign, Nil).map { origBlock => - val dupOwner = origToDupMap(origBlock.getOwnerDomain) - val dupBlock = origBlock.copy(ownerRef = DFRef.DuplicationRef(dupOwner)) - origToDupMap += origBlock -> dupBlock - dupBlock - } + lazy val dupDomainBlockToOrigMap: Map[DomainBlock, DomainBlock] = + val origToDupMap = mutable.Map.empty[DFDomainOwner, DFDomainOwner] + members.collect { case db: DomainBlock => + val ownerDomain = db.getOwnerDomain + val dupBlock = + db.copy(ownerRef = DFRef.DuplicationRef(origToDupMap.getOrElse(ownerDomain, ownerDomain))) + origToDupMap += db -> dupBlock + dupBlock -> db + }.toMap + + lazy val dupPortsByName: Map[DFDesignInst, ListMap[String, DFVal.Dcl]] = + val origMap = members.view + .collect { case m: DFVal.Dcl if m.isPort => m } + .groupBy(_.getOwnerDesign) + .map { case (design, dcls) => + design -> ListMap.from(dcls.view.map(m => m.getRelativeName(design) -> m)) + }.toMap + // Add entries for duplicate designs by copying origin Dcls with DuplicationRef owner. + // PBNS members carry the correct dfType for each duplicate's port (reflecting the + // actual instantiation parameters), so we use it to override the origin Dcl's dfType. + val pbnsByDesign = members.view + .collect { case m: DFVal.PortByNameSelect => m } + .groupBy(m => m.designInstRef.get) + .view.mapValues(_.map(m => m.portNamePath -> m.dfType).toMap).toMap + val dupDomainBlocksByFullName = dupDomainBlockToOrigMap.map { case (dup, orig) => + orig.getFullName -> dup } - origDomainBlocks ++ dupDomainBlocks - end dupDesignDomainBlockMap + val dupEntries = dupDesignToOrigMap.map { (dupDesign, origDesign) => + val pbnsTypes = pbnsByDesign.getOrElse(dupDesign, Map.empty) + dupDesign -> ListMap.from(origMap(origDesign).view.map { (name, dcl) => + val dfType = pbnsTypes.getOrElse(name, dcl.dfType) + val dupOwnerDomain = dcl.getOwnerDomain match + case _: DFDesignBlock => dupDesign + case domainBlock: DomainBlock => dupDomainBlocksByFullName(domainBlock.getFullName) + // TODO: missing interface handling + case interface: DFInterfaceOwner => ??? + name -> dcl.copy(ownerRef = DFRef.DuplicationRef(dupOwnerDomain), dfType = dfType) + }) + } + // dupEntries only fills in missing entries (designs without real port members) + dupEntries ++ origMap + end dupPortsByName - // holds a hash table that lists members of each owner block. The member list order is maintained. - lazy val designMemberTable: Map[DFDesignBlock, List[DFMember]] = - Map(designMemberList*) + lazy val dupDomainOwnerPublicMemberList: List[(DFDomainOwner, List[DFMember])] = + val dupDomainBlocksByFullName = dupDomainBlockToOrigMap.map { case (dup, orig) => + orig.getFullName -> dup + } + val dupDesignsByFullName = dupDesignToOrigMap.map { case (dup, orig) => + orig.getFullName -> dup + } + val dupPortsByFullName = dupPortsByName.view.values.flatMap { case ports => + ports.map { case (_, dcl) => dcl.getFullName -> dcl } + }.toMap + def getDupMember[M <: DFMember](m: M): M = + m.runtimeChecked match + case dcl: DFVal.Dcl => + dupPortsByFullName.getOrElse(dcl.getFullName, dcl).asInstanceOf[M] + case design: DFDesignBlock => + dupDesignsByFullName.getOrElse(design.getFullName, design).asInstanceOf[M] + case domainBlock: DomainBlock => + dupDomainBlocksByFullName.getOrElse(domainBlock.getFullName, domainBlock).asInstanceOf[M] + def publicMemberFilter(member: DFMember): Boolean = + member match + case dcl: DFVal.Dcl if dcl.isPort => true + case design: DFDesignBlock => true + case domainBlock: DomainBlock => true + case _ => false + domainOwnerMemberList.map { case (owner, members) => + owner match + case design: DFDesignBlock => + if (design.isDuplicate) + val dupMembers = + domainOwnerMemberTable(design) + .view.filter(publicMemberFilter).map(getDupMember).toList + getDupMember(design) -> dupMembers + else owner -> members.filter(publicMemberFilter) + case domainBlock: DomainBlock => + if (domainBlock.isDuplicate) + val dupMembers = + domainOwnerMemberTable(domainBlock) + .view.filter(publicMemberFilter).map(getDupMember).toList + getDupMember(domainBlock) -> dupMembers + else owner -> members.filter(publicMemberFilter) + // TODO: missing interface handling + case interface: DFInterfaceOwner => ??? + } + end dupDomainOwnerPublicMemberList + + lazy val dupDomainOwnerPublicMemberTable: Map[DFDomainOwner, List[DFMember]] = + Map(dupDomainOwnerPublicMemberList*) private def conditionalChainGen: Map[DFConditional.Header, List[DFConditional.Block]] = val handled = mutable.Set.empty[DFConditional.Block] diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index 1b40298bc..f645fbb32 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -1589,6 +1589,12 @@ final case class DomainBlock( ).asInstanceOf[this.type] end DomainBlock +object DomainBlock: + extension (domainBlock: DomainBlock) + def isDuplicate: Boolean = domainBlock.ownerRef match + case _: DFRef.DuplicationRef => true + case _ => false + // sealed trait Timer extends DFMember.Named // object Timer: // type Ref = DFRef.TwoWay[Timer, DFMember] diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala index bd2349b45..dbdd1edfc 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala @@ -21,7 +21,7 @@ object DFRef: val id: Int = 0 override def get(using getSet: MemberGetSet): DFMember.Empty = DFMember.Empty sealed trait OneWay[+M <: DFMember] extends DFRef[M]: - final def copyAsNewRef(using refGen: RefGen): this.type = + def copyAsNewRef(using refGen: RefGen): this.type = refGen.genOneWay[M].asInstanceOf[this.type] object OneWay: final case class Gen[M <: DFMember](grpId: (Int, Int), id: Int) extends OneWay[M] @@ -32,6 +32,7 @@ object DFRef: val id: Int = -1 override def get(using getSet: MemberGetSet): DFOwnerNamed = owner override def getOption(using getSet: MemberGetSet): Option[DFOwnerNamed] = Some(owner) + override def copyAsNewRef(using refGen: RefGen): this.type = this sealed trait TwoWay[+M <: DFMember, +O <: DFMember] extends DFRef[M]: def copyAsNewRef(using refGen: RefGen): this.type = @@ -72,7 +73,7 @@ object DFRef: case TypeRef(grpId, id) => s"TR_${grpId._1.toHexString}_${grpId._2.toHexString}_${id}" case TwoWay.Gen(grpId, id) => s"TW_${grpId._1.toHexString}_${grpId._2.toHexString}_${id}" case OneWay.Gen(grpId, id) => s"OW_${grpId._1.toHexString}_${grpId._2.toHexString}_${id}" - case _: DuplicationRef => + case _: DuplicationRef => throw new IllegalArgumentException("DuplicationRef must never be serialized") , str => From 2bed64741d8c3bf979d6328a219ce458f86a574f Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 30 Mar 2026 01:33:11 +0300 Subject: [PATCH 064/115] wip --- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) 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 aa3b1c7ea..825b0b36c 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -343,20 +343,22 @@ final case class DB( case _ => false domainOwnerMemberList.map { case (owner, members) => owner match - case design: DFDesignBlock => - if (design.isDuplicate) - val dupMembers = - domainOwnerMemberTable(design) - .view.filter(publicMemberFilter).map(getDupMember).toList - getDupMember(design) -> dupMembers - else owner -> members.filter(publicMemberFilter) - case domainBlock: DomainBlock => - if (domainBlock.isDuplicate) - val dupMembers = - domainOwnerMemberTable(domainBlock) - .view.filter(publicMemberFilter).map(getDupMember).toList - getDupMember(domainBlock) -> dupMembers - else owner -> members.filter(publicMemberFilter) + case dupDesign: DFDesignBlock if dupDesign.isDuplicate => + val origDesign = dupDesignToOrigMap(dupDesign) + val dupMembers = + domainOwnerMemberTable(origDesign) + .view.filter(publicMemberFilter).map(getDupMember).toList + dupDesign -> dupMembers + case origDesign: DFDesignBlock => + origDesign -> members.filter(publicMemberFilter) + case dupDomainBlock: DomainBlock if dupDomainBlock.isDuplicate => + val origDomainBlock = dupDomainBlockToOrigMap(dupDomainBlock) + val dupMembers = + domainOwnerMemberTable(origDomainBlock) + .view.filter(publicMemberFilter).map(getDupMember).toList + dupDomainBlock -> dupMembers + case origDomainBlock: DomainBlock => + origDomainBlock -> members.filter(publicMemberFilter) // TODO: missing interface handling case interface: DFInterfaceOwner => ??? } @@ -766,53 +768,51 @@ final case class DB( owner.domainType match case _: DomainType.RT => Some(owner) case _ => None - members.view.flatMap { - case domainOwner: DFDomainOwner => - domainOwner.domainType match - // only RT domain owners are saved - case DomainType.RT(cfg) => - cfg match - // derived configuration dependency is set according to various factors: - case RTDomainCfg.Derived => - domainOwner match - // for designs, the derived configuration is defined by the owner RT design, if such exists. - // if not, then there is no domain configuration dependency - case design: DFDesignBlock => - if (design.isTop) None - else design.getRTOwnerOption.map(design -> _) - // for domains, the derived configuration is defined according to the input ports source, - // if such ports exist (ignoring Clk/Rst ports). - // otherwise, the derived configuration is defined by the domain's owner. - case domain: DomainBlock => - val domainMembers = domainOwnerMemberTable(domain) - val inPorts = domainMembers.collect { - case dcl: DFVal.Dcl if dcl.isPortIn && !dcl.isClkDcl && !dcl.isRstDcl => dcl - } - val inSourceDomains = inPorts.view.flatMap { port => - connectionTable.getNets(port).headOption match - case Some(DFNet.Connection(_, from, _)) => from.getRTOwnerOption - case _ => None - }.toSet - if (inSourceDomains.isEmpty) domain.getRTOwnerOption.map(domain -> _) - else if (inSourceDomains.size > 1) - throw new IllegalArgumentException( - s"""|Found ambiguous source RT configurations for the domain: - |${domain.getFullName} - |Sources: - |${inSourceDomains.map(_.getFullName).mkString("\n")} - |Possible solution: - |Either explicitly define a configuration for the domain or drive it from a single source domain. - |""".stripMargin - ) - else Some(domain -> inSourceDomains.head) - case ifc: DFInterfaceOwner => - ??? // TODO: decide what are the rules are for interfaces - // related configuration is just dependent on the its related domain - case RTDomainCfg.Related(relatedDomainRef) => - Some(domainOwner -> relatedDomainRef.get) - case _ => None - case _ => None - case _ => None + // Use dupDomainOwnerPublicMemberList to include domain owners from duplicate designs + dupDomainOwnerPublicMemberList.view.flatMap { (domainOwner, domainMembers) => + domainOwner.domainType match + // only RT domain owners are saved + case DomainType.RT(cfg) => + cfg match + // derived configuration dependency is set according to various factors: + case RTDomainCfg.Derived => + domainOwner match + // for designs, the derived configuration is defined by the owner RT design, if such exists. + // if not, then there is no domain configuration dependency + case design: DFDesignBlock => + if (design.isTop) None + else design.getRTOwnerOption.map(design -> _) + // for domains, the derived configuration is defined according to the input ports source, + // if such ports exist (ignoring Clk/Rst ports). + // otherwise, the derived configuration is defined by the domain's owner. + case domain: DomainBlock => + val inPorts = domainMembers.collect { + case dcl: DFVal.Dcl if dcl.isPortIn && !dcl.isClkDcl && !dcl.isRstDcl => dcl + } + val inSourceDomains = inPorts.view.flatMap { port => + connectionTable.getNets(port).headOption match + case Some(DFNet.Connection(_, from, _)) => from.getRTOwnerOption + case _ => None + }.toSet + if (inSourceDomains.isEmpty) domain.getRTOwnerOption.map(domain -> _) + else if (inSourceDomains.size > 1) + throw new IllegalArgumentException( + s"""|Found ambiguous source RT configurations for the domain: + |${domain.getFullName} + |Sources: + |${inSourceDomains.map(_.getFullName).mkString("\n")} + |Possible solution: + |Either explicitly define a configuration for the domain or drive it from a single source domain. + |""".stripMargin + ) + else Some(domain -> inSourceDomains.head) + case ifc: DFInterfaceOwner => + ??? // TODO: decide what are the rules are for interfaces + // related configuration is just dependent on the its related domain + case RTDomainCfg.Related(relatedDomainRef) => + Some(domainOwner -> relatedDomainRef.get) + case _ => None + case _ => None } .toMap end dependentRTDomainOwners From be5c3cb7c51b481cbfc47228c80166438fb1353d Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 30 Mar 2026 03:03:36 +0300 Subject: [PATCH 065/115] fix duplication logic to recursively handle nested design instances in GlobalizePortVectorParams --- .../stages/GlobalizePortVectorParams.scala | 45 +++++++++++------ .../GlobalizePortVectorParamsSpec.scala | 48 +++++++++++++++++++ 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala index 186390e12..efd3faa98 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala @@ -31,24 +31,39 @@ case object GlobalizePortVectorParams extends Stage: val dupDesignDB = val dupRefTable = mutable.Map.empty[DFRefAny, DFMember] val dupDesignMembersMap = mutable.Map.empty[DFDesignBlock, List[DFMember]] - designDB.dupDesignToOrigMap.groupBy(_._2).foreach { (orig, dupMap) => + // Duplicate all members of a design, recursively handling nested designs. + def duplicateDesignMembers( + orig: DFDesignBlock, + dup: DFDesignBlock + ): Unit = val origMembers = designDB.designMemberTable(orig) - dupMap.keys.foreach { dup => - val origToDupMemberMap = mutable.Map.empty[DFMember, DFMember] - origToDupMemberMap += orig -> dup - def getReplacement(member: DFMember): DFMember = - origToDupMemberMap.getOrElse(member, member) - val dupMembers = origMembers.map { origMember => - val dupMember = origMember.copyWithNewRefs - origToDupMemberMap += origMember -> dupMember - dupRefTable += dupMember.ownerRef -> getReplacement(origMember.getOwner) - origMember.getRefs.lazyZip(dupMember.getRefs).foreach { (origRef, dupRef) => - dupRefTable += dupRef -> getReplacement(origRef.get) - } - dupMember + val origToDupMemberMap = mutable.Map.empty[DFMember, DFMember] + origToDupMemberMap += orig -> dup + def getReplacement(member: DFMember): DFMember = + origToDupMemberMap.getOrElse(member, member) + val dupMembers = origMembers.map { origMember => + val dupMember0 = origMember.copyWithNewRefs + // Tag nested design blocks as duplicates + val dupMember = dupMember0 match + case dsn: DFDesignBlock => dsn.setTags(_.tag(DuplicateTag)).asInstanceOf[dupMember0.type] + case _ => dupMember0 + origToDupMemberMap += origMember -> dupMember + dupRefTable += dupMember.ownerRef -> getReplacement(origMember.getOwner) + origMember.getRefs.lazyZip(dupMember.getRefs).foreach { (origRef, dupRef) => + dupRefTable += dupRef -> getReplacement(origRef.get) } - dupDesignMembersMap += dup -> dupMembers + dupMember + } + dupDesignMembersMap += dup -> dupMembers + // Recursively duplicate nested designs that were also removed + origMembers.lazyZip(dupMembers).foreach { + case (origNested: DFDesignBlock, dupNested: DFDesignBlock) + if !designDB.designMemberTable.contains(dupNested) => + duplicateDesignMembers(origNested, dupNested) + case _ => } + designDB.dupDesignToOrigMap.groupBy(_._2).foreach { (orig, dupMap) => + dupMap.keys.foreach { dup => duplicateDesignMembers(orig, dup) } } def populateWithDupMembers(members: List[DFMember]): List[DFMember] = members.flatMap { diff --git a/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala index 26164cdbb..a38402cdf 100644 --- a/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala @@ -319,4 +319,52 @@ class GlobalizePortVectorParamsSpec extends StageSpec(stageCreatesUnrefAnons = t | y3 <> id3.y |end IDTop""".stripMargin ) + + test("Duplicate designs with nested design instances"): + class ID extends EDDesign: + val x = Bits(8) <> IN + val y = Bits(8) <> OUT + y <> x + end ID + + class Internal(val param: Bits[8] <> CONST) extends EDDesign: + val x = Bits(8) <> IN + val y = Bits(8) <> OUT + val id = ID() + id.x <> x + y <> id.y + end Internal + + class Foo extends EDDesign: + val state = Bits(8) <> IN + val o00 = Internal(param = h"02") + val o01 = Internal(param = h"01") + o00.x <> state + o01.x <> state + val top = (new Foo).globalizePortVectorParams + assertCodeString( + top, + """|class ID extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | y <> x + |end ID + | + |class Internal(val param: Bits[8] <> CONST) extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | val id = ID() + | id.x <> x + | y <> id.y + |end Internal + | + |class Foo extends EDDesign: + | val state = Bits(8) <> IN + | val o00 = Internal(param = h"02") + | val o01 = Internal(param = h"01") + | o00.x <> state + | o01.x <> state + |end Foo + |""".stripMargin + ) end GlobalizePortVectorParamsSpec From 488ee5a1fa40113348fed5d079637ab3fd4c3bc9 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 30 Mar 2026 04:20:28 +0300 Subject: [PATCH 066/115] zero members in deduplication. all tests are green. --- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 95 ++++++++++--------- 1 file changed, 50 insertions(+), 45 deletions(-) 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 825b0b36c..47ccf1f88 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -272,17 +272,26 @@ final case class DB( case (design, _) if design.isDuplicate => design -> origByName(design.dclName) }.toMap - // Maps each DomainBlock to a copy with DuplicationRef as ownerRef. - // Nested domain blocks get DuplicationRef pointing to the duplicated parent block. - lazy val dupDomainBlockToOrigMap: Map[DomainBlock, DomainBlock] = - val origToDupMap = mutable.Map.empty[DFDomainOwner, DFDomainOwner] - members.collect { case db: DomainBlock => - val ownerDomain = db.getOwnerDomain - val dupBlock = - db.copy(ownerRef = DFRef.DuplicationRef(origToDupMap.getOrElse(ownerDomain, ownerDomain))) - origToDupMap += db -> dupBlock - dupBlock -> db - }.toMap + // For each duplicate design, maps origin DomainBlocks to dup-copy DomainBlocks + // with DuplicationRef owners pointing to the correct dup design/domain hierarchy. + // Key: (dupDesign, origDomainBlock) -> dupDomainBlock + lazy val dupDesignDomainBlockMap: Map[(DFDesignBlock, DomainBlock), DomainBlock] = + val origDomainBlocks: Map[DFDesignBlock, List[DomainBlock]] = + members.view + .collect { case db: DomainBlock => db } + .groupBy(_.getOwnerDesign) + .map { (design, blocks) => design -> blocks.toList } + .toMap + dupDesignToOrigMap.flatMap { (dupDesign, origDesign) => + val origToDupMap = mutable.Map.empty[DFDomainOwner, DFDomainOwner] + origToDupMap += origDesign -> dupDesign + origDomainBlocks.getOrElse(origDesign, Nil).map { origBlock => + val dupOwner = origToDupMap(origBlock.getOwnerDomain) + val dupBlock = origBlock.copy(ownerRef = DFRef.DuplicationRef(dupOwner)) + origToDupMap += origBlock -> dupBlock + (dupDesign, origBlock) -> dupBlock + } + } lazy val dupPortsByName: Map[DFDesignInst, ListMap[String, DFVal.Dcl]] = val origMap = members.view @@ -298,16 +307,14 @@ final case class DB( .collect { case m: DFVal.PortByNameSelect => m } .groupBy(m => m.designInstRef.get) .view.mapValues(_.map(m => m.portNamePath -> m.dfType).toMap).toMap - val dupDomainBlocksByFullName = dupDomainBlockToOrigMap.map { case (dup, orig) => - orig.getFullName -> dup - } val dupEntries = dupDesignToOrigMap.map { (dupDesign, origDesign) => val pbnsTypes = pbnsByDesign.getOrElse(dupDesign, Map.empty) dupDesign -> ListMap.from(origMap(origDesign).view.map { (name, dcl) => val dfType = pbnsTypes.getOrElse(name, dcl.dfType) val dupOwnerDomain = dcl.getOwnerDomain match case _: DFDesignBlock => dupDesign - case domainBlock: DomainBlock => dupDomainBlocksByFullName(domainBlock.getFullName) + case domainBlock: DomainBlock => + dupDesignDomainBlockMap((dupDesign, domainBlock)) // TODO: missing interface handling case interface: DFInterfaceOwner => ??? name -> dcl.copy(ownerRef = DFRef.DuplicationRef(dupOwnerDomain), dfType = dfType) @@ -318,47 +325,45 @@ final case class DB( end dupPortsByName lazy val dupDomainOwnerPublicMemberList: List[(DFDomainOwner, List[DFMember])] = - val dupDomainBlocksByFullName = dupDomainBlockToOrigMap.map { case (dup, orig) => - orig.getFullName -> dup - } - val dupDesignsByFullName = dupDesignToOrigMap.map { case (dup, orig) => - orig.getFullName -> dup - } - val dupPortsByFullName = dupPortsByName.view.values.flatMap { case ports => - ports.map { case (_, dcl) => dcl.getFullName -> dcl } - }.toMap - def getDupMember[M <: DFMember](m: M): M = - m.runtimeChecked match - case dcl: DFVal.Dcl => - dupPortsByFullName.getOrElse(dcl.getFullName, dcl).asInstanceOf[M] - case design: DFDesignBlock => - dupDesignsByFullName.getOrElse(design.getFullName, design).asInstanceOf[M] - case domainBlock: DomainBlock => - dupDomainBlocksByFullName.getOrElse(domainBlock.getFullName, domainBlock).asInstanceOf[M] def publicMemberFilter(member: DFMember): Boolean = member match case dcl: DFVal.Dcl if dcl.isPort => true case design: DFDesignBlock => true case domainBlock: DomainBlock => true case _ => false - domainOwnerMemberList.map { case (owner, members) => + // For duplicate designs, generate entries for their dup-copy domain blocks + // by mirroring the origin's domain owner structure. + def dupEntriesFor( + origOwner: DFDomainOwner, + dupDesign: DFDesignBlock + ): List[(DFDomainOwner, List[DFMember])] = + val dupOwner: DFDomainOwner = origOwner match + case _: DFDesignBlock => dupDesign + case db: DomainBlock => dupDesignDomainBlockMap((dupDesign, db)) + case _: DFInterfaceOwner => ??? // TODO + val origMembers = domainOwnerMemberTable(origOwner) + .view.filter(publicMemberFilter).toList + val dupDesignPorts = dupPortsByName.getOrElse(dupDesign, ListMap.empty) + val dupMembers = origMembers.map { + case dcl: DFVal.Dcl => + val relName = dcl.getRelativeName(origOwner.getThisOrOwnerDesign) + dupDesignPorts.getOrElse(relName, dcl) + case design: DFDesignBlock => design // nested designs stay as-is + case db: DomainBlock => dupDesignDomainBlockMap((dupDesign, db)) + case m => m + } + (dupOwner -> dupMembers) :: origMembers.collect { case db: DomainBlock => + dupEntriesFor(db, dupDesign) + }.flatten + domainOwnerMemberList.flatMap { case (owner, members) => owner match case dupDesign: DFDesignBlock if dupDesign.isDuplicate => val origDesign = dupDesignToOrigMap(dupDesign) - val dupMembers = - domainOwnerMemberTable(origDesign) - .view.filter(publicMemberFilter).map(getDupMember).toList - dupDesign -> dupMembers + dupEntriesFor(origDesign, dupDesign) case origDesign: DFDesignBlock => - origDesign -> members.filter(publicMemberFilter) - case dupDomainBlock: DomainBlock if dupDomainBlock.isDuplicate => - val origDomainBlock = dupDomainBlockToOrigMap(dupDomainBlock) - val dupMembers = - domainOwnerMemberTable(origDomainBlock) - .view.filter(publicMemberFilter).map(getDupMember).toList - dupDomainBlock -> dupMembers + List(origDesign -> members.filter(publicMemberFilter)) case origDomainBlock: DomainBlock => - origDomainBlock -> members.filter(publicMemberFilter) + List(origDomainBlock -> members.filter(publicMemberFilter)) // TODO: missing interface handling case interface: DFInterfaceOwner => ??? } From 5468e7d8f8026daafccd20af240b4e27d87d14a6 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 30 Mar 2026 04:47:25 +0300 Subject: [PATCH 067/115] refactor duplication logic to compute domain blocks and ports in a single pass --- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) 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 47ccf1f88..f17073d71 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -272,57 +272,50 @@ final case class DB( case (design, _) if design.isDuplicate => design -> origByName(design.dclName) }.toMap - // For each duplicate design, maps origin DomainBlocks to dup-copy DomainBlocks - // with DuplicationRef owners pointing to the correct dup design/domain hierarchy. - // Key: (dupDesign, origDomainBlock) -> dupDomainBlock - lazy val dupDesignDomainBlockMap: Map[(DFDesignBlock, DomainBlock), DomainBlock] = + // Single pass: for each duplicate design, compute dup domain blocks AND dup ports together. + // dupDesignDomainBlockMap: maps (dupDesign, origDomainBlock) -> dupDomainBlock + // dupPortsByName: maps design -> port name -> Dcl (including dup entries with DuplicationRef) + lazy val (dupDesignDomainBlockMap, dupPortsByName) = val origDomainBlocks: Map[DFDesignBlock, List[DomainBlock]] = members.view .collect { case db: DomainBlock => db } .groupBy(_.getOwnerDesign) .map { (design, blocks) => design -> blocks.toList } .toMap - dupDesignToOrigMap.flatMap { (dupDesign, origDesign) => - val origToDupMap = mutable.Map.empty[DFDomainOwner, DFDomainOwner] - origToDupMap += origDesign -> dupDesign - origDomainBlocks.getOrElse(origDesign, Nil).map { origBlock => - val dupOwner = origToDupMap(origBlock.getOwnerDomain) - val dupBlock = origBlock.copy(ownerRef = DFRef.DuplicationRef(dupOwner)) - origToDupMap += origBlock -> dupBlock - (dupDesign, origBlock) -> dupBlock - } - } - - lazy val dupPortsByName: Map[DFDesignInst, ListMap[String, DFVal.Dcl]] = - val origMap = members.view + val origPortMap = members.view .collect { case m: DFVal.Dcl if m.isPort => m } .groupBy(_.getOwnerDesign) .map { case (design, dcls) => design -> ListMap.from(dcls.view.map(m => m.getRelativeName(design) -> m)) }.toMap - // Add entries for duplicate designs by copying origin Dcls with DuplicationRef owner. // PBNS members carry the correct dfType for each duplicate's port (reflecting the // actual instantiation parameters), so we use it to override the origin Dcl's dfType. val pbnsByDesign = members.view .collect { case m: DFVal.PortByNameSelect => m } .groupBy(m => m.designInstRef.get) .view.mapValues(_.map(m => m.portNamePath -> m.dfType).toMap).toMap - val dupEntries = dupDesignToOrigMap.map { (dupDesign, origDesign) => + val domainBlockMap = mutable.Map.empty[(DFDesignBlock, DomainBlock), DomainBlock] + val portEntries = mutable.Map.empty[DFDesignInst, ListMap[String, DFVal.Dcl]] + dupDesignToOrigMap.foreach { (dupDesign, origDesign) => + // 1. Build domain block copies + val origToDupMap = mutable.Map.empty[DFDomainOwner, DFDomainOwner] + origToDupMap += origDesign -> dupDesign + origDomainBlocks.getOrElse(origDesign, Nil).foreach { origBlock => + val dupOwner = origToDupMap(origBlock.getOwnerDomain) + val dupBlock = origBlock.copy(ownerRef = DFRef.DuplicationRef(dupOwner)) + origToDupMap += origBlock -> dupBlock + domainBlockMap += (dupDesign, origBlock) -> dupBlock + } + // 2. Build port copies (reusing origToDupMap from step 1) val pbnsTypes = pbnsByDesign.getOrElse(dupDesign, Map.empty) - dupDesign -> ListMap.from(origMap(origDesign).view.map { (name, dcl) => + portEntries += dupDesign -> ListMap.from(origPortMap(origDesign).view.map { (name, dcl) => val dfType = pbnsTypes.getOrElse(name, dcl.dfType) - val dupOwnerDomain = dcl.getOwnerDomain match - case _: DFDesignBlock => dupDesign - case domainBlock: DomainBlock => - dupDesignDomainBlockMap((dupDesign, domainBlock)) - // TODO: missing interface handling - case interface: DFInterfaceOwner => ??? + val dupOwnerDomain = origToDupMap(dcl.getOwnerDomain) name -> dcl.copy(ownerRef = DFRef.DuplicationRef(dupOwnerDomain), dfType = dfType) }) } // dupEntries only fills in missing entries (designs without real port members) - dupEntries ++ origMap - end dupPortsByName + (domainBlockMap.toMap, portEntries.toMap ++ origPortMap) lazy val dupDomainOwnerPublicMemberList: List[(DFDomainOwner, List[DFMember])] = def publicMemberFilter(member: DFMember): Boolean = From 778710568b424039dd31360fca8bb141b02a6044 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 30 Mar 2026 05:04:26 +0300 Subject: [PATCH 068/115] update skills --- .claude/commands/ir-reference.md | 25 +++++++++++++++++++++++-- .claude/commands/new-stage.md | 8 ++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.claude/commands/ir-reference.md b/.claude/commands/ir-reference.md index 110a6c59b..fb0729542 100644 --- a/.claude/commands/ir-reference.md +++ b/.claude/commands/ir-reference.md @@ -474,7 +474,8 @@ enum InstMode.BlackBox: NA, Files(path), Library(libName, nameSpace), VendorIP(v **Extension methods:** ```scala -design.isDuplicate // tagged DuplicateTag (multiple identical instantiations) +design.isDuplicate // tagged DuplicateTag — has NO members in DB (ports/domains removed) + // Use dupPortsByName/dupDesignDomainBlockMap for synthetic members design.isBlackBox // instMode is BlackBox design.isVendorIPBlackbox design.inSimulation // instMode is Simulation @@ -786,7 +787,7 @@ DFTags.empty **Built-in tags:** ```scala -case object DuplicateTag // design has multiple identical instantiations +case object DuplicateTag // duplicate design instance — NO members in DB; use dupPortsByName case object IteratorTag // Dcl is a for-loop iterator variable case object IdentTag // Alias.AsIs is a pure identity (named alias of itself) case object BindTag // Alias is a pattern-match bind variable @@ -817,6 +818,7 @@ type DFRefAny = DFRef[DFMember] - `DFRef.OneWay[M]` — unidirectional (e.g. `ownerRef`) - `DFRef.TwoWay[M, O]` — bidirectional; `O` is the member that owns this ref (enables reverse lookup) - `DFRef.TypeRef` — used for `IntParamRef` (width/index parameters) +- `DFRef.DuplicationRef(owner: DFOwnerNamed)` — special `OneWay` ref for analysis-only members (not in `refTable` or `members`). Overrides `get` to return `owner` directly, bypassing `MemberGetSet` lookup. Used by `dupPortsByName` and `dupDesignDomainBlockMap` to create synthetic port Dcls and domain blocks for duplicate designs. **Pattern extractor** (very common in stages): ```scala @@ -867,6 +869,25 @@ db.inSimulation // top has no ports (simulation context) db.inBuild // top has a device constraint tag ``` +**Design duplication properties:** + +Duplicate designs (tagged `DuplicateTag`) have **no members** in the DB — their ports, domain blocks, and other members are removed during immutable DB creation. Instead, synthetic members with `DuplicationRef` owners are created on-demand: + +```scala +db.dupDesignToOrigMap // Map[DFDesignBlock, DFDesignBlock] — dup design → origin design +db.dupPortsByName // Map[DFDesignInst, ListMap[String, DFVal.Dcl]] — design → named ports + // includes both real ports (for origins) and DuplicationRef-backed + // synthetic ports (for duplicates, with PBNS dfType overrides) +db.dupDesignDomainBlockMap // Map[(DFDesignBlock, DomainBlock), DomainBlock] + // maps (dupDesign, origDomainBlock) → dupDomainBlock +db.dupDomainOwnerPublicMemberList // List[(DFDomainOwner, List[DFMember])] + // domain owners with their public members (ports, designs, domains), + // including dup-copy entries for duplicate designs +db.dupDomainOwnerPublicMemberTable // Map form of the above +``` + +**Important:** `dupPortsByName` is the primary port lookup — use it instead of iterating `members` for ports. It handles duplicate designs transparently. `getPortDcl` on `PortByNameSelect` uses it internally. + **Patching:** ```scala db.patch(patches: List[(DFMember, Patch)]): DB diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index dcd663ae6..16986a152 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -976,6 +976,14 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) fails with "wrong number of argument patterns" because `DFForBlock` has multiple fields. Use a type pattern instead: `case x: DFLoop.DFForBlock`. The same applies to other multi-field IR case classes that have no dedicated single-argument unapply. +16. **Assuming duplicate designs have members** — designs tagged `DuplicateTag` have **no members** + in the DB (ports, domain blocks, and values are removed during immutable DB creation). Use + `db.dupPortsByName` for port lookups (works for both origin and duplicate designs) and + `db.dupDomainOwnerPublicMemberList` for domain owner analysis. If your stage needs the full + member hierarchy of duplicates (e.g. for parameter analysis), you must reconstruct it by + duplicating the origin design's members with `copyWithNewRefs` — see `GlobalizePortVectorParams` + for the pattern. Never iterate `designMemberTable` or `domainOwnerMemberTable` expecting to + find members for duplicate designs. --- From c87cee655f8e10e6ad73a2b0c1152b665609a341 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 30 Mar 2026 05:48:31 +0300 Subject: [PATCH 069/115] bump versions for scalafmt and dependencies to latest stable releases --- .scalafmt.conf | 2 +- build.sbt | 6 +++--- .../hello-world/scala-project/.scalafmt.conf | 2 +- project/build.properties | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index d815d9c92..88fe92390 100755 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.10.6 +version = 3.10.7 runner.dialect = scala3 maxColumn = 100 diff --git a/build.sbt b/build.sbt index 86a28b5b9..64e17d8a4 100755 --- a/build.sbt +++ b/build.sbt @@ -144,11 +144,11 @@ lazy val platforms = project lazy val dependencies = new { private val scodecV = "1.2.4" - private val munitV = "1.2.2" - private val airframelogV = "2026.1.0" + private val munitV = "1.2.4" + private val airframelogV = "2026.1.4" private val oslibV = "0.11.8" private val scallopV = "6.0.0" - private val upickleV = "4.4.2" + private val upickleV = "4.4.3" val scodec = "org.scodec" %% "scodec-bits" % scodecV val munit = "org.scalameta" %% "munit" % munitV % Test diff --git a/docs/getting-started/hello-world/scala-project/.scalafmt.conf b/docs/getting-started/hello-world/scala-project/.scalafmt.conf index 63a221d96..75af615b0 100644 --- a/docs/getting-started/hello-world/scala-project/.scalafmt.conf +++ b/docs/getting-started/hello-world/scala-project/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.10.6 +version = 3.10.7 runner.dialect = scala3 maxColumn = 100 diff --git a/project/build.properties b/project/build.properties index 4d5412b8f..8430e165c 100755 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.12.2 \ No newline at end of file +sbt.version = 1.12.8 \ No newline at end of file From 31966132fc7113b4cf42407807699e632fe18fca Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 31 Mar 2026 00:14:59 +0300 Subject: [PATCH 070/115] remove redundant global files inclusion when empty --- .../dfhdl/compiler/printing/Printer.scala | 23 ++-- .../stages/verilog/VerilogOwnerPrinter.scala | 9 +- .../stages/verilog/VerilogPrinter.scala | 50 +++---- .../stages/vhdl/VHDLOwnerPrinter.scala | 11 +- .../compiler/stages/vhdl/VHDLPrinter.scala | 122 +++++++++--------- .../scala/StagesSpec/PrintVHDLCodeSpec.scala | 22 +--- .../StagesSpec/PrintVerilogCodeSpec.scala | 26 +--- .../verilog.sv2009/hdl/FullAdder1.sv | 1 - .../verilog.v2001/hdl/FullAdder1.v | 2 - .../verilog.v95/hdl/FullAdder1.v | 2 - .../vhdl.v2008/hdl/FullAdder1.vhd | 1 - .../vhdl.v93/hdl/FullAdder1.vhd | 1 - .../verilog.sv2009/hdl/FullAdder1.sv | 1 - .../verilog.sv2009/hdl/FullAdderN.sv | 1 - .../verilog.v2001/hdl/FullAdder1.v | 2 - .../verilog.v2001/hdl/FullAdderN.v | 2 - .../verilog.v95/hdl/FullAdder1.v | 2 - .../verilog.v95/hdl/FullAdderN.v | 2 - .../vhdl.v2008/hdl/FullAdder1.vhd | 1 - .../vhdl.v2008/hdl/FullAdderN.vhd | 1 - .../vhdl.v93/hdl/FullAdder1.vhd | 1 - .../vhdl.v93/hdl/FullAdderN.vhd | 1 - .../verilog.sv2009/hdl/LeftShift2.sv | 1 - .../verilog.v2001/hdl/LeftShift2.v | 2 - .../verilog.v95/hdl/LeftShift2.v | 2 - .../vhdl.v2008/hdl/LeftShift2.vhd | 1 - .../vhdl.v93/hdl/LeftShift2.vhd | 1 - .../verilog.sv2009/hdl/LeftShiftBasic.sv | 1 - .../verilog.v2001/hdl/LeftShiftBasic.v | 2 - .../verilog.v95/hdl/LeftShiftBasic.v | 2 - .../vhdl.v2008/hdl/LeftShiftBasic.vhd | 1 - .../vhdl.v93/hdl/LeftShiftBasic.vhd | 1 - 32 files changed, 119 insertions(+), 179 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala index a40727937..a245b7030 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala @@ -145,6 +145,9 @@ trait Printer end csDFMember def designFileName(designName: String): String def globalFileName: String + protected def hasGlobalContentCheck: Boolean = + getSet.designDB.membersGlobals.nonEmpty || csGlobalTypeDcls.nonEmpty + lazy val hasGlobalContent: Boolean = hasGlobalContentCheck def csGlobalFileContent: String = sn"""|$csGlobalConstIntDcls |$csGlobalTypeDcls @@ -183,16 +186,20 @@ trait Printer ) ) else None - val globalSourceFile = - SourceFile( - SourceOrigin.Compiled, - SourceType.GlobalDef, - hdlFolderName + separatorChar + globalFileName, - formatCode(csGlobalFileContent, withColor = false) - ) + val globalSourceFile: Option[SourceFile] = + if (hasGlobalContent) + Some( + SourceFile( + SourceOrigin.Compiled, + SourceType.GlobalDef, + hdlFolderName + separatorChar + globalFileName, + formatCode(csGlobalFileContent, withColor = false) + ) + ) + else None val compiledFiles = Iterable( dfhdlSourceFile, - Some(globalSourceFile), + globalSourceFile, designDB.uniqueDesignMemberList.view.map { case (block: DFDesignBlock, _) => val sourceType = block.instMode match case _: DFDesignBlock.InstMode.BlackBox => SourceType.BlackBox diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala index 22fd55f62..cd13275f0 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala @@ -24,9 +24,9 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: val precisionUnit = unitToStr(TimeNumber(1e-3, unit).normalize.unit) s"`timescale 1${scaleUnit}/1${precisionUnit}" }.getOrElse(s"`timescale 1ns/1ps") - s"""`default_nettype none - |$csTimeScale - |`include "${printer.globalFileName}"""".stripMargin + sn"""|`default_nettype none + |$csTimeScale + |${if (printer.hasGlobalContent) s"""`include "${printer.globalFileName}"""" else ""}""" def moduleName(design: DFDesignBlock): String = design.dclName val parameterizedModuleSupport: Boolean = printer.dialect match @@ -125,7 +125,8 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: else if (designParamList.length == 1) designParamList.mkString("#(", ", ", ")") else "#(" + designParamList.mkString("\n", ",\n", "\n").hindent(2) + ")" val includeModuleDefs = - if (printer.allowTypeDef) "" else s"""`include "${printer.globalFileName}"""" + if (printer.allowTypeDef || !printer.hasGlobalContent) "" + else s"""`include "${printer.globalFileName}"""" // include parameter definitions only when parameters are used in the design val paramDefines = if (printer.supportGlobalParameters) "" diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala index aa62a32bb..b32c46117 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala @@ -173,30 +173,32 @@ class VerilogPrinter(val dialect: VerilogDialect)(using def globalFileName: String = s"${printer.defsName}.$verilogFileHeaderSuffix" override def csGlobalFileContent: String = - val defName = printer.defsName.toUpperCase - // the module defs are alternating between outside of and inside of the module - // because we will include the module defs twice, once in the top of the file - // and second time inside the module. - val globalParams = - if (printer.supportGlobalParameters) super.csGlobalFileContent else "" - val globalToLocalParams = - if (printer.supportGlobalParameters) "" else super.csGlobalFileContent - val moduleDefs = - if (printer.allowTypeDef) "" - else - sn"""|`ifndef ${defName}_MODULE - |`define ${defName}_MODULE - |`else - |$globalToLocalParams - |${printer.csGlobalTypeFuncDcls} - |`undef ${defName}_MODULE - |`endif""" - sn"""|`ifndef $defName - |`define $defName - |$globalParams - |`endif - |$moduleDefs - |""" + if (hasGlobalContent) + val defName = printer.defsName.toUpperCase + // the module defs are alternating between outside of and inside of the module + // because we will include the module defs twice, once in the top of the file + // and second time inside the module. + val globalParams = + if (printer.supportGlobalParameters) super.csGlobalFileContent else "" + val globalToLocalParams = + if (printer.supportGlobalParameters) "" else super.csGlobalFileContent + val moduleDefs = + if (printer.allowTypeDef) "" + else + sn"""|`ifndef ${defName}_MODULE + |`define ${defName}_MODULE + |`else + |$globalToLocalParams + |${printer.csGlobalTypeFuncDcls} + |`undef ${defName}_MODULE + |`endif""" + sn"""|`ifndef $defName + |`define $defName + |$globalParams + |`endif + |$moduleDefs + |""" + else "" end csGlobalFileContent def dfhdlDefsFileName: String = s"dfhdl_defs.$verilogFileHeaderSuffix" def dfhdlSourceContents: String = diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala index aad9c18f7..a4eca66cd 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala @@ -15,11 +15,12 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: def packageName: String = s"${getSet.topName}_pkg" def csLibrary(inSimulation: Boolean, usesMathReal: Boolean): String = val default = - s"""library ieee; - |use ieee.std_logic_1164.all; - |use ieee.numeric_std.all;${if (usesMathReal) "\nuse ieee.math_real.all;" else ""} - |use work.dfhdl_pkg.all; - |use work.$packageName.all;""".stripMargin + sn"""|library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |${if (usesMathReal) "use ieee.math_real.all;" else ""} + |use work.dfhdl_pkg.all; + |${if (printer.hasGlobalContent) s"use work.$packageName.all;" else ""}""" if (useStdSimLibrary && inSimulation) s"""$default | diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala index 1bd8fcc18..cd7676f9f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala @@ -159,67 +159,71 @@ class VHDLPrinter(val dialect: VHDLDialect)(using def dfhdlDefsFileName: String = s"dfhdl_pkg.vhd" def dfhdlSourceContents: String = scala.io.Source.fromResource(dfhdlDefsFileName).getLines().mkString("\n") + override protected def hasGlobalContentCheck: Boolean = + super.hasGlobalContentCheck || printer.globalVectorTypes.nonEmpty override def csGlobalFileContent: String = - // In VHDL the vectors need to be named, and put in dependency order of other named types. - // So first we prepare the vector type declarations in a mutable map and later we remove - // entries that were already placed in the final type printing. - val vectorTypeDcls = mutable.Map.from( - printer.globalVectorTypes.view.map { case (tpName, (vecType, depth)) => - tpName -> printer.csDFVectorDclsGlobal(DclScope.Pkg)(tpName, vecType, depth) - } - ) - // The body declarations can be in any order, as long as it's consistent between compilations. - val vectorTypeDclsBody = - printer.globalVectorTypes.view.map { case (tpName, (vecType, depth)) => - printer.csDFVectorDclsGlobal(DclScope.PkgBody)(tpName, vecType, depth) - }.mkString("\n") - // collect the global named types, including vectors - val namedDFTypes = ListSet.from(getSet.designDB.members.view.collect { - case port @ DclPort() => port.dfType - case const @ DclConst() if const.isGlobal => const.dfType - }.flatMap(_.decompose { case dt: (DFVector | NamedDFType) => dt })) - // declarations of the types and relevant functions - val namedTypeConvFuncsDcl = namedDFTypes.view - .flatMap { - // vector types can have different dimensions, but we only need the declaration once - case dfType: DFVector => - val tpName = printer.getVecDepthAndCellTypeName(dfType)._1 - vectorTypeDcls.get(tpName) match - case Some(desc) => - vectorTypeDcls -= tpName - Some(desc) - case None => None - case dfType: NamedDFType => - List( - printer.csNamedDFTypeDcl(dfType, global = true), - printer.csNamedDFTypeConvFuncsDcl(dfType) - ) - } - .mkString("\n") - val namedTypeConvFuncsBody = - getSet.designDB.getGlobalNamedDFTypes.view - .collect { case dfType: NamedDFType => printer.csNamedDFTypeConvFuncsBody(dfType) } + if (hasGlobalContent) + // In VHDL the vectors need to be named, and put in dependency order of other named types. + // So first we prepare the vector type declarations in a mutable map and later we remove + // entries that were already placed in the final type printing. + val vectorTypeDcls = mutable.Map.from( + printer.globalVectorTypes.view.map { case (tpName, (vecType, depth)) => + tpName -> printer.csDFVectorDclsGlobal(DclScope.Pkg)(tpName, vecType, depth) + } + ) + // The body declarations can be in any order, as long as it's consistent between compilations. + val vectorTypeDclsBody = + printer.globalVectorTypes.view.map { case (tpName, (vecType, depth)) => + printer.csDFVectorDclsGlobal(DclScope.PkgBody)(tpName, vecType, depth) + }.mkString("\n") + // collect the global named types, including vectors + val namedDFTypes = ListSet.from(getSet.designDB.members.view.collect { + case port @ DclPort() => port.dfType + case const @ DclConst() if const.isGlobal => const.dfType + }.flatMap(_.decompose { case dt: (DFVector | NamedDFType) => dt })) + // declarations of the types and relevant functions + val namedTypeConvFuncsDcl = namedDFTypes.view + .flatMap { + // vector types can have different dimensions, but we only need the declaration once + case dfType: DFVector => + val tpName = printer.getVecDepthAndCellTypeName(dfType)._1 + vectorTypeDcls.get(tpName) match + case Some(desc) => + vectorTypeDcls -= tpName + Some(desc) + case None => None + case dfType: NamedDFType => + List( + printer.csNamedDFTypeDcl(dfType, global = true), + printer.csNamedDFTypeConvFuncsDcl(dfType) + ) + } .mkString("\n") - val usesMathReal = getSet.designDB.membersGlobals.exists { - _.dfType.decompose { case dt: DFDouble => dt }.nonEmpty - } - sn"""|library ieee; - |use ieee.std_logic_1164.all; - |use ieee.numeric_std.all; - |${if (usesMathReal) "use ieee.math_real.all;" else ""} - |use work.dfhdl_pkg.all; - | - |package ${printer.packageName} is - |${csGlobalConstIntDcls} - |${namedTypeConvFuncsDcl} - |${csGlobalConstNonIntDcls} - |end package ${printer.packageName}; - | - |package body ${printer.packageName} is - |${namedTypeConvFuncsBody} - |${vectorTypeDclsBody} - |end package body ${printer.packageName}; - |""" + val namedTypeConvFuncsBody = + getSet.designDB.getGlobalNamedDFTypes.view + .collect { case dfType: NamedDFType => printer.csNamedDFTypeConvFuncsBody(dfType) } + .mkString("\n") + val usesMathReal = getSet.designDB.membersGlobals.exists { + _.dfType.decompose { case dt: DFDouble => dt }.nonEmpty + } + sn"""|library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |${if (usesMathReal) "use ieee.math_real.all;" else ""} + |use work.dfhdl_pkg.all; + | + |package ${printer.packageName} is + |${csGlobalConstIntDcls} + |${namedTypeConvFuncsDcl} + |${csGlobalConstNonIntDcls} + |end package ${printer.packageName}; + | + |package body ${printer.packageName} is + |${namedTypeConvFuncsBody} + |${vectorTypeDclsBody} + |end package body ${printer.packageName}; + |""" + else "" end csGlobalFileContent def alignCode(cs: String): String = cs diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index ac5ca6080..1623f0bcb 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -39,7 +39,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.ID_pkg.all; | |entity ID is |port ( @@ -64,7 +63,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity ID is |port ( @@ -82,7 +80,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity IDTop is |port ( @@ -223,7 +220,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Top_pkg.all; | |entity Top is |port ( @@ -290,7 +286,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.numeric_std.all; |use ieee.math_real.all; |use work.dfhdl_pkg.all; - |use work.Top_pkg.all; | |entity Top is |end Top; @@ -414,7 +409,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Example_pkg.all; | |entity Example is |port ( @@ -611,7 +605,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity IDTop is |port ( @@ -657,7 +650,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.SelOp_pkg.all; | |entity SelOp is |port ( @@ -694,7 +686,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Empty_pkg.all; | |entity Empty is |end Empty; @@ -719,7 +710,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.HighZ_pkg.all; | |entity HighZ is |port ( @@ -756,7 +746,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -795,7 +784,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -907,7 +895,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -960,7 +947,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1077,7 +1063,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1122,7 +1107,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1231,7 +1215,7 @@ class PrintVHDLCodeSpec extends StageSpec: | println("These are the values: " & to_string(param3) & ", " & to_string(param4) & ", " & to_string(param5) & ", " & to_string(param6) & ", " & to_string(param7) & ", " & to_string(param8) & ", " & to_string(param9) & ", " & t_enum_MyEnum'image(param10) & ""); | report | "Debug at Foo" & LF & - | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1177:9" & LF & + | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1161:9" & LF & | "param3 = " & to_string(param3) & LF & | "param4 = " & to_string(param4) & LF & | "param5 = " & to_string(param5) & LF & @@ -1293,7 +1277,7 @@ class PrintVHDLCodeSpec extends StageSpec: | println("These are the values: " & to_string(param3) & ", " & to_string(param4) & ", " & to_string(param5) & ", " & to_string(param6) & ", " & to_string(param7) & ", " & to_string(param8) & ", " & to_string(param9) & ", " & t_enum_MyEnum'image(param10) & ""); | report | "Debug at Foo" & LF & - | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1177:9" & LF & + | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1161:9" & LF & | "param3 = " & to_string(param3) & LF & | "param4 = " & to_string(param4) & LF & | "param5 = " & to_string(param5) & LF & @@ -1374,7 +1358,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1517,7 +1500,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 7b3efa223..5c10366f3 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -41,7 +41,6 @@ class PrintVerilogCodeSpec extends StageSpec: id, """|`default_nettype none |`timescale 1ns/1ps - |`include "ID_defs.svh" | |module ID( | input wire logic signed [15:0] x, @@ -62,7 +61,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module ID( | input wire logic signed [15:0] x, @@ -76,7 +74,6 @@ class PrintVerilogCodeSpec extends StageSpec: | |`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module IDTop( | input wire logic signed [15:0] x, @@ -295,7 +292,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Top_defs.svh" | |module Top( | input wire logic clk, @@ -353,7 +349,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Top_defs.svh" | |module Top; | `include "dfhdl_defs.svh" @@ -415,7 +410,6 @@ class PrintVerilogCodeSpec extends StageSpec: """|/* HasDocs has docs */ |`default_nettype none |`timescale 1ns/1ps - |`include "HasDocs_defs.svh" | |module HasDocs( | /* My in */ @@ -600,7 +594,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module IDTop( | input wire logic clk, @@ -637,7 +630,6 @@ class PrintVerilogCodeSpec extends StageSpec: id, """|`default_nettype none |`timescale 1ns/1ps - |`include "SelOp_defs.svh" | |module SelOp( | input wire logic c, @@ -668,7 +660,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Empty_defs.svh" | |module Empty; | `include "dfhdl_defs.svh" @@ -688,7 +679,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "HighZ_defs.svh" | |module HighZ( | input wire logic [7:0] x, @@ -718,7 +708,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | input wire logic [15:0] x, @@ -751,14 +740,12 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo( | x, | y |); | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | input wire [15:0] x; | output reg [15:0] y; | always @(x) @@ -864,7 +851,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | output logic x, @@ -915,7 +901,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | output logic [9:0] matrix [0:7] [0:7] @@ -979,13 +964,11 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo( | output reg [639:0] matrix |); | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | | always | begin @@ -1036,7 +1019,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | output logic x, @@ -1135,7 +1117,7 @@ class PrintVerilogCodeSpec extends StageSpec: | $display("These are the values: %d", param3, ", %d", param4, ", %h", param5, ", %h", param6, ", %d", param7, ", %b", param8, ", %s", param9 ? "true" : "false", ", %s", param10.name(), ""); | $info( | "Debug at Foo\n", - | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1087:9\n", + | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1069:9\n", | "param3 = %d\n", param3, | "param4 = %d\n", param4, | "param5 = %h\n", param5, @@ -1207,7 +1189,7 @@ class PrintVerilogCodeSpec extends StageSpec: | $display("These are the values: %d", param3, ", %d", param4, ", %h", param5, ", %h", param6, ", %d", param7, ", %b", param8, ", %s", param9 ? "true" : "false", ", %s", MyEnum_to_string(param10), ""); | $display("INFO: ", | "Debug at Foo\n", - | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1087:9\n", + | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1069:9\n", | "param3 = %d\n", param3, | "param4 = %d\n", param4, | "param5 = %h\n", param5, @@ -1455,7 +1437,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | input wire logic clk, @@ -1484,7 +1465,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | input wire logic signed [7:0] x, @@ -1508,14 +1488,12 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo( | x, | y |); | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | input wire [7:0] x; | output wire [7:0] y; | assign y = `ABS(x); diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv index 73001a45a..0b6f43b99 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdder1_defs.svh" module FullAdder1( input wire logic a, diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v index dfa5fcd0f..4d3248626 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdder1_defs.vh" module FullAdder1( input wire a, @@ -10,7 +9,6 @@ module FullAdder1( output wire c_out ); `include "dfhdl_defs.vh" - `include "FullAdder1_defs.vh" assign sum = (a ^ b) ^ c_in; assign c_out = ((a & b) | (b & c_in)) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v index 4255f1b54..d994fb3ff 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdder1_defs.vh" module FullAdder1( a, @@ -10,7 +9,6 @@ module FullAdder1( c_out ); `include "dfhdl_defs.vh" - `include "FullAdder1_defs.vh" input wire a; input wire b; input wire c_in; diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd index 5fb6396f8..e2be3df93 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdder1_pkg.all; entity FullAdder1 is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd index 5fb6396f8..e2be3df93 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdder1_pkg.all; entity FullAdder1 is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv index 7689a1359..0b6f43b99 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.svh" module FullAdder1( input wire logic a, diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN.sv b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN.sv index 36f54e96c..97be2f5c3 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN.sv +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.svh" module FullAdderN( input wire logic [3:0] a, diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v index 7149b5fd6..4d3248626 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.vh" module FullAdder1( input wire a, @@ -10,7 +9,6 @@ module FullAdder1( output wire c_out ); `include "dfhdl_defs.vh" - `include "FullAdderN_defs.vh" assign sum = (a ^ b) ^ c_in; assign c_out = ((a & b) | (b & c_in)) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN.v index 39cd161a8..23c85f734 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.vh" module FullAdderN( input wire [3:0] a, @@ -10,7 +9,6 @@ module FullAdderN( output wire c_out ); `include "dfhdl_defs.vh" - `include "FullAdderN_defs.vh" wire adder_0_a; wire adder_0_b; wire adder_0_c_in; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v index 652b63bb0..d994fb3ff 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.vh" module FullAdder1( a, @@ -10,7 +9,6 @@ module FullAdder1( c_out ); `include "dfhdl_defs.vh" - `include "FullAdderN_defs.vh" input wire a; input wire b; input wire c_in; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN.v index 3fd4b2615..77588f115 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.vh" module FullAdderN( a, @@ -10,7 +9,6 @@ module FullAdderN( c_out ); `include "dfhdl_defs.vh" - `include "FullAdderN_defs.vh" input wire [3:0] a; input wire [3:0] b; input wire c_in; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd index 3dc80d3de..e2be3df93 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdderN_pkg.all; entity FullAdder1 is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN.vhd index b0b2f60bf..65c615061 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdderN_pkg.all; entity FullAdderN is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd index 3dc80d3de..e2be3df93 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdderN_pkg.all; entity FullAdder1 is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN.vhd index 56085de99..d6ac5d84d 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdderN_pkg.all; entity FullAdderN is port ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2.sv b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2.sv index a7b381e2e..319d630ea 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2.sv +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2.sv @@ -1,7 +1,6 @@ /* A two-bits left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShift2_defs.svh" module LeftShift2( /* bits input */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2.v index c8454b4a6..e5af17844 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2.v @@ -1,7 +1,6 @@ /* A two-bits left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShift2_defs.vh" module LeftShift2( /* bits input */ @@ -10,6 +9,5 @@ module LeftShift2( output wire [7:0] oBits ); `include "dfhdl_defs.vh" - `include "LeftShift2_defs.vh" assign oBits = iBits << 2; endmodule diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2.v index b4efff3a8..ed0b812b1 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2.v @@ -1,14 +1,12 @@ /* A two-bits left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShift2_defs.vh" module LeftShift2( iBits, oBits ); `include "dfhdl_defs.vh" - `include "LeftShift2_defs.vh" /* bits input */ input wire [7:0] iBits; /* bits output */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2.vhd index dafcd564d..b0c28bffd 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShift2_pkg.all; entity LeftShift2 is port ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2.vhd index dafcd564d..b0c28bffd 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShift2_pkg.all; entity LeftShift2 is port ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic.sv b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic.sv index 0e47cdaa0..24e5c224f 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic.sv +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic.sv @@ -1,7 +1,6 @@ /* A basic left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftBasic_defs.svh" module LeftShiftBasic( /* bits input */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic.v index dedfaef60..7a9c0da50 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic.v @@ -1,7 +1,6 @@ /* A basic left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftBasic_defs.vh" module LeftShiftBasic( /* bits input */ @@ -12,6 +11,5 @@ module LeftShiftBasic( output wire [7:0] oBits ); `include "dfhdl_defs.vh" - `include "LeftShiftBasic_defs.vh" assign oBits = iBits << shift; endmodule diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic.v index 0b0d55fee..2526d4263 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic.v @@ -1,7 +1,6 @@ /* A basic left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftBasic_defs.vh" module LeftShiftBasic( iBits, @@ -9,7 +8,6 @@ module LeftShiftBasic( oBits ); `include "dfhdl_defs.vh" - `include "LeftShiftBasic_defs.vh" /* bits input */ input wire [7:0] iBits; /* requested shift */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic.vhd index 1f7627399..ace9fd9ec 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShiftBasic_pkg.all; entity LeftShiftBasic is port ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic.vhd index 1f7627399..ace9fd9ec 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShiftBasic_pkg.all; entity LeftShiftBasic is port ( From d0002a390b68f21340a09d924c5a8f0a4ca214fe Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 31 Mar 2026 02:13:04 +0300 Subject: [PATCH 071/115] further removal of redundant package inclusion and option to rename the global package --- .../dfhdl/compiler/printing/Printer.scala | 2 +- .../scala/dfhdl/options/PrinterOptions.scala | 14 +++- .../stages/verilog/VerilogOwnerPrinter.scala | 7 +- .../stages/verilog/VerilogPrinter.scala | 4 +- .../stages/vhdl/VHDLOwnerPrinter.scala | 7 +- .../compiler/stages/vhdl/VHDLPrinter.scala | 5 +- .../scala/StagesSpec/PrintVHDLCodeSpec.scala | 81 ++++++++++++++++--- .../StagesSpec/PrintVerilogCodeSpec.scala | 81 ++++++++++++++----- lib/src/main/scala/dfhdl/app/DFApp.scala | 3 + .../scala/dfhdl/app/ParsedCommandLine.scala | 7 ++ .../verilog.sv2009/hdl/Blinker.sv | 1 - .../verilog.v2001/hdl/Blinker.v | 2 - .../verilog.v95/hdl/Blinker.v | 2 - .../vhdl.v2008/hdl/Blinker.vhd | 1 - .../vhdl.v93/hdl/Blinker.vhd | 1 - .../verilog.sv2009/hdl/Counter.sv | 1 - .../verilog.v2001/hdl/Counter.v | 2 - .../verilog.v95/hdl/Counter.v | 2 - .../vhdl.v2008/hdl/Counter.vhd | 1 - .../vhdl.v93/hdl/Counter.vhd | 1 - .../verilog.sv2009/hdl/RegFile.sv | 1 - .../verilog.v2001/hdl/RegFile.v | 2 - .../verilog.v95/hdl/RegFile.v | 2 - .../vhdl.v2008/hdl/RegFile.vhd | 1 - .../vhdl.v93/hdl/RegFile.vhd | 1 - .../verilog.sv2009/hdl/TrueDPR.sv | 1 - .../verilog.v2001/hdl/TrueDPR.v | 2 - .../verilog.v95/hdl/TrueDPR.v | 2 - .../vhdl.v2008/hdl/TrueDPR.vhd | 1 - .../vhdl.v93/hdl/TrueDPR.vhd | 1 - .../verilog.sv2009/hdl/UART_Tx.sv | 1 - .../verilog.v2001/hdl/UART_Tx.v | 2 - .../verilog.v95/hdl/UART_Tx.v | 2 - .../vhdl.v2008/hdl/UART_Tx.vhd | 1 - .../vhdl.v93/hdl/UART_Tx.vhd | 1 - .../verilog.sv2009/hdl/LeftShiftGen.sv | 1 - .../verilog.v2001/hdl/LeftShiftGen.v | 2 - .../verilog.v95/hdl/LeftShiftGen.v | 2 - .../vhdl.v2008/hdl/LeftShiftGen.vhd | 1 - .../vhdl.v93/hdl/LeftShiftGen.vhd | 1 - lib/src/test/scala/issues/IssueSpec.scala | 2 - 41 files changed, 175 insertions(+), 80 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala index a245b7030..a82669390 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala @@ -146,7 +146,7 @@ trait Printer def designFileName(designName: String): String def globalFileName: String protected def hasGlobalContentCheck: Boolean = - getSet.designDB.membersGlobals.nonEmpty || csGlobalTypeDcls.nonEmpty + getSet.designDB.membersGlobals.exists(!_.isAnonymous) || csGlobalTypeDcls.nonEmpty lazy val hasGlobalContent: Boolean = hasGlobalContentCheck def csGlobalFileContent: String = sn"""|$csGlobalConstIntDcls diff --git a/compiler/ir/src/main/scala/dfhdl/options/PrinterOptions.scala b/compiler/ir/src/main/scala/dfhdl/options/PrinterOptions.scala index be345ee12..715137eac 100644 --- a/compiler/ir/src/main/scala/dfhdl/options/PrinterOptions.scala +++ b/compiler/ir/src/main/scala/dfhdl/options/PrinterOptions.scala @@ -8,7 +8,8 @@ final case class PrinterOptions( align: Align, color: Color, showGlobals: ShowGlobals, - designPrintFilter: DesignPrintFilter + designPrintFilter: DesignPrintFilter, + globalDefsFileName: GlobalDefsFileName ) object PrinterOptions: opaque type Defaults[-T] <: PrinterOptions = PrinterOptions @@ -17,12 +18,14 @@ object PrinterOptions: align: Align, color: Color, showGlobals: ShowGlobals, - designPrintFilter: DesignPrintFilter + designPrintFilter: DesignPrintFilter, + globalDefsFileName: GlobalDefsFileName ): Defaults[Any] = PrinterOptions( align = align, color = color, showGlobals = showGlobals, - designPrintFilter = designPrintFilter + designPrintFilter = designPrintFilter, + globalDefsFileName = globalDefsFileName ) given (using defaults: Defaults[Any]): PrinterOptions = defaults into opaque type Align <: Boolean = Boolean @@ -41,6 +44,11 @@ object PrinterOptions: given ShowGlobals = false given Conversion[Boolean, ShowGlobals] = identity + into opaque type GlobalDefsFileName <: String = String + object GlobalDefsFileName: + given GlobalDefsFileName = "" + given Conversion[String, GlobalDefsFileName] = identity + trait DesignPrintFilter: def apply(design: ir.DFDesignBlock): Boolean diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala index cd13275f0..b48b209a6 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala @@ -13,7 +13,12 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: type TPrinter <: VerilogPrinter val useStdSimLibrary: Boolean = true def fileSuffix = "v" - def defsName: String = s"${getSet.topName}_defs" + def defsName: String = + val name = printerOptions.globalDefsFileName + if (name.nonEmpty) + val dotIdx = name.lastIndexOf('.') + if (dotIdx > 0) name.substring(0, dotIdx) else name + else s"${getSet.topName}_defs" def csLibrary(inSimulation: Boolean, minTimeUnitOpt: Option[TimeNumber.Unit]): String = val csTimeScale = minTimeUnitOpt.map { unit => def unitToStr(unit: TimeNumber.Unit): String = diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala index b32c46117..a9caa0a1f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala @@ -171,7 +171,9 @@ class VerilogPrinter(val dialect: VerilogDialect)(using case VerilogDialect.v2001 | VerilogDialect.v95 => "vh" case _ => "svh" def globalFileName: String = - s"${printer.defsName}.$verilogFileHeaderSuffix" + val name = printerOptions.globalDefsFileName + if (name.nonEmpty && name.contains('.')) name + else s"${printer.defsName}.$verilogFileHeaderSuffix" override def csGlobalFileContent: String = if (hasGlobalContent) val defName = printer.defsName.toUpperCase diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala index a4eca66cd..3589c2d84 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala @@ -12,7 +12,12 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: type TPrinter <: VHDLPrinter val useStdSimLibrary: Boolean = true def fileSuffix = "vhdl" - def packageName: String = s"${getSet.topName}_pkg" + def packageName: String = + val name = printerOptions.globalDefsFileName + if (name.nonEmpty) + val dotIdx = name.lastIndexOf('.') + if (dotIdx > 0) name.substring(0, dotIdx) else name + else s"${getSet.topName}_pkg" def csLibrary(inSimulation: Boolean, usesMathReal: Boolean): String = val default = sn"""|library ieee; diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala index cd7676f9f..d16fd5388 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala @@ -154,7 +154,10 @@ class VHDLPrinter(val dialect: VHDLDialect)(using def csDocString(doc: String): String = doc.linesIterator.mkString("--", "\n--", "") def csAnnotations(annotations: List[annotation.HWAnnotation]): String = "" // def csTimer(timer: Timer): String = unsupported - def globalFileName: String = s"${printer.packageName}.vhd" + def globalFileName: String = + val name = printerOptions.globalDefsFileName + if (name.nonEmpty && name.contains('.')) name + else s"${printer.packageName}.vhd" def designFileName(designName: String): String = s"$designName.vhd" def dfhdlDefsFileName: String = s"dfhdl_pkg.vhd" def dfhdlSourceContents: String = diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index 1623f0bcb..89e21aec3 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -130,7 +130,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity ID is |generic ( @@ -151,7 +150,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity IDTop is |generic ( @@ -355,7 +353,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Blinker_pkg.all; | |entity Blinker is |generic ( @@ -1172,7 +1169,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |generic ( @@ -1215,7 +1211,7 @@ class PrintVHDLCodeSpec extends StageSpec: | println("These are the values: " & to_string(param3) & ", " & to_string(param4) & ", " & to_string(param5) & ", " & to_string(param6) & ", " & to_string(param7) & ", " & to_string(param8) & ", " & to_string(param9) & ", " & t_enum_MyEnum'image(param10) & ""); | report | "Debug at Foo" & LF & - | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1161:9" & LF & + | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1158:9" & LF & | "param3 = " & to_string(param3) & LF & | "param4 = " & to_string(param4) & LF & | "param5 = " & to_string(param5) & LF & @@ -1234,7 +1230,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |generic ( @@ -1277,7 +1272,7 @@ class PrintVHDLCodeSpec extends StageSpec: | println("These are the values: " & to_string(param3) & ", " & to_string(param4) & ", " & to_string(param5) & ", " & to_string(param6) & ", " & to_string(param7) & ", " & to_string(param8) & ", " & to_string(param9) & ", " & t_enum_MyEnum'image(param10) & ""); | report | "Debug at Foo" & LF & - | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1161:9" & LF & + | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1158:9" & LF & | "param3 = " & to_string(param3) & LF & | "param4 = " & to_string(param4) & LF & | "param5 = " & to_string(param5) & LF & @@ -1309,7 +1304,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |generic ( @@ -1458,7 +1452,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1514,4 +1507,74 @@ class PrintVHDLCodeSpec extends StageSpec: |end Foo_arch;""".stripMargin ) } + test("globalDefsFileName override") { + given options.PrinterOptions.GlobalDefsFileName = "otherGlobal" + enum MyEnum extends Encoded: + case A, B + class Foo extends RTDesign: + val x = MyEnum <> IN + val y = MyEnum <> OUT + y := x + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|type t_enum_MyEnum is ( + | MyEnum_A, MyEnum_B + |); + | + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + |use work.otherGlobal.all; + | + |entity Foo is + |port ( + | x : in t_enum_MyEnum; + | y : out t_enum_MyEnum + |); + |end Foo; + | + |architecture Foo_arch of Foo is + |begin + | y <= x; + |end Foo_arch; + |""".stripMargin + ) + } + test("globalDefsFileName override with suffix") { + given options.PrinterOptions.GlobalDefsFileName = "otherGlobal.vhd" + enum MyEnum extends Encoded: + case A, B + class Foo extends RTDesign: + val x = MyEnum <> IN + val y = MyEnum <> OUT + y := x + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|type t_enum_MyEnum is ( + | MyEnum_A, MyEnum_B + |); + | + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + |use work.otherGlobal.all; + | + |entity Foo is + |port ( + | x : in t_enum_MyEnum; + | y : out t_enum_MyEnum + |); + |end Foo; + | + |architecture Foo_arch of Foo is + |begin + | y <= x; + |end Foo_arch; + |""".stripMargin + ) + } end PrintVHDLCodeSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 5c10366f3..59040569c 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -121,7 +121,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module ID#(parameter int width = 7)( | input wire logic signed [width - 1:0] x, @@ -133,7 +132,6 @@ class PrintVerilogCodeSpec extends StageSpec: | |`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module IDTop#(parameter int width = 16)( | input wire logic signed [width - 1:0] x, @@ -184,14 +182,12 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.vh" | |module ID( | x, | y |); | `include "dfhdl_defs.vh" - | `include "IDTop_defs.vh" | parameter integer width = 7; | input wire [width - 1:0] x; | output wire [width - 1:0] y; @@ -200,14 +196,12 @@ class PrintVerilogCodeSpec extends StageSpec: | |`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.vh" | |module IDTop( | x, | y |); | `include "dfhdl_defs.vh" - | `include "IDTop_defs.vh" | parameter integer width = 16; | input wire [width - 1:0] x; | output wire [width - 1:0] y; @@ -438,7 +432,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Counter_defs.svh" | |module Counter#(parameter int width = 8)( | input wire logic clk, @@ -476,7 +469,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Test_defs.svh" | |module Test#(parameter int width = 10)( | output logic [width - 1:0] x, @@ -509,7 +501,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Counter_defs.svh" | |module Counter#(parameter int width = 8)( | input wire logic clk, @@ -551,7 +542,6 @@ class PrintVerilogCodeSpec extends StageSpec: """|/* This is a led blinker */ |`default_nettype none |`timescale 1ns/1ps - |`include "Blinker_defs.svh" | |module Blinker#( | parameter int CLK_FREQ_KHz = 50000, @@ -1078,7 +1068,6 @@ class PrintVerilogCodeSpec extends StageSpec: sv2005.csTop, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo#(parameter string param = "Hello\n..\"World\"!"); | `include "dfhdl_defs.svh" @@ -1117,7 +1106,7 @@ class PrintVerilogCodeSpec extends StageSpec: | $display("These are the values: %d", param3, ", %d", param4, ", %h", param5, ", %h", param6, ", %d", param7, ", %b", param8, ", %s", param9 ? "true" : "false", ", %s", param10.name(), ""); | $info( | "Debug at Foo\n", - | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1069:9\n", + | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1059:9\n", | "param3 = %d\n", param3, | "param4 = %d\n", param4, | "param5 = %h\n", param5, @@ -1134,11 +1123,9 @@ class PrintVerilogCodeSpec extends StageSpec: v95.csTop, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo; | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | parameter param = "Hello\n..\"World\"!"; | parameter integer param3 = 42; | `define MyEnum_A 0 @@ -1189,7 +1176,7 @@ class PrintVerilogCodeSpec extends StageSpec: | $display("These are the values: %d", param3, ", %d", param4, ", %h", param5, ", %h", param6, ", %d", param7, ", %b", param8, ", %s", param9 ? "true" : "false", ", %s", MyEnum_to_string(param10), ""); | $display("INFO: ", | "Debug at Foo\n", - | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1069:9\n", + | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1059:9\n", | "param3 = %d\n", param3, | "param4 = %d\n", param4, | "param5 = %h\n", param5, @@ -1219,7 +1206,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo#(parameter int PORT_WIDTH = 5)( | input wire logic clk, @@ -1259,11 +1245,9 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo; | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | parameter integer PORT_WIDTH = 8; | parameter integer PORT_DEPTH = 4; | parameter [(PORT_WIDTH * PORT_DEPTH) - 1:0] initArg = {8'h01, 8'h02, 8'h03, 8'h04}; @@ -1409,7 +1393,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | input wire logic x, @@ -1500,4 +1483,64 @@ class PrintVerilogCodeSpec extends StageSpec: |endmodule""".stripMargin ) } + test("globalDefsFileName override") { + given options.PrinterOptions.GlobalDefsFileName = "otherGlobal" + enum MyEnum extends Encoded: + case A, B + class Foo extends RTDesign: + val x = MyEnum <> IN + val y = MyEnum <> OUT + y := x + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|typedef enum logic [0:0] { + | MyEnum_A = 0, + | MyEnum_B = 1 + |} t_enum_MyEnum; + | + |`default_nettype none + |`timescale 1ns/1ps + |`include "otherGlobal.svh" + | + |module Foo( + | input wire t_enum_MyEnum x, + | output t_enum_MyEnum y + |); + | `include "dfhdl_defs.svh" + | assign y = x; + |endmodule + |""".stripMargin + ) + } + test("globalDefsFileName override with suffix") { + given options.PrinterOptions.GlobalDefsFileName = "otherGlobal.svh" + enum MyEnum extends Encoded: + case A, B + class Foo extends RTDesign: + val x = MyEnum <> IN + val y = MyEnum <> OUT + y := x + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|typedef enum logic [0:0] { + | MyEnum_A = 0, + | MyEnum_B = 1 + |} t_enum_MyEnum; + | + |`default_nettype none + |`timescale 1ns/1ps + |`include "otherGlobal.svh" + | + |module Foo( + | input wire t_enum_MyEnum x, + | output t_enum_MyEnum y + |); + | `include "dfhdl_defs.svh" + | assign y = x; + |endmodule + |""".stripMargin + ) + } end PrintVerilogCodeSpec diff --git a/lib/src/main/scala/dfhdl/app/DFApp.scala b/lib/src/main/scala/dfhdl/app/DFApp.scala index 38543f61b..499a9f95a 100644 --- a/lib/src/main/scala/dfhdl/app/DFApp.scala +++ b/lib/src/main/scala/dfhdl/app/DFApp.scala @@ -423,6 +423,9 @@ trait DFApp: printDFHDLCode = mode.`print-compile`.toOption.get, printBackendCode = mode.`print-backend`.toOption.get ) + printerOptions = printerOptions.copy( + globalDefsFileName = mode.`global-defs-name`.toOption.get + ) case _ => // update linter options from command line parsedCommandLine.mode match diff --git a/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala b/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala index 7943fef06..d320d9683 100644 --- a/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala +++ b/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala @@ -13,6 +13,7 @@ class ParsedCommandLine( )(using eo: ElaborationOptions, co: CompilerOptions, + pto: PrinterOptions, lo: LinterOptions, so: SimulatorOptions, bo: BuilderOptions, @@ -81,6 +82,12 @@ class ParsedCommandLine( hidden = hidden, noshort = true ) + val `global-defs-name` = opt[String]( + descr = "override the global definitions file name (without suffix)", + default = Some(pto.globalDefsFileName), + noshort = true, + hidden = hidden + ) end CompileMode trait CommitMode extends CompileMode: this: ScallopConf & Mode => diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker.sv b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker.sv index 0a122d928..b1180e8c8 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker.sv +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker.sv @@ -1,7 +1,6 @@ /* This is a led blinker */ `default_nettype none `timescale 1ns/1ps -`include "Blinker_defs.svh" module Blinker#( parameter int CLK_FREQ_KHz = 50000, diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker.v b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker.v index de1d0356e..f7d2673d3 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker.v +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker.v @@ -1,7 +1,6 @@ /* This is a led blinker */ `default_nettype none `timescale 1ns/1ps -`include "Blinker_defs.vh" module Blinker#( parameter integer CLK_FREQ_KHz = 50000, @@ -13,7 +12,6 @@ module Blinker#( output reg led ); `include "dfhdl_defs.vh" - `include "Blinker_defs.vh" /* Half-count of the toggle for 50% duty cycle */ parameter integer HALF_PERIOD = (CLK_FREQ_KHz * 1000) / (LED_FREQ_Hz * 2); reg [clog2(HALF_PERIOD) - 1:0] cnt; diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker.v b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker.v index c55460553..69e54c06d 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker.v +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker.v @@ -1,7 +1,6 @@ /* This is a led blinker */ `default_nettype none `timescale 1ns/1ps -`include "Blinker_defs.vh" module Blinker( clk, @@ -9,7 +8,6 @@ module Blinker( led ); `include "dfhdl_defs.vh" - `include "Blinker_defs.vh" parameter integer CLK_FREQ_KHz = 50000; parameter integer LED_FREQ_Hz = 1; /* Half-count of the toggle for 50% duty cycle */ diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker.vhd b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker.vhd index 79dac3e62..8da5dd70c 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker.vhd +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.Blinker_pkg.all; entity Blinker is generic ( diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker.vhd b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker.vhd index cec07a688..f06111cec 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker.vhd +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.Blinker_pkg.all; entity Blinker is generic ( diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter.sv b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter.sv index fb082b6f9..c5ccd846f 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter.sv +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "Counter_defs.svh" module Counter#(parameter int width = 8)( input wire logic clk, diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter.v b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter.v index 9c3c0e012..2eb7f222a 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter.v +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "Counter_defs.vh" module Counter#(parameter integer width = 8)( input wire clk, @@ -9,7 +8,6 @@ module Counter#(parameter integer width = 8)( output reg [width - 1:0] cnt ); `include "dfhdl_defs.vh" - `include "Counter_defs.vh" always @(posedge clk) begin if (rst == 1'b1) cnt <= `dPW(0, 1, width); diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter.v b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter.v index dbcb73567..23514d912 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter.v +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "Counter_defs.vh" module Counter( clk, @@ -9,7 +8,6 @@ module Counter( cnt ); `include "dfhdl_defs.vh" - `include "Counter_defs.vh" parameter integer width = 8; input wire clk; input wire rst; diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter.vhd b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter.vhd index 9572b7058..884b62e07 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter.vhd +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.Counter_pkg.all; entity Counter is generic ( diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter.vhd b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter.vhd index ca06ff824..2921c2804 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter.vhd +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.Counter_pkg.all; entity Counter is generic ( diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile.sv b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile.sv index 32d8138c0..991b74167 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile.sv +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "RegFile_defs.svh" module RegFile#( parameter int DATA_WIDTH = 32, diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile.v b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile.v index cef0a6c8e..81b50ae73 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile.v +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "RegFile_defs.vh" module RegFile#( parameter integer DATA_WIDTH = 32, @@ -16,7 +15,6 @@ module RegFile#( input wire rd_wren ); `include "dfhdl_defs.vh" - `include "RegFile_defs.vh" reg [DATA_WIDTH - 1:0] regs [0:REG_NUM - 1]; always @(posedge clk) begin diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile.v b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile.v index d21b85bca..222e49e9b 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile.v +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "RegFile_defs.vh" module RegFile( clk, @@ -13,7 +12,6 @@ module RegFile( rd_wren ); `include "dfhdl_defs.vh" - `include "RegFile_defs.vh" parameter integer DATA_WIDTH = 32; parameter integer REG_NUM = 32; input wire clk; diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile.vhd b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile.vhd index b9ad459b4..df5c33443 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile.vhd +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.RegFile_pkg.all; entity RegFile is generic ( diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile.vhd b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile.vhd index df815655b..de8f519da 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile.vhd +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.RegFile_pkg.all; entity RegFile is generic ( diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR.sv b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR.sv index d141c2eac..7909a4580 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR.sv +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "TrueDPR_defs.svh" module TrueDPR#( parameter int DATA_WIDTH = 8, diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR.v b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR.v index 8f11982e7..200600c9c 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR.v +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "TrueDPR_defs.vh" module TrueDPR#( parameter integer DATA_WIDTH = 8, @@ -18,7 +17,6 @@ module TrueDPR#( input wire b_we ); `include "dfhdl_defs.vh" - `include "TrueDPR_defs.vh" reg [DATA_WIDTH - 1:0] ram [0:(2 ** ADDR_WIDTH) - 1]; always @(posedge a_clk) begin diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR.v b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR.v index cc52f0d25..db25bf457 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR.v +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "TrueDPR_defs.vh" module TrueDPR( a_clk, @@ -15,7 +14,6 @@ module TrueDPR( b_we ); `include "dfhdl_defs.vh" - `include "TrueDPR_defs.vh" parameter integer DATA_WIDTH = 8; parameter integer ADDR_WIDTH = 8; input wire a_clk; diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR.vhd b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR.vhd index 643f0a6c4..2360f9c9e 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR.vhd +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.TrueDPR_pkg.all; entity TrueDPR is generic ( diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR.vhd b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR.vhd index 5bfabb3a6..47592fe69 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR.vhd +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.TrueDPR_pkg.all; entity TrueDPR is generic ( diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv index 1704e8e35..a5dc48038 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "UART_Tx_defs.svh" module UART_Tx#( parameter int CLK_FREQ_KHz = 50000, diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx.v b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx.v index c07e7d415..875dff3c9 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx.v +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "UART_Tx_defs.vh" module UART_Tx#( parameter integer CLK_FREQ_KHz = 50000, @@ -15,7 +14,6 @@ module UART_Tx#( output reg tx_done ); `include "dfhdl_defs.vh" - `include "UART_Tx_defs.vh" parameter integer BIT_CLOCKS = (CLK_FREQ_KHz * 1000) / BAUD_RATE_BPS; `define Status_Idle 1 `define Status_StartBit 2 diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx.v b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx.v index df71b3854..13a9d8a07 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx.v +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "UART_Tx_defs.vh" module UART_Tx( clk, @@ -12,7 +11,6 @@ module UART_Tx( tx_done ); `include "dfhdl_defs.vh" - `include "UART_Tx_defs.vh" parameter integer CLK_FREQ_KHz = 50000; parameter integer BAUD_RATE_BPS = 115200; parameter integer BIT_CLOCKS = (CLK_FREQ_KHz * 1000) / BAUD_RATE_BPS; diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx.vhd b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx.vhd index 80ac54ec1..5c8dd2df1 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx.vhd +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.UART_Tx_pkg.all; entity UART_Tx is generic ( diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx.vhd b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx.vhd index 15e99debc..05ccdb87b 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx.vhd +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.UART_Tx_pkg.all; entity UART_Tx is generic ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen.sv b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen.sv index fad9cc77b..8aa8b2510 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen.sv +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen.sv @@ -5,7 +5,6 @@ */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftGen_defs.svh" module LeftShiftGen#(parameter int width = 8)( /* bits input */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen.v index 9c6b302d4..380beb54a 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen.v @@ -5,7 +5,6 @@ */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftGen_defs.vh" module LeftShiftGen#(parameter integer width = 8)( /* bits input */ @@ -16,6 +15,5 @@ module LeftShiftGen#(parameter integer width = 8)( output wire [width - 1:0] oBits ); `include "dfhdl_defs.vh" - `include "LeftShiftGen_defs.vh" assign oBits = iBits << shift; endmodule diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen.v index 4e9ef9368..0b268a7ef 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen.v @@ -5,7 +5,6 @@ */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftGen_defs.vh" module LeftShiftGen( iBits, @@ -13,7 +12,6 @@ module LeftShiftGen( oBits ); `include "dfhdl_defs.vh" - `include "LeftShiftGen_defs.vh" parameter integer width = 8; /* bits input */ input wire [width - 1:0] iBits; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen.vhd index 92a5b0696..eec45aff2 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen.vhd @@ -6,7 +6,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShiftGen_pkg.all; entity LeftShiftGen is generic ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen.vhd index 92a5b0696..eec45aff2 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen.vhd @@ -6,7 +6,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShiftGen_pkg.all; entity LeftShiftGen is generic ( diff --git a/lib/src/test/scala/issues/IssueSpec.scala b/lib/src/test/scala/issues/IssueSpec.scala index 1becff9d1..dce572d81 100644 --- a/lib/src/test/scala/issues/IssueSpec.scala +++ b/lib/src/test/scala/issues/IssueSpec.scala @@ -23,7 +23,6 @@ class IssuesSpec extends FunSuite: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.ArrayIssue_pkg.all; | |entity ArrayIssue is |port ( @@ -61,7 +60,6 @@ class IssuesSpec extends FunSuite: i135.VerilogSRA().getCompiledCodeString, """|`default_nettype none |`timescale 1ns/1ps - |`include "VerilogSRA_defs.svh" | |module VerilogSRA( | input wire logic signed [9:0] a From e0af93140bbff3512172ae3b8411b821589bda9a Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 31 Mar 2026 02:17:08 +0300 Subject: [PATCH 072/115] add some info about processes --- docs/user-guide/processes/index.md | 228 ++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/processes/index.md b/docs/user-guide/processes/index.md index eba028ee2..0422d4340 100644 --- a/docs/user-guide/processes/index.md +++ b/docs/user-guide/processes/index.md @@ -1 +1,227 @@ -# Processes \ No newline at end of file +# Processes + +Processes define *when* a block of logic runs. DFHDL supports processes in two domains: + +- **RT domain**: A **clock-bound** process used to describe finite-state machines (FSMs). The process runs in lockstep with the domain clock and uses steps, waits, and control flow that the compiler lowers to registers and combinational logic. +- **ED domain**: **Sensitivity-driven** processes that run when listed signals change (or all read signals with `process(all)`), giving the same level of control as `process` in VHDL or `always` in Verilog. + +Processes are not available in the dataflow (DF) domain. Processes cannot be nested inside another process. + +## RT domain: clock-bound FSM process + +In an [RT design][design-domains], a process is used to describe a finite-state machine that is **clock-bound**: it advances on the domain clock and is compiled to a state register plus combinational next-state and output logic. + +### Syntax: `process` / `process.forever` + +Use the shorthand `process:` (or `process.forever`) inside an `RTDesign` or `RTDomain`. The block contains either plain combinational logic (assignments, no steps) or step definitions that form an FSM. + +### Step-based FSM + +Define states as `def Name: Step = ...` and control flow with: + +- **`NextStep`** — advance to the next step in definition order. +- **`ThisStep`** — stay in the current step for another cycle. +- **`FirstStep`** — go to the first step (e.g. reset to initial state). +- **Step name** (e.g. `S1`, `S2`) — jump to that step. + +You can optionally name the process (e.g. `val my_fsm = process:`) so the compiler uses that name for the generated state enum and state register. + +```scala +class SimpleFSM extends RTDesign: + val x = Bit <> IN + val y = Bit <> OUT.REG init 0 + + process: + def S0: Step = + y.din := 0 + if (x) NextStep else S0 + def S1: Step = + y.din := 1 + if (x) S2 else FirstStep + def S2: Step = + y.din := 0 + if (x) ThisStep else FirstStep +``` + +The compiler lowers this to a state enum, a state register, and a `match` on the state; see [Design Domains][design-domains] for the compilation flow. + +### fallThrough + +A step can define **`def fallThrough = cond`** where `cond` is a Boolean/Bit expression. When the condition holds, the step advances to the next step in the same cycle (conditional advancement); when it does not, the FSM stays in the current step. + +### onEntry and onExit + +Inside a step you can define: + +- **`def onEntry = ...`** — run when entering the step (once per transition into this state). +- **`def onExit = ...`** — run when leaving the step (once per transition out of this state). + +```scala +def S1: Step = + def onEntry = + y.din := 1 + if (x) S2 else FirstStep +def S2: Step = + def onExit = + y.din := 0 + if (x) ThisStep else FirstStep +``` + +### Waits and loops + +RT processes can use **cycle waits** (e.g. `1.cy.wait`, `n.cy.wait`) and **wait conditions** (`waitUntil(cond)`, `waitWhile(cond)`). The compiler converts these into step blocks and counters so that the behavior remains clock-bound and synthesizable. + +### Process with no steps + +If the process body has no step definitions, it is purely combinational and runs every cycle: + +```scala +process: + y.din := x +``` + +## ED domain: sensitivity-driven processes + +In an [ED design][design-domains], processes are **sensitivity-driven**: they run when an event occurs on one of their sensitivity signals (or on any read signal with `process(all)`). + +## ED process forms + +### Sensitivity list: `process(sig1, sig2, ...)` + +The process runs whenever any of the listed signals change. + +```scala +class CombAndSeq extends EDDesign: + val clk = Bit <> IN + val rst = Bit <> IN + val x = UInt(8) <> IN + val y = UInt(8) <> OUT + + // Combinational logic: runs when x changes + process(x): + y := x + 1 + + // Sequential logic: runs on clock (and optionally reset) events + val r = UInt(8) <> VAR init 0 + process(clk): + if (clk.rising) + r :== x +``` + +You can list multiple signals, including edge-qualified signals (see [Edge sensitivity](#edge-sensitivity)). + +### Combinational-style: `process(all)` + +The process is sensitive to *all* signals that are read in the block. Use this for combinational logic that should react to any input change. The compiler infers the actual sensitivity list from the block body. + +```scala +class CombLogic extends EDDesign: + val a = UInt(8) <> IN + val b = UInt(8) <> IN + val y = UInt(8) <> OUT + + process(all): + y := a + b +``` + +### Forever process: `process.forever` / `process` + +A process with no sensitivity list runs continuously. It is allowed in RT and ED, but **not** in DF. The shorthand `process:` (no arguments) is rewritten by the compiler to `process.forever`. + +- **In RT**: `process:` is the [clock-bound FSM process](#rt-domain-clock-bound-fsm-process) described above (steps, waits, etc.). +- **In ED**: Use it for testbenches or clock generation (e.g. toggling a clock with `wait`). + +```scala +class Testbench extends EDDesign: + val clk = Bit <> VAR + process.forever: + clk := !clk + 5.ns.wait +``` + +## Edge sensitivity + +For sequential (clocked) logic you typically want the process to run only on a specific clock edge. You can either: + +1. **List the clock and check the edge inside the block** (VHDL-style): + +```scala +process(clk): + if (clk.rising) + reg :== nextVal +``` + +2. **Put the edge in the sensitivity list** (Verilog-style; compiler may normalize to this): + +```scala +process(clk.rising): + reg :== nextVal +``` + +Edge options are `.rising` and `.falling` on clock (or bit) signals. When reset is used, list both clock and reset and branch on reset then clock edge: + +```scala +process(clk, rst): + if (rst) + out :== 0 + else if (clk.rising) + out :== nextVal +``` + +## Assignments inside processes + +### Blocking assignment `:=` + +Takes effect immediately within the process. Use for combinational logic and for intermediate values that are read later in the same process. + +```scala +process(all): + val temp = a + b // read a, b + y := temp // immediate update of y +``` + +### Non-blocking assignment `:==` + +Schedules an update at the end of the current evaluation step. Use for registers and outputs that should not create combinational feedback within the same process. + +```scala +process(clk): + if (clk.rising) + counter :== counter + 1 // register update +``` + +!!! tip "Rule of thumb" + Use `:=` for combinational (e.g. in `process(all)` or combinational branches). Use `:==` for register and sequential outputs in clocked processes. + +## 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. + +```scala +process(all): + val z = UInt(8) <> VAR + if (x > 10) + z := x + 1 + else + z := x - 1 + y := z +``` + +## Relation to design domains + +| Domain | Processes | +|--------|-----------| +| **DF** | No processes. Behavior is expressed with dataflow and `.prev`; the compiler introduces registers and eventually ED processes. | +| **RT** | **Clock-bound FSM process**: `process:` (or `process.forever`) with optional step definitions (`def Name: Step = ...`), `onEntry`/`onExit`, and waits. Compiled to a state register and match logic. Plain RT register code (no process) is also lowered to ED processes by the compiler. | +| **ED** | **Sensitivity-driven**: `process(sig1, sig2, ...)`, `process(all)`, and `process.forever` / `process`. Full control over sensitivity and blocking vs non-blocking assignment. | + +See [Design Domains][design-domains] for the overall flow from DF → RT → ED and how processes fit into compilation. + +## Summary + +- **RT**: Use **`process:`** in **RTDesign** / **RTDomain** for a clock-bound FSM with **`def Name: Step = ...`**, **`NextStep`** / **`ThisStep`** / **`FirstStep`**, and optional **`onEntry`** / **`onExit`** and waits. +- **ED**: Use **`process(sig1, sig2, ...)`** or **`process(all)`** in **EDDesign** / **EDDomain** to define when a block runs; **`process(all)`** for combinational logic; **`process(clk)`** (and optionally **`process(clk, rst)`**) with **`clk.rising`** / **`clk.falling`** for sequential logic. +- Use **`:=`** for immediate (blocking) updates and **`:==`** for register (non-blocking) updates in ED processes. +- Processes cannot be nested and are not available in the DF domain. + +[design-domains]: ../design-domains/index.md From dd334c24268729170e5b76903fc0a308a4a0aec3 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 31 Mar 2026 02:32:59 +0300 Subject: [PATCH 073/115] remove unused signal lint-off comments and clean up code --- compiler/stages/src/main/resources/dfhdl_defs.vh | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/compiler/stages/src/main/resources/dfhdl_defs.vh b/compiler/stages/src/main/resources/dfhdl_defs.vh index a561bc783..a1b9ace64 100644 --- a/compiler/stages/src/main/resources/dfhdl_defs.vh +++ b/compiler/stages/src/main/resources/dfhdl_defs.vh @@ -23,9 +23,7 @@ // OTHER DEALINGS IN THE SOFTWARE. // // For more information, please refer to -// -// TODO: remove UNUSEDSIGNAL lint-off after -// https://github.com/verilator/verilator/issues/6893 is fixed + `define MAX(a, b) ((a) > (b) ? (a) : (b)) `define MIN(a, b) ((a) < (b) ? (a) : (b)) `define ABS(a) ((a) < 0 ? -(a) : (a)) @@ -75,7 +73,6 @@ `define SIGNED_SHIFT_RIGHT(data, shift, width) \ ((data[width-1] == 1'b1) ? ((data >> shift) | ({width{1'b1}} << (width - shift))) : (data >> shift)) function integer clog2; -/* verilator lint_off UNUSEDSIGNAL */ input integer n; integer result, temp; begin @@ -86,12 +83,10 @@ begin result = result + 1; end clog2 = result; -/* verilator lint_on UNUSEDSIGNAL */ end endfunction // Function to perform base raised to the power of exp (base ** exp) function integer power; -/* verilator lint_off UNUSEDSIGNAL */ input integer base; input integer exp; integer i; // Loop variable @@ -104,7 +99,6 @@ begin power = power * base; end end -/* verilator lint_on UNUSEDSIGNAL */ end endfunction From a76e6a2c2450f824da8e3e192c914003a40052e8 Mon Sep 17 00:00:00 2001 From: Oron Date: Tue, 31 Mar 2026 04:33:23 +0300 Subject: [PATCH 074/115] docs: address knowledge gaps from UART translation tickets - Type system: document that comparisons return Boolean (not Bit) and how to convert with .bit; add Bit-to-UInt conversion note (.bits.uint); add dynamic bit indexing examples with UInt variables - Conditionals: document that match patterns do not support named Bits constants (use if/else if chains instead) - Transitioning from Verilog: add sections on literal syntax (warning about mixing Verilog prefixes), elaboration-time computation ($clog2 replacement), process block mapping (always->process), init behavior in ED domain, and multi-file given conflict avoidance Closes #1 #2 #3 #4 #5 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/transitioning/from-verilog/index.md | 141 ++++++++++++++++++++++- docs/user-guide/conditionals/index.md | 23 ++++ docs/user-guide/type-system/index.md | 43 ++++++- 3 files changed, 204 insertions(+), 3 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 271e4882e..2d68f7d90 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -105,9 +105,148 @@ reg [7:0] v = 8’b1011; ``` ```scala linenums="0" title="DFHDL" -val v = Bits(8) <> VAR init b"8'1011" +val v = Bits(8) <> VAR init b"8’1011" ``` /// +/// admonition | Numeric Literals + type: verilog +DFHDL uses string interpolators for sized literals. Each type has its own interpolator -- do not mix Verilog base prefixes (`’b`, `’d`, `’h`) inside them. + +
+ +```sv linenums="0" title="Verilog" +8’b1011_0000 // binary +8’hB0 // hex +8’d176 // decimal +5’d27 // 5-bit decimal +``` + +```scala linenums="0" title="DFHDL" +b"8’1011_0000" // binary (b"...") +h"8’B0" // hex (h"...") +d"8’176" // unsigned decimal (d"...") +d"5’27" // 5-bit unsigned decimal +``` + +
+ +`b"..."` accepts only binary digits (`0`, `1`, `?`). Writing `b"5’d27"` is an error -- use `d"5’27"` for decimal values. +/// + +/// admonition | Elaboration-Time Computation (System Functions) + type: verilog +Verilog system functions like `$clog2` have no DFHDL equivalent because DFHDL designs are Scala programs. Use standard Scala expressions at elaboration time instead. + +
+ +```sv linenums="0" title="Verilog" +parameter RATE = 5208; +localparam WIDTH = $clog2(RATE); +reg [WIDTH-1:0] counter; +``` + +```scala linenums="0" title="DFHDL" +val RATE: Int <> CONST = 5208 +val WIDTH = scala.math.ceil( + scala.math.log(RATE.toDouble) / + scala.math.log(2) +).toInt +val counter = UInt(WIDTH) <> VAR +``` + +
+ +Any Scala expression can be used to compute widths, initial values, and other elaboration-time parameters. This replaces `$clog2`, `$bits`, `$size`, and similar Verilog system functions. +/// + +/// admonition | Process Blocks (always) + type: verilog +Verilog `always` blocks map to DFHDL ED domain `process(...)` blocks. + +
+ +```sv linenums="0" title="Verilog" +// Combinational +always @(*) begin + y = a + b; +end + +// Sequential (clocked) +always @(posedge clk) begin + counter <= counter + 1; +end + +// Sequential with async reset +always @(posedge clk or posedge rst) + if (rst) + q <= 0; + else + q <= d; +``` + +```scala linenums="0" title="DFHDL" +// Combinational +process(all): + y := a + b + + +// Sequential (clocked) +process(clk): + if (clk.rising) + counter :== counter + 1 + +// Sequential with async reset +process(clk, rst): + if (rst) + q :== 0 + else if (clk.rising) + q :== d +``` + +
+ +- `always @(*)` becomes `process(all):` +- `always @(posedge clk)` becomes `process(clk):` with `if (clk.rising)` inside +- Verilog blocking `=` becomes DFHDL `:=` (use in combinational processes) +- Verilog non-blocking `<=` becomes DFHDL `:==` (use in clocked processes) +/// + +/// admonition | Variable Initialization (init) + type: verilog +In ED domain, `init` on a `VAR` generates a Verilog `reg` with an initial value. This maps to both `initial begin` blocks and `reg ... = value` declarations. + +
+ +```sv linenums="0" title="Verilog" +reg [7:0] counter = 8’d0; +// or equivalently: +// initial counter = 8’d0; +``` + +```scala linenums="0" title="DFHDL" +val counter = UInt(8) <> VAR init 0 +``` + +
+/// + +/// admonition | Multi-File Projects + type: verilog +In a scala-cli project with multiple `.scala` files, shared `given` declarations (such as compiler options) must appear in exactly one file. Place them in your `project.scala` file to avoid duplicate definition errors. + +```scala title="project.scala" +//> using scala 3.8.1 +//> using dep io.github.dfianthdl::dfhdl::0.17.0 +//> using plugin io.github.dfianthdl:::dfhdl-plugin:0.17.0 + +import dfhdl.* +given options.CompilerOptions.Backend = backends.verilog +given options.CompilerOptions.PrintBackendCode = true +``` + +Individual design files should `import dfhdl.*` but not redeclare the shared `given` options. +/// + diff --git a/docs/user-guide/conditionals/index.md b/docs/user-guide/conditionals/index.md index 86c6f9840..512e01b66 100644 --- a/docs/user-guide/conditionals/index.md +++ b/docs/user-guide/conditionals/index.md @@ -184,6 +184,29 @@ else - Optimizes bit pattern matching into efficient comparisons - Extracts struct fields into temporary variables when needed +!!! warning "Match patterns only support literal values and enum members" + DFHDL `match` patterns work with integer literals, bit-string literals (e.g., `b"00"`, `h"F"`), enum members, and wildcards. You **cannot** use named `Bits` constants as match patterns -- even with Scala's backtick stable-identifier syntax (e.g., `` case `MY_STATE` => ``), the DFHDL compiler will reject them with an "Unknown pattern" error. + + If you need to branch on named constants (common when translating Verilog `case` statements over `reg` state variables), use `if`/`else if` chains instead: + ```scala + // These are Bits constants used as state encodings + val STATE_IDLE = b"2'00" + val STATE_START = b"2'01" + val STATE_DATA = b"2'10" + + // WRONG: match with named constants fails + // state match + // case `STATE_IDLE` => ... // Error: Unknown pattern + + // CORRECT: use if/else if chains + if (state == STATE_IDLE) + // idle logic + else if (state == STATE_START) + // start logic + else if (state == STATE_DATA) + // data logic + ``` + ### Best Practices 1. **Use Match for Multi-Way Branching**: When dealing with multiple cases, match is often clearer than nested if-else diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 75aac1062..c2a90fb97 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -460,11 +460,23 @@ val ms1 = b8(7, 7) // MSB only val ls7 = b8(6, 0) // 7 LSBs val ls1 = b8(0, 0) // LSB only -// Single bit access +// Single bit access (static index) val msbit = b8(7) // MSB val lsbit = b8(0) // LSB + +// Dynamic bit access (index is a UInt variable) +val idx = UInt(3) <> VAR +val dynbit = b8(idx) // Single bit at position idx (returns Bit) ``` +!!! tip "Dynamic bit indexing" + You can index into a `Bits` value using a `UInt` variable, not just integer literals. This is equivalent to Verilog's `bits_val[idx]` where `idx` is a register or wire. Dynamic indexing works for both reads and writes inside processes: + ```scala + process(clk): + if (clk.rising) + data(bitpos) :== rx // dynamic bit-indexed write + ``` + ### Bit Operations ```scala val b8 = Bits(8) <> VAR @@ -661,6 +673,24 @@ These operations propagate constant modifiers, meaning that if the casted argume | `lhs.bit` | Cast to a DFHDL `Bit` value | `Boolean` DFHDL value | `Bit` DFHDL value | /// +!!! note "Comparison results are `Boolean`, not `Bit`" + All comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`) return `Boolean`. To assign a comparison result to a `Bit` port or variable, apply `.bit`: + ```scala + val tick = Bit <> OUT + val counter = UInt(8) <> VAR + val limit = UInt(8) <> IN + tick := (counter == limit).bit // Boolean -> Bit conversion + ``` + Conversely, `Bit` values can be used directly in `if` conditions (they are accepted as boolean expressions), and you can compare a `Bit` with integer literals `0` and `1`. + +!!! note "`Bit` does not have `.uint` -- use `.bits.uint`" + The `.uint` method is available on `Bits` values but not directly on `Bit`. To convert a `Bit` to `UInt(1)`, go through `Bits(1)` first: + ```scala + val b = Bit <> IN + val u = UInt(1) <> VAR + u := b.bits.uint // Bit -> Bits(1) -> UInt(1) + ``` + ```scala val bt1 = Bit <> VAR val bl1 = bt1.bool @@ -1106,7 +1136,7 @@ These operations propagate constant modifiers and maintain proper bit widths: | `lhs % rhs` | Modulo | Both decimal types | Result with rhs width | #### Comparison Operations -Return Boolean values: +Return `Boolean` values (not `Bit`). To assign a comparison result to a `Bit` port or variable, use `.bit`: /// html | div.operations | Operation | Description | LHS/RHS Constraints | Returns | @@ -1118,6 +1148,15 @@ Return Boolean values: | `lhs == rhs` | Equal | Both decimal types | Boolean | | `lhs != rhs` | Not equal | Both decimal types | Boolean | +```scala +val counter = UInt(8) <> VAR +val limit = UInt(8) <> IN +val tick = Bit <> OUT + +// Comparison returns Boolean; convert to Bit for assignment to a Bit port +tick := (counter == limit).bit +``` + ### Constant Generation #### Decimal String-Interpolator {#d-interp} From d60007cdbba8c9f10cbcefc080eea7f811f1fd0d Mon Sep 17 00:00:00 2001 From: Oron Date: Tue, 31 Mar 2026 04:51:07 +0300 Subject: [PATCH 075/115] docs: recommend enums for FSM matching, use UInt.until for width computation - Replace warning about Bits constants in match with positive tip recommending enumerations with appropriate encoding (Encoded, Manual, etc.) - Replace verbose scala.math.log clog2 workaround with UInt.until/UInt.to Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/transitioning/from-verilog/index.md | 13 ++++----- docs/user-guide/conditionals/index.md | 37 ++++++++++++------------ 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 2d68f7d90..210d95d67 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -138,7 +138,10 @@ d"5’27" // 5-bit unsigned decimal /// admonition | Elaboration-Time Computation (System Functions) type: verilog -Verilog system functions like `$clog2` have no DFHDL equivalent because DFHDL designs are Scala programs. Use standard Scala expressions at elaboration time instead. +Instead of computing widths manually with `$clog2`, use `UInt.until` / `UInt.to` (or `Bits.until` / `Bits.to`) which set the width automatically based on the value range: + +- `.until(sup)` — width = `clog2(sup)` (value can be 0 to sup-1) +- `.to(max)` — width = `clog2(max+1)` (value can be 0 to max)
@@ -150,16 +153,12 @@ reg [WIDTH-1:0] counter; ```scala linenums="0" title="DFHDL" val RATE: Int <> CONST = 5208 -val WIDTH = scala.math.ceil( - scala.math.log(RATE.toDouble) / - scala.math.log(2) -).toInt -val counter = UInt(WIDTH) <> VAR +val counter = UInt.until(RATE) <> VAR ```
-Any Scala expression can be used to compute widths, initial values, and other elaboration-time parameters. This replaces `$clog2`, `$bits`, `$size`, and similar Verilog system functions. +See [UInt constructors](../../user-guide/type-system/index.md#unsigned-integer-uint) and [Bits constructors](../../user-guide/type-system/index.md#bit-vector-bits) for details. /// /// admonition | Process Blocks (always) diff --git a/docs/user-guide/conditionals/index.md b/docs/user-guide/conditionals/index.md index 512e01b66..4f58980b1 100644 --- a/docs/user-guide/conditionals/index.md +++ b/docs/user-guide/conditionals/index.md @@ -184,27 +184,26 @@ else - Optimizes bit pattern matching into efficient comparisons - Extracts struct fields into temporary variables when needed -!!! warning "Match patterns only support literal values and enum members" - DFHDL `match` patterns work with integer literals, bit-string literals (e.g., `b"00"`, `h"F"`), enum members, and wildcards. You **cannot** use named `Bits` constants as match patterns -- even with Scala's backtick stable-identifier syntax (e.g., `` case `MY_STATE` => ``), the DFHDL compiler will reject them with an "Unknown pattern" error. +!!! tip "Use enumerations for state matching" + For FSM-style `match` over states, define an enumeration with the appropriate [encoding](../type-system/index.md#dfhdl-enumeration-enum--extends-encoded) and match on its members: - If you need to branch on named constants (common when translating Verilog `case` statements over `reg` state variables), use `if`/`else if` chains instead: ```scala - // These are Bits constants used as state encodings - val STATE_IDLE = b"2'00" - val STATE_START = b"2'01" - val STATE_DATA = b"2'10" - - // WRONG: match with named constants fails - // state match - // case `STATE_IDLE` => ... // Error: Unknown pattern - - // CORRECT: use if/else if chains - if (state == STATE_IDLE) - // idle logic - else if (state == STATE_START) - // start logic - else if (state == STATE_DATA) - // data logic + enum State extends Encoded: // 00, 01, 10 + case Idle, Start, Data + + val state = State <> VAR + state match + case State.Idle => // idle logic + case State.Start => // start logic + case State.Data => // data logic + ``` + + For non-sequential state encodings, use `Encoded.Manual`: + ```scala + enum State(val value: UInt[3] <> CONST) extends Encoded.Manual(3): + case Idle extends State(d"3'0") + case Start extends State(d"3'3") + case Data extends State(d"3'5") ``` ### Best Practices From 03976e26c57007ac26173e93e17589f3076ffcb8 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Tue, 31 Mar 2026 07:36:58 +0300 Subject: [PATCH 076/115] define unique case for local system verilog enumeration --- .../dfhdl/compiler/printing/DFOwnerPrinter.scala | 12 +++++++++--- .../stages/verilog/VerilogOwnerPrinter.scala | 9 +++++++-- .../compiler/stages/verilog/VerilogTypePrinter.scala | 7 ++----- .../compiler/stages/vhdl/VHDLOwnerPrinter.scala | 2 +- .../test/scala/StagesSpec/PrintVerilogCodeSpec.scala | 2 +- .../scala/dfhdl/tools/toolsCore/IcarusVerilog.scala | 3 +++ .../verilog.sv2009/hdl/UART_Tx.sv | 4 ++-- 7 files changed, 25 insertions(+), 14 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala index f810e66d9..e1bfae900 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala @@ -122,7 +122,10 @@ trait AbstractOwnerPrinter extends AbstractPrinter: case DFMember.Empty => "" case _ => csDFCaseGuard(caseBlock.guardRef) s"$csDFCaseKeyword${csDFCasePattern(caseBlock.pattern)}$csGuard$csDFCaseSeparator" - def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean): String + // isUnique is true when the selector is a local enum type, enabling `unique case` in + // SystemVerilog to avoid lint warnings. Global enums are excluded because + // their full set of entries is not guaranteed to be covered at every match site. + def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean, isUnique: Boolean): String def csDFMatchEnd: String def csStepBlock(stepBlock: StepBlock): String def csDFForBlock(forBlock: DFLoop.DFForBlock): String @@ -162,7 +165,10 @@ trait AbstractOwnerPrinter extends AbstractPrinter: ch match case mh: DFConditional.DFMatchHeader => val csSelector = mh.selectorRef.refCodeString.applyBrackets() - sn"""|${csDFMatchStatement(csSelector, mh.hasWildcards)} + val isUnique = mh.selectorRef.get.dfType match + case e: DFEnum => !getSet.designDB.getGlobalNamedDFTypes.contains(e) + case _ => false + sn"""|${csDFMatchStatement(csSelector, mh.hasWildcards, isUnique)} |${csChains.hindent} |${csDFMatchEnd}""" case ih: DFConditional.DFIfHeader => csChains @@ -348,7 +354,7 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: def csDFCaseKeyword: String = "case " def csDFCaseSeparator: String = " =>" def csDFMatchEnd: String = "end match" - def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean): String = + def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean, isUnique: Boolean): String = s"$csSelector match" def csProcessBlock(pb: ProcessBlock): String = val body = csDFOwnerBody(pb) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala index b48b209a6..f4bf32d42 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala @@ -195,13 +195,18 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: def csDFCaseKeyword: String = "" def csDFCaseSeparator: String = ":" def csDFCaseGuard(guardRef: DFConditional.Block.GuardRef): String = printer.unsupported - def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean): String = + def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean, isUnique: Boolean): String = val insideSupport = printer.dialect match case VerilogDialect.v2001 | VerilogDialect.v95 => false case _ => true + val uniqueSupport = printer.dialect match + case VerilogDialect.v2001 | VerilogDialect.v95 => false + case _ => true + val uniquePrefix = + if (isUnique && uniqueSupport) "unique " else "" val keyWord = if (wildcardSupport && !insideSupport) "casez" else "case" val insideStr = if (wildcardSupport && insideSupport) " inside" else "" - s"$keyWord ($csSelector)$insideStr" + s"$uniquePrefix$keyWord ($csSelector)$insideStr" def csDFMatchEnd: String = "endcase" val sensitivityListSep = printer.dialect match diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogTypePrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogTypePrinter.scala index 345cc726b..d97221539 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogTypePrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogTypePrinter.scala @@ -74,11 +74,8 @@ protected trait VerilogTypePrinter extends AbstractTypePrinter: dfType.entries.view .map((n, v) => s"${enumName}_$n = $v") .mkString(",\n") - // TODO: quartus seems to not accept an explicit size, so we drop it locally where it's not required. - // Globally, size is required (at least for verilator linter), so we need to drop enumeration altogether - // in such a case (change to a vector and list of constants) and then remove the special case handling - // here. - val explicitWidth = if (global) s" logic [${dfType.width - 1}:0]" else "" + // TODO: quartus seems to not accept an explicit size Globally + val explicitWidth = s" logic [${dfType.width - 1}:0]" s"typedef enum$explicitWidth {\n${entries.hindent}\n} ${csDFEnumTypeName(dfType)};" else dfType.entries.view diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala index 3589c2d84..18babf197 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala @@ -217,7 +217,7 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: def csDFCaseKeyword: String = "when " def csDFCaseSeparator: String = " =>" def csDFCaseGuard(guardRef: DFConditional.Block.GuardRef): String = printer.unsupported - def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean): String = + def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean, isUnique: Boolean): String = s"case $csSelector is" def csDFMatchEnd: String = "end case;" def csProcessBlock(pb: ProcessBlock): String = diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 59040569c..cf768329f 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -1072,7 +1072,7 @@ class PrintVerilogCodeSpec extends StageSpec: |module Foo#(parameter string param = "Hello\n..\"World\"!"); | `include "dfhdl_defs.svh" | localparam int param3 = 42; - | typedef enum { + | typedef enum logic [1:0] { | MyEnum_A = 0, | MyEnum_B = 1, | MyEnum_C = 2 diff --git a/lib/src/main/scala/dfhdl/tools/toolsCore/IcarusVerilog.scala b/lib/src/main/scala/dfhdl/tools/toolsCore/IcarusVerilog.scala index 919421971..18b20820d 100644 --- a/lib/src/main/scala/dfhdl/tools/toolsCore/IcarusVerilog.scala +++ b/lib/src/main/scala/dfhdl/tools/toolsCore/IcarusVerilog.scala @@ -66,6 +66,9 @@ object IcarusVerilog extends VerilogLinter, VerilogSimulator: // suppress the "cannot be synthesized" warning when in simulation if (line.contains("cannot be synthesized") && getSet.designDB.inSimulation) true + // workaround for https://github.com/steveicarus/iverilog/issues/255 + else if (line.contains("Case unique/unique0 qualities are ignored")) + true else false ) diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv index a5dc48038..936cd1413 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv @@ -15,7 +15,7 @@ module UART_Tx#( ); `include "dfhdl_defs.svh" localparam int BIT_CLOCKS = (CLK_FREQ_KHz * 1000) / BAUD_RATE_BPS; - typedef enum { + typedef enum logic [4:0] { Status_Idle = 1, Status_StartBit = 2, Status_DataBits = 4, @@ -34,7 +34,7 @@ module UART_Tx#( dataBitCnt <= 3'd0; end else begin - case (status) + unique case (status) Status_Idle: begin tx_en <= 1'b0; tx <= 1'b1; From ff5e021fea7c4dbeed0676fc46cd9e72b90f1b80 Mon Sep 17 00:00:00 2001 From: Oron Date: Tue, 31 Mar 2026 11:12:26 +0300 Subject: [PATCH 077/115] docs: dynamic indexing, UInt slicing, FSM patterns, OUT init, auto-lift, closes #6 #7 #8 #9 - Expand dynamic bit indexing tip with reads, writes, .truncate/.extend, and range slices - Add UInt/SInt bit selection, slicing, and width adjustment (.resize/.truncate/.extend) - Document that Scala Int constants auto-lift in comparisons with DFHDL types - Add Encoded.Manual constructor parameter requirement note - Add output reg with init mapping (OUT init works directly in ED domain) - Add FSM state encoding Verilog-to-DFHDL translation pattern - Note that match does not support Bits CONST names; use enum or if/else chains Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/transitioning/from-verilog/index.md | 74 ++++++++++++++++++++++++ docs/user-guide/type-system/index.md | 73 ++++++++++++++++++++++- 2 files changed, 144 insertions(+), 3 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 210d95d67..d56da9a53 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -230,6 +230,80 @@ val counter = UInt(8) <> VAR init 0 ``` + +An `output reg` with an initial value maps directly to `OUT init`: + +
+ +```sv linenums="0" title="Verilog" +module Foo( + input clk, + input din, + output reg dout +); + initial dout = 1'b1; + always @(posedge clk) + dout <= din; +endmodule +``` + +```scala linenums="0" title="DFHDL" +@top class Foo extends EDDesign: + val clk = Bit <> IN + val din = Bit <> IN + val dout = Bit <> OUT init 1 + process(clk): + if (clk.rising) + dout :== din +``` + +
+/// + +/// admonition | FSM State Encoding + type: verilog +Verilog FSMs typically use `parameter` constants and `case`/`if` chains. In DFHDL, the idiomatic translation uses an `enum extends Encoded` and `match`: + +
+ +```sv linenums="0" title="Verilog" +parameter IDLE = 2'b00, + START = 2'b01, + DATA = 2'b10, + STOP = 2'b11; +reg [1:0] state = IDLE; + +always @(posedge clk) + case (state) + IDLE: if (go) state <= START; + START: state <= DATA; + DATA: state <= STOP; + STOP: state <= IDLE; + endcase +``` + +```scala linenums="0" title="DFHDL" +enum State extends Encoded: + case Idle, Start, Data, Stop + +val state = State <> VAR init State.Idle + +process(clk): + if (clk.rising) + state match + case State.Idle => + if (go) state :== State.Start + case State.Start => + state :== State.Data + case State.Data => + state :== State.Stop + case State.Stop => + state :== State.Idle +``` + +
+ +If the Verilog state values are non-sequential, use `Encoded.Manual` (see [Manual Encoding](../../user-guide/type-system/index.md#DFEnum)). Avoid modelling FSM states as `Bits` constants -- `match` does not support matching on `Bits <> CONST` names. Use `enum extends Encoded` instead, or fall back to `if`/`else if` chains. /// /// admonition | Multi-File Projects diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index c2a90fb97..2ee03a893 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -469,12 +469,37 @@ val idx = UInt(3) <> VAR val dynbit = b8(idx) // Single bit at position idx (returns Bit) ``` -!!! tip "Dynamic bit indexing" - You can index into a `Bits` value using a `UInt` variable, not just integer literals. This is equivalent to Verilog's `bits_val[idx]` where `idx` is a register or wire. Dynamic indexing works for both reads and writes inside processes: +!!! tip "Dynamic bit indexing (reads and writes)" + You can index into a `Bits` value using a `UInt` variable, not just integer literals. This is equivalent to Verilog's `data[idx]` where `idx` is a register or wire. + + The index must be a `UInt` whose width equals `clog2(bits_width)`. For example, indexing into `Bits(8)` requires a `UInt(3)` index. If the width does not match, the compiler will report an error and suggest a fix: + + - **Index too wide**: use `.truncate` to automatically narrow it to the expected width. + - **Index too narrow**: use `.extend` to automatically widen it to the expected width. + - You can also use `.resize(N)` for an explicit target width, or a range slice `(hi, lo)` to extract specific bits. + + Dynamic indexing works for both reads and writes: ```scala + val data = Bits(8) <> VAR init all(0) + val pos = UInt(3) <> VAR init 0 + val din = Bit <> IN + + // Dynamic read + val bit_out = data(pos) // read single bit at position + + // Dynamic write inside a clocked process process(clk): if (clk.rising) - data(bitpos) :== rx // dynamic bit-indexed write + data(pos) :== din // write single bit at position + ``` + + When the index register is wider than needed, narrow it with `.truncate` or a range slice: + ```scala + val wide_pos = UInt(5) <> VAR + // .truncate automatically narrows to clog2(8) = 3 bits + data(wide_pos.truncate) :== din + // Alternatively, use a range slice to pick specific bits + data(wide_pos(2, 0)) :== din ``` ### Bit Operations @@ -1209,6 +1234,46 @@ sd"8'42" // SInt[8], value = 42 sd"8'255" // Error: width too small to represent value with sign bit ``` +#### Bit Selection and Slicing + +`UInt` and `SInt` values support the same bit-selection syntax as `Bits`: + +- **Range slice**: `value(hi, lo)` extracts bits `hi` down to `lo`, returning a narrower `UInt` or `SInt`. +- **Single-bit access**: `value(idx)` returns the bit at position `idx` (as `Bit`). + +```scala +val u6 = UInt(6) <> VAR +val u4 = u6(3, 0) // lower 4 bits, returns UInt[4] +val b = u6(5) // MSB, returns Bit + +val s6 = SInt(6) <> VAR +val s4 = s6(3, 0) // lower 4 bits, returns SInt[4] +``` + +#### Width Adjustment: `.resize`, `.truncate`, `.extend` + +- `.resize(N)` sets the width to exactly `N` bits. For `UInt` and `Bits`, widening zero-extends; for `SInt`, widening sign-extends. Narrowing truncates the most-significant bits. +- `.truncate` automatically narrows to the width expected by the assignment or operation context. +- `.extend` automatically widens to the width expected by the context. + +```scala +val u8 = UInt(8) <> VAR +val u6 = UInt(6) <> VAR +u6 := u8.resize(6) // explicit truncate to 6 bits +u6 := u8.truncate // auto-narrow to match u6's width +u8 := u6.resize(8) // explicit zero-extend to 8 bits +u8 := u6.extend // auto-widen to match u8's width +``` + +!!! note "Scala `Int` constants auto-lift in comparisons" + Plain Scala `Int` values can be used directly in comparisons and arithmetic with DFHDL typed variables. No explicit coercion is needed: + ```scala + val LIMIT: Int <> CONST = 5208 + val counter = UInt.until(LIMIT) <> VAR + if (counter == LIMIT - 1) // Int <> CONST compared with UInt -- works directly + counter := 0 + ``` + ### Examples ```scala @@ -1278,6 +1343,8 @@ enum MyEnum(val value: UInt[8] <> CONST) extends Encoded.Manual(8): case C extends MyEnum(50) ``` + Note: the Manual encoding enum class **must** declare a constructor parameter `(val value: UInt[N] <> CONST)` and the bit width `N` must match the argument to `Encoded.Manual(N)`. Each `case` must explicitly extend the enum class and pass a constant value. Omitting the constructor parameter will cause a compile error. + ### Operations /// html | div.operations From c6e33243da79d7059786c7628461c36bece06788 Mon Sep 17 00:00:00 2001 From: Oron Date: Wed, 1 Apr 2026 05:11:21 +0300 Subject: [PATCH 078/115] docs: type conversions, shift ops, Bit operators, enum unique case, Bits init, syntax pitfalls Address 15 knowledge gap tickets from learner agents translating Verilog to DFHDL. Grouped by pattern rather than individual ticket: Type system (type-system/index.md): - Add UInt<->SInt<->Bits type conversion table and examples - Document shift operator semantics (>> is type-aware: logical for UInt, arithmetic for SInt) - Clarify UInt range slice returns UInt (not Bits), usable directly as Bits index - Add | vs || guidance for Bit values (bitwise vs logical) - Add Bits init warning: all(0) required, plain integer 0 rejected - Clarify UInt.until(1) is invalid, UInt.to mapping to $clog2 - Note subtraction LHS-must-be-wider constraint From-Verilog guide (transitioning/from-verilog/index.md): - Shift operators (no >>> in DFHDL) - UInt/SInt conversion via .bits.sint/.bits.uint - Bitwise | vs logical || for Bit assignments - Scala reserved keyword backtick escaping for port names - Bits initialization patterns (all(0), not integer 0) - Inline if-expression requires parentheses in process blocks - Unsigned literal minus signed expression restructuring - Parametric Bits constants workaround (Int CONST + .bits) - $clog2 mapping to .until/.to with edge cases - Enum match generates unique case: sparse enum gotcha Closes #10 #11 #12 #13 #14 #15 #16 #17 #18 #20 #21 #22 #23 #24 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/transitioning/from-verilog/index.md | 267 +++++++++++++++++++++++ docs/user-guide/type-system/index.md | 109 ++++++++- 2 files changed, 372 insertions(+), 4 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index d56da9a53..6ecb64cd7 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -323,3 +323,270 @@ given options.CompilerOptions.PrintBackendCode = true Individual design files should `import dfhdl.*` but not redeclare the shared `given` options. /// +/// admonition | Shift Operators + type: verilog +Verilog has separate `>>` (logical) and `>>>` (arithmetic) right shift operators. DFHDL uses only `>>`, but the behavior depends on the operand type: + +
+ +```sv linenums="0" title="Verilog" +// Logical right shift (unsigned) +out = data >> 2; + +// Arithmetic right shift (signed) +out = $signed(data) >>> 2; +``` + +```scala linenums="0" title="DFHDL" +// Logical right shift (UInt or Bits) +out := data >> 2 + +// Arithmetic right shift (SInt) +out := data_signed >> 2 +``` + +
+ +There is no `>>>` operator in DFHDL. The type of the LHS determines the shift semantics: `>>` on `UInt`/`Bits` zero-fills, `>>` on `SInt` sign-extends. +/// + +/// admonition | UInt/SInt Conversion + type: verilog +Verilog implicitly converts between signed and unsigned in mixed expressions. DFHDL requires explicit conversion through `Bits`: + +
+ +```sv linenums="0" title="Verilog" +reg [2:0] counter; // unsigned +reg signed [7:0] offset; + +// Verilog auto-converts +offset = 180 - 5 * counter; +``` + +```scala linenums="0" title="DFHDL" +val counter = UInt(3) <> VAR +val offset = SInt(8) <> VAR + +// Must convert UInt -> SInt explicitly +// UInt -> .bits -> .sint -> .resize +val cnt_s = counter.bits.sint.resize(8) +offset := sd"8'180" - cnt_s * 5 +``` + +
+ +Conversion summary: `uint_val.bits.sint` for UInt-to-SInt, `sint_val.bits.uint` for SInt-to-UInt. See [Type Conversion](../../user-guide/type-system/index.md#type-conversion) for details. +/// + +/// admonition | Bit Operators: `|` and `&` vs `||` and `&&` + type: verilog +In Verilog, `|` and `||` on single-bit wires produce the same result. In DFHDL, they behave differently: + +- `a | b` (bitwise OR) on `Bit` values returns `Bit` -- use for assignments to `Bit` ports/variables. +- `a || b` (logical OR) on `Bit` values returns `Boolean` -- use in `if` conditions. + +
+ +```sv linenums="0" title="Verilog" +wire a, b, c; +wire out; +assign out = a | b | c; +// or: assign out = a || b || c; +// (same result for 1-bit) +``` + +```scala linenums="0" title="DFHDL" +val a, b, c = Bit <> IN +val out = Bit <> OUT + +// Use bitwise | for Bit assignment +out := a | b | c + +// Use || only in conditions: +// if (a || b) ... +``` + +
+/// + +/// admonition | Scalar Reserved Keywords as Port Names + type: verilog +Some Verilog port names (`val`, `type`, `class`, `match`, `case`, `object`, etc.) are reserved in Scala. Use backtick escaping: + +
+ +```sv linenums="0" title="Verilog" +module mul( + output reg signed [15:0] val +); +``` + +```scala linenums="0" title="DFHDL" +class mul extends EDDesign: + val `val` = SInt(16) <> OUT +``` + +
+ +When connecting to a backtick-escaped port from a parent: `inst.`\``val`\`` <> parent_signal`. +/// + +/// admonition | Bits Initialization + type: verilog +`Bits` values cannot be initialized with plain integers. Use `all(0)` or a sized literal: + +
+ +```sv linenums="0" title="Verilog" +reg [7:0] flags = 8'd0; +reg [7:0] mask = 8'hFF; +``` + +```scala linenums="0" title="DFHDL" +val flags = Bits(8) <> VAR init all(0) +val mask = Bits(8) <> VAR init h"8'FF" +// NOT: Bits(8) <> VAR init 0 // error +``` + +
+ +`UInt` and `SInt` accept plain integer `0` for init; `Bits` does not. +/// + +/// admonition | Inline Conditional Expressions + type: verilog +Verilog's ternary operator `cond ? a : b` maps to Scala's `if`/`else`, but in process blocks the inline form requires parentheses: + +
+ +```sv linenums="0" title="Verilog" +assign out = sel ? a : b; + +always @(*) + out = sel ? a : b; +``` + +```scala linenums="0" title="DFHDL" +// Continuous: use <> with inline if +out <> (if (sel) a else b) + +// In process blocks: wrap in parentheses +process(all): + out := (if (sel) a else b) + +// Or use statement form: +process(all): + if (sel) out := a + else out := b +``` + +
+ +Without parentheses, `out := if (sel) a else b` causes a parse error. Either wrap the `if` in parentheses or use the statement form. +/// + +/// admonition | Unsigned Literal Minus Signed Expression + type: verilog +In Verilog, `2 - signed_expr` works because integer literals are implicitly 32-bit. In DFHDL, a plain `2` is unsigned and cannot be subtracted from by a wider signed value. Restructure as negation plus addition: + +
+ +```sv linenums="0" title="Verilog" +// Works: 2 is 32-bit integer +err <= 2 - (2 * r0); +``` + +```scala linenums="0" title="DFHDL" +// Restructure: negate then add +err :== (-(r0_wide + r0_wide) + sd"2").truncate + +// Or make the signed literal wide enough: +val two = sd"${CORDW+2}'2" +err :== (two - (r0_wide + r0_wide)).truncate +``` + +
+/// + +/// admonition | Parametric Bit-Vector Constants + type: verilog +DFHDL does not support `Bits` CONST parameters whose width depends on another parameter. Use `Int <> CONST` and extract bits internally: + +
+ +```sv linenums="0" title="Verilog" +parameter LEN=8; +parameter TAPS=8'b10111000; +// TAPS width depends on LEN +``` + +```scala linenums="0" title="DFHDL" +val LEN: Int <> CONST = 8 +val TAPS: Int <> CONST = 0xB8 + +// Extract bits internally +val taps_b = Bits(LEN) <> VAR +taps_b <> TAPS.bits(LEN-1, 0) +``` + +
+ +Note: `.bits(hi, lo)` is an extension method on `Int <> CONST` DFHDL values. It does **not** work on plain Scala `Int`. If you have a compile-time constant, pass it as an `Int <> CONST` parameter, or use a hex/binary literal directly: `h"21'140000"`. +/// + +/// admonition | `$clog2` Mapping: `.until` vs `.to` + type: verilog +When translating Verilog `$clog2` expressions, choose the right constructor: + +| Verilog | DFHDL | Bits | +|---------|-------|------| +| `$clog2(N)` | `UInt.until(N)` | `clog2(N)` bits, valid for N >= 2 | +| `$clog2(N+1)` | `UInt.to(N)` | `clog2(N+1)` bits, valid for N >= 1 | + +`UInt.until(1)` is **invalid** (would produce 0-bit width). For counters that count 0 to N inclusive (common with `$clog2(N+1)`), use `UInt.to(N)`: + +```scala linenums="0" title="DFHDL" +// Verilog: reg [$clog2(SCALE+1)-1:0] cnt = 0; +val cnt = UInt.to(SCALE) <> VAR init 0 +``` +/// + +/// admonition | Enum FSM and `unique case` + type: verilog +When you use `enum extends Encoded` with `match` in DFHDL, the generated SystemVerilog uses `unique case`. This has implications for formal verification: + +- **Exhaustive enums** (all bit patterns used, e.g., 4 states in 2 bits): `unique case` is safe because every possible value has a branch. +- **Sparse enums** (not all bit patterns used, e.g., 3 states in 2 bits): `unique case` has no `default` for the unused bit patterns. Formal tools may find counterexamples for unreachable states. + +If the original Verilog FSM has a `default` branch that handles invalid/unreachable states, use `if`/`else if` chains with a final `else` instead of `match` on an enum: + +
+ +```sv linenums="0" title="Verilog (3 states, has default)" +case (state) + IDLE: ... + DATA: ... + STOP: ... + default: state <= IDLE; +endcase +``` + +```scala linenums="0" title="DFHDL (if/else for default coverage)" +// Use Bits constants + if/else if/else +val IDLE = b"2'00" +val DATA = b"2'01" +val STOP = b"2'10" +val state = Bits(2) <> VAR init IDLE + +if (state == IDLE) ... +else if (state == DATA) ... +else if (state == STOP) ... +else state :== IDLE // covers 2'b11 +``` + +
+ +Use `enum` + `match` when the enum is exhaustive or when `default` coverage is not needed. +/// + diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 2ee03a893..b1792adb6 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -810,6 +810,26 @@ val e3 = 0 ^ true val sc: Boolean = true && true ``` +!!! tip "Bitwise `|`/`&` vs logical `||`/`&&` on Bit values" + For `Bit` values, both bitwise (`|`, `&`) and logical (`||`, `&&`) operators are available, but they behave differently: + + - **`|`, `&`**: Bitwise operators. When both operands are `Bit`, the result is `Bit`. + - **`||`, `&&`**: Logical operators. The result type matches the LHS type, but chaining multiple `||`/`&&` can produce intermediate types that do not support `.bit` conversion. + + **For combining `Bit` signals in assignments, always use `|` and `&`:** + ```scala + val a, b, c = Bit <> IN + val out = Bit <> OUT + + // CORRECT: bitwise OR returns Bit directly + out := a | b | c + + // PROBLEMATIC: logical OR chain may not convert cleanly to Bit + // out := (a || b || c).bit // may fail + ``` + + Use `||`/`&&` primarily in `if` conditions, where the result is consumed as a boolean, not assigned to a `Bit` port. + /// details | Transitioning from Verilog type: verilog Under the ED domain, the following operations are equivalent: @@ -1053,7 +1073,19 @@ This interpolation covers the VHDL hexadecimal literal use-cases, but also adds * DFHDL `Bit` or `Boolean` values. This candidate produces a single bit `Bits[1]` vector. * DFHDL `UInt` values * Scala `Tuple` combination of any DFHDL values and `1`/`0` literal values. This candidate performs bit concatenation of all values, according their order in the tuple, encoded from the most-significant value position down to the least-significant value position. - * Application-only candidate - Same-Element Vector (`all(elem)`). + * Application-only candidate - Same-Element Vector (`all(elem)`). + +!!! warning "`Bits` does not accept plain integer candidates" + Unlike `UInt`/`SInt`, `Bits` values **cannot** be initialized or assigned with plain integers. Use `all(0)` for zero initialization, or a sized literal: + ```scala + // CORRECT + val b8 = Bits(8) <> VAR init all(0) // zero via all(0) + val b4 = Bits(4) <> VAR init b"4'0" // zero via binary literal + val b6 = Bits(6) <> VAR init h"6'00" // zero via hex literal + + // WRONG: integer 0 is not a Bits candidate + // val b8 = Bits(8) <> VAR init 0 // compile error + ``` ```scala val b8 = Bits(8) <> VAR @@ -1135,8 +1167,8 @@ DFHDL provides three decimal numeric types: | Constructor | Description | Arg Constraints | Returns | | ------------ | ----------- | ------------------- | ------- | | `UInt(width)`| Construct an unsigned integer DFType with the given `width` as number of bits. | `width` is a positive Scala `Int` or constant DFHDL `Int` value. | `UInt[width.type]` DFType | -| `UInt.until(sup)`| Construct an unsigned integer DFType with the given `sup` supremum number the value is expected to reach. The number of bits is set as `clog2(sup)`. | `sup` is a Scala `Int` or constant DFHDL `Int` value larger than 1. | `UInt[CLog2[width.type]]` DFType | -| `UInt.to(max)`| Construct an unsigned integer DFType with the given `max` maximum number the value is expected to reach. The number of bits is set as `clog2(max+1)`. | `max` is a positive Scala `Int` or constant DFHDL `Int` value. | `UInt[CLog2[width.type+1]]` DFType | +| `UInt.until(sup)`| Construct an unsigned integer DFType with the given `sup` supremum number the value is expected to reach. The number of bits is set as `clog2(sup)`. | `sup` is a Scala `Int` or constant DFHDL `Int` value **larger than 1**. `UInt.until(1)` is invalid (would produce 0-bit width). | `UInt[CLog2[width.type]]` DFType | +| `UInt.to(max)`| Construct an unsigned integer DFType with the given `max` maximum number the value is expected to reach. The number of bits is set as `clog2(max+1)`. | `max` is a positive Scala `Int` or constant DFHDL `Int` value. `UInt.to(1)` is valid (produces 1-bit width). | `UInt[CLog2[width.type+1]]` DFType | | `SInt(width)`| Construct a signed integer DFType with the given `width` as number of bits. | `width` is a positive Scala `Int` or constant DFHDL `Int` value. | `SInt[width.type]` DFType | | `Int`| Construct a constant integer DFType. Used mainly for parameters. | None | `Int` DFType | @@ -1155,7 +1187,7 @@ These operations propagate constant modifiers and maintain proper bit widths: | Operation | Description | LHS/RHS Constraints | Returns | | ------------ | ----------- | ------------------- | ------- | | `lhs + rhs` | Addition | Both decimal types | Result with appropriate width | -| `lhs - rhs` | Subtraction | Both decimal types | Result with appropriate width | +| `lhs - rhs` | Subtraction (LHS must be at least as wide as RHS) | Both decimal types | Result with appropriate width | | `lhs * rhs` | Multiplication | Both decimal types | Result with width = lhs.width + rhs.width | | `lhs / rhs` | Division | Both decimal types | Result with lhs width | | `lhs % rhs` | Modulo | Both decimal types | Result with rhs width | @@ -1182,6 +1214,62 @@ val tick = Bit <> OUT tick := (counter == limit).bit ``` +#### Shift Operations + +/// html | div.operations +| Operation | Description | LHS/RHS Constraints | Returns | +| ------------ | ----------- | ------------------- | ------- | +| `lhs << rhs` | Left shift | LHS: `UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | +| `lhs >> rhs` | Right shift (logical for `UInt`, arithmetic for `SInt`) | LHS: `UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | + +The `>>` operator is **type-aware**: on `UInt` it performs a logical (zero-filling) right shift, and on `SInt` it performs an arithmetic (sign-extending) right shift. There is no separate `>>>` operator in DFHDL -- the operand type determines the behavior. + +```scala +val u = UInt(8) <> VAR +val s = SInt(8) <> VAR + +val u_shifted = u >> 2 // logical right shift (zero-fills MSBs) +val s_shifted = s >> 2 // arithmetic right shift (sign-extends MSBs) +``` + +#### Type Conversion Between UInt, SInt, and Bits {#type-conversion} + +DFHDL does not provide direct `.toSInt` or `.toUInt` methods. Instead, conversions go through `Bits` as an intermediate: + +| From | To | Method | +|------|-----|--------| +| `UInt` | `Bits` | `.bits` | +| `SInt` | `Bits` | `.bits` | +| `Bits` | `UInt` | `.uint` | +| `Bits` | `SInt` | `.sint` | +| `UInt` | `SInt` | `.bits.sint` | +| `SInt` | `UInt` | `.bits.uint` | + +When converting `UInt` to `SInt`, the bit pattern is reinterpreted (not sign-extended). If you need the unsigned value represented as a wider signed value, `.resize` after conversion: + +```scala +val u3 = UInt(3) <> VAR // range 0..7 +val s8 = SInt(8) <> VAR + +// UInt(3) -> Bits(3) -> SInt(3) -> SInt(8) +s8 := u3.bits.sint.resize(8) + +// SInt(8) -> Bits(8) -> UInt(8) +val u8 = UInt(8) <> VAR +u8 := s8.bits.uint +``` + +!!! warning "Unsigned/signed mixing in arithmetic" + DFHDL does not allow mixing `UInt` and `SInt` in arithmetic expressions. Convert explicitly before operating: + ```scala + val counter = UInt(3) <> VAR + val offset = SInt(8) <> VAR + // ERROR: Cannot mix unsigned and signed + // val result = offset - counter + // CORRECT: convert counter to SInt first + val result = offset - counter.bits.sint.resize(8) + ``` + ### Constant Generation #### Decimal String-Interpolator {#d-interp} @@ -1250,6 +1338,19 @@ val s6 = SInt(6) <> VAR val s4 = s6(3, 0) // lower 4 bits, returns SInt[4] ``` +!!! note "Range slices preserve the original type" + A range slice on `UInt` returns `UInt`, and on `SInt` returns `SInt` -- it does **not** return `Bits`. This means a `UInt` range slice can be used directly as a dynamic index into `Bits` without `.uint`: + ```scala + val scratch = Bits(8) <> VAR + val bitpos = UInt(4) <> VAR // 4-bit counter + + // CORRECT: range slice of UInt returns UInt, usable directly as index + scratch(bitpos(2, 0)) :== din + + // WRONG: .uint is not needed and will cause a compile error + // scratch(bitpos(2, 0).uint) :== din + ``` + #### Width Adjustment: `.resize`, `.truncate`, `.extend` - `.resize(N)` sets the width to exactly `N` bits. For `UInt` and `Bits`, widening zero-extends; for `SInt`, widening sign-extends. Narrowing truncates the most-significant bits. From 5b62b093392f192dcc1177f19c44643475409093 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 1 Apr 2026 06:17:36 +0300 Subject: [PATCH 079/115] fix and improve logical operators for Verilog. --- .../stages/verilog/VerilogValPrinter.scala | 2 + .../StagesSpec/PrintVerilogCodeSpec.scala | 34 ++++++++++++++-- docs/transitioning/from-verilog/index.md | 35 +++++++++-------- docs/user-guide/type-system/index.md | 39 +++++++------------ 4 files changed, 64 insertions(+), 46 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala index 3e96f6893..4938150a8 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala @@ -149,6 +149,8 @@ protected trait VerilogValPrinter extends AbstractValPrinter: argL.get.dfType match case DFSInt(_) => ">>>" case _ => ">>" + case Func.Op.| if argL.get.dfType == DFBool => "||" + case Func.Op.& if argL.get.dfType == DFBool => "&&" case _ => commonOpStr (argL.get.dfType, dfVal.op) match case ( diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index cf768329f..4d38e335c 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -256,6 +256,34 @@ class PrintVerilogCodeSpec extends StageSpec: ) } + test("Boolean logical operators") { + class BoolLogic extends RTDesign: + val a = Boolean <> IN + val b = Boolean <> IN + val y1 = Boolean <> OUT + val y2 = Boolean <> OUT + y1 := a || b + y2 := a && b + val top = (new BoolLogic).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module BoolLogic( + | input wire logic a, + | input wire logic b, + | output logic y1, + | output logic y2 + |); + | `include "dfhdl_defs.svh" + | assign y1 = a || b; + | assign y2 = a && b; + |endmodule + |""".stripMargin + ) + } + test("process block") { given options.PrinterOptions.Align = true class Top extends EDDesign: @@ -740,7 +768,7 @@ class PrintVerilogCodeSpec extends StageSpec: | output reg [15:0] y; | always @(x) | begin - | if ((x[15:8] == 8'h12) | (x[15:4] == 12'h345)) y = 16'h22??; + | if ((x[15:8] == 8'h12) || (x[15:4] == 12'h345)) y = 16'h22??; | else y = 16'hffff; | end |endmodule @@ -1106,7 +1134,7 @@ class PrintVerilogCodeSpec extends StageSpec: | $display("These are the values: %d", param3, ", %d", param4, ", %h", param5, ", %h", param6, ", %d", param7, ", %b", param8, ", %s", param9 ? "true" : "false", ", %s", param10.name(), ""); | $info( | "Debug at Foo\n", - | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1059:9\n", + | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1087:9\n", | "param3 = %d\n", param3, | "param4 = %d\n", param4, | "param5 = %h\n", param5, @@ -1176,7 +1204,7 @@ class PrintVerilogCodeSpec extends StageSpec: | $display("These are the values: %d", param3, ", %d", param4, ", %h", param5, ", %h", param6, ", %d", param7, ", %b", param8, ", %s", param9 ? "true" : "false", ", %s", MyEnum_to_string(param10), ""); | $display("INFO: ", | "Debug at Foo\n", - | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1059:9\n", + | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1087:9\n", | "param3 = %d\n", param3, | "param4 = %d\n", param4, | "param5 = %h\n", param5, diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 6ecb64cd7..14783bd12 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -379,32 +379,33 @@ offset := sd"8'180" - cnt_s * 5 Conversion summary: `uint_val.bits.sint` for UInt-to-SInt, `sint_val.bits.uint` for SInt-to-UInt. See [Type Conversion](../../user-guide/type-system/index.md#type-conversion) for details. /// -/// admonition | Bit Operators: `|` and `&` vs `||` and `&&` +/// admonition | Bit Operators: `|`/`&` and `||`/`&&` type: verilog -In Verilog, `|` and `||` on single-bit wires produce the same result. In DFHDL, they behave differently: - -- `a | b` (bitwise OR) on `Bit` values returns `Bit` -- use for assignments to `Bit` ports/variables. -- `a || b` (logical OR) on `Bit` values returns `Boolean` -- use in `if` conditions. +In DFHDL, `||` and `&&` are equivalent to `|` and `&`, respectively, when applied on `Bit` or `Boolean` types. In the generated Verilog, the operator depends on the LHS type: `Bit` produces `|`/`&`, `Boolean` produces `||`/`&&`.
```sv linenums="0" title="Verilog" -wire a, b, c; -wire out; -assign out = a | b | c; -// or: assign out = a || b || c; -// (same result for 1-bit) +input a, b, c; +output o1, o2, o3; +// same result for 1-bit +assign o1 = a | b | c; +assign o2 = a | b | c; +assign o3 = a || b || c; ``` ```scala linenums="0" title="DFHDL" val a, b, c = Bit <> IN -val out = Bit <> OUT - -// Use bitwise | for Bit assignment -out := a | b | c - -// Use || only in conditions: -// if (a || b) ... +val o1, o2, o3 = Bit <> OUT + +// Both are equivalent for Bit LHS: +o1 <> a | b | c +o2 <> a || b || c +// a.bool makes the LHS Boolean, so RHS +// auto-converts to Boolean and the result +// auto-converts back to Bit. +// Produces || in Verilog. +o3 <> a.bool || b || c ```
diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index b1792adb6..ba04e4755 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -777,7 +777,9 @@ These operations propagate constant modifiers, meaning that if all arguments are | Operation | Description | LHS/RHS Constraints | Returns | | ------------ | ----------- | ------------------- | ------- | | `lhs && rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `lhs || rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs \|\| rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs & rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs \| rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | | `lhs ^ rhs` | Logical XOR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | | `!lhs` | Logical NOT | The argument must be a `Bit`/`Boolean` DFHDL value. | LHS-Type DFHDL value | /// @@ -810,36 +812,21 @@ val e3 = 0 ^ true val sc: Boolean = true && true ``` -!!! tip "Bitwise `|`/`&` vs logical `||`/`&&` on Bit values" - For `Bit` values, both bitwise (`|`, `&`) and logical (`||`, `&&`) operators are available, but they behave differently: - - - **`|`, `&`**: Bitwise operators. When both operands are `Bit`, the result is `Bit`. - - **`||`, `&&`**: Logical operators. The result type matches the LHS type, but chaining multiple `||`/`&&` can produce intermediate types that do not support `.bit` conversion. - - **For combining `Bit` signals in assignments, always use `|` and `&`:** - ```scala - val a, b, c = Bit <> IN - val out = Bit <> OUT - - // CORRECT: bitwise OR returns Bit directly - out := a | b | c - - // PROBLEMATIC: logical OR chain may not convert cleanly to Bit - // out := (a || b || c).bit // may fail - ``` - - Use `||`/`&&` primarily in `if` conditions, where the result is consumed as a boolean, not assigned to a `Bit` port. +!!! tip "Logical `||`/`&&` and bitwise `|`/`&` on Bit and Boolean values" + In DFHDL, the operators `||` and `&&` are equivalent to `|` and `&`, respectively, when applied on either DFHDL `Bit` or `Boolean` types. In Verilog, the actual operator printed depends on the LHS argument of the operation: if it's `Bit`, the operator will be `|`/`&`; if it's `Boolean`, the operator will be `||`/`&&`. /// details | Transitioning from Verilog type: verilog Under the ED domain, the following operations are equivalent: -| DFHDL Operation | Verilog Operation | -|-----------------|-------------------| -| `lhs && rhs` | `lhs & rhs` | -| `lhs || rhs` | `lhs | rhs` | -| `lhs ^ rhs` | `lhs ^ rhs` | -| `!lhs` | `!lhs` | +| DFHDL Operation | Verilog Operation (Bit LHS) | Verilog Operation (Boolean LHS) | +|-----------------|-----------------------------|---------------------------------| +| `lhs && rhs` | `lhs & rhs` | `lhs && rhs` | +| `lhs \|\| rhs` | `lhs \| rhs` | `lhs \|\| rhs` | +| `lhs & rhs` | `lhs & rhs` | `lhs && rhs` | +| `lhs \| rhs` | `lhs \| rhs` | `lhs \|\| rhs` | +| `lhs ^ rhs` | `lhs ^ rhs` | `lhs ^ rhs` | +| `!lhs` | `!lhs` | `!lhs` | /// /// details | Transitioning from VHDL From 454ec720f67056ab3294833b7aec9cf75a8d5b4e Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 1 Apr 2026 06:18:42 +0300 Subject: [PATCH 080/115] docs: clarify terminology for Bit/Boolean operators in DFHDL --- docs/transitioning/from-verilog/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 14783bd12..007c25d91 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -379,7 +379,7 @@ offset := sd"8'180" - cnt_s * 5 Conversion summary: `uint_val.bits.sint` for UInt-to-SInt, `sint_val.bits.uint` for SInt-to-UInt. See [Type Conversion](../../user-guide/type-system/index.md#type-conversion) for details. /// -/// admonition | Bit Operators: `|`/`&` and `||`/`&&` +/// admonition | Bit/Boolean Operators: `|`/`&` and `||`/`&&` type: verilog In DFHDL, `||` and `&&` are equivalent to `|` and `&`, respectively, when applied on `Bit` or `Boolean` types. In the generated Verilog, the operator depends on the LHS type: `Bit` produces `|`/`&`, `Boolean` produces `||`/`&&`. From 5daa657c8634d6f7b0402cceaf6afe5a734a1296 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 1 Apr 2026 06:24:05 +0300 Subject: [PATCH 081/115] clarify keyword workaround --- docs/transitioning/from-verilog/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 007c25d91..07c2fc193 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -418,19 +418,19 @@ Some Verilog port names (`val`, `type`, `class`, `match`, `case`, `object`, etc.
```sv linenums="0" title="Verilog" -module mul( +module foo( output reg signed [15:0] val ); + assign val = 16'sd42; ``` ```scala linenums="0" title="DFHDL" -class mul extends EDDesign: +class foo extends EDDesign: val `val` = SInt(16) <> OUT + `val` <> 42 ```
- -When connecting to a backtick-escaped port from a parent: `inst.`\``val`\`` <> parent_signal`. /// /// admonition | Bits Initialization From 9eaabce9b4e9c128e51e7a515d04980cab61d5fb Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 2 Apr 2026 02:03:15 +0300 Subject: [PATCH 082/115] fix explicit named vars for ED domains --- .../compiler/stages/ExplicitNamedVars.scala | 5 +++- .../StagesSpec/ExplicitNamedVarsSpec.scala | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala index 1254db55c..b30f2f816 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala @@ -131,7 +131,10 @@ case object ExplicitNamedVars extends Stage: plantedNewVar := anonValue plantedNewReg.din := plantedNewVar else if (varUse) - plantedNewVar := anonValue + if (named.isInEDDomain && !named.isInProcess) + plantedNewVar <> anonValue + else + plantedNewVar := anonValue else if (regUse) plantedNewReg.din := anonValue val varPatchOpt = diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala index a2f88836a..699c32431 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala @@ -233,4 +233,32 @@ class ExplicitNamedVarsSpec extends StageSpec: |end ID""".stripMargin ) } + test("EDDomain rules") { + class ID extends EDDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val py = SInt(16) <> OUT + val v = x + y <> v + process: + val pv = x + py := pv + end ID + val id = (new ID).explicitNamedVars + assertCodeString( + id, + """|class ID extends EDDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val py = SInt(16) <> OUT + | val v = SInt(16) <> VAR + | v <> x + | y <> v + | process: + | val pv = SInt(16) <> VAR + | pv := x + | py := pv + |end ID""".stripMargin + ) + } end ExplicitNamedVarsSpec From fdafbac2e616cac5a4d1221547d60c5e2f0becf4 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 2 Apr 2026 02:18:35 +0300 Subject: [PATCH 083/115] fix vector of Bit to/from Bits conversion --- .../stages/verilog/VerilogValPrinter.scala | 10 ++++-- .../StagesSpec/PrintVerilogCodeSpec.scala | 31 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala index 4938150a8..ce5a9bc1e 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala @@ -151,7 +151,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: case _ => ">>" case Func.Op.| if argL.get.dfType == DFBool => "||" case Func.Op.& if argL.get.dfType == DFBool => "&&" - case _ => commonOpStr + case _ => commonOpStr (argL.get.dfType, dfVal.op) match case ( DFSInt(widthRef), @@ -322,9 +322,14 @@ protected trait VerilogValPrinter extends AbstractValPrinter: List.tabulate(vecLength)(i => s"$relValStr[${relHighIdx - i * cellWidth}:${relHighIdx - (i + 1) * cellWidth + 1}]" ).csList(literalGroupOpen, ",", "}") + case _: DFBoolOrBit => + List.tabulate(vecLength)(i => + s"$relValStr[${relHighIdx - i}]" + ).csList(literalGroupOpen, ",", "}") case x => println(x) printer.unsupported + end match end to_vector_conv to_vector_conv(toVector, toVector.width - 1) case (DFBits(Int(tWidth)), fromVector: DFVector) => @@ -335,7 +340,8 @@ protected trait VerilogValPrinter extends AbstractValPrinter: List.tabulate(vecLength)(i => from_vector_conv(cellType, s"[$i]")) .csList("{", ",", "}") case cellType: DFBits => - val cellWidth = cellType.width + List.tabulate(vecLength)(i => s"$relValStr$prevSelect[$i]").csList("{", ",", "}") + case _: DFBoolOrBit => List.tabulate(vecLength)(i => s"$relValStr$prevSelect[$i]").csList("{", ",", "}") case x => println(x) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 4d38e335c..16a1eecd4 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -258,8 +258,8 @@ class PrintVerilogCodeSpec extends StageSpec: test("Boolean logical operators") { class BoolLogic extends RTDesign: - val a = Boolean <> IN - val b = Boolean <> IN + val a = Boolean <> IN + val b = Boolean <> IN val y1 = Boolean <> OUT val y2 = Boolean <> OUT y1 := a || b @@ -1571,4 +1571,31 @@ class PrintVerilogCodeSpec extends StageSpec: |""".stripMargin ) } + test("vector to bits conversion") { + class Foo extends EDDesign: + val i1 = Bit X 8 <> IN + val o1 = Bits(8) <> OUT + val i2 = Bits(8) <> IN + val o2 = Bit X 8 <> OUT + o1 <> i1.bits + o2 <> i2.as(Bit X 8) + end Foo + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module Foo( + | input wire logic i1 [0:7], + | output logic [7:0] o1, + | input wire logic [7:0] i2, + | output logic o2 [0:7] + |); + | `include "dfhdl_defs.svh" + | assign o1 = {i1[0], i1[1], i1[2], i1[3], i1[4], i1[5], i1[6], i1[7]}; + | assign o2 = '{i2[7], i2[6], i2[5], i2[4], i2[3], i2[2], i2[1], i2[0]}; + |endmodule""".stripMargin + ) + } end PrintVerilogCodeSpec From 0923c36f9bc6310b2f1c8688750199e0bd1485da Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 2 Apr 2026 02:19:21 +0300 Subject: [PATCH 084/115] remove redundant def/pkg files from comparison --- .../verilog.sv2009/hdl/Blinker_defs.svh | 3 --- .../verilog.v2001/hdl/Blinker_defs.vh | 8 -------- .../verilog.v95/hdl/Blinker_defs.vh | 8 -------- .../vhdl.v2008/hdl/Blinker_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/Blinker_pkg.vhd | 10 ---------- .../verilog.sv2009/hdl/Counter_defs.svh | 3 --- .../verilog.v2001/hdl/Counter_defs.vh | 8 -------- .../verilog.v95/hdl/Counter_defs.vh | 8 -------- .../vhdl.v2008/hdl/Counter_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/Counter_pkg.vhd | 10 ---------- .../verilog.sv2009/hdl/FullAdder1_defs.svh | 3 --- .../verilog.v2001/hdl/FullAdder1_defs.vh | 8 -------- .../verilog.v95/hdl/FullAdder1_defs.vh | 8 -------- .../vhdl.v2008/hdl/FullAdder1_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/FullAdder1_pkg.vhd | 10 ---------- .../verilog.sv2009/hdl/FullAdderN_defs.svh | 3 --- .../verilog.v2001/hdl/FullAdderN_defs.vh | 8 -------- .../verilog.v95/hdl/FullAdderN_defs.vh | 8 -------- .../vhdl.v2008/hdl/FullAdderN_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/FullAdderN_pkg.vhd | 10 ---------- .../verilog.sv2009/hdl/RegFile_defs.svh | 3 --- .../verilog.v2001/hdl/RegFile_defs.vh | 8 -------- .../verilog.v95/hdl/RegFile_defs.vh | 8 -------- .../vhdl.v2008/hdl/RegFile_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/RegFile_pkg.vhd | 10 ---------- .../verilog.sv2009/hdl/TrueDPR_defs.svh | 3 --- .../verilog.v2001/hdl/TrueDPR_defs.vh | 8 -------- .../verilog.v95/hdl/TrueDPR_defs.vh | 8 -------- .../vhdl.v2008/hdl/TrueDPR_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/TrueDPR_pkg.vhd | 10 ---------- .../verilog.sv2009/hdl/UART_Tx_defs.svh | 3 --- .../verilog.v2001/hdl/UART_Tx_defs.vh | 8 -------- .../verilog.v95/hdl/UART_Tx_defs.vh | 8 -------- .../vhdl.v2008/hdl/UART_Tx_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/UART_Tx_pkg.vhd | 10 ---------- .../verilog.sv2009/hdl/LeftShift2_defs.svh | 3 --- .../verilog.v2001/hdl/LeftShift2_defs.vh | 8 -------- .../verilog.v95/hdl/LeftShift2_defs.vh | 8 -------- .../vhdl.v2008/hdl/LeftShift2_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/LeftShift2_pkg.vhd | 10 ---------- .../verilog.sv2009/hdl/LeftShiftBasic_defs.svh | 3 --- .../verilog.v2001/hdl/LeftShiftBasic_defs.vh | 8 -------- .../verilog.v95/hdl/LeftShiftBasic_defs.vh | 8 -------- .../vhdl.v2008/hdl/LeftShiftBasic_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/LeftShiftBasic_pkg.vhd | 10 ---------- .../verilog.sv2009/hdl/LeftShiftGen_defs.svh | 3 --- .../verilog.v2001/hdl/LeftShiftGen_defs.vh | 8 -------- .../verilog.v95/hdl/LeftShiftGen_defs.vh | 8 -------- .../vhdl.v2008/hdl/LeftShiftGen_pkg.vhd | 10 ---------- .../vhdl.v93/hdl/LeftShiftGen_pkg.vhd | 10 ---------- 50 files changed, 390 deletions(-) delete mode 100644 lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen_defs.svh delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen_defs.vh delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen_pkg.vhd delete mode 100644 lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen_pkg.vhd diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker_defs.svh b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker_defs.svh deleted file mode 100644 index 5c11a419d..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef BLINKER_DEFS -`define BLINKER_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker_defs.vh b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker_defs.vh deleted file mode 100644 index 44cc9875c..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef BLINKER_DEFS -`define BLINKER_DEFS -`endif -`ifndef BLINKER_DEFS_MODULE -`define BLINKER_DEFS_MODULE -`else -`undef BLINKER_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker_defs.vh b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker_defs.vh deleted file mode 100644 index 44cc9875c..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef BLINKER_DEFS -`define BLINKER_DEFS -`endif -`ifndef BLINKER_DEFS_MODULE -`define BLINKER_DEFS_MODULE -`else -`undef BLINKER_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker_pkg.vhd b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker_pkg.vhd deleted file mode 100644 index 9eef929ff..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package Blinker_pkg is -end package Blinker_pkg; - -package body Blinker_pkg is -end package body Blinker_pkg; diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker_pkg.vhd b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker_pkg.vhd deleted file mode 100644 index 9eef929ff..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package Blinker_pkg is -end package Blinker_pkg; - -package body Blinker_pkg is -end package body Blinker_pkg; diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter_defs.svh b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter_defs.svh deleted file mode 100644 index eb369b141..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef COUNTER_DEFS -`define COUNTER_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter_defs.vh b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter_defs.vh deleted file mode 100644 index ee2ed1b95..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef COUNTER_DEFS -`define COUNTER_DEFS -`endif -`ifndef COUNTER_DEFS_MODULE -`define COUNTER_DEFS_MODULE -`else -`undef COUNTER_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter_defs.vh b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter_defs.vh deleted file mode 100644 index ee2ed1b95..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef COUNTER_DEFS -`define COUNTER_DEFS -`endif -`ifndef COUNTER_DEFS_MODULE -`define COUNTER_DEFS_MODULE -`else -`undef COUNTER_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter_pkg.vhd b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter_pkg.vhd deleted file mode 100644 index 74ad8b33e..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package Counter_pkg is -end package Counter_pkg; - -package body Counter_pkg is -end package body Counter_pkg; diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter_pkg.vhd b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter_pkg.vhd deleted file mode 100644 index 74ad8b33e..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package Counter_pkg is -end package Counter_pkg; - -package body Counter_pkg is -end package body Counter_pkg; diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1_defs.svh b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1_defs.svh deleted file mode 100644 index 83e5e9ff9..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef FULLADDER1_DEFS -`define FULLADDER1_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1_defs.vh b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1_defs.vh deleted file mode 100644 index 652b16c28..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef FULLADDER1_DEFS -`define FULLADDER1_DEFS -`endif -`ifndef FULLADDER1_DEFS_MODULE -`define FULLADDER1_DEFS_MODULE -`else -`undef FULLADDER1_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1_defs.vh b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1_defs.vh deleted file mode 100644 index 652b16c28..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef FULLADDER1_DEFS -`define FULLADDER1_DEFS -`endif -`ifndef FULLADDER1_DEFS_MODULE -`define FULLADDER1_DEFS_MODULE -`else -`undef FULLADDER1_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1_pkg.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1_pkg.vhd deleted file mode 100644 index 44a6d88c9..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package FullAdder1_pkg is -end package FullAdder1_pkg; - -package body FullAdder1_pkg is -end package body FullAdder1_pkg; diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1_pkg.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1_pkg.vhd deleted file mode 100644 index 44a6d88c9..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package FullAdder1_pkg is -end package FullAdder1_pkg; - -package body FullAdder1_pkg is -end package body FullAdder1_pkg; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN_defs.svh b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN_defs.svh deleted file mode 100644 index 3cab3ebce..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef FULLADDERN_DEFS -`define FULLADDERN_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN_defs.vh b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN_defs.vh deleted file mode 100644 index 0ec185153..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef FULLADDERN_DEFS -`define FULLADDERN_DEFS -`endif -`ifndef FULLADDERN_DEFS_MODULE -`define FULLADDERN_DEFS_MODULE -`else -`undef FULLADDERN_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN_defs.vh b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN_defs.vh deleted file mode 100644 index 0ec185153..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef FULLADDERN_DEFS -`define FULLADDERN_DEFS -`endif -`ifndef FULLADDERN_DEFS_MODULE -`define FULLADDERN_DEFS_MODULE -`else -`undef FULLADDERN_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN_pkg.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN_pkg.vhd deleted file mode 100644 index 2912cd37e..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package FullAdderN_pkg is -end package FullAdderN_pkg; - -package body FullAdderN_pkg is -end package body FullAdderN_pkg; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN_pkg.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN_pkg.vhd deleted file mode 100644 index 2912cd37e..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package FullAdderN_pkg is -end package FullAdderN_pkg; - -package body FullAdderN_pkg is -end package body FullAdderN_pkg; diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile_defs.svh b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile_defs.svh deleted file mode 100644 index 8505bc1e4..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef REGFILE_DEFS -`define REGFILE_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile_defs.vh b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile_defs.vh deleted file mode 100644 index 1dcaad88b..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef REGFILE_DEFS -`define REGFILE_DEFS -`endif -`ifndef REGFILE_DEFS_MODULE -`define REGFILE_DEFS_MODULE -`else -`undef REGFILE_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile_defs.vh b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile_defs.vh deleted file mode 100644 index 1dcaad88b..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef REGFILE_DEFS -`define REGFILE_DEFS -`endif -`ifndef REGFILE_DEFS_MODULE -`define REGFILE_DEFS_MODULE -`else -`undef REGFILE_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile_pkg.vhd b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile_pkg.vhd deleted file mode 100644 index 25ee852ce..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package RegFile_pkg is -end package RegFile_pkg; - -package body RegFile_pkg is -end package body RegFile_pkg; diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile_pkg.vhd b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile_pkg.vhd deleted file mode 100644 index 25ee852ce..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package RegFile_pkg is -end package RegFile_pkg; - -package body RegFile_pkg is -end package body RegFile_pkg; diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR_defs.svh b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR_defs.svh deleted file mode 100644 index 19c985635..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef TRUEDPR_DEFS -`define TRUEDPR_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR_defs.vh b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR_defs.vh deleted file mode 100644 index 1719dbb00..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef TRUEDPR_DEFS -`define TRUEDPR_DEFS -`endif -`ifndef TRUEDPR_DEFS_MODULE -`define TRUEDPR_DEFS_MODULE -`else -`undef TRUEDPR_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR_defs.vh b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR_defs.vh deleted file mode 100644 index 1719dbb00..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef TRUEDPR_DEFS -`define TRUEDPR_DEFS -`endif -`ifndef TRUEDPR_DEFS_MODULE -`define TRUEDPR_DEFS_MODULE -`else -`undef TRUEDPR_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR_pkg.vhd b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR_pkg.vhd deleted file mode 100644 index edd333ecd..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package TrueDPR_pkg is -end package TrueDPR_pkg; - -package body TrueDPR_pkg is -end package body TrueDPR_pkg; diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR_pkg.vhd b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR_pkg.vhd deleted file mode 100644 index edd333ecd..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package TrueDPR_pkg is -end package TrueDPR_pkg; - -package body TrueDPR_pkg is -end package body TrueDPR_pkg; diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx_defs.svh b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx_defs.svh deleted file mode 100644 index ef4db63a6..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef UART_TX_DEFS -`define UART_TX_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx_defs.vh b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx_defs.vh deleted file mode 100644 index 186b0a671..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef UART_TX_DEFS -`define UART_TX_DEFS -`endif -`ifndef UART_TX_DEFS_MODULE -`define UART_TX_DEFS_MODULE -`else -`undef UART_TX_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx_defs.vh b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx_defs.vh deleted file mode 100644 index 186b0a671..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef UART_TX_DEFS -`define UART_TX_DEFS -`endif -`ifndef UART_TX_DEFS_MODULE -`define UART_TX_DEFS_MODULE -`else -`undef UART_TX_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx_pkg.vhd b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx_pkg.vhd deleted file mode 100644 index 487a30013..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package UART_Tx_pkg is -end package UART_Tx_pkg; - -package body UART_Tx_pkg is -end package body UART_Tx_pkg; diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx_pkg.vhd b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx_pkg.vhd deleted file mode 100644 index 487a30013..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package UART_Tx_pkg is -end package UART_Tx_pkg; - -package body UART_Tx_pkg is -end package body UART_Tx_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2_defs.svh b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2_defs.svh deleted file mode 100644 index 7c012faf5..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef LEFTSHIFT2_DEFS -`define LEFTSHIFT2_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2_defs.vh deleted file mode 100644 index 9dc730828..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFT2_DEFS -`define LEFTSHIFT2_DEFS -`endif -`ifndef LEFTSHIFT2_DEFS_MODULE -`define LEFTSHIFT2_DEFS_MODULE -`else -`undef LEFTSHIFT2_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2_defs.vh deleted file mode 100644 index 9dc730828..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFT2_DEFS -`define LEFTSHIFT2_DEFS -`endif -`ifndef LEFTSHIFT2_DEFS_MODULE -`define LEFTSHIFT2_DEFS_MODULE -`else -`undef LEFTSHIFT2_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2_pkg.vhd deleted file mode 100644 index 98e980ea8..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShift2_pkg is -end package LeftShift2_pkg; - -package body LeftShift2_pkg is -end package body LeftShift2_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2_pkg.vhd deleted file mode 100644 index 98e980ea8..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShift2_pkg is -end package LeftShift2_pkg; - -package body LeftShift2_pkg is -end package body LeftShift2_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic_defs.svh b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic_defs.svh deleted file mode 100644 index c1d7b0a2c..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef LEFTSHIFTBASIC_DEFS -`define LEFTSHIFTBASIC_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic_defs.vh deleted file mode 100644 index 6cb4101a1..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFTBASIC_DEFS -`define LEFTSHIFTBASIC_DEFS -`endif -`ifndef LEFTSHIFTBASIC_DEFS_MODULE -`define LEFTSHIFTBASIC_DEFS_MODULE -`else -`undef LEFTSHIFTBASIC_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic_defs.vh deleted file mode 100644 index 6cb4101a1..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFTBASIC_DEFS -`define LEFTSHIFTBASIC_DEFS -`endif -`ifndef LEFTSHIFTBASIC_DEFS_MODULE -`define LEFTSHIFTBASIC_DEFS_MODULE -`else -`undef LEFTSHIFTBASIC_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic_pkg.vhd deleted file mode 100644 index 4e02534fe..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShiftBasic_pkg is -end package LeftShiftBasic_pkg; - -package body LeftShiftBasic_pkg is -end package body LeftShiftBasic_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic_pkg.vhd deleted file mode 100644 index 4e02534fe..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShiftBasic_pkg is -end package LeftShiftBasic_pkg; - -package body LeftShiftBasic_pkg is -end package body LeftShiftBasic_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen_defs.svh b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen_defs.svh deleted file mode 100644 index 458c38ad4..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef LEFTSHIFTGEN_DEFS -`define LEFTSHIFTGEN_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen_defs.vh deleted file mode 100644 index a0dcc749d..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFTGEN_DEFS -`define LEFTSHIFTGEN_DEFS -`endif -`ifndef LEFTSHIFTGEN_DEFS_MODULE -`define LEFTSHIFTGEN_DEFS_MODULE -`else -`undef LEFTSHIFTGEN_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen_defs.vh deleted file mode 100644 index a0dcc749d..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFTGEN_DEFS -`define LEFTSHIFTGEN_DEFS -`endif -`ifndef LEFTSHIFTGEN_DEFS_MODULE -`define LEFTSHIFTGEN_DEFS_MODULE -`else -`undef LEFTSHIFTGEN_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen_pkg.vhd deleted file mode 100644 index bffa2421d..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShiftGen_pkg is -end package LeftShiftGen_pkg; - -package body LeftShiftGen_pkg is -end package body LeftShiftGen_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen_pkg.vhd deleted file mode 100644 index bffa2421d..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShiftGen_pkg is -end package LeftShiftGen_pkg; - -package body LeftShiftGen_pkg is -end package body LeftShiftGen_pkg; From a28bbdc45fb68e260a4d22f3195d1130cf42ef65 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 2 Apr 2026 02:50:15 +0300 Subject: [PATCH 085/115] clarifying bits initialization or assignment with integers --- docs/transitioning/from-verilog/index.md | 6 +++--- docs/user-guide/type-system/index.md | 24 +++++++++++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 07c2fc193..554417ee8 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -433,9 +433,9 @@ class foo extends EDDesign: /// -/// admonition | Bits Initialization +/// admonition | `Bits` Initialization or Assignment type: verilog -`Bits` values cannot be initialized with plain integers. Use `all(0)` or a sized literal: +`Bits` values cannot be initialized or assigned with plain integers. Use `all(0)` or a sized literal:
@@ -452,7 +452,7 @@ val mask = Bits(8) <> VAR init h"8'FF"
-`UInt` and `SInt` accept plain integer `0` for init; `Bits` does not. +Note: `UInt` and `SInt` can be initialized or assigned with plain integers. /// /// admonition | Inline Conditional Expressions diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index ba04e4755..eaa729a0d 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1062,17 +1062,19 @@ This interpolation covers the VHDL hexadecimal literal use-cases, but also adds * Scala `Tuple` combination of any DFHDL values and `1`/`0` literal values. This candidate performs bit concatenation of all values, according their order in the tuple, encoded from the most-significant value position down to the least-significant value position. * Application-only candidate - Same-Element Vector (`all(elem)`). -!!! warning "`Bits` does not accept plain integer candidates" - Unlike `UInt`/`SInt`, `Bits` values **cannot** be initialized or assigned with plain integers. Use `all(0)` for zero initialization, or a sized literal: - ```scala - // CORRECT - val b8 = Bits(8) <> VAR init all(0) // zero via all(0) - val b4 = Bits(4) <> VAR init b"4'0" // zero via binary literal - val b6 = Bits(6) <> VAR init h"6'00" // zero via hex literal - - // WRONG: integer 0 is not a Bits candidate - // val b8 = Bits(8) <> VAR init 0 // compile error - ``` +/// details | `Bits` does not accept plain integer candidates + type: note +Unlike `UInt`/`SInt`, `Bits` values **cannot** be initialized or assigned with plain integers. Use `all(0)` for zero initialization, or a sized literal: +```scala +// CORRECT +val b8 = Bits(8) <> VAR init all(0) // zero via all(0) +val b4 = Bits(4) <> VAR init b"4'0" // zero via binary literal +val b6 = Bits(6) <> VAR init h"6'00" // zero via hex literal + +// error: An integer value cannot be a candidate for a Bits type. +// Try explicitly using a decimal constant via the `d"'"` string interpolation. +val b16 = Bits(16) <> VAR init 0 // compile error +/// ```scala val b8 = Bits(8) <> VAR From adc1a872f4270cb261fb0dadba2ffe0dd09706bc Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 2 Apr 2026 02:54:47 +0300 Subject: [PATCH 086/115] update all admonitions to the new style --- docs/user-guide/conditionals/index.md | 44 +++---- docs/user-guide/processes/index.md | 6 +- docs/user-guide/type-system/index.md | 162 ++++++++++++++------------ 3 files changed, 115 insertions(+), 97 deletions(-) diff --git a/docs/user-guide/conditionals/index.md b/docs/user-guide/conditionals/index.md index 4f58980b1..7da6797de 100644 --- a/docs/user-guide/conditionals/index.md +++ b/docs/user-guide/conditionals/index.md @@ -184,27 +184,29 @@ else - Optimizes bit pattern matching into efficient comparisons - Extracts struct fields into temporary variables when needed -!!! tip "Use enumerations for state matching" - For FSM-style `match` over states, define an enumeration with the appropriate [encoding](../type-system/index.md#dfhdl-enumeration-enum--extends-encoded) and match on its members: - - ```scala - enum State extends Encoded: // 00, 01, 10 - case Idle, Start, Data - - val state = State <> VAR - state match - case State.Idle => // idle logic - case State.Start => // start logic - case State.Data => // data logic - ``` - - For non-sequential state encodings, use `Encoded.Manual`: - ```scala - enum State(val value: UInt[3] <> CONST) extends Encoded.Manual(3): - case Idle extends State(d"3'0") - case Start extends State(d"3'3") - case Data extends State(d"3'5") - ``` +/// admonition | Use enumerations for state matching + type: tip +For FSM-style `match` over states, define an enumeration with the appropriate [encoding](../type-system/index.md#dfhdl-enumeration-enum--extends-encoded) and match on its members: + +```scala +enum State extends Encoded: // 00, 01, 10 + case Idle, Start, Data + +val state = State <> VAR +state match + case State.Idle => // idle logic + case State.Start => // start logic + case State.Data => // data logic +``` + +For non-sequential state encodings, use `Encoded.Manual`: +```scala +enum State(val value: UInt[3] <> CONST) extends Encoded.Manual(3): + case Idle extends State(d"3'0") + case Start extends State(d"3'3") + case Data extends State(d"3'5") +``` +/// ### Best Practices diff --git a/docs/user-guide/processes/index.md b/docs/user-guide/processes/index.md index 0422d4340..b1ccf6321 100644 --- a/docs/user-guide/processes/index.md +++ b/docs/user-guide/processes/index.md @@ -190,8 +190,10 @@ process(clk): counter :== counter + 1 // register update ``` -!!! tip "Rule of thumb" - Use `:=` for combinational (e.g. in `process(all)` or combinational branches). Use `:==` for register and sequential outputs in clocked processes. +/// admonition | Rule of thumb + type: tip +Use `:=` for combinational (e.g. in `process(all)` or combinational branches). Use `:==` for register and sequential outputs in clocked processes. +/// ## Local variables diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index eaa729a0d..9089b11c2 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -469,38 +469,40 @@ val idx = UInt(3) <> VAR val dynbit = b8(idx) // Single bit at position idx (returns Bit) ``` -!!! tip "Dynamic bit indexing (reads and writes)" - You can index into a `Bits` value using a `UInt` variable, not just integer literals. This is equivalent to Verilog's `data[idx]` where `idx` is a register or wire. +/// admonition | Dynamic bit indexing (reads and writes) + type: tip +You can index into a `Bits` value using a `UInt` variable, not just integer literals. This is equivalent to Verilog's `data[idx]` where `idx` is a register or wire. - The index must be a `UInt` whose width equals `clog2(bits_width)`. For example, indexing into `Bits(8)` requires a `UInt(3)` index. If the width does not match, the compiler will report an error and suggest a fix: +The index must be a `UInt` whose width equals `clog2(bits_width)`. For example, indexing into `Bits(8)` requires a `UInt(3)` index. If the width does not match, the compiler will report an error and suggest a fix: - - **Index too wide**: use `.truncate` to automatically narrow it to the expected width. - - **Index too narrow**: use `.extend` to automatically widen it to the expected width. - - You can also use `.resize(N)` for an explicit target width, or a range slice `(hi, lo)` to extract specific bits. +- **Index too wide**: use `.truncate` to automatically narrow it to the expected width. +- **Index too narrow**: use `.extend` to automatically widen it to the expected width. +- You can also use `.resize(N)` for an explicit target width, or a range slice `(hi, lo)` to extract specific bits. - Dynamic indexing works for both reads and writes: - ```scala - val data = Bits(8) <> VAR init all(0) - val pos = UInt(3) <> VAR init 0 - val din = Bit <> IN +Dynamic indexing works for both reads and writes: +```scala +val data = Bits(8) <> VAR init all(0) +val pos = UInt(3) <> VAR init 0 +val din = Bit <> IN - // Dynamic read - val bit_out = data(pos) // read single bit at position +// Dynamic read +val bit_out = data(pos) // read single bit at position - // Dynamic write inside a clocked process - process(clk): - if (clk.rising) - data(pos) :== din // write single bit at position - ``` +// Dynamic write inside a clocked process +process(clk): + if (clk.rising) + data(pos) :== din // write single bit at position +``` - When the index register is wider than needed, narrow it with `.truncate` or a range slice: - ```scala - val wide_pos = UInt(5) <> VAR - // .truncate automatically narrows to clog2(8) = 3 bits - data(wide_pos.truncate) :== din - // Alternatively, use a range slice to pick specific bits - data(wide_pos(2, 0)) :== din - ``` +When the index register is wider than needed, narrow it with `.truncate` or a range slice: +```scala +val wide_pos = UInt(5) <> VAR +// .truncate automatically narrows to clog2(8) = 3 bits +data(wide_pos.truncate) :== din +// Alternatively, use a range slice to pick specific bits +data(wide_pos(2, 0)) :== din +``` +/// ### Bit Operations ```scala @@ -631,7 +633,7 @@ Although they are interchangeable, it's generally recommended to use `Boolean` D /// /// details | Why have both `Bit` and `Boolean` DFTypes? - type: note + type: note The main reason to differentiate between `Bit` and `Boolean` is that VHDL has both `std_logic` and `boolean` types, respectively. Verilog has only a single `logic` or `wire` to represent both. Indeed VHDL'2008 has relaxed some of the type constraints, but not enough. And nevertheless, DFHDL aims to support various HDL dialects, and thus enables simple implicit or explicit conversion between these two DFType values. /// @@ -698,23 +700,27 @@ These operations propagate constant modifiers, meaning that if the casted argume | `lhs.bit` | Cast to a DFHDL `Bit` value | `Boolean` DFHDL value | `Bit` DFHDL value | /// -!!! note "Comparison results are `Boolean`, not `Bit`" - All comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`) return `Boolean`. To assign a comparison result to a `Bit` port or variable, apply `.bit`: - ```scala - val tick = Bit <> OUT - val counter = UInt(8) <> VAR - val limit = UInt(8) <> IN - tick := (counter == limit).bit // Boolean -> Bit conversion - ``` - Conversely, `Bit` values can be used directly in `if` conditions (they are accepted as boolean expressions), and you can compare a `Bit` with integer literals `0` and `1`. - -!!! note "`Bit` does not have `.uint` -- use `.bits.uint`" - The `.uint` method is available on `Bits` values but not directly on `Bit`. To convert a `Bit` to `UInt(1)`, go through `Bits(1)` first: - ```scala - val b = Bit <> IN - val u = UInt(1) <> VAR - u := b.bits.uint // Bit -> Bits(1) -> UInt(1) - ``` +/// admonition | Comparison results are `Boolean`, not `Bit` + type: note +All comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`) return `Boolean`. To assign a comparison result to a `Bit` port or variable, apply `.bit`: +```scala +val tick = Bit <> OUT +val counter = UInt(8) <> VAR +val limit = UInt(8) <> IN +tick := (counter == limit).bit // Boolean -> Bit conversion +``` +Conversely, `Bit` values can be used directly in `if` conditions (they are accepted as boolean expressions), and you can compare a `Bit` with integer literals `0` and `1`. +/// + +/// admonition | `Bit` does not have `.uint` -- use `.bits.uint` + type: note +The `.uint` method is available on `Bits` values but not directly on `Bit`. To convert a `Bit` to `UInt(1)`, go through `Bits(1)` first: +```scala +val b = Bit <> IN +val u = UInt(1) <> VAR +u := b.bits.uint // Bit -> Bits(1) -> UInt(1) +``` +/// ```scala val bt1 = Bit <> VAR @@ -812,8 +818,10 @@ val e3 = 0 ^ true val sc: Boolean = true && true ``` -!!! tip "Logical `||`/`&&` and bitwise `|`/`&` on Bit and Boolean values" - In DFHDL, the operators `||` and `&&` are equivalent to `|` and `&`, respectively, when applied on either DFHDL `Bit` or `Boolean` types. In Verilog, the actual operator printed depends on the LHS argument of the operation: if it's `Bit`, the operator will be `|`/`&`; if it's `Boolean`, the operator will be `||`/`&&`. +/// admonition | Logical `||`/`&&` and bitwise `|`/`&` on Bit and Boolean values + type: tip +In DFHDL, the operators `||` and `&&` are equivalent to `|` and `&`, respectively, when applied on either DFHDL `Bit` or `Boolean` types. In Verilog, the actual operator printed depends on the LHS argument of the operation: if it's `Bit`, the operator will be `|`/`&`; if it's `Boolean`, the operator will be `||`/`&&`. +/// /// details | Transitioning from Verilog type: verilog @@ -1248,16 +1256,18 @@ val u8 = UInt(8) <> VAR u8 := s8.bits.uint ``` -!!! warning "Unsigned/signed mixing in arithmetic" - DFHDL does not allow mixing `UInt` and `SInt` in arithmetic expressions. Convert explicitly before operating: - ```scala - val counter = UInt(3) <> VAR - val offset = SInt(8) <> VAR - // ERROR: Cannot mix unsigned and signed - // val result = offset - counter - // CORRECT: convert counter to SInt first - val result = offset - counter.bits.sint.resize(8) - ``` +/// admonition | Unsigned/signed mixing in arithmetic + type: warning +DFHDL does not allow mixing `UInt` and `SInt` in arithmetic expressions. Convert explicitly before operating: +```scala +val counter = UInt(3) <> VAR +val offset = SInt(8) <> VAR +// ERROR: Cannot mix unsigned and signed +// val result = offset - counter +// CORRECT: convert counter to SInt first +val result = offset - counter.bits.sint.resize(8) +``` +/// ### Constant Generation @@ -1327,18 +1337,20 @@ val s6 = SInt(6) <> VAR val s4 = s6(3, 0) // lower 4 bits, returns SInt[4] ``` -!!! note "Range slices preserve the original type" - A range slice on `UInt` returns `UInt`, and on `SInt` returns `SInt` -- it does **not** return `Bits`. This means a `UInt` range slice can be used directly as a dynamic index into `Bits` without `.uint`: - ```scala - val scratch = Bits(8) <> VAR - val bitpos = UInt(4) <> VAR // 4-bit counter +/// admonition | Range slices preserve the original type + type: note +A range slice on `UInt` returns `UInt`, and on `SInt` returns `SInt` -- it does **not** return `Bits`. This means a `UInt` range slice can be used directly as a dynamic index into `Bits` without `.uint`: +```scala +val scratch = Bits(8) <> VAR +val bitpos = UInt(4) <> VAR // 4-bit counter - // CORRECT: range slice of UInt returns UInt, usable directly as index - scratch(bitpos(2, 0)) :== din +// CORRECT: range slice of UInt returns UInt, usable directly as index +scratch(bitpos(2, 0)) :== din - // WRONG: .uint is not needed and will cause a compile error - // scratch(bitpos(2, 0).uint) :== din - ``` +// WRONG: .uint is not needed and will cause a compile error +// scratch(bitpos(2, 0).uint) :== din +``` +/// #### Width Adjustment: `.resize`, `.truncate`, `.extend` @@ -1355,14 +1367,16 @@ u8 := u6.resize(8) // explicit zero-extend to 8 bits u8 := u6.extend // auto-widen to match u8's width ``` -!!! note "Scala `Int` constants auto-lift in comparisons" - Plain Scala `Int` values can be used directly in comparisons and arithmetic with DFHDL typed variables. No explicit coercion is needed: - ```scala - val LIMIT: Int <> CONST = 5208 - val counter = UInt.until(LIMIT) <> VAR - if (counter == LIMIT - 1) // Int <> CONST compared with UInt -- works directly - counter := 0 - ``` +/// details | Scala `Int` constants auto-lift in comparisons + type: note +Plain Scala `Int` values can be used directly in comparisons and arithmetic with DFHDL typed variables. No explicit coercion is needed: +```scala +val LIMIT: Int <> CONST = 5208 +val counter = UInt.until(LIMIT) <> VAR +if (counter == LIMIT - 1) // Int <> CONST compared with UInt -- works directly + counter := 0 +``` +/// ### Examples From e79ba9cdc66403e962d03a3aeba683d772f4a9b8 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 2 Apr 2026 03:05:26 +0300 Subject: [PATCH 087/115] More clarification on the naming collisions with scala keywords --- docs/transitioning/from-verilog/index.md | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 554417ee8..228f8da35 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -411,7 +411,7 @@ o3 <> a.bool || b || c /// -/// admonition | Scalar Reserved Keywords as Port Names +/// admonition | Scala Reserved Keywords as DFHDL Port or Variable Names type: verilog Some Verilog port names (`val`, `type`, `class`, `match`, `case`, `object`, etc.) are reserved in Scala. Use backtick escaping: @@ -431,6 +431,31 @@ class foo extends EDDesign: ``` + +Alternatively, use a non-keyword name with the Scala `@targetName` annotation to set the actual HDL name: + +
+ +```sv linenums="0" title="Verilog" +module foo( + output logic signed [15:0] class +); + `include "dfhdl_defs.svh" + assign class = 16'sd42; +endmodule +``` + +```scala linenums="0" title="DFHDL" +import scala.annotation.targetName +class foo extends EDDesign: + @targetName("class") + val class_ = SInt(16) <> OUT + class_ <> 42 +``` + +
+ + /// /// admonition | `Bits` Initialization or Assignment From e9f73d05f0feca6bd12d4f4dcb7fe1fb97c6925e Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 2 Apr 2026 03:40:18 +0300 Subject: [PATCH 088/115] missing closing block --- docs/user-guide/type-system/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 9089b11c2..7fe89ae00 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1082,6 +1082,7 @@ val b6 = Bits(6) <> VAR init h"6'00" // zero via hex literal // error: An integer value cannot be a candidate for a Bits type. // Try explicitly using a decimal constant via the `d"'"` string interpolation. val b16 = Bits(16) <> VAR init 0 // compile error +``` /// ```scala From 275f5f64749df153f3feb85405ded938560e362b Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 2 Apr 2026 03:44:43 +0300 Subject: [PATCH 089/115] better placement of admonition --- docs/user-guide/type-system/index.md | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 7fe89ae00..c94992587 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1070,21 +1070,6 @@ This interpolation covers the VHDL hexadecimal literal use-cases, but also adds * Scala `Tuple` combination of any DFHDL values and `1`/`0` literal values. This candidate performs bit concatenation of all values, according their order in the tuple, encoded from the most-significant value position down to the least-significant value position. * Application-only candidate - Same-Element Vector (`all(elem)`). -/// details | `Bits` does not accept plain integer candidates - type: note -Unlike `UInt`/`SInt`, `Bits` values **cannot** be initialized or assigned with plain integers. Use `all(0)` for zero initialization, or a sized literal: -```scala -// CORRECT -val b8 = Bits(8) <> VAR init all(0) // zero via all(0) -val b4 = Bits(4) <> VAR init b"4'0" // zero via binary literal -val b6 = Bits(6) <> VAR init h"6'00" // zero via hex literal - -// error: An integer value cannot be a candidate for a Bits type. -// Try explicitly using a decimal constant via the `d"'"` string interpolation. -val b16 = Bits(16) <> VAR init 0 // compile error -``` -/// - ```scala val b8 = Bits(8) <> VAR val b1 = Bits(1) <> VAR @@ -1106,6 +1091,21 @@ val s4 = SInt(4) <> VAR b8 := (1, s4, b1, b"10") ``` +/// admonition | `Bits` does not accept plain integer candidates + type: note +Unlike `UInt`/`SInt`, `Bits` values **cannot** be initialized or assigned with plain integers. Use `all(0)` for zero initialization, or a sized literal: +```scala +// CORRECT +val b8 = Bits(8) <> VAR init all(0) // zero via all(0) +val b4 = Bits(4) <> VAR init b"4'0" // zero via binary literal +val b6 = Bits(6) <> VAR init h"6'00" // zero via hex literal + +// error: An integer value cannot be a candidate for a Bits type. +// Try explicitly using a decimal constant via the `d"'"` string interpolation. +val b16 = Bits(16) <> VAR init 0 // compile error +``` +/// + ### Concatenated Assignment DFHDL supports a special-case assignment of concatenated DFHDL Bits variables, using a Scala `Tuple` syntax on LHS of the assignment operator. Both LHS and RHS bits width must be the same. This assignment is just syntactic sugar for multiple separate assignments and carried out during the design [elaboration][elaboration]. The assignment ordering is from the first value at most-significant position down to the last value at least-significant position. From ff70d8dc9a71270fa75dc99eea59f1ace5b941f9 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 00:43:08 +0300 Subject: [PATCH 090/115] docs: clarifying more conversions --- docs/css/user-guide.css | 7 + docs/user-guide/type-system/index.md | 192 ++++++----- .../type-system/type-conversion-dark.svg | 133 ++++++++ .../type-system/type-conversion-light.svg | 133 ++++++++ .../type-system/type-conversion.graphml | 317 ++++++++++++++++++ 5 files changed, 707 insertions(+), 75 deletions(-) create mode 100644 docs/user-guide/type-system/type-conversion-dark.svg create mode 100644 docs/user-guide/type-system/type-conversion-light.svg create mode 100644 docs/user-guide/type-system/type-conversion.graphml diff --git a/docs/css/user-guide.css b/docs/css/user-guide.css index 036b10233..313ade59a 100644 --- a/docs/css/user-guide.css +++ b/docs/css/user-guide.css @@ -1,3 +1,10 @@ +/* column separator between the two halves of the conversion table */ +div.conversion td:nth-child(4), +div.conversion th:nth-child(4) { + border-left: 2px solid var(--md-typeset-table-color, #0000001f); + padding-left: 1em; +} + /* different styles for tabs inside tabs */ html.js-focus-visible.js body div.md-container main.md-main div.md-main__inner.md-grid div.md-content article.md-content__inner.md-typeset div.admonition div.tabbed-set.tabbed-alternate div.tabbed-content div.tabbed-block div.tabbed-set.tabbed-alternate div.tabbed-labels.tabbed-labels--linked label a { font-size: smaller; diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index c94992587..5927b5ac8 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -687,6 +687,30 @@ val TrueVal: Boolean = 1 bool := TrueVal ``` +/// admonition | `Bit` variables accept `Boolean` comparison values as condidates + type: note +All comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`) return `Boolean`, and can be directly assigned to `Bit` variables: +```scala +class Foo extends RTDesign: + val limit = UInt(8) <> IN + val counter = UInt(8) <> VAR.REG init 0 + val tick = Bit <> OUT + tick := counter == limit // Implicit Boolean -> Bit conversion +``` +/// + +/// admonition | `if` and `while` conditionals accept both `Boolean` and `Bit` values + type: note +`if` and `while` conditional expression and statements accept both `Boolean` and `Bit` values (no conversion is taking place). In stricter backends like `vhdl.v93`, an automatic conversion is applied `Boolean` where needed. +```scala +class Foo extends RTDesign: + val tick = Bit <> IN + if (tick) // if condition accepts both Bit and Boolean values + //do something + end if +``` +/// + ### Operations #### Explicit Casting Operations @@ -700,28 +724,6 @@ These operations propagate constant modifiers, meaning that if the casted argume | `lhs.bit` | Cast to a DFHDL `Bit` value | `Boolean` DFHDL value | `Bit` DFHDL value | /// -/// admonition | Comparison results are `Boolean`, not `Bit` - type: note -All comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`) return `Boolean`. To assign a comparison result to a `Bit` port or variable, apply `.bit`: -```scala -val tick = Bit <> OUT -val counter = UInt(8) <> VAR -val limit = UInt(8) <> IN -tick := (counter == limit).bit // Boolean -> Bit conversion -``` -Conversely, `Bit` values can be used directly in `if` conditions (they are accepted as boolean expressions), and you can compare a `Bit` with integer literals `0` and `1`. -/// - -/// admonition | `Bit` does not have `.uint` -- use `.bits.uint` - type: note -The `.uint` method is available on `Bits` values but not directly on `Bit`. To convert a `Bit` to `UInt(1)`, go through `Bits(1)` first: -```scala -val b = Bit <> IN -val u = UInt(1) <> VAR -u := b.bits.uint // Bit -> Bits(1) -> UInt(1) -``` -/// - ```scala val bt1 = Bit <> VAR val bl1 = bt1.bool @@ -1155,9 +1157,10 @@ given options.AppOptions.AppMode = options.AppOptions.AppMode.elaborate ## `UInt`/`SInt`/`Int` DFHDL Values {#DFDecimal} DFHDL provides three decimal numeric types: -- `UInt` - Unsigned integer values -- `SInt` - Signed integer values -- `Int` - Constant integer values (used mainly for parameters) + +- `UInt` - Unsigned bit-accurate integer values +- `SInt` - Signed bit-accurate integer values +- `Int` - 32-bit integer values (used mainly for parameters) ### DFType Constructors @@ -1169,6 +1172,7 @@ DFHDL provides three decimal numeric types: | `UInt.to(max)`| Construct an unsigned integer DFType with the given `max` maximum number the value is expected to reach. The number of bits is set as `clog2(max+1)`. | `max` is a positive Scala `Int` or constant DFHDL `Int` value. `UInt.to(1)` is valid (produces 1-bit width). | `UInt[CLog2[width.type+1]]` DFType | | `SInt(width)`| Construct a signed integer DFType with the given `width` as number of bits. | `width` is a positive Scala `Int` or constant DFHDL `Int` value. | `SInt[width.type]` DFType | | `Int`| Construct a constant integer DFType. Used mainly for parameters. | None | `Int` DFType | +/// ### Candidates * DFHDL decimal values of the same type @@ -1189,9 +1193,9 @@ These operations propagate constant modifiers and maintain proper bit widths: | `lhs * rhs` | Multiplication | Both decimal types | Result with width = lhs.width + rhs.width | | `lhs / rhs` | Division | Both decimal types | Result with lhs width | | `lhs % rhs` | Modulo | Both decimal types | Result with rhs width | +/// #### Comparison Operations -Return `Boolean` values (not `Bit`). To assign a comparison result to a `Bit` port or variable, use `.bit`: /// html | div.operations | Operation | Description | LHS/RHS Constraints | Returns | @@ -1202,15 +1206,7 @@ Return `Boolean` values (not `Bit`). To assign a comparison result to a `Bit` po | `lhs >= rhs` | Greater than or equal | Both decimal types | Boolean | | `lhs == rhs` | Equal | Both decimal types | Boolean | | `lhs != rhs` | Not equal | Both decimal types | Boolean | - -```scala -val counter = UInt(8) <> VAR -val limit = UInt(8) <> IN -val tick = Bit <> OUT - -// Comparison returns Boolean; convert to Bit for assignment to a Bit port -tick := (counter == limit).bit -``` +/// #### Shift Operations @@ -1219,6 +1215,7 @@ tick := (counter == limit).bit | ------------ | ----------- | ------------------- | ------- | | `lhs << rhs` | Left shift | LHS: `UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | | `lhs >> rhs` | Right shift (logical for `UInt`, arithmetic for `SInt`) | LHS: `UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | +/// The `>>` operator is **type-aware**: on `UInt` it performs a logical (zero-filling) right shift, and on `SInt` it performs an arithmetic (sign-extending) right shift. There is no separate `>>>` operator in DFHDL -- the operand type determines the behavior. @@ -1230,46 +1227,6 @@ val u_shifted = u >> 2 // logical right shift (zero-fills MSBs) val s_shifted = s >> 2 // arithmetic right shift (sign-extends MSBs) ``` -#### Type Conversion Between UInt, SInt, and Bits {#type-conversion} - -DFHDL does not provide direct `.toSInt` or `.toUInt` methods. Instead, conversions go through `Bits` as an intermediate: - -| From | To | Method | -|------|-----|--------| -| `UInt` | `Bits` | `.bits` | -| `SInt` | `Bits` | `.bits` | -| `Bits` | `UInt` | `.uint` | -| `Bits` | `SInt` | `.sint` | -| `UInt` | `SInt` | `.bits.sint` | -| `SInt` | `UInt` | `.bits.uint` | - -When converting `UInt` to `SInt`, the bit pattern is reinterpreted (not sign-extended). If you need the unsigned value represented as a wider signed value, `.resize` after conversion: - -```scala -val u3 = UInt(3) <> VAR // range 0..7 -val s8 = SInt(8) <> VAR - -// UInt(3) -> Bits(3) -> SInt(3) -> SInt(8) -s8 := u3.bits.sint.resize(8) - -// SInt(8) -> Bits(8) -> UInt(8) -val u8 = UInt(8) <> VAR -u8 := s8.bits.uint -``` - -/// admonition | Unsigned/signed mixing in arithmetic - type: warning -DFHDL does not allow mixing `UInt` and `SInt` in arithmetic expressions. Convert explicitly before operating: -```scala -val counter = UInt(3) <> VAR -val offset = SInt(8) <> VAR -// ERROR: Cannot mix unsigned and signed -// val result = offset - counter -// CORRECT: convert counter to SInt first -val result = offset - counter.bits.sint.resize(8) -``` -/// - ### Constant Generation #### Decimal String-Interpolator {#d-interp} @@ -1401,6 +1358,87 @@ val u4 = UInt(4) <> VAR init d"4'10" val s4 = SInt(4) <> VAR init sd"4'-2" ``` +## Common Conversions and Casts Between Types {#type-conversion} +The diagram below shows the conversion/cast paths between DFHDL types. Solid arrows are simple casts that preserve width; dashed arrows involve width changes. + +![type-conversion](type-conversion-light.svg#only-light){ width="70%" } +![type-conversion](type-conversion-dark.svg#only-dark){ width="70%" } + +/// html | div.conversion +| From | To | Method | From | To | Method | +|------|-----|--------|------|-----|--------| +| `T` | `Bits` | `.bits` | `Bit` | `Boolean` | `.bool` | +| `Bits` | `T` | `.as(T)` | `Boolean` | `Bit` | `.bit` | +| `Bits(w)` | `UInt(w)` | `.uint` | `Bit`/`Boolean` | `Bits(1)` | `.bits` | +| `Bits(w)` | `SInt(w)` | `.sint` | `Bit`/`Boolean` | `Bits(w)` | `.toBits(w)` | +| `UInt(w)` | `SInt(w+1)` | `.signed` | `Bit`/`Boolean` | `UInt(w)` | `.toUInt(w)` | +| `UInt`/`SInt` | `Int` | `.ToInt` | `Bit`/`Boolean` | `SInt(w)` | `.toSInt(w)` | +/// + +### Any Type to/from `Bits`: `.bits` and `.as(T)` {#bits-cast} + +Every DFHDL type can be converted to its raw bit representation with `.bits`. The inverse operation, `.as(T)`, reinterprets a `Bits` value as a target type `T`, provided the bit widths match exactly: + +```scala +val u8 = UInt(8) <> VAR +val b8 = u8.bits // UInt(8) -> Bits(8) +val back = b8.as(UInt(8)) // Bits(8) -> UInt(8) +``` + +This also works with composite types such as enums, structs, and opaques: + +```scala +val e = MyEnum <> VAR +val eBits = e.bits // Enum -> Bits +val eBack = eBits.as(MyEnum) // Bits -> Enum +``` + +### `Bits` to `UInt`/`SInt`: `.uint` and `.sint` {#uint-sint-cast} + +These are shorthand conversions from `Bits` that preserve width. The same bits are simply reinterpreted as unsigned or signed: + +```scala +val b8 = Bits(8) <> VAR +val u8 = b8.uint // Bits(8) -> UInt(8), same bit pattern +val s8 = b8.sint // Bits(8) -> SInt(8), same bit pattern +``` + +### `UInt` to `SInt`: `.signed` {#signed-cast} + +Converting an unsigned value to signed requires an extra bit for the sign, so `.signed` widens the result by one bit: + +```scala +val u8 = UInt(8) <> VAR +val s9 = u8.signed // UInt(8) -> SInt(9) +``` + +To get an `SInt` with the **same** width (reinterpreting the bit pattern without expanding), go through `Bits`: + +```scala +val s8 = u8.bits.sint // UInt(8) -> Bits(8) -> SInt(8) +``` + +### `Bit` and `Boolean` Conversions {#bit-bool-cast} + +`Bit` is the hardware single-bit type and `Boolean` is the logical type. They are convertible to each other with `.bit` and `.bool`: + +```scala +val myBit = Bit <> VAR +val myBool = myBit.bool // Bit -> Boolean +val back = myBool.bit // Boolean -> Bit +``` + +Both `Bit` and `Boolean` can be widened (zero-extended) into `Bits`, `UInt`, or `SInt` with an explicit target width: + +```scala +val flag = Bit <> VAR +val b4 = flag.toBits(4) // Bit -> Bits(4) +val u4 = flag.toUInt(4) // Bit -> UInt(4) +val s4 = flag.toSInt(4) // Bit -> SInt(4) +``` + +When the value is `1`, these produce the value `1` at the given width (not sign-extended). The single-bit `.bits` conversion is also available, returning `Bits(1)`. + ## Enumeration DFHDL Values {#DFEnum} DFHDL supports enumerated types through Scala's enum feature with special encoding traits. Enums provide a type-safe way to represent a fixed set of values. @@ -1459,6 +1497,7 @@ enum MyEnum(val value: UInt[8] <> CONST) extends Encoded.Manual(8): | `lhs != rhs` | Inequality comparison | Same enum type | Boolean | | `lhs.bits` | Get raw bits representation | Enum value | Bits | | `lhs.uint` | Get unsigned int representation | Enum value | UInt | +/// ### Pattern Matching @@ -1546,6 +1585,7 @@ vec(idx) := newValue // Write element at index | `vec.elements` | Get all elements as Scala sequence | Seq[BaseType] | | `vec.size` | Get vector dimension | Int | | `vec.bits` | Get bits representation | Bits | +/// ### Multi-dimensional Vectors @@ -1637,6 +1677,7 @@ rect.bottomRight.y := 100 | `lhs == rhs` | Equality comparison | Same struct type | Boolean | | `lhs != rhs` | Inequality comparison | Same struct type | Boolean | | `lhs.bits` | Get raw bits representation | Struct value | Bits | +/// ### Pattern Matching @@ -1715,6 +1756,7 @@ val (x, y) = pair | `tuple.bits` | Get bits representation | Bits | | `tuple == other` | Equality comparison | Boolean | | `tuple != other` | Inequality comparison | Boolean | +/// ## Opaque DFHDL Values {#DFOpaque} diff --git a/docs/user-guide/type-system/type-conversion-dark.svg b/docs/user-guide/type-system/type-conversion-dark.svg new file mode 100644 index 000000000..a12db8b89 --- /dev/null +++ b/docs/user-guide/type-system/type-conversion-dark.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UInt + + + + + + + Bits + + + + + + + SInt + + + + + + + + Type T + + + + + Boolean + + + + + + + Bit + + + + + + + Int + + + + + .toUInt(w) + + + + + .bits + + + .bits + + + .toBits(w) + + + .uint + + + .sint + + + + + .toSInt(w) + + + + + .signed + + + .bits + + + .as(T) + + + .bits + + + .bit + + + .bool + + + .toInt + + + .toInt + + + diff --git a/docs/user-guide/type-system/type-conversion-light.svg b/docs/user-guide/type-system/type-conversion-light.svg new file mode 100644 index 000000000..3ce450256 --- /dev/null +++ b/docs/user-guide/type-system/type-conversion-light.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UInt + + + + + + + Bits + + + + + + + SInt + + + + + + + + Type T + + + + + Boolean + + + + + + + Bit + + + + + + + Int + + + + + .toUInt(w) + + + + + .bits + + + .bits + + + .toBits(w) + + + .uint + + + .sint + + + + + .toSInt(w) + + + + + .signed + + + .bits + + + .as(T) + + + .bits + + + .bit + + + .bool + + + .toInt + + + .toInt + + + diff --git a/docs/user-guide/type-system/type-conversion.graphml b/docs/user-guide/type-system/type-conversion.graphml new file mode 100644 index 000000000..3bf943b70 --- /dev/null +++ b/docs/user-guide/type-system/type-conversion.graphml @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UInt + + + + + + + + + + + Bits + + + + + + + + + + + SInt + + + + + + + + + + + + Type T + + + + + + + + + + + + + + Boolean + + + + + + + + + + + Bit + + + + + + + + + + + Int + + + + + + + + + + + + + .bits + + + + + + + + + + + + + .uint + + + + + + + + + + + + + .sint + + + + + + + + + + + + + .bits + + + + + + + + + + + + + .bits + + + + + + + + + + + + + .as(T) + + + + + + + + + + + + + + .bit + + + + + + + + + + + + + .bool + + + + + + + + + + + + + .toUInt(w) + + + + + + + + + + + + + .toSInt(w) + + + + + + + + + + + + .bits + + + + + + + + + + + + .toBits(w) + + + + + + + + + + + + + + + .signed + + + + + + + + + + + + + + .toInt + + + + + + + + + + + + + + .toInt + + + + + + + + From 6b0fc69b060be15a53ae8801a7d1ad6a4c750c21 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 02:28:32 +0300 Subject: [PATCH 091/115] docs: no need for escaping `|` values in code segments in tables --- docs/user-guide/type-system/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 5927b5ac8..bbd499a30 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -785,9 +785,9 @@ These operations propagate constant modifiers, meaning that if all arguments are | Operation | Description | LHS/RHS Constraints | Returns | | ------------ | ----------- | ------------------- | ------- | | `lhs && rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `lhs \|\| rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs || rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | | `lhs & rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `lhs \| rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs | rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | | `lhs ^ rhs` | Logical XOR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | | `!lhs` | Logical NOT | The argument must be a `Bit`/`Boolean` DFHDL value. | LHS-Type DFHDL value | /// @@ -831,11 +831,11 @@ Under the ED domain, the following operations are equivalent: | DFHDL Operation | Verilog Operation (Bit LHS) | Verilog Operation (Boolean LHS) | |-----------------|-----------------------------|---------------------------------| -| `lhs && rhs` | `lhs & rhs` | `lhs && rhs` | -| `lhs \|\| rhs` | `lhs \| rhs` | `lhs \|\| rhs` | -| `lhs & rhs` | `lhs & rhs` | `lhs && rhs` | -| `lhs \| rhs` | `lhs \| rhs` | `lhs \|\| rhs` | -| `lhs ^ rhs` | `lhs ^ rhs` | `lhs ^ rhs` | +| `lhs && rhs` | `lhs & rhs` | `lhs && rhs` | +| `lhs || rhs` | `lhs | rhs` | `lhs || rhs` | +| `lhs & rhs` | `lhs & rhs` | `lhs && rhs` | +| `lhs | rhs` | `lhs | rhs` | `lhs || rhs` | +| `lhs ^ rhs` | `lhs ^ rhs` | `lhs ^ rhs` | | `!lhs` | `!lhs` | `!lhs` | /// From cd816326e33d1ba756ac9a2d5b5a330c5113b7fb Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 02:29:13 +0300 Subject: [PATCH 092/115] docs: move and improve documentation on `rising` and `falling` operations --- docs/user-guide/type-system/index.md | 105 +++++++++++++++++---------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index bbd499a30..a5420927e 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -736,46 +736,6 @@ val bt4: Bit <> CONST = bt4.bit // error: bt1 is not a constant val err: Bit <> CONST = bt1 ``` - -#### Bit History Operations - -Currently these operations are only supported under ED domains. However, in upcoming DFHDL updates, support will be added across all domain abstractions. - -/// html | div.operations -| Operation | Description | LHS Constraints | Returns | -| ------------ | --------------------------------|-----------------------|-----------------------| -| `lhs.rising` | True when a value changes from `0` to `1` | `Bit` DFHDL value | `Boolean` DFHDL value | -| `lhs.falling` | True when a value changes from `1` to `0` | `Bit` DFHDL value | `Boolean` DFHDL value | -/// - -```scala -class Foo extends EDDesign: - val clk = Bit <> IN - - /* VHDL-style */ - process(clk): - if (clk.rising) - //some sequential logic - - /* Verilog-style */ - process(clk.rising): - //some sequential logic -``` - -/// details | Transitioning from Verilog - type: verilog -Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the Verilog `posedge x` and `negedge x`, respectively. -In future releases these operations will have an expanded functionality under the other design domains. -/// - -/// details | Transitioning from VHDL - type: vhdl -Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the VHDL `rising_edge(x)` and `falling_edge(x)`, respectively. -In future releases these operations will have an expanded functionality under the other design domains. -/// - -For more information see either the [design domains][design-domains] or [processes][processes] sections. - #### Logical Operations Logical operations' return type always match the LHS argument's type. @@ -851,6 +811,71 @@ Under the ED domain, the following operations are equivalent: | `!lhs` | `not lhs` | /// +#### Bit History Operations + +These operations are supported under both RT and ED domains. Under RT domain, these operations are synthesizable expression. + +/// html | div.operations +| Operation | Description | LHS Constraints | Returns | +| ------------ | --------------------------------|-----------------------|-----------------------| +| `lhs.rising` | True when a value changes from `0` to `1` | `Bit` DFHDL value | `Boolean` DFHDL value | +| `lhs.falling` | True when a value changes from `1` to `0` | `Bit` DFHDL value | `Boolean` DFHDL value | +/// + +/// tab | `ED` + +```scala +class Foo extends EDDesign: + val clk = Bit <> IN + + /* VHDL-style */ + process(clk): + if (clk.rising) + //some sequential logic + + /* Verilog-style */ + process(clk.rising): + //some sequential logic +``` +/// details | Transitioning from Verilog + type: verilog +Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the Verilog `posedge x` and `negedge x`, respectively. +/// + +/// details | Transitioning from VHDL + type: vhdl +Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the VHDL `rising_edge(x)` and `falling_edge(x)`, respectively. +/// +/// + +/// tab | `RT` +The following `RT` domain edge detection design: +```scala +class Detector extends RTDesign: + val i = Bit <> IN + val o = Bit <> OUT + o := i.rising +``` +is compiled down to the following `ED` design (depending on the clock and reset configurations): +```scala +class Detector extends EDDesign: + val clk = Bit <> IN + val i = Bit <> IN + val o = Bit <> OUT + val i_reg = Bit <> VAR init 1 + process(clk.rising): + i_reg :== i + o <> !i_reg && i +``` +The initial (reset) register value is `1`/`0` for `rising`/`falling` operations, respectively. +This inherently prevents triggering immediately after reset, without sampling at least two input clock cycles. + +Both Verilog and VHDL have no equivalent synthesizable shorthand syntax. +/// + +For more information see either the [design domains][design-domains] or [processes][processes] sections. + + #### Constant Meta Operations These operations are activated during the [elaboration stage][elaboration] of the DFHDL compilation, and are only available for constant `Bit`/`Boolean` DFHDL values. From f6a9fbdf9e6fca92599ea9b8b2bef6834b3cd0c9 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 03:39:03 +0300 Subject: [PATCH 093/115] docs: document the arithmetic operations and their constraints --- docs/user-guide/type-system/index.md | 192 ++++++++++++++++++++++++--- 1 file changed, 172 insertions(+), 20 deletions(-) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index a5420927e..8d7883b0a 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1203,37 +1203,156 @@ DFHDL provides three decimal numeric types: * DFHDL decimal values of the same type * DFHDL `Bits` values (via `.uint` or `.sint` casting) * Scala numeric values (Int, Long, etc.) for constant values - * Decimal string interpolation values + * Decimal literals (string interpolation values) ### Operations -#### Arithmetic Operations -These operations propagate constant modifiers and maintain proper bit widths: +#### Arithmetic Operations (`+`, `-`, `*`, `/`, `%`) + +The result of a standard arithmetic operation always has the **same type as the LHS** operand. The RHS is resized to match the LHS before the operation is applied. + +/// html | div.decimal_arithmetic +| Operation | Description | Returns | +| ------------ | -------------- | ---------------- | +| `lhs + rhs` | Addition | Same type as LHS | +| `lhs - rhs` | Subtraction | Same type as LHS | +| `lhs * rhs` | Multiplication | Same type as LHS | +| `lhs / rhs` | Division | Same type as LHS | +| `lhs % rhs` | Modulo | Same type as LHS | +/// + +##### Type Constraints + +**Sign rule** -- The LHS sign must be greater than or equal to the RHS sign (`signed >= unsigned`): /// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs + rhs` | Addition | Both decimal types | Result with appropriate width | -| `lhs - rhs` | Subtraction (LHS must be at least as wide as RHS) | Both decimal types | Result with appropriate width | -| `lhs * rhs` | Multiplication | Both decimal types | Result with width = lhs.width + rhs.width | -| `lhs / rhs` | Division | Both decimal types | Result with lhs width | -| `lhs % rhs` | Modulo | Both decimal types | Result with rhs width | +| LHS | RHS | Allowed | Note | +| --- | --- | ------- | ---- | +| `UInt[W1]` | `UInt[W2]` | Yes | W1 >= W2 | +| `SInt[W1]` | `SInt[W2]` | Yes | W1 >= W2 | +| `SInt[W1]` | `UInt[W2]` | Yes | W1 >= W2 + 1 (RHS is implicitly widened by 1 bit for the sign bit) | +| `UInt[W1]` | `SInt[W2]` | **No** | Compile error: an explicit conversion is required | +/// + +**Width rule** -- The LHS width must be greater than or equal to the (effective) RHS width. When applying `SInt op UInt`, the effective RHS width is `RHS width + 1` because the unsigned value gains an implicit sign bit. + +**Scala `Int` literals** are auto-promoted to a matching DFHDL type with the minimum required bit width. A negative `Int` literal on the RHS of a `UInt` operation is a compile error (unsigned LHS cannot accept a signed RHS). + +```scala +val u8 = UInt(8) <> VAR +val u4 = UInt(4) <> VAR +val s8 = SInt(8) <> VAR + +// UInt + UInt (same width) +val r1 = u8 + u8 // UInt[8] +// SInt + SInt +val r2 = s8 + s8 // SInt[8] +// SInt + UInt: RHS widened to 5 bits (4+1); 8 >= 5, OK +val r3 = s8 + u4 // SInt[8] +// UInt + Scala Int literal (200 fits in 8 bits) +val r4 = u8 + 200 // UInt[8] +// Scala Int literal on LHS (200 is promoted to UInt[8]) +val r5 = 200 - u8 // UInt[8] + +// error: Cannot apply this operation between an unsigned +// value (LHS) and a signed value (RHS). +// An explicit conversion must be applied. +val e1 = u8 + s8 +// error: The applied RHS value width (8) is larger than +// the LHS variable width (4). +val e2 = u4 + u8 +// error: Cannot apply this operation between an unsigned +// value (LHS) and a signed value (RHS). +// An explicit conversion must be applied. +val e3 = u8 + (-22) +``` + +/// admonition | Overflow behavior + type: warning +Standard arithmetic operations wrap on overflow. For example, `d"8'255" + d"8'1"` produces `d"8'0"`. Use the carry variants (`+^`, `-^`, `*^`) described below to get a wider result that preserves the full value. /// -#### Comparison Operations +#### Carry Operations (`+^`, `-^`, `*^`) + +Carry operations widen the result to prevent overflow. Unlike standard arithmetic, carry operations require both operands to have the **same sign**. /// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs < rhs` | Less than | Both decimal types | Boolean | -| `lhs <= rhs` | Less than or equal | Both decimal types | Boolean | -| `lhs > rhs` | Greater than | Both decimal types | Boolean | -| `lhs >= rhs` | Greater than or equal | Both decimal types | Boolean | -| `lhs == rhs` | Equal | Both decimal types | Boolean | -| `lhs != rhs` | Not equal | Both decimal types | Boolean | +| Operation | Description | Result Width | Result Sign | +| ------------- | -------------------- | ------------------ | -------------------- | +| `lhs +^ rhs` | Carry Addition | max(LW, RW) + 1 | Same sign as operands | +| `lhs -^ rhs` | Carry Subtraction | max(LW, RW) + 1 | Same sign as operands | +| `lhs *^ rhs` | Carry Multiplication | LW + RW | Same sign as operands | +/// + +```scala +val u8 = UInt(8) <> VAR + +// Carry addition: width = max(8, 8) + 1 = 9 +val r1 = u8 +^ u8 // UInt[9] +// d"8'255" +^ d"8'1" == d"9'256" (no overflow) + +// Carry subtraction: width = max(8, 8) + 1 = 9 +val r2 = u8 -^ u8 // UInt[9] + +// Carry multiplication: width = 8 + 8 = 16 +val r3 = u8 *^ u8 // UInt[16] + +// Scala Int literal: 100 needs 7 bits +// width = 7 + 8 = 15 +val r4 = 100 *^ u8 // UInt[15] +``` + +/// admonition | Carry vs standard sign rules + type: tip +Unlike standard arithmetic where `SInt op UInt` is allowed (the unsigned RHS is implicitly widened), carry operations require both operands to have the **same sign**. This is because carry operations produce a wider result, and mixed-sign widening semantics would be ambiguous. Scala `Int` literals are still accepted when the literal's sign matches the DFHDL value's sign. +/// + +#### Comparison Operations (`==`, `!=`, `<`, `>`, `<=`, `>=`) + +Comparison operations return a `Boolean` DFHDL value and have **stricter constraints** than arithmetic: + +/// html | div.operations +| Operation | Description | Returns | +| ------------- | ---------------------- | -------- | +| `lhs == rhs` | Equal | Boolean | +| `lhs != rhs` | Not equal | Boolean | +| `lhs < rhs` | Less than | Boolean | +| `lhs > rhs` | Greater than | Boolean | +| `lhs <= rhs` | Less than or equal | Boolean | +| `lhs >= rhs` | Greater than or equal | Boolean | /// -#### Shift Operations +Unlike arithmetic operations which use relaxed rules (LHS sign >= RHS sign, LHS width >= RHS width), comparisons require **exact matching**: + +- **Sign:** Must match exactly (`UInt` with `UInt`, `SInt` with `SInt`). +- **Width:** Must match exactly (both operands must have the same bit width). +- **Scala `Int` literals:** The literal's bit width (adjusted +1 if the DFHDL value is signed and the literal is positive) must fit within the DFHDL value's width. + +```scala +val u8 = UInt(8) <> VAR +val u4 = UInt(4) <> VAR +val s8 = SInt(8) <> VAR + +val c1 = u8 == u8 // Boolean: same sign, same width +val c2 = u8 < 200 // Boolean: 200 fits in UInt[8] +val c3 = 0 < u8 // Boolean: Scala Int on LHS +val c4 = s8 >= 1 // Boolean: 1 is promoted to SInt (width 2 fits in 8) + +// error: Cannot apply this operation between an unsigned +// value (LHS) and a signed value (RHS). +// An explicit conversion must be applied. +val e1 = u8 == s8 +// error: Cannot apply this operation between a value of +// 8 bits width (LHS) to a value of 4 bits width (RHS). +// An explicit conversion must be applied. +val e2 = u8 == u4 +// error: Cannot compare a DFHDL value (width = 8) with a +// Scala `Int` argument that is wider (width = 10). +// An explicit conversion must be applied. +val e3 = u8 > 1000 +``` + +#### Shift Operations (`<<`, `>>`) /// html | div.operations | Operation | Description | LHS/RHS Constraints | Returns | @@ -1252,6 +1371,39 @@ val u_shifted = u >> 2 // logical right shift (zero-fills MSBs) val s_shifted = s >> 2 // arithmetic right shift (sign-extends MSBs) ``` +#### `Int` Parameter Arithmetic + +When both operands are Scala `Int` or DFHDL `Int <> CONST` values, the following operations are available. The result is always an `Int <> CONST` value. + +/// html | div.operations +| Operation | Description | +| --------------- | -------------- | +| `lhs + rhs` | Addition | +| `lhs - rhs` | Subtraction | +| `lhs * rhs` | Multiplication | +| `lhs / rhs` | Division | +| `lhs % rhs` | Modulo | +| `lhs ** rhs` | Power | +| `lhs max rhs` | Maximum | +| `lhs min rhs` | Minimum | +/// + +```scala +val param: Int <> CONST = 2 +val t1 = 1 + param // Int <> CONST = 3 +val t2 = 4 * param // Int <> CONST = 8 +val t3 = 10 / param // Int <> CONST = 5 +val t4 = 10 % param // Int <> CONST = 0 +val t5 = 3 ** param // Int <> CONST = 9 +val t6 = 1 max param // Int <> CONST = 2 +val t7 = 1 min param // Int <> CONST = 1 +``` + +/// admonition | Non-constant DFHDL `Int` values + type: note +Non-constant DFHDL `Int` values (e.g., `Int <> VAR`) are possible and support the same arithmetic operations. However, they are discouraged for synthesizable designs because they map to a fixed 32-bit signed representation -- use `SInt[32]` instead for explicit control over the hardware. For simulation purposes, non-constant `Int` values are acceptable as long as the 32-bit width limitation is understood. +/// + ### Constant Generation #### Decimal String-Interpolator {#d-interp} From d1c2e1c7bc26d7e4d5dc54baf45777f080a14f1d Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 04:56:21 +0300 Subject: [PATCH 094/115] feat: auto-promote anonymous arithmetic to carry ops on wider assignment When an anonymous +, -, or * operation is assigned/connected to a variable wider than its result, the operation is automatically promoted to its carry variant (+^, -^, *^). This prevents silent information loss and matches Verilog's behavior where the assignment target width determines the operation width. Named expressions are not affected. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ExplicitCondExprAssignSpec.scala | 2 +- .../StagesSpec/ExplicitNamedVarsSpec.scala | 2 +- .../src/main/scala/dfhdl/core/DFDecimal.scala | 37 +++++++++++--- .../test/scala/CoreSpec/DFDecimalSpec.scala | 50 +++++++++++++++++++ docs/user-guide/type-system/index.md | 18 ++++++- 5 files changed, 99 insertions(+), 10 deletions(-) diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala index f72bb7255..66b31d69f 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala @@ -84,7 +84,7 @@ class ExplicitCondExprAssignSpec extends StageSpec(stageCreatesUnrefAnons = true | case sd"16'1" => sd"4'5" | case sd"16'2" => sd"4'3" | end match - | if (x < sd"16'11") z2 := (zz + sd"4'3").resize(16) + | if (x < sd"16'11") z2 := (zz +^ sd"4'3").resize(16) | else z2 := zz.resize(16) | case _ => z2 := z + sd"16'12" | end match diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala index 699c32431..8a2e76d42 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala @@ -103,7 +103,7 @@ class ExplicitNamedVarsSpec extends StageSpec: | 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) + | if (x < sd"16'11") z2 := (zz +^ sd"4'3").resize(16) | else z2 := zz.resize(16) | case _ => z2 := z + sd"16'12" | end match diff --git a/core/src/main/scala/dfhdl/core/DFDecimal.scala b/core/src/main/scala/dfhdl/core/DFDecimal.scala index 9d72011ca..d2a3e33f0 100644 --- a/core/src/main/scala/dfhdl/core/DFDecimal.scala +++ b/core/src/main/scala/dfhdl/core/DFDecimal.scala @@ -978,16 +978,38 @@ object DFXInt: if (dfType.signed != lhsSigned && !lhs.dfType.asIR.isDFInt32) lhs.asValOf[DFUInt[Int]].signed.asValOf[DFSInt[Int]] else lhs.asValOf[DFSInt[Int]] - val nativeTypeChanged = dfType.nativeType != lhs.dfType.nativeType + // Auto-promote anonymous +/-/* to carry when target is wide enough + import IntParam.+ + val funcWidth = lhsSignFix.widthIntParam + val lhsCarryPromo: DFValOf[DFSInt[Int]] = + lhsSignFix.asIR match + case func @ ir.DFVal.Func( + dfType = dt: ir.DFDecimal, + op = op @ (FuncOp.+ | FuncOp.- | FuncOp.*) + ) + if func.isAnonymous && !dt.isDFInt32 && dfType.widthInt > func.width => + val cw: IntParam[Int] = func.op.runtimeChecked match + case FuncOp.+ | FuncOp.- => funcWidth + 1 + case FuncOp.* => funcWidth + funcWidth + val newDT = dt.copy(widthParamRef = cw.ref) + dfc.mutableDB + .setMember(func, _.updateDFType(newDT)) + .asValOf[DFSInt[Int]] + case _ => lhsSignFix + val nativeTypeChanged = dfType.nativeType != lhsCarryPromo.dfType.nativeType if (nativeTypeChanged) dfType.asIR.nativeType match case Int32 => - lhsSignFix.toInt.asIR + lhsCarryPromo.toInt.asIR case BitAccurate => - DFVal.Alias.AsIs(dfType, lhsSignFix).asIR - else if (!dfType.asIR.widthParamRef.isSimilarTo(lhsSignFix.dfType.asIR.widthParamRef)) - lhsSignFix.resize(dfType.widthIntParam).asIR - else lhsSignFix.asIR + DFVal.Alias.AsIs(dfType, lhsCarryPromo).asIR + else if ( + !dfType.asIR.widthParamRef.isSimilarTo( + lhsCarryPromo.dfType.asIR.widthParamRef + ) + ) + lhsCarryPromo.resize(dfType.widthIntParam).asIR + else lhsCarryPromo.asIR end if end if end dfValIR @@ -1045,7 +1067,8 @@ object DFXInt: end arithOp type ArithOp = - FuncOp.+.type | FuncOp.-.type | FuncOp.*.type | FuncOp./.type | FuncOp.%.type | FuncOp.max.type | FuncOp.min.type + FuncOp.+.type | FuncOp.-.type | FuncOp.*.type | FuncOp./.type | FuncOp.%.type | + FuncOp.max.type | FuncOp.min.type given evOpArithIntDFInt32[ Op <: ArithOp, L <: Int, diff --git a/core/src/test/scala/CoreSpec/DFDecimalSpec.scala b/core/src/test/scala/CoreSpec/DFDecimalSpec.scala index 03266c499..675208ce3 100644 --- a/core/src/test/scala/CoreSpec/DFDecimalSpec.scala +++ b/core/src/test/scala/CoreSpec/DFDecimalSpec.scala @@ -598,6 +598,56 @@ class DFDecimalSpec extends DFSpec: """u8 % d"9'22"""" ) } + test("Arithmetic auto-carry promotion") { + val u8 = UInt(8) <> VAR + val u5 = UInt(5) <> VAR + val s8 = SInt(8) <> VAR + val u8b = UInt(8) <> VAR + val u9 = UInt(9) <> VAR + val u10 = UInt(10) <> VAR + val u12 = UInt(12) <> VAR + val u16 = UInt(16) <> VAR + val s9 = SInt(9) <> VAR + assertCodeString { + """|u9 := u8 +^ u8 + |u9 := u8 -^ u8 + |u16 := u8 *^ u8 + |u10 := (u8 +^ u8).resize(10) + |u8b := u8 + u8 + |val sum = u8 + u8 + |u9 := sum.resize(9) + |s9 := s8 +^ s8 + |u9 := (u8 / u8).resize(9) + |u9 := u8 +^ u5.resize(8) + |u9 := u8 +^ d"8'200" + |u12 := (u8 *^ u8).resize(12) + |""".stripMargin + } { + // Basic carry promotion for + + u9 := u8 + u8 + // Basic carry promotion for - + u9 := u8 - u8 + // Basic carry promotion for * + u16 := u8 * u8 + // Target wider than carry width: promote to 9, resize to 10 + u10 := u8 + u8 + // Target = func width: no promotion + u8b := u8 + u8 + // Named value: no promotion + val sum = u8 + u8 + u9 := sum + // SInt version + s9 := s8 + s8 + // Division: no carry variant, normal resize + u9 := u8 / u8 + // Asymmetric widths: u8 + u5 → func width 8, carry = 9 + u9 := u8 + u5 + // Int literal: 200 is 8 bits, carry width = 9 + u9 := u8 + 200 + // Partial mul promotion: target (12) > funcWidth (8), promote to 16, resize to 12 + u12 := u8 * u8 + } + } test("Int32 arithmetic") { val param: Int <> CONST = 2 val t1 = 1 + param diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 8d7883b0a..9cda2e738 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1267,9 +1267,25 @@ val e2 = u4 + u8 val e3 = u8 + (-22) ``` -/// admonition | Overflow behavior +/// admonition | Overflow and automatic carry promotion type: warning Standard arithmetic operations wrap on overflow. For example, `d"8'255" + d"8'1"` produces `d"8'0"`. Use the carry variants (`+^`, `-^`, `*^`) described below to get a wider result that preserves the full value. + +However, when an **anonymous** arithmetic expression (`+`, `-`, `*`) is assigned or connected to a variable that is **wider** than the operation's result, the operation is **automatically promoted** to a carry operation. This matches Verilog's behavior where the assignment target width determines the operation width. The carry result is then resized to fit the target if needed. + +```scala +val u8 = UInt(8) <> VAR +val u9 = UInt(9) <> VAR +val u12 = UInt(12) <> VAR +val u16 = UInt(16) <> VAR +u9 := u8 + u8 // promoted to carry addition (width 9), exact fit +u16 := u8 * u8 // promoted to carry multiplication (width 16), exact fit +u12 := u8 * u8 // promoted to carry multiplication (width 16), resized to 12 + +// Named expressions are NOT promoted: +val sum = u8 + u8 // UInt[8], named value +u9 := sum // resized from 8 to 9, no carry promotion +``` /// #### Carry Operations (`+^`, `-^`, `*^`) From ba9a87179db77669d1484567dd7a4e44e410b58c Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 06:15:31 +0300 Subject: [PATCH 095/115] docs: wip unifying operation sections --- docs/user-guide/type-system/index.md | 245 ++++++++++----------------- 1 file changed, 87 insertions(+), 158 deletions(-) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 9cda2e738..246e1ad8e 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -445,112 +445,6 @@ class Foo extends DFDesign: /// -## Bit-Accurate Operations and Type Inference - -DFHDL provides bit-accurate operations and strong type inference for bit-level manipulations. Here are the key features: - -### Bit Selection and Slicing -```scala -val b8 = Bits(8) <> VAR -// Most significant bits selection -val ms7 = b8(7, 1) // 7 MSBs -val ms1 = b8(7, 7) // MSB only - -// Least significant bits selection -val ls7 = b8(6, 0) // 7 LSBs -val ls1 = b8(0, 0) // LSB only - -// Single bit access (static index) -val msbit = b8(7) // MSB -val lsbit = b8(0) // LSB - -// Dynamic bit access (index is a UInt variable) -val idx = UInt(3) <> VAR -val dynbit = b8(idx) // Single bit at position idx (returns Bit) -``` - -/// admonition | Dynamic bit indexing (reads and writes) - type: tip -You can index into a `Bits` value using a `UInt` variable, not just integer literals. This is equivalent to Verilog's `data[idx]` where `idx` is a register or wire. - -The index must be a `UInt` whose width equals `clog2(bits_width)`. For example, indexing into `Bits(8)` requires a `UInt(3)` index. If the width does not match, the compiler will report an error and suggest a fix: - -- **Index too wide**: use `.truncate` to automatically narrow it to the expected width. -- **Index too narrow**: use `.extend` to automatically widen it to the expected width. -- You can also use `.resize(N)` for an explicit target width, or a range slice `(hi, lo)` to extract specific bits. - -Dynamic indexing works for both reads and writes: -```scala -val data = Bits(8) <> VAR init all(0) -val pos = UInt(3) <> VAR init 0 -val din = Bit <> IN - -// Dynamic read -val bit_out = data(pos) // read single bit at position - -// Dynamic write inside a clocked process -process(clk): - if (clk.rising) - data(pos) :== din // write single bit at position -``` - -When the index register is wider than needed, narrow it with `.truncate` or a range slice: -```scala -val wide_pos = UInt(5) <> VAR -// .truncate automatically narrows to clog2(8) = 3 bits -data(wide_pos.truncate) :== din -// Alternatively, use a range slice to pick specific bits -data(wide_pos(2, 0)) :== din -``` -/// - -### Bit Operations -```scala -val b8 = Bits(8) <> VAR - -// Shift operations -val shifted_left = b8 << 2 // Logical left shift -val shifted_right = b8 >> 2 // Logical right shift - -// Bit reduction operations -val or_reduced = b8.| // OR reduction -val and_reduced = b8.& // AND reduction -val xor_reduced = b8.^ // XOR reduction - -// Bit concatenation -val concat = (b"100", b"1", b"0", b"11").toBits // Creates 8-bit value -``` - -### Multiple Variable Assignment -```scala -val b4M, b4L = Bits(4) <> VAR // Declare multiple variables -val b3M = Bits(3) <> VAR -val u5L = UInt(5) <> VAR - -// Assign to multiple variables using tuple pattern -(b4M, b4L) := (h"1", 1, 0, b"11") // Values are concatenated and split - -// Mix different types in assignment -(b3M, u5L) := (h"1", 1, 0, b"11") // Values automatically cast to appropriate types - -// Assign bit slices to multiple variables -(b4M, b4L) := (u8.bits(3, 0), u8.bits(7, 4)) // Split byte into nibbles - -// Complex multiple assignment -(b4M, b3M, u5L, b4L) := (u8, b8) // Automatically extracts appropriate bits for each variable -``` - -### Width Inference and Resizing -```scala -// Automatic width inference -val b3 = Bits(3) <> VAR -val b8 = Bits(8) <> VAR - -// Explicit resizing required when widths don't match -b8 := b3.resize(8) // Zero-extend to 8 bits -b3 := b8.resize(3) // Truncate to 3 bits -``` - ## Bubble Values {#bubble} * RT and ED - Don't Care / Unknown @@ -1179,6 +1073,11 @@ given options.AppOptions.AppMode = options.AppOptions.AppMode.elaborate ``` /// +/// admonition | Additional operations + type: info +See [Common Bit-Vector Operations][common-bit-vector-ops] for bit selection, slicing, resizing, and concatenation that apply to `Bits` as well as `UInt` and `SInt`. See [Common Conversions and Casts][type-conversion] for `.bits`, `.uint`, `.sint`, and other type conversion methods. +/// + ## `UInt`/`SInt`/`Int` DFHDL Values {#DFDecimal} DFHDL provides three decimal numeric types: @@ -1368,6 +1267,17 @@ val e2 = u8 == u4 val e3 = u8 > 1000 ``` +/// details | Scala `Int` constants auto-lift in comparisons + type: note +Plain Scala `Int` values can be used directly in comparisons and arithmetic with DFHDL typed variables. No explicit coercion is needed: +```scala +val LIMIT: Int <> CONST = 5208 +val counter = UInt.until(LIMIT) <> VAR +if (counter == LIMIT - 1) // Int <> CONST compared with UInt -- works directly + counter := 0 +``` +/// + #### Shift Operations (`<<`, `>>`) /// html | div.operations @@ -1472,34 +1382,76 @@ sd"8'42" // SInt[8], value = 42 sd"8'255" // Error: width too small to represent value with sign bit ``` -#### Bit Selection and Slicing - -`UInt` and `SInt` values support the same bit-selection syntax as `Bits`: +/// admonition | Additional operations + type: info +See [Common Bit-Vector Operations][common-bit-vector-ops] for bit selection, slicing, resizing, and concatenation that apply to `UInt` and `SInt` as well as `Bits`. See [Common Conversions and Casts][type-conversion] for `.bits`, `.signed`, and other type conversion methods. +/// -- **Range slice**: `value(hi, lo)` extracts bits `hi` down to `lo`, returning a narrower `UInt` or `SInt`. -- **Single-bit access**: `value(idx)` returns the bit at position `idx` (as `Bit`). +### Examples ```scala -val u6 = UInt(6) <> VAR -val u4 = u6(3, 0) // lower 4 bits, returns UInt[4] -val b = u6(5) // MSB, returns 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 + +// Arithmetic +val sum = u8 + s8.uint // Addition with casting +val diff = s8 - 5 // Subtraction with constant +val prod = u8 * u8 // Multiplication -val s6 = SInt(6) <> VAR -val s4 = s6(3, 0) // lower 4 bits, returns SInt[4] +// Comparisons +val lt = u8 < 100 +val eq = s8 == sd"8'0" + +// Initialization +val u4 = UInt(4) <> VAR init d"4'10" +val s4 = SInt(4) <> VAR init sd"4'-2" ``` -/// admonition | Range slices preserve the original type - type: note -A range slice on `UInt` returns `UInt`, and on `SInt` returns `SInt` -- it does **not** return `Bits`. This means a `UInt` range slice can be used directly as a dynamic index into `Bits` without `.uint`: +## Common Operations Across Types +### Common Bit-Vector Operations {#common-bit-vector-ops} + +The following operations are shared by `Bits`, `UInt`, and `SInt` values. + +#### Bit Selection and Slicing + +- **Range slice**: `value(hi, lo)` extracts bits `hi` down to `lo`, returning a narrower value of the **same type** (`Bits` → `Bits`, `UInt` → `UInt`, `SInt` → `SInt`). +- **Single-bit access**: `value(idx)` returns the bit at position `idx` (as `Bit`). The index can be a static integer or a dynamic `UInt` variable. + ```scala -val scratch = Bits(8) <> VAR -val bitpos = UInt(4) <> VAR // 4-bit counter +val b8 = Bits(8) <> VAR +val u8 = UInt(8) <> VAR +val s8 = SInt(8) <> VAR -// CORRECT: range slice of UInt returns UInt, usable directly as index -scratch(bitpos(2, 0)) :== din +// Range slicing — preserves the original type +val b4 = b8(7, 4) // Bits[4]: upper nibble +val u4 = u8(3, 0) // UInt[4]: lower nibble +val s4 = s8(3, 0) // SInt[4]: lower nibble -// WRONG: .uint is not needed and will cause a compile error -// scratch(bitpos(2, 0).uint) :== din +// Single-bit access +val msb = b8(7) // Bit +val lsb = u8(0) // Bit + +// Dynamic bit access (index is a UInt variable) +val idx = UInt(3) <> VAR +val dynbit = b8(idx) // Bit at position idx +``` + +/// admonition | Dynamic bit indexing + type: tip +You can index into a bit-vector value using a `UInt` variable, not just integer literals. The index must be a `UInt` whose width equals `clog2(bits_width)`. For example, indexing into `Bits(8)` requires a `UInt(3)` index. If the width does not match, the compiler will report an error and suggest using `.truncate` (to narrow) or `.extend` (to widen). + +Dynamic indexing works for both reads and writes: +```scala +val data = Bits(8) <> VAR init all(0) +val pos = UInt(3) <> VAR init 0 +val din = Bit <> IN + +val bit_out = data(pos) // dynamic read +process(clk): + if (clk.rising) + data(pos) :== din // dynamic write ``` /// @@ -1518,40 +1470,17 @@ u8 := u6.resize(8) // explicit zero-extend to 8 bits u8 := u6.extend // auto-widen to match u8's width ``` -/// details | Scala `Int` constants auto-lift in comparisons - type: note -Plain Scala `Int` values can be used directly in comparisons and arithmetic with DFHDL typed variables. No explicit coercion is needed: -```scala -val LIMIT: Int <> CONST = 5208 -val counter = UInt.until(LIMIT) <> VAR -if (counter == LIMIT - 1) // Int <> CONST compared with UInt -- works directly - counter := 0 -``` -/// +#### Bit Concatenation -### Examples +Multiple bit-vector values can be concatenated using Scala tuple syntax with `.toBits`: ```scala -// 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 - -// Arithmetic -val sum = u8 + s8.uint // Addition with casting -val diff = s8 - 5 // Subtraction with constant -val prod = u8 * u8 // Multiplication - -// Comparisons -val lt = u8 < 100 -val eq = s8 == sd"8'0" - -// Initialization -val u4 = UInt(4) <> VAR init d"4'10" -val s4 = SInt(4) <> VAR init sd"4'-2" +val concat = (b"100", b"1", b"0", b"11").toBits // Bits[8] ``` -## Common Conversions and Casts Between Types {#type-conversion} +Values are concatenated from the first (most-significant) to the last (least-significant) position. + +### Common Conversions and Casts {#type-conversion} The diagram below shows the conversion/cast paths between DFHDL types. Solid arrows are simple casts that preserve width; dashed arrows involve width changes. ![type-conversion](type-conversion-light.svg#only-light){ width="70%" } @@ -1568,7 +1497,7 @@ The diagram below shows the conversion/cast paths between DFHDL types. Solid arr | `UInt`/`SInt` | `Int` | `.ToInt` | `Bit`/`Boolean` | `SInt(w)` | `.toSInt(w)` | /// -### Any Type to/from `Bits`: `.bits` and `.as(T)` {#bits-cast} +#### Any Type to/from `Bits`: `.bits` and `.as(T)` {#bits-cast} Every DFHDL type can be converted to its raw bit representation with `.bits`. The inverse operation, `.as(T)`, reinterprets a `Bits` value as a target type `T`, provided the bit widths match exactly: @@ -1586,7 +1515,7 @@ val eBits = e.bits // Enum -> Bits val eBack = eBits.as(MyEnum) // Bits -> Enum ``` -### `Bits` to `UInt`/`SInt`: `.uint` and `.sint` {#uint-sint-cast} +#### `Bits` to `UInt`/`SInt`: `.uint` and `.sint` {#uint-sint-cast} These are shorthand conversions from `Bits` that preserve width. The same bits are simply reinterpreted as unsigned or signed: @@ -1596,7 +1525,7 @@ val u8 = b8.uint // Bits(8) -> UInt(8), same bit pattern val s8 = b8.sint // Bits(8) -> SInt(8), same bit pattern ``` -### `UInt` to `SInt`: `.signed` {#signed-cast} +#### `UInt` to `SInt`: `.signed` {#signed-cast} Converting an unsigned value to signed requires an extra bit for the sign, so `.signed` widens the result by one bit: @@ -1611,7 +1540,7 @@ To get an `SInt` with the **same** width (reinterpreting the bit pattern without val s8 = u8.bits.sint // UInt(8) -> Bits(8) -> SInt(8) ``` -### `Bit` and `Boolean` Conversions {#bit-bool-cast} +#### `Bit` and `Boolean` Conversions {#bit-bool-cast} `Bit` is the hardware single-bit type and `Boolean` is the logical type. They are convertible to each other with `.bit` and `.bool`: From 8fd99878d99ffb7ea9163e475095130900bad2a6 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 06:39:39 +0300 Subject: [PATCH 096/115] docs: overhaul of the type-system document --- docs/user-guide/type-system/index.md | 1525 +++++++++++++------------- 1 file changed, 747 insertions(+), 778 deletions(-) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 246e1ad8e..d9ec81b75 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -517,7 +517,9 @@ val x = b8 ++ h"FF" //ok val y = b8 ++ all(0) //error ``` -## `Bit`/`Boolean` DFHDL Values {#DFBitOrBool} +## DFHDL Value Types + +### `Bit`/`Boolean` {#DFBitOrBool} `Bit` DFHDL values represent binary `1` or `0` values, whereas `Boolean` DFHDL values represent `true` or `false` values, respectively. The `Bit` and `Boolean` DFHDL values are generally interchangeable, and automatically converted between one and the other. @@ -531,7 +533,7 @@ Although they are interchangeable, it's generally recommended to use `Boolean` D The main reason to differentiate between `Bit` and `Boolean` is that VHDL has both `std_logic` and `boolean` types, respectively. Verilog has only a single `logic` or `wire` to represent both. Indeed VHDL'2008 has relaxed some of the type constraints, but not enough. And nevertheless, DFHDL aims to support various HDL dialects, and thus enables simple implicit or explicit conversion between these two DFType values. /// -### DFType Constructors +#### DFType Constructors Use the `Bit` or `Boolean` objects/types to construct `Bit` or `Boolean` DFHDL values, respectively. @@ -542,7 +544,7 @@ val c_bit: Bit <> CONST = 1 val c_bool: Boolean <> CONST = false ``` -### Candidates +#### Candidates * DFHDL `Bit` values. * DFHDL `Boolean` values. @@ -605,243 +607,7 @@ class Foo extends RTDesign: ``` /// -### Operations - -#### Explicit Casting Operations - -These operations propagate constant modifiers, meaning that if the casted argument is a constant, the returned value is also a constant. - -/// html | div.operations -| Operation | Description | LHS Constraints | Returns | -| ----------- | --------------------------------|-----------------------|-----------------------| -| `lhs.bool` | Cast to a DFHDL `Boolean` value | `Bit` DFHDL value | `Boolean` DFHDL value | -| `lhs.bit` | Cast to a DFHDL `Bit` value | `Boolean` DFHDL value | `Bit` DFHDL value | -/// - -```scala -val bt1 = Bit <> VAR -val bl1 = bt1.bool -val bl2 = Boolean <> VAR -val bt2 = bl2.bit -val bt3: Bit <> CONST = 0 -val bl3: Boolean <> CONST = bt3.bool -val bl4: Boolean <> CONST = true -val bt4: Bit <> CONST = bt4.bit -// error: bt1 is not a constant -val err: Bit <> CONST = bt1 -``` -#### Logical Operations - -Logical operations' return type always match the LHS argument's type. -These operations propagate constant modifiers, meaning that if all arguments are constant, the returned value is also a constant. - -/// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs && rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `lhs || rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `lhs & rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `lhs | rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `lhs ^ rhs` | Logical XOR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `!lhs` | Logical NOT | The argument must be a `Bit`/`Boolean` DFHDL value. | LHS-Type DFHDL value | -/// - -```scala -val bt = Bit <> VAR -val bl = Boolean <> VAR -val t1 = bt && bl //result type: Bit -val t2 = bt ^ 1 //result type: Bit -val t3 = bl || false //result type: Boolean -val t4 = bt && true //result type: Bit -val t5 = bl || bt //result type: Boolean -val t6 = bl ^ 0 || !bt -//`t7` after the candidate implicit -//conversions, looks like so: -//(bl && bt.bool) ^ (!(bt || bl.bit)).bool -val t7 = (bl && bt) ^ !(bt || bl) -//error: swap argument positions to have -//the DFHDL value on the LHS. -val e1 = 0 ^ bt -//error: swap argument positions to have -//the DFHDL value on the LHS. -val e2 = false ^ bt -//not supported since both arguments -//are just candidates -val e3 = 0 ^ true -//This just yields a Scala Boolean, -//as a basic operation between Scala -//Boolean values. -val sc: Boolean = true && true -``` - -/// admonition | Logical `||`/`&&` and bitwise `|`/`&` on Bit and Boolean values - type: tip -In DFHDL, the operators `||` and `&&` are equivalent to `|` and `&`, respectively, when applied on either DFHDL `Bit` or `Boolean` types. In Verilog, the actual operator printed depends on the LHS argument of the operation: if it's `Bit`, the operator will be `|`/`&`; if it's `Boolean`, the operator will be `||`/`&&`. -/// - -/// details | Transitioning from Verilog - type: verilog -Under the ED domain, the following operations are equivalent: - -| DFHDL Operation | Verilog Operation (Bit LHS) | Verilog Operation (Boolean LHS) | -|-----------------|-----------------------------|---------------------------------| -| `lhs && rhs` | `lhs & rhs` | `lhs && rhs` | -| `lhs || rhs` | `lhs | rhs` | `lhs || rhs` | -| `lhs & rhs` | `lhs & rhs` | `lhs && rhs` | -| `lhs | rhs` | `lhs | rhs` | `lhs || rhs` | -| `lhs ^ rhs` | `lhs ^ rhs` | `lhs ^ rhs` | -| `!lhs` | `!lhs` | `!lhs` | -/// - -/// details | Transitioning from VHDL - type: vhdl -Under the ED domain, the following operations are equivalent: - -| DFHDL Operation | VHDL Operation | -|-----------------|-------------------| -| `lhs && rhs` | `lhs and rhs` | -| `lhs || rhs` | `lhs or rhs` | -| `lhs ^ rhs` | `lhs xor rhs` | -| `!lhs` | `not lhs` | -/// - -#### Bit History Operations - -These operations are supported under both RT and ED domains. Under RT domain, these operations are synthesizable expression. - -/// html | div.operations -| Operation | Description | LHS Constraints | Returns | -| ------------ | --------------------------------|-----------------------|-----------------------| -| `lhs.rising` | True when a value changes from `0` to `1` | `Bit` DFHDL value | `Boolean` DFHDL value | -| `lhs.falling` | True when a value changes from `1` to `0` | `Bit` DFHDL value | `Boolean` DFHDL value | -/// - -/// tab | `ED` - -```scala -class Foo extends EDDesign: - val clk = Bit <> IN - - /* VHDL-style */ - process(clk): - if (clk.rising) - //some sequential logic - - /* Verilog-style */ - process(clk.rising): - //some sequential logic -``` -/// details | Transitioning from Verilog - type: verilog -Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the Verilog `posedge x` and `negedge x`, respectively. -/// - -/// details | Transitioning from VHDL - type: vhdl -Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the VHDL `rising_edge(x)` and `falling_edge(x)`, respectively. -/// -/// - -/// tab | `RT` -The following `RT` domain edge detection design: -```scala -class Detector extends RTDesign: - val i = Bit <> IN - val o = Bit <> OUT - o := i.rising -``` -is compiled down to the following `ED` design (depending on the clock and reset configurations): -```scala -class Detector extends EDDesign: - val clk = Bit <> IN - val i = Bit <> IN - val o = Bit <> OUT - val i_reg = Bit <> VAR init 1 - process(clk.rising): - i_reg :== i - o <> !i_reg && i -``` -The initial (reset) register value is `1`/`0` for `rising`/`falling` operations, respectively. -This inherently prevents triggering immediately after reset, without sampling at least two input clock cycles. - -Both Verilog and VHDL have no equivalent synthesizable shorthand syntax. -/// - -For more information see either the [design domains][design-domains] or [processes][processes] sections. - - -#### Constant Meta Operations - -These operations are activated during the [elaboration stage][elaboration] of the DFHDL compilation, and are only available for constant `Bit`/`Boolean` DFHDL values. -Their use case is for meta-programming purposes, to control the generated code without the knowledge of the DFHDL compiler (could be considered as pre-processing steps). - -/// html | div.operations -| Operation | Description | LHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs.toScalaBitNum` | Extracts the known elaboration Scala `BitNum`(`1 | 0`) value from a constant DFHDL `Bit`/`Boolean` value | Constant `Bit`/`Boolean` DFHDL value | Scala `BitNum` value | -| `lhs.toScalaBoolean` | Extracts the known elaboration Scala `Boolean` value from a constant DFHDL `Bit`/`Boolean` value | Constant `Bit`/`Boolean` DFHDL value | Scala `Boolean` value | -/// - -The following runnable example demonstrates how such meta operation affect the elaborated design. -The `Boolean` argument `arg` of a design `Foo` is used twice within the design: -first, in an `if` condition directly; and second, in an `if` condition after a Scala value extraction. -When referenced directly, the `if` is elaborated as-is, but when the `if` is applied on the extracted Scala value, -the `if` is completely removed and either the block inside the `if` is elaborated when the argument is true or completely removed if false. - -/// tab | `Foo` -```scala -class Foo( - val arg: Boolean <> CONST -) extends DFDesign: - val o = Bit <> OUT - if (!arg) o := 1 - if (arg.toScalaBoolean) o := 0 -``` -/// - - -/// tab | `Foo(true)` -```scala -class Foo( - val arg: Boolean <> CONST -) extends DFDesign: - val o = Bit <> OUT - if (!arg) o := 1 - o := 0 -``` -/// - -/// tab | `Foo(false)` -```scala -class Foo( - val arg: Boolean <> CONST -) extends DFDesign: - val o = Bit <> OUT - if (!arg) o := 1 -``` -/// - -/// details | Runnable example - type: dfhdl -```scastie -import dfhdl.* - -@top(false) class Foo( - val arg: Boolean <> CONST -) extends DFDesign: - val o = Bit <> OUT - if (!arg) o := 1 - if (arg.toScalaBoolean) o := 0 - -@main def main = - println("Foo(true) Elaboration:") - Foo(true).printCodeString - println("Foo(false) Elaboration:") - Foo(false).printCodeString -``` -/// - -## `Bits` DFHDL Values {#DFBits} +### `Bits` {#DFBits} `Bits` DFHDL values represent vectors of DFHDL `Bit` values as elements. The vector bits width (length) is a positive constant number (nilable [zero-width] vectors will be supported in the future). @@ -855,7 +621,7 @@ in their implementations and externally in their API. Where applicable, both `Bi vector of `Bits` have overlapping equivalent APIs. /// -### DFType Constructors +#### DFType Constructors /// html | div.operations | Constructor | Description | Arg Constraints | Returns | @@ -890,11 +656,11 @@ val b6: Bits[6] <> CONST = all(0) * __Additional constructors:__ DFHDL provides additional constructs to simplify some common VHDL bit vector declaration. For example, instead of declaring `signal addr: std_logic_vector(clog2(DEPTH)-1 downto 0)` in VHDL, in DFHDL simply declare `val addr = Bits.until(DEPTH) <> VAR`. /// -### Literal (Constant) Value Generation +#### Literal (Constant) Value Generation Literal (constant) DFHDL `Bits` value generation is carried out through [binary][b-interp] and [hexadecimal][h-interp] string interpolation, a core [Scala feature](https://docs.scala-lang.org/scala3/book/string-interpolation.html){target="_blank"} that was customized for DFHDL's exact use-case. There are also bit-accurate [decimal][d-interp] and [signed decimal][sd-interp] interpolations available that produce `UInt` and `SInt` DFHDL values. If needed, those values can be cast to `Bits`. No octal interpolation is currently available or planned. -#### Binary Bits String-Interpolator {#b-interp} +##### Binary Bits String-Interpolator {#b-interp} ```scala linenums="0" title="Binary Bits string-interpolation syntax" b"width'bin" @@ -938,7 +704,7 @@ This interpolation covers the Verilog binary literal use-cases, but also adds th This interpolation covers the VHDL binary literal use-cases, but also adds the ability for parametric `width` to be set. The high impedance (high-Z) use-cases will be supported in the future, likely using a different language construct. /// -#### Hexadecimal Bits String-Interpolator {#h-interp} +##### Hexadecimal Bits String-Interpolator {#h-interp} ```scala linenums="0" title="Hexadecimal Bits string-interpolation syntax" h"width'hex" @@ -984,7 +750,7 @@ This interpolation covers the Verilog hexadecimal literal use-cases, but also ad This interpolation covers the VHDL hexadecimal literal use-cases, but also adds the ability for parametric `width` to be set. The high impedance (high-Z) use-cases will be supported in the future, likely using a different language construct. /// -### Candidates +#### Candidates * DFHDL `Bits` values * DFHDL `Bit` or `Boolean` values. This candidate produces a single bit `Bits[1]` vector. * DFHDL `UInt` values @@ -1027,7 +793,7 @@ val b16 = Bits(16) <> VAR init 0 // compile error ``` /// -### Concatenated Assignment +#### Concatenated Assignment DFHDL supports a special-case assignment of concatenated DFHDL Bits variables, using a Scala `Tuple` syntax on LHS of the assignment operator. Both LHS and RHS bits width must be the same. This assignment is just syntactic sugar for multiple separate assignments and carried out during the design [elaboration][elaboration]. The assignment ordering is from the first value at most-significant position down to the last value at least-significant position. /// tab | `Foo Declaration` @@ -1073,12 +839,7 @@ given options.AppOptions.AppMode = options.AppOptions.AppMode.elaborate ``` /// -/// admonition | Additional operations - type: info -See [Common Bit-Vector Operations][common-bit-vector-ops] for bit selection, slicing, resizing, and concatenation that apply to `Bits` as well as `UInt` and `SInt`. See [Common Conversions and Casts][type-conversion] for `.bits`, `.uint`, `.sint`, and other type conversion methods. -/// - -## `UInt`/`SInt`/`Int` DFHDL Values {#DFDecimal} +### `UInt`/`SInt`/`Int` {#DFDecimal} DFHDL provides three decimal numeric types: @@ -1086,7 +847,7 @@ DFHDL provides three decimal numeric types: - `SInt` - Signed bit-accurate integer values - `Int` - 32-bit integer values (used mainly for parameters) -### DFType Constructors +#### DFType Constructors /// html | div.operations | Constructor | Description | Arg Constraints | Returns | @@ -1098,257 +859,31 @@ DFHDL provides three decimal numeric types: | `Int`| Construct a constant integer DFType. Used mainly for parameters. | None | `Int` DFType | /// -### Candidates +#### Candidates * DFHDL decimal values of the same type * DFHDL `Bits` values (via `.uint` or `.sint` casting) * Scala numeric values (Int, Long, etc.) for constant values * Decimal literals (string interpolation values) -### Operations - -#### Arithmetic Operations (`+`, `-`, `*`, `/`, `%`) - -The result of a standard arithmetic operation always has the **same type as the LHS** operand. The RHS is resized to match the LHS before the operation is applied. - -/// html | div.decimal_arithmetic -| Operation | Description | Returns | -| ------------ | -------------- | ---------------- | -| `lhs + rhs` | Addition | Same type as LHS | -| `lhs - rhs` | Subtraction | Same type as LHS | -| `lhs * rhs` | Multiplication | Same type as LHS | -| `lhs / rhs` | Division | Same type as LHS | -| `lhs % rhs` | Modulo | Same type as LHS | -/// - -##### Type Constraints +#### Constant Generation -**Sign rule** -- The LHS sign must be greater than or equal to the RHS sign (`signed >= unsigned`): +##### Decimal String-Interpolator {#d-interp} -/// html | div.operations -| LHS | RHS | Allowed | Note | -| --- | --- | ------- | ---- | -| `UInt[W1]` | `UInt[W2]` | Yes | W1 >= W2 | -| `SInt[W1]` | `SInt[W2]` | Yes | W1 >= W2 | -| `SInt[W1]` | `UInt[W2]` | Yes | W1 >= W2 + 1 (RHS is implicitly widened by 1 bit for the sign bit) | -| `UInt[W1]` | `SInt[W2]` | **No** | Compile error: an explicit conversion is required | -/// +The decimal string interpolator `d` creates unsigned/signed integer constants (`UInt`) from decimal values. -**Width rule** -- The LHS width must be greater than or equal to the (effective) RHS width. When applying `SInt op UInt`, the effective RHS width is `RHS width + 1` because the unsigned value gains an implicit sign bit. +```scala linenums="0" title="Decimal string-interpolation syntax" +d"width'dec" +``` -**Scala `Int` literals** are auto-promoted to a matching DFHDL type with the minimum required bit width. A negative `Int` literal on the RHS of a `UInt` operation is a compile error (unsigned LHS cannot accept a signed RHS). +- __dec__ is a sequence of decimal characters ('0'-'9') with an optional prefix `-` for negative values +- __width__ followed by a `'` is optional and specifies the exact width of the integer's bit representation +- Separators `_` (underscore) and `,` (comma) within `dec` are ignored +- If width is omitted, it is inferred from the value's size +- If specified, the output is padded with zeros or extended for signed numbers using two's complement +- Returns an unsigned `UInt[W]` for natural numbers and signed `SInt[W]` for negative numbers, where `W` is the width in bits +- An error occurs if the specified width is less than required to represent the value -```scala -val u8 = UInt(8) <> VAR -val u4 = UInt(4) <> VAR -val s8 = SInt(8) <> VAR - -// UInt + UInt (same width) -val r1 = u8 + u8 // UInt[8] -// SInt + SInt -val r2 = s8 + s8 // SInt[8] -// SInt + UInt: RHS widened to 5 bits (4+1); 8 >= 5, OK -val r3 = s8 + u4 // SInt[8] -// UInt + Scala Int literal (200 fits in 8 bits) -val r4 = u8 + 200 // UInt[8] -// Scala Int literal on LHS (200 is promoted to UInt[8]) -val r5 = 200 - u8 // UInt[8] - -// error: Cannot apply this operation between an unsigned -// value (LHS) and a signed value (RHS). -// An explicit conversion must be applied. -val e1 = u8 + s8 -// error: The applied RHS value width (8) is larger than -// the LHS variable width (4). -val e2 = u4 + u8 -// error: Cannot apply this operation between an unsigned -// value (LHS) and a signed value (RHS). -// An explicit conversion must be applied. -val e3 = u8 + (-22) -``` - -/// admonition | Overflow and automatic carry promotion - type: warning -Standard arithmetic operations wrap on overflow. For example, `d"8'255" + d"8'1"` produces `d"8'0"`. Use the carry variants (`+^`, `-^`, `*^`) described below to get a wider result that preserves the full value. - -However, when an **anonymous** arithmetic expression (`+`, `-`, `*`) is assigned or connected to a variable that is **wider** than the operation's result, the operation is **automatically promoted** to a carry operation. This matches Verilog's behavior where the assignment target width determines the operation width. The carry result is then resized to fit the target if needed. - -```scala -val u8 = UInt(8) <> VAR -val u9 = UInt(9) <> VAR -val u12 = UInt(12) <> VAR -val u16 = UInt(16) <> VAR -u9 := u8 + u8 // promoted to carry addition (width 9), exact fit -u16 := u8 * u8 // promoted to carry multiplication (width 16), exact fit -u12 := u8 * u8 // promoted to carry multiplication (width 16), resized to 12 - -// Named expressions are NOT promoted: -val sum = u8 + u8 // UInt[8], named value -u9 := sum // resized from 8 to 9, no carry promotion -``` -/// - -#### Carry Operations (`+^`, `-^`, `*^`) - -Carry operations widen the result to prevent overflow. Unlike standard arithmetic, carry operations require both operands to have the **same sign**. - -/// html | div.operations -| Operation | Description | Result Width | Result Sign | -| ------------- | -------------------- | ------------------ | -------------------- | -| `lhs +^ rhs` | Carry Addition | max(LW, RW) + 1 | Same sign as operands | -| `lhs -^ rhs` | Carry Subtraction | max(LW, RW) + 1 | Same sign as operands | -| `lhs *^ rhs` | Carry Multiplication | LW + RW | Same sign as operands | -/// - -```scala -val u8 = UInt(8) <> VAR - -// Carry addition: width = max(8, 8) + 1 = 9 -val r1 = u8 +^ u8 // UInt[9] -// d"8'255" +^ d"8'1" == d"9'256" (no overflow) - -// Carry subtraction: width = max(8, 8) + 1 = 9 -val r2 = u8 -^ u8 // UInt[9] - -// Carry multiplication: width = 8 + 8 = 16 -val r3 = u8 *^ u8 // UInt[16] - -// Scala Int literal: 100 needs 7 bits -// width = 7 + 8 = 15 -val r4 = 100 *^ u8 // UInt[15] -``` - -/// admonition | Carry vs standard sign rules - type: tip -Unlike standard arithmetic where `SInt op UInt` is allowed (the unsigned RHS is implicitly widened), carry operations require both operands to have the **same sign**. This is because carry operations produce a wider result, and mixed-sign widening semantics would be ambiguous. Scala `Int` literals are still accepted when the literal's sign matches the DFHDL value's sign. -/// - -#### Comparison Operations (`==`, `!=`, `<`, `>`, `<=`, `>=`) - -Comparison operations return a `Boolean` DFHDL value and have **stricter constraints** than arithmetic: - -/// html | div.operations -| Operation | Description | Returns | -| ------------- | ---------------------- | -------- | -| `lhs == rhs` | Equal | Boolean | -| `lhs != rhs` | Not equal | Boolean | -| `lhs < rhs` | Less than | Boolean | -| `lhs > rhs` | Greater than | Boolean | -| `lhs <= rhs` | Less than or equal | Boolean | -| `lhs >= rhs` | Greater than or equal | Boolean | -/// - -Unlike arithmetic operations which use relaxed rules (LHS sign >= RHS sign, LHS width >= RHS width), comparisons require **exact matching**: - -- **Sign:** Must match exactly (`UInt` with `UInt`, `SInt` with `SInt`). -- **Width:** Must match exactly (both operands must have the same bit width). -- **Scala `Int` literals:** The literal's bit width (adjusted +1 if the DFHDL value is signed and the literal is positive) must fit within the DFHDL value's width. - -```scala -val u8 = UInt(8) <> VAR -val u4 = UInt(4) <> VAR -val s8 = SInt(8) <> VAR - -val c1 = u8 == u8 // Boolean: same sign, same width -val c2 = u8 < 200 // Boolean: 200 fits in UInt[8] -val c3 = 0 < u8 // Boolean: Scala Int on LHS -val c4 = s8 >= 1 // Boolean: 1 is promoted to SInt (width 2 fits in 8) - -// error: Cannot apply this operation between an unsigned -// value (LHS) and a signed value (RHS). -// An explicit conversion must be applied. -val e1 = u8 == s8 -// error: Cannot apply this operation between a value of -// 8 bits width (LHS) to a value of 4 bits width (RHS). -// An explicit conversion must be applied. -val e2 = u8 == u4 -// error: Cannot compare a DFHDL value (width = 8) with a -// Scala `Int` argument that is wider (width = 10). -// An explicit conversion must be applied. -val e3 = u8 > 1000 -``` - -/// details | Scala `Int` constants auto-lift in comparisons - type: note -Plain Scala `Int` values can be used directly in comparisons and arithmetic with DFHDL typed variables. No explicit coercion is needed: -```scala -val LIMIT: Int <> CONST = 5208 -val counter = UInt.until(LIMIT) <> VAR -if (counter == LIMIT - 1) // Int <> CONST compared with UInt -- works directly - counter := 0 -``` -/// - -#### Shift Operations (`<<`, `>>`) - -/// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs << rhs` | Left shift | LHS: `UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | -| `lhs >> rhs` | Right shift (logical for `UInt`, arithmetic for `SInt`) | LHS: `UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | -/// - -The `>>` operator is **type-aware**: on `UInt` it performs a logical (zero-filling) right shift, and on `SInt` it performs an arithmetic (sign-extending) right shift. There is no separate `>>>` operator in DFHDL -- the operand type determines the behavior. - -```scala -val u = UInt(8) <> VAR -val s = SInt(8) <> VAR - -val u_shifted = u >> 2 // logical right shift (zero-fills MSBs) -val s_shifted = s >> 2 // arithmetic right shift (sign-extends MSBs) -``` - -#### `Int` Parameter Arithmetic - -When both operands are Scala `Int` or DFHDL `Int <> CONST` values, the following operations are available. The result is always an `Int <> CONST` value. - -/// html | div.operations -| Operation | Description | -| --------------- | -------------- | -| `lhs + rhs` | Addition | -| `lhs - rhs` | Subtraction | -| `lhs * rhs` | Multiplication | -| `lhs / rhs` | Division | -| `lhs % rhs` | Modulo | -| `lhs ** rhs` | Power | -| `lhs max rhs` | Maximum | -| `lhs min rhs` | Minimum | -/// - -```scala -val param: Int <> CONST = 2 -val t1 = 1 + param // Int <> CONST = 3 -val t2 = 4 * param // Int <> CONST = 8 -val t3 = 10 / param // Int <> CONST = 5 -val t4 = 10 % param // Int <> CONST = 0 -val t5 = 3 ** param // Int <> CONST = 9 -val t6 = 1 max param // Int <> CONST = 2 -val t7 = 1 min param // Int <> CONST = 1 -``` - -/// admonition | Non-constant DFHDL `Int` values - type: note -Non-constant DFHDL `Int` values (e.g., `Int <> VAR`) are possible and support the same arithmetic operations. However, they are discouraged for synthesizable designs because they map to a fixed 32-bit signed representation -- use `SInt[32]` instead for explicit control over the hardware. For simulation purposes, non-constant `Int` values are acceptable as long as the 32-bit width limitation is understood. -/// - -### Constant Generation - -#### Decimal String-Interpolator {#d-interp} - -The decimal string interpolator `d` creates unsigned/signed integer constants (`UInt`) from decimal values. - -```scala linenums="0" title="Decimal string-interpolation syntax" -d"width'dec" -``` - -- __dec__ is a sequence of decimal characters ('0'-'9') with an optional prefix `-` for negative values -- __width__ followed by a `'` is optional and specifies the exact width of the integer's bit representation -- Separators `_` (underscore) and `,` (comma) within `dec` are ignored -- If width is omitted, it is inferred from the value's size -- If specified, the output is padded with zeros or extended for signed numbers using two's complement -- Returns an unsigned `UInt[W]` for natural numbers and signed `SInt[W]` for negative numbers, where `W` is the width in bits -- An error occurs if the specified width is less than required to represent the value - -Examples: +Examples: ```scala d"0" // UInt[1], value = 0 d"-1" // SInt[2], value = -1 @@ -1358,7 +893,7 @@ d"1,023" // UInt[10], value = 1023 d"1_000" // UInt[10], value = 1000 ``` -#### Signed Decimal String-Interpolator {#sd-interp} +##### Signed Decimal String-Interpolator {#sd-interp} The signed decimal string interpolator `sd` creates signed integer constants (`SInt`) from decimal values. @@ -1382,12 +917,7 @@ sd"8'42" // SInt[8], value = 42 sd"8'255" // Error: width too small to represent value with sign bit ``` -/// admonition | Additional operations - type: info -See [Common Bit-Vector Operations][common-bit-vector-ops] for bit selection, slicing, resizing, and concatenation that apply to `UInt` and `SInt` as well as `Bits`. See [Common Conversions and Casts][type-conversion] for `.bits`, `.signed`, and other type conversion methods. -/// - -### Examples +#### Examples ```scala // Basic declarations @@ -1409,192 +939,40 @@ val u4 = UInt(4) <> VAR init d"4'10" val s4 = SInt(4) <> VAR init sd"4'-2" ``` -## Common Operations Across Types -### Common Bit-Vector Operations {#common-bit-vector-ops} +### Enumeration {#DFEnum} -The following operations are shared by `Bits`, `UInt`, and `SInt` values. - -#### Bit Selection and Slicing +DFHDL supports enumerated types through Scala's enum feature with special encoding traits. Enums provide a type-safe way to represent a fixed set of values. -- **Range slice**: `value(hi, lo)` extracts bits `hi` down to `lo`, returning a narrower value of the **same type** (`Bits` → `Bits`, `UInt` → `UInt`, `SInt` → `SInt`). -- **Single-bit access**: `value(idx)` returns the bit at position `idx` (as `Bit`). The index can be a static integer or a dynamic `UInt` variable. +#### Enum Type Definition ```scala -val b8 = Bits(8) <> VAR -val u8 = UInt(8) <> VAR -val s8 = SInt(8) <> VAR +enum MyEnum extends Encoded: + case A, B, C, D +``` -// Range slicing — preserves the original type -val b4 = b8(7, 4) // Bits[4]: upper nibble -val u4 = u8(3, 0) // UInt[4]: lower nibble -val s4 = s8(3, 0) // SInt[4]: lower nibble +#### Encoding Types -// Single-bit access -val msb = b8(7) // Bit -val lsb = u8(0) // Bit +DFHDL supports several encoding schemes for enums: -// Dynamic bit access (index is a UInt variable) -val idx = UInt(3) <> VAR -val dynbit = b8(idx) // Bit at position idx +1. **Binary Encoded** (default) +```scala +enum MyEnum extends Encoded: + case A, B, C, D // Encoded as 00,01,10,11 ``` -/// admonition | Dynamic bit indexing - type: tip -You can index into a bit-vector value using a `UInt` variable, not just integer literals. The index must be a `UInt` whose width equals `clog2(bits_width)`. For example, indexing into `Bits(8)` requires a `UInt(3)` index. If the width does not match, the compiler will report an error and suggest using `.truncate` (to narrow) or `.extend` (to widen). - -Dynamic indexing works for both reads and writes: +2. **One-Hot Encoded** ```scala -val data = Bits(8) <> VAR init all(0) -val pos = UInt(3) <> VAR init 0 -val din = Bit <> IN - -val bit_out = data(pos) // dynamic read -process(clk): - if (clk.rising) - data(pos) :== din // dynamic write +enum MyEnum extends Encoded.OneHot: + case A, B, C // Encoded as 001,010,100 ``` -/// -#### Width Adjustment: `.resize`, `.truncate`, `.extend` - -- `.resize(N)` sets the width to exactly `N` bits. For `UInt` and `Bits`, widening zero-extends; for `SInt`, widening sign-extends. Narrowing truncates the most-significant bits. -- `.truncate` automatically narrows to the width expected by the assignment or operation context. -- `.extend` automatically widens to the width expected by the context. +3. **Gray Encoded** +```scala +enum MyEnum extends Encoded.Gray: + case A, B, C // Encoded as 00,01,11 +``` -```scala -val u8 = UInt(8) <> VAR -val u6 = UInt(6) <> VAR -u6 := u8.resize(6) // explicit truncate to 6 bits -u6 := u8.truncate // auto-narrow to match u6's width -u8 := u6.resize(8) // explicit zero-extend to 8 bits -u8 := u6.extend // auto-widen to match u8's width -``` - -#### Bit Concatenation - -Multiple bit-vector values can be concatenated using Scala tuple syntax with `.toBits`: - -```scala -val concat = (b"100", b"1", b"0", b"11").toBits // Bits[8] -``` - -Values are concatenated from the first (most-significant) to the last (least-significant) position. - -### Common Conversions and Casts {#type-conversion} -The diagram below shows the conversion/cast paths between DFHDL types. Solid arrows are simple casts that preserve width; dashed arrows involve width changes. - -![type-conversion](type-conversion-light.svg#only-light){ width="70%" } -![type-conversion](type-conversion-dark.svg#only-dark){ width="70%" } - -/// html | div.conversion -| From | To | Method | From | To | Method | -|------|-----|--------|------|-----|--------| -| `T` | `Bits` | `.bits` | `Bit` | `Boolean` | `.bool` | -| `Bits` | `T` | `.as(T)` | `Boolean` | `Bit` | `.bit` | -| `Bits(w)` | `UInt(w)` | `.uint` | `Bit`/`Boolean` | `Bits(1)` | `.bits` | -| `Bits(w)` | `SInt(w)` | `.sint` | `Bit`/`Boolean` | `Bits(w)` | `.toBits(w)` | -| `UInt(w)` | `SInt(w+1)` | `.signed` | `Bit`/`Boolean` | `UInt(w)` | `.toUInt(w)` | -| `UInt`/`SInt` | `Int` | `.ToInt` | `Bit`/`Boolean` | `SInt(w)` | `.toSInt(w)` | -/// - -#### Any Type to/from `Bits`: `.bits` and `.as(T)` {#bits-cast} - -Every DFHDL type can be converted to its raw bit representation with `.bits`. The inverse operation, `.as(T)`, reinterprets a `Bits` value as a target type `T`, provided the bit widths match exactly: - -```scala -val u8 = UInt(8) <> VAR -val b8 = u8.bits // UInt(8) -> Bits(8) -val back = b8.as(UInt(8)) // Bits(8) -> UInt(8) -``` - -This also works with composite types such as enums, structs, and opaques: - -```scala -val e = MyEnum <> VAR -val eBits = e.bits // Enum -> Bits -val eBack = eBits.as(MyEnum) // Bits -> Enum -``` - -#### `Bits` to `UInt`/`SInt`: `.uint` and `.sint` {#uint-sint-cast} - -These are shorthand conversions from `Bits` that preserve width. The same bits are simply reinterpreted as unsigned or signed: - -```scala -val b8 = Bits(8) <> VAR -val u8 = b8.uint // Bits(8) -> UInt(8), same bit pattern -val s8 = b8.sint // Bits(8) -> SInt(8), same bit pattern -``` - -#### `UInt` to `SInt`: `.signed` {#signed-cast} - -Converting an unsigned value to signed requires an extra bit for the sign, so `.signed` widens the result by one bit: - -```scala -val u8 = UInt(8) <> VAR -val s9 = u8.signed // UInt(8) -> SInt(9) -``` - -To get an `SInt` with the **same** width (reinterpreting the bit pattern without expanding), go through `Bits`: - -```scala -val s8 = u8.bits.sint // UInt(8) -> Bits(8) -> SInt(8) -``` - -#### `Bit` and `Boolean` Conversions {#bit-bool-cast} - -`Bit` is the hardware single-bit type and `Boolean` is the logical type. They are convertible to each other with `.bit` and `.bool`: - -```scala -val myBit = Bit <> VAR -val myBool = myBit.bool // Bit -> Boolean -val back = myBool.bit // Boolean -> Bit -``` - -Both `Bit` and `Boolean` can be widened (zero-extended) into `Bits`, `UInt`, or `SInt` with an explicit target width: - -```scala -val flag = Bit <> VAR -val b4 = flag.toBits(4) // Bit -> Bits(4) -val u4 = flag.toUInt(4) // Bit -> UInt(4) -val s4 = flag.toSInt(4) // Bit -> SInt(4) -``` - -When the value is `1`, these produce the value `1` at the given width (not sign-extended). The single-bit `.bits` conversion is also available, returning `Bits(1)`. - -## Enumeration DFHDL Values {#DFEnum} - -DFHDL supports enumerated types through Scala's enum feature with special encoding traits. Enums provide a type-safe way to represent a fixed set of values. - -### Enum Type Definition - -```scala -enum MyEnum extends Encoded: - case A, B, C, D -``` - -### Encoding Types - -DFHDL supports several encoding schemes for enums: - -1. **Binary Encoded** (default) -```scala -enum MyEnum extends Encoded: - case A, B, C, D // Encoded as 00,01,10,11 -``` - -2. **One-Hot Encoded** -```scala -enum MyEnum extends Encoded.OneHot: - case A, B, C // Encoded as 001,010,100 -``` - -3. **Gray Encoded** -```scala -enum MyEnum extends Encoded.Gray: - case A, B, C // Encoded as 00,01,11 -``` - -4. **Custom Start Value** +4. **Custom Start Value** ```scala enum MyEnum extends Encoded.StartAt(4): case A, B, C // Encoded as 100,101,110 @@ -1610,18 +988,7 @@ enum MyEnum(val value: UInt[8] <> CONST) extends Encoded.Manual(8): Note: the Manual encoding enum class **must** declare a constructor parameter `(val value: UInt[N] <> CONST)` and the bit width `N` must match the argument to `Encoded.Manual(N)`. Each `case` must explicitly extend the enum class and pass a constant value. Omitting the constructor parameter will cause a compile error. -### Operations - -/// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs == rhs` | Equality comparison | Same enum type | Boolean | -| `lhs != rhs` | Inequality comparison | Same enum type | Boolean | -| `lhs.bits` | Get raw bits representation | Enum value | Bits | -| `lhs.uint` | Get unsigned int representation | Enum value | UInt | -/// - -### Pattern Matching +#### Pattern Matching Enums can be used in pattern matching expressions: @@ -1634,7 +1001,7 @@ state match case MyEnum.C => // handle C ``` -### Examples +#### Examples ```scala // State machine enum @@ -1655,11 +1022,11 @@ class CPU extends RTDesign: // Store state logic ``` -## Vector DFHDL Values {#DFVector} +### Vector {#DFVector} DFHDL vectors allow creating arrays of any DFHDL type. Unlike `Bits` which is specialized for bit vectors, generic vectors can hold any DFHDL type and support multi-dimensional arrays. -### Vector Type Construction +#### Vector Type Construction The vector type is constructed using the `X` operator between a base type and dimension: @@ -1674,7 +1041,7 @@ val vec2 = Bit X 8 X 8 <> VAR // 2D 8x8 vector of bits val vec3 = MyEnum X 16 <> VAR // Vector of 16 enum values ``` -### Initialization +#### Initialization Vectors can be initialized in several ways: @@ -1689,27 +1056,7 @@ val vec2 = UInt(8) X 4 <> VAR init Vector(1, 2, 3, 4) val mem = UInt(32) X 1024 <> VAR initFile "mem.hex" ``` -### Operations - -#### Element Access -Access individual elements using array indexing: - -```scala -val elem = vec(idx) // Read element at index -vec(idx) := newValue // Write element at index -``` - -#### Vector-wide Operations - -/// html | div.operations -| Operation | Description | Returns | -| ------------ | ----------- | ------- | -| `vec.elements` | Get all elements as Scala sequence | Seq[BaseType] | -| `vec.size` | Get vector dimension | Int | -| `vec.bits` | Get bits representation | Bits | -/// - -### Multi-dimensional Vectors +#### Multi-dimensional Vectors Multi-dimensional vectors are created by chaining `X` operators: @@ -1725,7 +1072,7 @@ matrix(1)(2) := 42 matrix := all(all(0)) // All elements to 0 ``` -### Memory/RAM Implementation +#### Memory/RAM Implementation Vectors are commonly used to implement memories and RAMs: @@ -1741,7 +1088,7 @@ class RAM extends RTDesign: data := mem(addr) // Read ``` -### File Initialization +#### File Initialization Vectors support initialization from files in various formats: @@ -1753,11 +1100,11 @@ val rom = UInt(8) X 256 <> VAR initFile("rom.hex", InitFileFormat.VerilogHex) val ram = UInt(32) X 1024 <> VAR initFile "ram.bin" ``` -## Struct DFHDL Values {#DFStruct} +### Struct {#DFStruct} DFHDL structures allow creating composite types by combining multiple DFHDL values into a single type. Structs are defined using Scala case classes that extend the `Struct` trait. -### Struct Type Definition +#### Struct Type Definition ```scala case class MyStruct( @@ -1767,7 +1114,7 @@ case class MyStruct( ) extends Struct ``` -### Field Access and Assignment +#### Field Access and Assignment Fields are accessed using dot notation and can be assigned individually: @@ -1778,7 +1125,7 @@ s.field2 := b"1010" // Assign bits s := MyStruct(1, b"0101", true) // Assign whole struct ``` -### Nested Structs +#### Nested Structs Structs can be nested to create more complex data structures: @@ -1791,17 +1138,7 @@ rect.topLeft.x := 0 rect.bottomRight.y := 100 ``` -### Operations - -/// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs == rhs` | Equality comparison | Same struct type | Boolean | -| `lhs != rhs` | Inequality comparison | Same struct type | Boolean | -| `lhs.bits` | Get raw bits representation | Struct value | Bits | -/// - -### Pattern Matching +#### Pattern Matching Structs support pattern matching for field extraction: @@ -1812,7 +1149,7 @@ point match case Point(0, _) => // Match x=0, any y ``` -### Examples +#### Examples ```scala // AXI-like interface struct @@ -1834,17 +1171,17 @@ class MyDesign extends RTDesign: axi.data := h"DEADBEEF" ``` -## Tuple DFHDL Values {#DFTuple} +### Tuple {#DFTuple} DFHDL tuples provide a way to group multiple DFHDL values together without defining a named structure. They are similar to Scala tuples but operate on DFHDL values. -### Tuple Type Construction +#### Tuple Type Construction ```scala val tuple = (Type1, Type2, ..., TypeN) <> Modifier ``` -### Examples +#### Examples ```scala // Basic tuple declaration @@ -1858,7 +1195,7 @@ pair := (42, 1) complex := ((100, 0), b"1010") ``` -### Element Access +#### Element Access Tuple elements can be accessed using ._N notation or pattern matching: @@ -1870,21 +1207,11 @@ val second = pair._2 // Access second element val (x, y) = pair ``` -### Operations - -/// html | div.operations -| Operation | Description | Returns | -| ------------ | ----------- | ------- | -| `tuple.bits` | Get bits representation | Bits | -| `tuple == other` | Equality comparison | Boolean | -| `tuple != other` | Inequality comparison | Boolean | -/// - -## Opaque DFHDL Values {#DFOpaque} +### Opaque {#DFOpaque} Opaque types allow creating new DFHDL types that wrap existing types while hiding their internal representation. This is useful for creating abstraction layers and type-safe interfaces. -### Opaque Type Definition +#### Opaque Type Definition ```scala // Define opaque type wrapping UInt(8) @@ -1897,7 +1224,7 @@ case class Counter() extends Opaque(UInt(32)): (c.actual + 1).as(Counter) ``` -### Usage +#### Usage ```scala val op = MyOpaque <> VAR @@ -1905,7 +1232,7 @@ val wrapped: UInt[8] <> VAL = op.actual // Access wrapped value op := 42.as(MyOpaque) // Assign using .as conversion ``` -### Examples +#### Examples ```scala // AES byte type with custom operations @@ -1922,42 +1249,21 @@ class AESCircuit extends DFDesign: out := in1 + in2 // Uses custom + operation ``` -## Double DFHDL Values {#DFDouble} +### Double {#DFDouble} DFHDL Double values represent IEEE-754 double-precision floating-point numbers. -### Type Construction +#### Type Construction ```scala val d = Double <> Modifier ``` -### Operations - -Supports standard arithmetic operations: - -```scala -val d1 = Double <> VAR -val d2 = Double <> VAR - -val sum = d1 + d2 -val prod = d1 * d2 -val quot = d1 / d2 -val comp = d1 < d2 -``` - -### Conversion - -```scala -val bits = d1.bits // Get bits representation -val d3 = bits.as(Double) // Convert back to Double -``` - -## Time/Freq DFHDL Values {#DFPhysical} +### Time/Freq {#DFPhysical} DFHDL provides special types for representing time and frequency values in hardware designs through physical units. These types help ensure correct timing specifications and frequency calculations. -### Time Values +#### Time Values Time values can be created using various unit suffixes: @@ -1979,7 +1285,7 @@ val t9 = 1.5.ns // 1.5 nanoseconds val t10 = 10.ms // 10 milliseconds ``` -### Frequency Values +#### Frequency Values Frequency values can be specified using standard frequency units: @@ -1997,7 +1303,7 @@ val f5 = 100.MHz // 100 megahertz val f6 = 2.5.GHz // 2.5 gigahertz ``` -### Usage in RT Domains +#### Usage in RT Domains Physical values are particularly useful when configuring RT domains and specifying clock frequencies: @@ -2016,7 +1322,7 @@ class TimingExample extends RTDesign: val clockFreq = 1.GHz // Clock frequency ``` -### Cycles in RT Domain +#### Cycles in RT Domain In RT domains, you can also specify cycle counts using the `.cy` unit: @@ -2027,11 +1333,11 @@ class RTExample extends RTDesign: Note: The `.cy` unit is only available within register-transfer (RT) domains. -## Unit (Void) DFHDL Values {#DFUnit} +### Unit (Void) {#DFUnit} The Unit type in DFHDL represents a void or no-value type, similar to Scala's Unit type. It's typically used when an operation doesn't need to return a meaningful value. -### Usage +#### Usage ```scala // Method returning Unit @@ -2044,7 +1350,7 @@ val x = Bit <> VAR val y: Unit <> VAL = x := 1 ``` -### Common Use Cases +#### Common Use Cases 1. Side-effect operations 2. Void method returns @@ -2059,3 +1365,666 @@ class Example extends EDDesign: // Process body returns Unit doSomething ``` +## Operations + +### Conversions and Casts {#type-conversion} +The diagram below shows the conversion/cast paths between DFHDL types. Solid arrows are simple casts that preserve width; dashed arrows involve width changes. + +![type-conversion](type-conversion-light.svg#only-light){ width="70%" } +![type-conversion](type-conversion-dark.svg#only-dark){ width="70%" } + +/// html | div.conversion +| From | To | Method | From | To | Method | +|------|-----|--------|------|-----|--------| +| `T` | `Bits` | `.bits` | `Bit` | `Boolean` | `.bool` | +| `Bits` | `T` | `.as(T)` | `Boolean` | `Bit` | `.bit` | +| `Bits(w)` | `UInt(w)` | `.uint` | `Bit`/`Boolean` | `Bits(1)` | `.bits` | +| `Bits(w)` | `SInt(w)` | `.sint` | `Bit`/`Boolean` | `Bits(w)` | `.toBits(w)` | +| `UInt(w)` | `SInt(w+1)` | `.signed` | `Bit`/`Boolean` | `UInt(w)` | `.toUInt(w)` | +| `UInt`/`SInt` | `Int` | `.ToInt` | `Bit`/`Boolean` | `SInt(w)` | `.toSInt(w)` | +/// + +#### Any Type to/from `Bits`: `.bits` and `.as(T)` {#bits-cast} + +Every DFHDL type can be converted to its raw bit representation with `.bits`. The inverse operation, `.as(T)`, reinterprets a `Bits` value as a target type `T`, provided the bit widths match exactly: + +```scala +val u8 = UInt(8) <> VAR +val b8 = u8.bits // UInt(8) -> Bits(8) +val back = b8.as(UInt(8)) // Bits(8) -> UInt(8) +``` + +This also works with composite types such as enums, structs, and opaques: + +```scala +val e = MyEnum <> VAR +val eBits = e.bits // Enum -> Bits +val eBack = eBits.as(MyEnum) // Bits -> Enum +``` + +#### `Bits` to `UInt`/`SInt`: `.uint` and `.sint` {#uint-sint-cast} + +These are shorthand conversions from `Bits` that preserve width. The same bits are simply reinterpreted as unsigned or signed: + +```scala +val b8 = Bits(8) <> VAR +val u8 = b8.uint // Bits(8) -> UInt(8), same bit pattern +val s8 = b8.sint // Bits(8) -> SInt(8), same bit pattern +``` + +#### `UInt` to `SInt`: `.signed` {#signed-cast} + +Converting an unsigned value to signed requires an extra bit for the sign, so `.signed` widens the result by one bit: + +```scala +val u8 = UInt(8) <> VAR +val s9 = u8.signed // UInt(8) -> SInt(9) +``` + +To get an `SInt` with the **same** width (reinterpreting the bit pattern without expanding), go through `Bits`: + +```scala +val s8 = u8.bits.sint // UInt(8) -> Bits(8) -> SInt(8) +``` + +#### `Bit` and `Boolean` Conversions {#bit-bool-cast} + +`Bit` is the hardware single-bit type and `Boolean` is the logical type. They are convertible to each other with `.bit` and `.bool`: + +```scala +val myBit = Bit <> VAR +val myBool = myBit.bool // Bit -> Boolean +val back = myBool.bit // Boolean -> Bit +``` + +Both `Bit` and `Boolean` can be widened (zero-extended) into `Bits`, `UInt`, or `SInt` with an explicit target width: + +```scala +val flag = Bit <> VAR +val b4 = flag.toBits(4) // Bit -> Bits(4) +val u4 = flag.toUInt(4) // Bit -> UInt(4) +val s4 = flag.toSInt(4) // Bit -> SInt(4) +``` + +When the value is `1`, these produce the value `1` at the given width (not sign-extended). The single-bit `.bits` conversion is also available, returning `Bits(1)`. + +#### Enum to `UInt`: `.uint` {#enum-uint-cast} + +Enum values can be converted to their underlying unsigned integer representation: + +```scala +val e = MyEnum <> VAR +val u = e.uint // Enum -> UInt (encoding-dependent width) +``` + +### Bit Selection and Slicing {#common-bit-vector-ops} + +Applies to: `Bits`, `UInt`, `SInt` + +- **Range slice**: `value(hi, lo)` extracts bits `hi` down to `lo`, returning a narrower value of the **same type** (`Bits` → `Bits`, `UInt` → `UInt`, `SInt` → `SInt`). +- **Single-bit access**: `value(idx)` returns the bit at position `idx` (as `Bit`). The index can be a static integer or a dynamic `UInt` variable. + +```scala +val b8 = Bits(8) <> VAR +val u8 = UInt(8) <> VAR +val s8 = SInt(8) <> VAR + +// Range slicing — preserves the original type +val b4 = b8(7, 4) // Bits[4]: upper nibble +val u4 = u8(3, 0) // UInt[4]: lower nibble +val s4 = s8(3, 0) // SInt[4]: lower nibble + +// Single-bit access +val msb = b8(7) // Bit +val lsb = u8(0) // Bit + +// Dynamic bit access (index is a UInt variable) +val idx = UInt(3) <> VAR +val dynbit = b8(idx) // Bit at position idx +``` + +/// admonition | Dynamic bit indexing + type: tip +You can index into a bit-vector value using a `UInt` variable, not just integer literals. The index must be a `UInt` whose width equals `clog2(bits_width)`. For example, indexing into `Bits(8)` requires a `UInt(3)` index. If the width does not match, the compiler will report an error and suggest using `.truncate` (to narrow) or `.extend` (to widen). + +Dynamic indexing works for both reads and writes: +```scala +val data = Bits(8) <> VAR init all(0) +val pos = UInt(3) <> VAR init 0 +val din = Bit <> IN + +val bit_out = data(pos) // dynamic read +process(clk): + if (clk.rising) + data(pos) :== din // dynamic write +``` +/// + +### Width Adjustment {#width-adjustment} + +Applies to: `Bits`, `UInt`, `SInt` + +- `.resize(N)` sets the width to exactly `N` bits. For `UInt` and `Bits`, widening zero-extends; for `SInt`, widening sign-extends. Narrowing truncates the most-significant bits. +- `.truncate` automatically narrows to the width expected by the assignment or operation context. +- `.extend` automatically widens to the width expected by the context. + +```scala +val u8 = UInt(8) <> VAR +val u6 = UInt(6) <> VAR +u6 := u8.resize(6) // explicit truncate to 6 bits +u6 := u8.truncate // auto-narrow to match u6's width +u8 := u6.resize(8) // explicit zero-extend to 8 bits +u8 := u6.extend // auto-widen to match u8's width +``` + +### Bit Concatenation {#bit-concat} + +Applies to: `Bits`, `UInt`, `SInt` + +Multiple bit-vector values can be concatenated using Scala tuple syntax with `.toBits`: + +```scala +val concat = (b"100", b"1", b"0", b"11").toBits // Bits[8] +``` + +Values are concatenated from the first (most-significant) to the last (least-significant) position. + +### Logical Operations {#logical-ops} + +Applies to: `Bit`, `Boolean` + +Logical operations' return type always matches the LHS argument's type. +These operations propagate constant modifiers, meaning that if all arguments are constant, the returned value is also a constant. + +/// html | div.operations +| Operation | Description | LHS/RHS Constraints | Returns | +| ------------ | ----------- | ------------------- | ------- | +| `lhs && rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs || rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs & rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs | rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs ^ rhs` | Logical XOR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `!lhs` | Logical NOT | The argument must be a `Bit`/`Boolean` DFHDL value. | LHS-Type DFHDL value | +/// + +```scala +val bt = Bit <> VAR +val bl = Boolean <> VAR +val t1 = bt && bl //result type: Bit +val t2 = bt ^ 1 //result type: Bit +val t3 = bl || false //result type: Boolean +val t4 = bt && true //result type: Bit +val t5 = bl || bt //result type: Boolean +val t6 = bl ^ 0 || !bt +//`t7` after the candidate implicit +//conversions, looks like so: +//(bl && bt.bool) ^ (!(bt || bl.bit)).bool +val t7 = (bl && bt) ^ !(bt || bl) +//error: swap argument positions to have +//the DFHDL value on the LHS. +val e1 = 0 ^ bt +//error: swap argument positions to have +//the DFHDL value on the LHS. +val e2 = false ^ bt +//not supported since both arguments +//are just candidates +val e3 = 0 ^ true +//This just yields a Scala Boolean, +//as a basic operation between Scala +//Boolean values. +val sc: Boolean = true && true +``` + +/// admonition | Logical `||`/`&&` and bitwise `|`/`&` on Bit and Boolean values + type: tip +In DFHDL, the operators `||` and `&&` are equivalent to `|` and `&`, respectively, when applied on either DFHDL `Bit` or `Boolean` types. In Verilog, the actual operator printed depends on the LHS argument of the operation: if it's `Bit`, the operator will be `|`/`&`; if it's `Boolean`, the operator will be `||`/`&&`. +/// + +/// details | Transitioning from Verilog + type: verilog +Under the ED domain, the following operations are equivalent: + +| DFHDL Operation | Verilog Operation (Bit LHS) | Verilog Operation (Boolean LHS) | +|-----------------|-----------------------------|---------------------------------| +| `lhs && rhs` | `lhs & rhs` | `lhs && rhs` | +| `lhs || rhs` | `lhs | rhs` | `lhs || rhs` | +| `lhs & rhs` | `lhs & rhs` | `lhs && rhs` | +| `lhs | rhs` | `lhs | rhs` | `lhs || rhs` | +| `lhs ^ rhs` | `lhs ^ rhs` | `lhs ^ rhs` | +| `!lhs` | `!lhs` | `!lhs` | +/// + +/// details | Transitioning from VHDL + type: vhdl +Under the ED domain, the following operations are equivalent: + +| DFHDL Operation | VHDL Operation | +|-----------------|-------------------| +| `lhs && rhs` | `lhs and rhs` | +| `lhs || rhs` | `lhs or rhs` | +| `lhs ^ rhs` | `lhs xor rhs` | +| `!lhs` | `not lhs` | +/// + +### Arithmetic Operations (`+`, `-`, `*`, `/`, `%`) {#arithmetic-ops} + +Applies to: `UInt`, `SInt` + +The result of a standard arithmetic operation always has the **same type as the LHS** operand. The RHS is resized to match the LHS before the operation is applied. + +/// html | div.decimal_arithmetic +| Operation | Description | Returns | +| ------------ | -------------- | ---------------- | +| `lhs + rhs` | Addition | Same type as LHS | +| `lhs - rhs` | Subtraction | Same type as LHS | +| `lhs * rhs` | Multiplication | Same type as LHS | +| `lhs / rhs` | Division | Same type as LHS | +| `lhs % rhs` | Modulo | Same type as LHS | +/// + +#### Type Constraints + +**Sign rule** -- The LHS sign must be greater than or equal to the RHS sign (`signed >= unsigned`): + +/// html | div.operations +| LHS | RHS | Allowed | Note | +| --- | --- | ------- | ---- | +| `UInt[W1]` | `UInt[W2]` | Yes | W1 >= W2 | +| `SInt[W1]` | `SInt[W2]` | Yes | W1 >= W2 | +| `SInt[W1]` | `UInt[W2]` | Yes | W1 >= W2 + 1 (RHS is implicitly widened by 1 bit for the sign bit) | +| `UInt[W1]` | `SInt[W2]` | **No** | Compile error: an explicit conversion is required | +/// + +**Width rule** -- The LHS width must be greater than or equal to the (effective) RHS width. When applying `SInt op UInt`, the effective RHS width is `RHS width + 1` because the unsigned value gains an implicit sign bit. + +**Scala `Int` literals** are auto-promoted to a matching DFHDL type with the minimum required bit width. A negative `Int` literal on the RHS of a `UInt` operation is a compile error (unsigned LHS cannot accept a signed RHS). + +```scala +val u8 = UInt(8) <> VAR +val u4 = UInt(4) <> VAR +val s8 = SInt(8) <> VAR + +// UInt + UInt (same width) +val r1 = u8 + u8 // UInt[8] +// SInt + SInt +val r2 = s8 + s8 // SInt[8] +// SInt + UInt: RHS widened to 5 bits (4+1); 8 >= 5, OK +val r3 = s8 + u4 // SInt[8] +// UInt + Scala Int literal (200 fits in 8 bits) +val r4 = u8 + 200 // UInt[8] +// Scala Int literal on LHS (200 is promoted to UInt[8]) +val r5 = 200 - u8 // UInt[8] + +// error: Cannot apply this operation between an unsigned +// value (LHS) and a signed value (RHS). +// An explicit conversion must be applied. +val e1 = u8 + s8 +// error: The applied RHS value width (8) is larger than +// the LHS variable width (4). +val e2 = u4 + u8 +// error: Cannot apply this operation between an unsigned +// value (LHS) and a signed value (RHS). +// An explicit conversion must be applied. +val e3 = u8 + (-22) +``` + +/// admonition | Overflow and automatic carry promotion + type: warning +Standard arithmetic operations wrap on overflow. For example, `d"8'255" + d"8'1"` produces `d"8'0"`. Use the carry variants (`+^`, `-^`, `*^`) described below to get a wider result that preserves the full value. + +However, when an **anonymous** arithmetic expression (`+`, `-`, `*`) is assigned or connected to a variable that is **wider** than the operation's result, the operation is **automatically promoted** to a carry operation. This matches Verilog's behavior where the assignment target width determines the operation width. The carry result is then resized to fit the target if needed. + +```scala +val u8 = UInt(8) <> VAR +val u9 = UInt(9) <> VAR +val u12 = UInt(12) <> VAR +val u16 = UInt(16) <> VAR +u9 := u8 + u8 // promoted to carry addition (width 9), exact fit +u16 := u8 * u8 // promoted to carry multiplication (width 16), exact fit +u12 := u8 * u8 // promoted to carry multiplication (width 16), resized to 12 + +// Named expressions are NOT promoted: +val sum = u8 + u8 // UInt[8], named value +u9 := sum // resized from 8 to 9, no carry promotion +``` +/// + +### Carry Arithmetic (`+^`, `-^`, `*^`) {#carry-ops} + +Applies to: `UInt`, `SInt` + +Carry operations widen the result to prevent overflow. Unlike standard arithmetic, carry operations require both operands to have the **same sign**. + +/// html | div.operations +| Operation | Description | Result Width | Result Sign | +| ------------- | -------------------- | ------------------ | -------------------- | +| `lhs +^ rhs` | Carry Addition | max(LW, RW) + 1 | Same sign as operands | +| `lhs -^ rhs` | Carry Subtraction | max(LW, RW) + 1 | Same sign as operands | +| `lhs *^ rhs` | Carry Multiplication | LW + RW | Same sign as operands | +/// + +```scala +val u8 = UInt(8) <> VAR + +// Carry addition: width = max(8, 8) + 1 = 9 +val r1 = u8 +^ u8 // UInt[9] +// d"8'255" +^ d"8'1" == d"9'256" (no overflow) + +// Carry subtraction: width = max(8, 8) + 1 = 9 +val r2 = u8 -^ u8 // UInt[9] + +// Carry multiplication: width = 8 + 8 = 16 +val r3 = u8 *^ u8 // UInt[16] + +// Scala Int literal: 100 needs 7 bits +// width = 7 + 8 = 15 +val r4 = 100 *^ u8 // UInt[15] +``` + +/// admonition | Carry vs standard sign rules + type: tip +Unlike standard arithmetic where `SInt op UInt` is allowed (the unsigned RHS is implicitly widened), carry operations require both operands to have the **same sign**. This is because carry operations produce a wider result, and mixed-sign widening semantics would be ambiguous. Scala `Int` literals are still accepted when the literal's sign matches the DFHDL value's sign. +/// + +### Comparison Operations (`==`, `!=`, `<`, `>`, `<=`, `>=`) {#comparison-ops} + +Applies to: `UInt`, `SInt` (all comparisons); `Enum`, `Struct`, `Tuple` (`==`/`!=` only) + +#### Decimal Comparisons + +Comparison operations on `UInt`/`SInt` return a `Boolean` DFHDL value and have **stricter constraints** than arithmetic: + +/// html | div.operations +| Operation | Description | Returns | +| ------------- | ---------------------- | -------- | +| `lhs == rhs` | Equal | Boolean | +| `lhs != rhs` | Not equal | Boolean | +| `lhs < rhs` | Less than | Boolean | +| `lhs > rhs` | Greater than | Boolean | +| `lhs <= rhs` | Less than or equal | Boolean | +| `lhs >= rhs` | Greater than or equal | Boolean | +/// + +Unlike arithmetic operations which use relaxed rules (LHS sign >= RHS sign, LHS width >= RHS width), comparisons require **exact matching**: + +- **Sign:** Must match exactly (`UInt` with `UInt`, `SInt` with `SInt`). +- **Width:** Must match exactly (both operands must have the same bit width). +- **Scala `Int` literals:** The literal's bit width (adjusted +1 if the DFHDL value is signed and the literal is positive) must fit within the DFHDL value's width. + +```scala +val u8 = UInt(8) <> VAR +val u4 = UInt(4) <> VAR +val s8 = SInt(8) <> VAR + +val c1 = u8 == u8 // Boolean: same sign, same width +val c2 = u8 < 200 // Boolean: 200 fits in UInt[8] +val c3 = 0 < u8 // Boolean: Scala Int on LHS +val c4 = s8 >= 1 // Boolean: 1 is promoted to SInt (width 2 fits in 8) + +// error: Cannot apply this operation between an unsigned +// value (LHS) and a signed value (RHS). +// An explicit conversion must be applied. +val e1 = u8 == s8 +// error: Cannot apply this operation between a value of +// 8 bits width (LHS) to a value of 4 bits width (RHS). +// An explicit conversion must be applied. +val e2 = u8 == u4 +// error: Cannot compare a DFHDL value (width = 8) with a +// Scala `Int` argument that is wider (width = 10). +// An explicit conversion must be applied. +val e3 = u8 > 1000 +``` + +/// details | Scala `Int` constants auto-lift in comparisons + type: note +Plain Scala `Int` values can be used directly in comparisons and arithmetic with DFHDL typed variables. No explicit coercion is needed: +```scala +val LIMIT: Int <> CONST = 5208 +val counter = UInt.until(LIMIT) <> VAR +if (counter == LIMIT - 1) // Int <> CONST compared with UInt -- works directly + counter := 0 +``` +/// + +#### Enum, Struct, and Tuple Comparisons + +Enums, structs, and tuples support equality comparisons (`==` and `!=`) between values of the same type: + +```scala +val e1 = MyEnum <> VAR +val e2 = MyEnum <> VAR +val eq = e1 == e2 // Boolean + +val s1 = MyStruct <> VAR +val s2 = MyStruct <> VAR +val eq2 = s1 == s2 // Boolean +``` + +### Shift Operations (`<<`, `>>`) {#shift-ops} + +Applies to: `Bits`, `UInt`, `SInt` + +/// html | div.operations +| Operation | Description | LHS/RHS Constraints | Returns | +| ------------ | ----------- | ------------------- | ------- | +| `lhs << rhs` | Left shift | LHS: `Bits`/`UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | +| `lhs >> rhs` | Right shift (logical for `Bits`/`UInt`, arithmetic for `SInt`) | LHS: `Bits`/`UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | +/// + +The `>>` operator is **type-aware**: on `UInt` and `Bits` it performs a logical (zero-filling) right shift, and on `SInt` it performs an arithmetic (sign-extending) right shift. There is no separate `>>>` operator in DFHDL -- the operand type determines the behavior. + +```scala +val u = UInt(8) <> VAR +val s = SInt(8) <> VAR + +val u_shifted = u >> 2 // logical right shift (zero-fills MSBs) +val s_shifted = s >> 2 // arithmetic right shift (sign-extends MSBs) +``` + +### `Int` Parameter Arithmetic {#int-param-arith} + +Applies to: `Int` + +When both operands are Scala `Int` or DFHDL `Int <> CONST` values, the following operations are available. The result is always an `Int <> CONST` value. + +/// html | div.operations +| Operation | Description | +| --------------- | -------------- | +| `lhs + rhs` | Addition | +| `lhs - rhs` | Subtraction | +| `lhs * rhs` | Multiplication | +| `lhs / rhs` | Division | +| `lhs % rhs` | Modulo | +| `lhs ** rhs` | Power | +| `lhs max rhs` | Maximum | +| `lhs min rhs` | Minimum | +/// + +```scala +val param: Int <> CONST = 2 +val t1 = 1 + param // Int <> CONST = 3 +val t2 = 4 * param // Int <> CONST = 8 +val t3 = 10 / param // Int <> CONST = 5 +val t4 = 10 % param // Int <> CONST = 0 +val t5 = 3 ** param // Int <> CONST = 9 +val t6 = 1 max param // Int <> CONST = 2 +val t7 = 1 min param // Int <> CONST = 1 +``` + +/// admonition | Non-constant DFHDL `Int` values + type: note +Non-constant DFHDL `Int` values (e.g., `Int <> VAR`) are possible and support the same arithmetic operations. However, they are discouraged for synthesizable designs because they map to a fixed 32-bit signed representation -- use `SInt[32]` instead for explicit control over the hardware. For simulation purposes, non-constant `Int` values are acceptable as long as the 32-bit width limitation is understood. +/// + +### History Operations {#history-ops} + +Applies to: `Bit` (`.rising`, `.falling`) + +These operations are supported under both RT and ED domains. Under RT domain, these operations are synthesizable expressions. + +/// html | div.operations +| Operation | Description | LHS Constraints | Returns | +| ------------ | --------------------------------|-----------------------|-----------------------| +| `lhs.rising` | True when a value changes from `0` to `1` | `Bit` DFHDL value | `Boolean` DFHDL value | +| `lhs.falling` | True when a value changes from `1` to `0` | `Bit` DFHDL value | `Boolean` DFHDL value | +/// + +/// tab | `ED` + +```scala +class Foo extends EDDesign: + val clk = Bit <> IN + + /* VHDL-style */ + process(clk): + if (clk.rising) + //some sequential logic + + /* Verilog-style */ + process(clk.rising): + //some sequential logic +``` +/// details | Transitioning from Verilog + type: verilog +Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the Verilog `posedge x` and `negedge x`, respectively. +/// + +/// details | Transitioning from VHDL + type: vhdl +Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the VHDL `rising_edge(x)` and `falling_edge(x)`, respectively. +/// +/// + +/// tab | `RT` +The following `RT` domain edge detection design: +```scala +class Detector extends RTDesign: + val i = Bit <> IN + val o = Bit <> OUT + o := i.rising +``` +is compiled down to the following `ED` design (depending on the clock and reset configurations): +```scala +class Detector extends EDDesign: + val clk = Bit <> IN + val i = Bit <> IN + val o = Bit <> OUT + val i_reg = Bit <> VAR init 1 + process(clk.rising): + i_reg :== i + o <> !i_reg && i +``` +The initial (reset) register value is `1`/`0` for `rising`/`falling` operations, respectively. +This inherently prevents triggering immediately after reset, without sampling at least two input clock cycles. + +Both Verilog and VHDL have no equivalent synthesizable shorthand syntax. +/// + +For more information see either the [design domains][design-domains] or [processes][processes] sections. + +### Constant Meta Operations {#const-meta-ops} + +Applies to: constant `Bit`/`Boolean` values + +These operations are activated during the [elaboration stage][elaboration] of the DFHDL compilation, and are only available for constant `Bit`/`Boolean` DFHDL values. +Their use case is for meta-programming purposes, to control the generated code without the knowledge of the DFHDL compiler (could be considered as pre-processing steps). + +/// html | div.operations +| Operation | Description | LHS Constraints | Returns | +| ------------ | ----------- | ------------------- | ------- | +| `lhs.toScalaBitNum` | Extracts the known elaboration Scala `BitNum`(`1 | 0`) value from a constant DFHDL `Bit`/`Boolean` value | Constant `Bit`/`Boolean` DFHDL value | Scala `BitNum` value | +| `lhs.toScalaBoolean` | Extracts the known elaboration Scala `Boolean` value from a constant DFHDL `Bit`/`Boolean` value | Constant `Bit`/`Boolean` DFHDL value | Scala `Boolean` value | +/// + +The following runnable example demonstrates how such meta operation affect the elaborated design. +The `Boolean` argument `arg` of a design `Foo` is used twice within the design: +first, in an `if` condition directly; and second, in an `if` condition after a Scala value extraction. +When referenced directly, the `if` is elaborated as-is, but when the `if` is applied on the extracted Scala value, +the `if` is completely removed and either the block inside the `if` is elaborated when the argument is true or completely removed if false. + +/// tab | `Foo` +```scala +class Foo( + val arg: Boolean <> CONST +) extends DFDesign: + val o = Bit <> OUT + if (!arg) o := 1 + if (arg.toScalaBoolean) o := 0 +``` +/// + + +/// tab | `Foo(true)` +```scala +class Foo( + val arg: Boolean <> CONST +) extends DFDesign: + val o = Bit <> OUT + if (!arg) o := 1 + o := 0 +``` +/// + +/// tab | `Foo(false)` +```scala +class Foo( + val arg: Boolean <> CONST +) extends DFDesign: + val o = Bit <> OUT + if (!arg) o := 1 +``` +/// + +/// details | Runnable example + type: dfhdl +```scastie +import dfhdl.* + +@top(false) class Foo( + val arg: Boolean <> CONST +) extends DFDesign: + val o = Bit <> OUT + if (!arg) o := 1 + if (arg.toScalaBoolean) o := 0 + +@main def main = + println("Foo(true) Elaboration:") + Foo(true).printCodeString + println("Foo(false) Elaboration:") + Foo(false).printCodeString +``` +/// + +### Vector Element Access {#vector-ops} + +Applies to: `Vector` + +```scala +val elem = vec(idx) // Read element at index +vec(idx) := newValue // Write element at index +``` + +/// html | div.operations +| Operation | Description | Returns | +| ------------ | ----------- | ------- | +| `vec(idx)` | Access element at index | Element type | +| `vec.elements` | Get all elements as Scala sequence | Seq[BaseType] | +| `vec.size` | Get vector dimension | Int | +/// + +### Double Arithmetic {#double-ops} + +Applies to: `Double` + +Supports standard arithmetic and comparison operations: + +```scala +val d1 = Double <> VAR +val d2 = Double <> VAR + +val sum = d1 + d2 +val prod = d1 * d2 +val quot = d1 / d2 +val comp = d1 < d2 +``` + From 20f674b59cebf0a4d7c27791ee234043ab84aefd Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 07:08:19 +0300 Subject: [PATCH 097/115] docs: define how Bits can be used in arithmetic --- docs/user-guide/type-system/index.md | 88 +++++++++++++++------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index d9ec81b75..b3bbf1d30 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1608,9 +1608,7 @@ Under the ED domain, the following operations are equivalent: ### Arithmetic Operations (`+`, `-`, `*`, `/`, `%`) {#arithmetic-ops} -Applies to: `UInt`, `SInt` - -The result of a standard arithmetic operation always has the **same type as the LHS** operand. The RHS is resized to match the LHS before the operation is applied. +Applies to: `UInt`, `SInt`, `Bits` (via implicit conversion to `UInt`), `Int`, `Double` (`%` not available for `Double`) /// html | div.decimal_arithmetic | Operation | Description | Returns | @@ -1622,7 +1620,9 @@ The result of a standard arithmetic operation always has the **same type as the | `lhs % rhs` | Modulo | Same type as LHS | /// -#### Type Constraints +#### Bit-Accurate Type Constraints (`UInt`, `SInt`) + +The result of a standard arithmetic operation on bit-accurate types always has the **same type as the LHS** operand. The RHS is resized to match the LHS before the operation is applied. **Sign rule** -- The LHS sign must be greater than or equal to the RHS sign (`signed >= unsigned`): @@ -1639,6 +1639,20 @@ The result of a standard arithmetic operation always has the **same type as the **Scala `Int` literals** are auto-promoted to a matching DFHDL type with the minimum required bit width. A negative `Int` literal on the RHS of a `UInt` operation is a compile error (unsigned LHS cannot accept a signed RHS). +/// admonition | `Bits` values in arithmetic + type: tip +`Bits` values are implicit `UInt` candidates, so they can participate in arithmetic directly. The compiler automatically inserts `.uint` conversions on the operands and `.bits` on the result: +```scala +val i = Bits(8) <> IN +val o = Bits(8) <> OUT +o := i + i +``` +Elaborates to: +```scala +o := (i.uint + i.uint).bits +``` +/// + ```scala val u8 = UInt(8) <> VAR val u4 = UInt(4) <> VAR @@ -1728,7 +1742,7 @@ Unlike standard arithmetic where `SInt op UInt` is allowed (the unsigned RHS is ### Comparison Operations (`==`, `!=`, `<`, `>`, `<=`, `>=`) {#comparison-ops} -Applies to: `UInt`, `SInt` (all comparisons); `Enum`, `Struct`, `Tuple` (`==`/`!=` only) +Applies to: `UInt`, `SInt`, `Int`, `Double` (all comparisons); `Enum`, `Struct`, `Tuple` (`==`/`!=` only) #### Decimal Comparisons @@ -1821,39 +1835,46 @@ val u_shifted = u >> 2 // logical right shift (zero-fills MSBs) val s_shifted = s >> 2 // arithmetic right shift (sign-extends MSBs) ``` -### `Int` Parameter Arithmetic {#int-param-arith} +### Max/Min Operations (`max`, `min`) {#max-min-ops} -Applies to: `Int` +Applies to: `Int`, `Double` -When both operands are Scala `Int` or DFHDL `Int <> CONST` values, the following operations are available. The result is always an `Int <> CONST` value. +/// html | div.operations +| Operation | Description | Returns | +| --------------- | ----------- | ------- | +| `lhs max rhs` | Maximum of two values | Same type | +| `lhs min rhs` | Minimum of two values | Same type | +/// + +```scala +val param: Int <> CONST = 2 +val t1 = 1 max param // Int <> CONST = 2 +val t2 = 1 min param // Int <> CONST = 1 +``` + +### `Int` Parameter Operations (`**`, `clog2`) {#int-param-ops} + +Applies to: `Int` (constant parameters) + +These operations are available for Scala `Int` or DFHDL `Int <> CONST` values and are primarily used for compile-time calculations such as computing bit widths. /// html | div.operations -| Operation | Description | -| --------------- | -------------- | -| `lhs + rhs` | Addition | -| `lhs - rhs` | Subtraction | -| `lhs * rhs` | Multiplication | -| `lhs / rhs` | Division | -| `lhs % rhs` | Modulo | -| `lhs ** rhs` | Power | -| `lhs max rhs` | Maximum | -| `lhs min rhs` | Minimum | +| Operation | Description | Returns | +| --------------- | ----------- | ------- | +| `lhs ** rhs` | Power (exponentiation) | `Int` | +| `clog2(value)` | Ceiling of log base 2 | `Int` | /// ```scala val param: Int <> CONST = 2 -val t1 = 1 + param // Int <> CONST = 3 -val t2 = 4 * param // Int <> CONST = 8 -val t3 = 10 / param // Int <> CONST = 5 -val t4 = 10 % param // Int <> CONST = 0 -val t5 = 3 ** param // Int <> CONST = 9 -val t6 = 1 max param // Int <> CONST = 2 -val t7 = 1 min param // Int <> CONST = 1 +val t1 = 3 ** param // Int <> CONST = 9 +val t2 = 2 ** param // Int <> CONST = 4 +val w = clog2(256) // Int = 8 (bits needed to represent 0..255) ``` /// admonition | Non-constant DFHDL `Int` values type: note -Non-constant DFHDL `Int` values (e.g., `Int <> VAR`) are possible and support the same arithmetic operations. However, they are discouraged for synthesizable designs because they map to a fixed 32-bit signed representation -- use `SInt[32]` instead for explicit control over the hardware. For simulation purposes, non-constant `Int` values are acceptable as long as the 32-bit width limitation is understood. +Non-constant DFHDL `Int` values (e.g., `Int <> VAR`) are possible and support the same arithmetic operations (`+`, `-`, `*`, `/`, `%`). However, they are discouraged for synthesizable designs because they map to a fixed 32-bit signed representation -- use `SInt[32]` instead for explicit control over the hardware. For simulation purposes, non-constant `Int` values are acceptable as long as the 32-bit width limitation is understood. /// ### History Operations {#history-ops} @@ -2012,19 +2033,4 @@ vec(idx) := newValue // Write element at index | `vec.size` | Get vector dimension | Int | /// -### Double Arithmetic {#double-ops} - -Applies to: `Double` - -Supports standard arithmetic and comparison operations: - -```scala -val d1 = Double <> VAR -val d2 = Double <> VAR - -val sum = d1 + d2 -val prod = d1 * d2 -val quot = d1 / d2 -val comp = d1 < d2 -``` From f0e6334650998f600a328b8ed1bf355a45f7d10e Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 07:08:35 +0300 Subject: [PATCH 098/115] docs: add physical operations --- docs/user-guide/type-system/index.md | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index b3bbf1d30..50d7c73d7 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1852,6 +1852,52 @@ val t1 = 1 max param // Int <> CONST = 2 val t2 = 1 min param // Int <> CONST = 1 ``` +### Physical Arithmetic (`Time`, `Freq`) {#physical-ops} + +Applies to: `Time`, `Freq` + +Physical types follow dimensional analysis rules. Operations between `Time` and `Freq` produce dimensionally correct results. + +/// html | div.operations +| Operation | LHS | RHS | Returns | +| --------- | --- | --- | ------- | +| `lhs + rhs` | `Time` | `Time` | `Time` | +| `lhs - rhs` | `Time` | `Time` | `Time` | +| `lhs * rhs` | `Time` | Number | `Time` | +| `lhs * rhs` | `Freq` | Number | `Freq` | +| `lhs * rhs` | `Time` | `Freq` | Number | +| `lhs * rhs` | `Freq` | `Time` | Number | +| `lhs / rhs` | `Time` | Number | `Time` | +| `lhs / rhs` | `Freq` | Number | `Freq` | +| `lhs / rhs` | `Time` | `Time` | Number | +| `lhs / rhs` | `Freq` | `Freq` | Number | +| `lhs / rhs` | Number | `Time` | `Freq` | +| `lhs / rhs` | Number | `Freq` | `Time` | +/// + +```scala +val period = 10.ns +val freq = 100.MHz + +// Scaling +val half_period = period / 2 // Time: 5 ns +val double_freq = freq * 2 // Freq: 200 MHz + +// Dimensional conversions +val cycles = period * freq // Number: 1.0 +val calc_freq = 1 / period // Freq: 100 MHz +val calc_period = 1 / freq // Time: 10 ns +``` + +/// admonition | Cycle-based waits in RT domains + type: tip +The `.cy` unit creates cycle-count values for use with `.wait` in register-transfer domains: +```scala +class Example extends RTDesign: + 5.cy.wait // wait 5 clock cycles +``` +/// + ### `Int` Parameter Operations (`**`, `clog2`) {#int-param-ops} Applies to: `Int` (constant parameters) From ef2b601a5a324aefeebbc724bf6616c6863f8ba4 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 07:14:55 +0300 Subject: [PATCH 099/115] docs: add more operation examples --- docs/user-guide/type-system/index.md | 41 ++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 50d7c73d7..8f526edb4 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1509,12 +1509,20 @@ Applies to: `Bits`, `UInt`, `SInt` - `.extend` automatically widens to the width expected by the context. ```scala +val b8 = Bits(8) <> VAR +val b4 = Bits(4) <> VAR +b4 := b8.resize(4) // truncate to 4 bits +b8 := b4.extend // auto-widen to match b8's width + val u8 = UInt(8) <> VAR val u6 = UInt(6) <> VAR -u6 := u8.resize(6) // explicit truncate to 6 bits u6 := u8.truncate // auto-narrow to match u6's width u8 := u6.resize(8) // explicit zero-extend to 8 bits -u8 := u6.extend // auto-widen to match u8's width + +val s8 = SInt(8) <> VAR +val s4 = SInt(4) <> VAR +s8 := s4.extend // sign-extend to match s8's width +s4 := s8.resize(4) // truncate to 4 bits ``` ### Bit Concatenation {#bit-concat} @@ -1525,6 +1533,9 @@ Multiple bit-vector values can be concatenated using Scala tuple syntax with `.t ```scala val concat = (b"100", b"1", b"0", b"11").toBits // Bits[8] +val u8 = UInt(8) <> VAR +val u4 = UInt(4) <> VAR +val wide = (u8, u4).toBits // Bits[12] ``` Values are concatenated from the first (most-significant) to the last (least-significant) position. @@ -1680,6 +1691,17 @@ val e2 = u4 + u8 // value (LHS) and a signed value (RHS). // An explicit conversion must be applied. val e3 = u8 + (-22) + +// Int arithmetic +val param: Int <> CONST = 10 +val r6 = param * 2 // Int <> CONST = 20 +val r7 = param % 3 // Int <> CONST = 1 + +// Double arithmetic +val d1 = Double <> VAR +val d2 = Double <> VAR +val r8 = d1 + d2 // Double +val r9 = d1 / d2 // Double ``` /// admonition | Overflow and automatic carry promotion @@ -1733,6 +1755,10 @@ val r3 = u8 *^ u8 // UInt[16] // Scala Int literal: 100 needs 7 bits // width = 7 + 8 = 15 val r4 = 100 *^ u8 // UInt[15] + +val s8 = SInt(8) <> VAR +val r5 = s8 +^ s8 // SInt[9] +val r6 = s8 *^ s8 // SInt[16] ``` /// admonition | Carry vs standard sign rules @@ -1812,6 +1838,10 @@ val eq = e1 == e2 // Boolean val s1 = MyStruct <> VAR val s2 = MyStruct <> VAR val eq2 = s1 == s2 // Boolean + +val t1 = (UInt(8), Bit) <> VAR +val t2 = (UInt(8), Bit) <> VAR +val eq3 = t1 == t2 // Boolean ``` ### Shift Operations (`<<`, `>>`) {#shift-ops} @@ -1828,9 +1858,11 @@ Applies to: `Bits`, `UInt`, `SInt` The `>>` operator is **type-aware**: on `UInt` and `Bits` it performs a logical (zero-filling) right shift, and on `SInt` it performs an arithmetic (sign-extending) right shift. There is no separate `>>>` operator in DFHDL -- the operand type determines the behavior. ```scala +val b = Bits(8) <> VAR val u = UInt(8) <> VAR val s = SInt(8) <> VAR +val b_shifted = b << 2 // logical left shift val u_shifted = u >> 2 // logical right shift (zero-fills MSBs) val s_shifted = s >> 2 // arithmetic right shift (sign-extends MSBs) ``` @@ -1850,6 +1882,11 @@ Applies to: `Int`, `Double` val param: Int <> CONST = 2 val t1 = 1 max param // Int <> CONST = 2 val t2 = 1 min param // Int <> CONST = 1 + +val d1 = Double <> VAR +val d2 = Double <> VAR +val t3 = d1 max d2 // Double +val t4 = d1 min d2 // Double ``` ### Physical Arithmetic (`Time`, `Freq`) {#physical-ops} From e646a26bac8fd25fc731c176db3f463a3484c1ea Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 07:20:23 +0300 Subject: [PATCH 100/115] docs: clarification on using clog2 --- docs/user-guide/type-system/index.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 8f526edb4..58e7c8ddf 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1955,6 +1955,23 @@ val t2 = 2 ** param // Int <> CONST = 4 val w = clog2(256) // Int = 8 (bits needed to represent 0..255) ``` +/// admonition | Avoid using `clog2` directly for widths + type: warning +A common anti-pattern is using `clog2` to declare the width of bit-accurate values: +```scala +// DON'T do this: +val addr = UInt(clog2(DEPTH)) <> VAR +val mask = Bits(clog2(SIZE)) <> VAR +``` +Instead, use the `.until` or `.to` constructors which handle this automatically and are more readable: +```scala +// DO this instead: +val addr = UInt.until(DEPTH) <> VAR // width = clog2(DEPTH) +val mask = Bits.until(SIZE) <> VAR // width = clog2(SIZE) +``` +See the [DFType Constructors][DFDecimal] and [Bits constructors][DFBits] sections for details on `.until` and `.to`. +/// + /// admonition | Non-constant DFHDL `Int` values type: note Non-constant DFHDL `Int` values (e.g., `Int <> VAR`) are possible and support the same arithmetic operations (`+`, `-`, `*`, `/`, `%`). However, they are discouraged for synthesizable designs because they map to a fixed 32-bit signed representation -- use `SInt[32]` instead for explicit control over the hardware. For simulation purposes, non-constant `Int` values are acceptable as long as the 32-bit width limitation is understood. From 1c61d166819a954a1139d0641791f38cc572bfda Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 07:41:30 +0300 Subject: [PATCH 101/115] docs: add guidance for multi-file scala-cli projects --- docs/getting-started/hello-world/index.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/getting-started/hello-world/index.md b/docs/getting-started/hello-world/index.md index aceae8d67..948e256d1 100644 --- a/docs/getting-started/hello-world/index.md +++ b/docs/getting-started/hello-world/index.md @@ -73,6 +73,23 @@ scala run . For more information, please run `scala run --help` or consult the [online documentation](https://scala-cli.virtuslab.org/docs/commands/run){target="_blank"}. +/// admonition | Multi-file projects + type: tip +In a scala-cli project with multiple `.scala` files, shared `given` declarations (such as compiler options) must appear in exactly one file. Place them in your `project.scala` file to avoid duplicate definition errors. + +```scala title="project.scala" +//> using scala 3.8.1 +//> using dep io.github.dfianthdl::dfhdl::0.17.0 +//> using plugin io.github.dfianthdl:::dfhdl-plugin:0.17.0 + +import dfhdl.* +given options.CompilerOptions.Backend = backends.verilog +given options.CompilerOptions.PrintBackendCode = true +``` + +Individual design files should `import dfhdl.*` but not redeclare the shared `given` options. +/// + ### sbt Project The best way to get started with a DFHDL sbt project is to clone our template from GitHub: From 97cae07f03ff4c129e0d929b3507ae3258ff709c Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 11:14:04 +0300 Subject: [PATCH 102/115] docs: wip verilog transitioning chapter --- docs/transitioning/from-verilog/index.md | 106 ++++++++++------------- 1 file changed, 47 insertions(+), 59 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 228f8da35..4acad01b1 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -1,10 +1,8 @@ # Transitioning from Verilog to DFHDL -## Using ChatGPT +This guide helps Verilog/SystemVerilog users translate common patterns into DFHDL. For full type system details, see the [Type System reference][type-system]. -Help me ChatGPT, you're my only hope - -## Summary +## Design Structure /// admonition | Module Definition type: verilog @@ -111,6 +109,8 @@ val v = Bits(8) <> VAR init b"8’1011" /// +## Types and Literals + /// admonition | Numeric Literals type: verilog DFHDL uses string interpolators for sized literals. Each type has its own interpolator -- do not mix Verilog base prefixes (`’b`, `’d`, `’h`) inside them. @@ -136,12 +136,16 @@ d"5’27" // 5-bit unsigned decimal `b"..."` accepts only binary digits (`0`, `1`, `?`). Writing `b"5’d27"` is an error -- use `d"5’27"` for decimal values. /// -/// admonition | Elaboration-Time Computation (System Functions) +/// admonition | `$clog2` and Width Computation type: verilog -Instead of computing widths manually with `$clog2`, use `UInt.until` / `UInt.to` (or `Bits.until` / `Bits.to`) which set the width automatically based on the value range: +Instead of computing widths manually with `$clog2`, use `.until` or `.to` constructors which set the width automatically: + +| Verilog | DFHDL | Width | +|---------|-------|-------| +| `$clog2(N)` | `UInt.until(N)` / `Bits.until(N)` | `clog2(N)` bits, valid for N >= 2 | +| `$clog2(N+1)` | `UInt.to(N)` / `Bits.to(N)` | `clog2(N+1)` bits, valid for N >= 1 | -- `.until(sup)` — width = `clog2(sup)` (value can be 0 to sup-1) -- `.to(max)` — width = `clog2(max+1)` (value can be 0 to max) +`UInt.until(1)` is **invalid** (would produce 0-bit width). For counters that count 0 to N inclusive (common with `$clog2(N+1)`), use `UInt.to(N)`.
@@ -149,18 +153,27 @@ Instead of computing widths manually with `$clog2`, use `UInt.until` / `UInt.to` parameter RATE = 5208; localparam WIDTH = $clog2(RATE); reg [WIDTH-1:0] counter; + +// Counter 0..SCALE inclusive +reg [$clog2(SCALE+1)-1:0] cnt = 0; ``` ```scala linenums="0" title="DFHDL" val RATE: Int <> CONST = 5208 val counter = UInt.until(RATE) <> VAR + + +// Counter 0..SCALE inclusive +val cnt = UInt.to(SCALE) <> VAR init 0 ```
-See [UInt constructors](../../user-guide/type-system/index.md#unsigned-integer-uint) and [Bits constructors](../../user-guide/type-system/index.md#bit-vector-bits) for details. +See [UInt/SInt constructors][DFDecimal] and [Bits constructors][DFBits] for details. See also the [`clog2` anti-pattern warning][int-param-ops]. /// +## Processes and Sequential Logic + /// admonition | Process Blocks (always) type: verilog Verilog `always` blocks map to DFHDL ED domain `process(...)` blocks. @@ -303,25 +316,10 @@ process(clk): -If the Verilog state values are non-sequential, use `Encoded.Manual` (see [Manual Encoding](../../user-guide/type-system/index.md#DFEnum)). Avoid modelling FSM states as `Bits` constants -- `match` does not support matching on `Bits <> CONST` names. Use `enum extends Encoded` instead, or fall back to `if`/`else if` chains. +If the Verilog state values are non-sequential, use `Encoded.Manual` (see [Enumeration][DFEnum]). Avoid modelling FSM states as `Bits` constants -- `match` does not support matching on `Bits <> CONST` names. Use `enum extends Encoded` instead, or fall back to `if`/`else if` chains. /// -/// admonition | Multi-File Projects - type: verilog -In a scala-cli project with multiple `.scala` files, shared `given` declarations (such as compiler options) must appear in exactly one file. Place them in your `project.scala` file to avoid duplicate definition errors. - -```scala title="project.scala" -//> using scala 3.8.1 -//> using dep io.github.dfianthdl::dfhdl::0.17.0 -//> using plugin io.github.dfianthdl:::dfhdl-plugin:0.17.0 - -import dfhdl.* -given options.CompilerOptions.Backend = backends.verilog -given options.CompilerOptions.PrintBackendCode = true -``` - -Individual design files should `import dfhdl.*` but not redeclare the shared `given` options. -/// +## Operations /// admonition | Shift Operators type: verilog @@ -347,7 +345,7 @@ out := data_signed >> 2 -There is no `>>>` operator in DFHDL. The type of the LHS determines the shift semantics: `>>` on `UInt`/`Bits` zero-fills, `>>` on `SInt` sign-extends. +There is no `>>>` operator in DFHDL. The type of the LHS determines the shift semantics: `>>` on `UInt`/`Bits` zero-fills, `>>` on `SInt` sign-extends. See [Shift Operations][shift-ops] for details. /// /// admonition | UInt/SInt Conversion @@ -376,19 +374,23 @@ offset := sd"8'180" - cnt_s * 5 -Conversion summary: `uint_val.bits.sint` for UInt-to-SInt, `sint_val.bits.uint` for SInt-to-UInt. See [Type Conversion](../../user-guide/type-system/index.md#type-conversion) for details. +Conversion options: + +- **UInt → SInt (width+1):** `uint_val.signed` — adds a sign bit, widening by 1. Preferred when you can accept the extra bit. +- **UInt → SInt (same width):** `uint_val.bits.sint` — reinterprets the bit pattern without widening. +- **SInt → UInt (same width):** `sint_val.bits.uint` — reinterprets the bit pattern. + +See [Conversions and Casts][type-conversion] for the full conversion reference. /// /// admonition | Bit/Boolean Operators: `|`/`&` and `||`/`&&` type: verilog -In DFHDL, `||` and `&&` are equivalent to `|` and `&`, respectively, when applied on `Bit` or `Boolean` types. In the generated Verilog, the operator depends on the LHS type: `Bit` produces `|`/`&`, `Boolean` produces `||`/`&&`. - +In DFHDL, `||`/`&&` and `|`/`&` are interchangeable on `Bit` and `Boolean` types. The generated Verilog operator depends on the LHS type: `Bit` produces bitwise `|`/`&`, `Boolean` produces logical `||`/`&&`.
```sv linenums="0" title="Verilog" input a, b, c; output o1, o2, o3; -// same result for 1-bit assign o1 = a | b | c; assign o2 = a | b | c; assign o3 = a || b || c; @@ -397,20 +399,18 @@ assign o3 = a || b || c; ```scala linenums="0" title="DFHDL" val a, b, c = Bit <> IN val o1, o2, o3 = Bit <> OUT - -// Both are equivalent for Bit LHS: o1 <> a | b | c o2 <> a || b || c -// a.bool makes the LHS Boolean, so RHS -// auto-converts to Boolean and the result -// auto-converts back to Bit. -// Produces || in Verilog. o3 <> a.bool || b || c ```
+ +See [Logical Operations][logical-ops] for the full reference and Verilog/VHDL mapping tables. /// +## Common Pitfalls + /// admonition | Scala Reserved Keywords as DFHDL Port or Variable Names type: verilog Some Verilog port names (`val`, `type`, `class`, `match`, `case`, `object`, etc.) are reserved in Scala. Use backtick escaping: @@ -440,7 +440,6 @@ Alternatively, use a non-keyword name with the Scala `@targetName` annotation to module foo( output logic signed [15:0] class ); - `include "dfhdl_defs.svh" assign class = 16'sd42; endmodule ``` @@ -514,7 +513,9 @@ Without parentheses, `out := if (sel) a else b` causes a parse error. Either wra /// admonition | Unsigned Literal Minus Signed Expression type: verilog -In Verilog, `2 - signed_expr` works because integer literals are implicitly 32-bit. In DFHDL, a plain `2` is unsigned and cannot be subtracted from by a wider signed value. Restructure as negation plus addition: +In Verilog, `2 - signed_expr` works because integer literals are implicitly 32-bit signed. In DFHDL, a plain `2` is unsigned, so `2 - signed_val` is a compile error (unsigned LHS cannot accept signed RHS). + +The simplest fix is to use a signed literal with `sd"..."`:
@@ -524,15 +525,17 @@ err <= 2 - (2 * r0); ``` ```scala linenums="0" title="DFHDL" -// Restructure: negate then add -err :== (-(r0_wide + r0_wide) + sd"2").truncate - -// Or make the signed literal wide enough: +// Use a signed literal wide enough for the result: val two = sd"${CORDW+2}'2" err :== (two - (r0_wide + r0_wide)).truncate + +// Or restructure as negation + addition: +err :== (-(r0_wide + r0_wide) + sd"2").truncate ```
+ +See [Arithmetic type constraints][arithmetic-ops] for the sign and width rules. /// /// admonition | Parametric Bit-Vector Constants @@ -561,22 +564,7 @@ taps_b <> TAPS.bits(LEN-1, 0) Note: `.bits(hi, lo)` is an extension method on `Int <> CONST` DFHDL values. It does **not** work on plain Scala `Int`. If you have a compile-time constant, pass it as an `Int <> CONST` parameter, or use a hex/binary literal directly: `h"21'140000"`. /// -/// admonition | `$clog2` Mapping: `.until` vs `.to` - type: verilog -When translating Verilog `$clog2` expressions, choose the right constructor: - -| Verilog | DFHDL | Bits | -|---------|-------|------| -| `$clog2(N)` | `UInt.until(N)` | `clog2(N)` bits, valid for N >= 2 | -| `$clog2(N+1)` | `UInt.to(N)` | `clog2(N+1)` bits, valid for N >= 1 | - -`UInt.until(1)` is **invalid** (would produce 0-bit width). For counters that count 0 to N inclusive (common with `$clog2(N+1)`), use `UInt.to(N)`: - -```scala linenums="0" title="DFHDL" -// Verilog: reg [$clog2(SCALE+1)-1:0] cnt = 0; -val cnt = UInt.to(SCALE) <> VAR init 0 -``` -/// +## FSM and Enum Patterns /// admonition | Enum FSM and `unique case` type: verilog From 93b70c84709e90df81334b5de00169f91d029d67 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 11:41:03 +0300 Subject: [PATCH 103/115] docs: improved FSM example --- docs/transitioning/from-verilog/index.md | 77 +++++------------------- 1 file changed, 16 insertions(+), 61 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 4acad01b1..dce08e862 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -280,43 +280,38 @@ Verilog FSMs typically use `parameter` constants and `case`/`if` chains. In DFHD
```sv linenums="0" title="Verilog" -parameter IDLE = 2'b00, - START = 2'b01, - DATA = 2'b10, - STOP = 2'b11; -reg [1:0] state = IDLE; +parameter READY = 2'b00, + AIM = 2'b01, + FIRE = 2'b10; +reg [1:0] state = READY; always @(posedge clk) case (state) - IDLE: if (go) state <= START; - START: state <= DATA; - DATA: state <= STOP; - STOP: state <= IDLE; + READY: if (go) state <= AIM; + AIM: state <= FIRE; + FIRE: state <= READY; + default: state <= READY; endcase ``` ```scala linenums="0" title="DFHDL" enum State extends Encoded: - case Idle, Start, Data, Stop - -val state = State <> VAR init State.Idle + case Ready, Aim, Fire +import State.* +val state = State <> VAR init Ready process(clk): if (clk.rising) state match - case State.Idle => - if (go) state :== State.Start - case State.Start => - state :== State.Data - case State.Data => - state :== State.Stop - case State.Stop => - state :== State.Idle + case Ready => if (go) state :== Aim + case Aim => state :== Fire + case Fire => state :== Ready + case _ => state :== Ready ```
-If the Verilog state values are non-sequential, use `Encoded.Manual` (see [Enumeration][DFEnum]). Avoid modelling FSM states as `Bits` constants -- `match` does not support matching on `Bits <> CONST` names. Use `enum extends Encoded` instead, or fall back to `if`/`else if` chains. +If the encoded Verilog state values have no standard pattern (incremental, gray, one-hot), use `Encoded.Manual` (see [Enumeration][DFEnum]). Avoid modeling FSM states as `Bits` or `UInt` constants, it's an anti-pattern. When compiling to SystemVerilog (SV), the SV enums are being utilized as well. /// ## Operations @@ -564,43 +559,3 @@ taps_b <> TAPS.bits(LEN-1, 0) Note: `.bits(hi, lo)` is an extension method on `Int <> CONST` DFHDL values. It does **not** work on plain Scala `Int`. If you have a compile-time constant, pass it as an `Int <> CONST` parameter, or use a hex/binary literal directly: `h"21'140000"`. /// -## FSM and Enum Patterns - -/// admonition | Enum FSM and `unique case` - type: verilog -When you use `enum extends Encoded` with `match` in DFHDL, the generated SystemVerilog uses `unique case`. This has implications for formal verification: - -- **Exhaustive enums** (all bit patterns used, e.g., 4 states in 2 bits): `unique case` is safe because every possible value has a branch. -- **Sparse enums** (not all bit patterns used, e.g., 3 states in 2 bits): `unique case` has no `default` for the unused bit patterns. Formal tools may find counterexamples for unreachable states. - -If the original Verilog FSM has a `default` branch that handles invalid/unreachable states, use `if`/`else if` chains with a final `else` instead of `match` on an enum: - -
- -```sv linenums="0" title="Verilog (3 states, has default)" -case (state) - IDLE: ... - DATA: ... - STOP: ... - default: state <= IDLE; -endcase -``` - -```scala linenums="0" title="DFHDL (if/else for default coverage)" -// Use Bits constants + if/else if/else -val IDLE = b"2'00" -val DATA = b"2'01" -val STOP = b"2'10" -val state = Bits(2) <> VAR init IDLE - -if (state == IDLE) ... -else if (state == DATA) ... -else if (state == STOP) ... -else state :== IDLE // covers 2'b11 -``` - -
- -Use `enum` + `match` when the enum is exhaustive or when `default` coverage is not needed. -/// - From 8eed1803183956b40754ab719c05061b76ddf555 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 12:27:13 +0300 Subject: [PATCH 104/115] docs: bounded and unbounded types and signatures --- docs/user-guide/type-system/index.md | 137 +++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 58e7c8ddf..4d5fe88db 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -517,6 +517,108 @@ val x = b8 ++ h"FF" //ok val y = b8 ++ all(0) //error ``` +## Type Signatures and Parameterization {#type-sigs} + +Every DFHDL value has a type of the form `T <> M`, where `T` is the DFHDL type (shape) and `M` is the modifier that determines how the value can be used. + +### Modifier Categories + +Modifiers fall into two groups: + +**Declaration modifiers** — used in `val` declarations with the `<>` operator: + +- `VAR`, `VAR.REG`, `VAR.SHARED` — variables +- `IN`, `OUT`, `OUT.REG`, `INOUT` — ports + +**Type signature modifiers** — used in type annotations for parameters, struct fields, and method signatures: + +- `CONST` — compile-time or elaboration-time constant parameter +- `VAL` — read-only value (struct fields, method parameters) +- `DFRET` / `RTRET` / `EDRET` — method return types (DF, RT, or ED domain) + +### Design Parameters + +Design classes accept parameters as constructor arguments using `<> CONST`: + +```scala +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) +- Typed constants like `Bits[8] <> CONST` and `UInt[8] <> CONST` are also possible +- Default values are optional + +### `VAL` Modifier + +`VAL` marks a read-only value. It is used for: + +- **Struct field declarations:** + ```scala + case class Point(x: UInt[8] <> VAL, y: UInt[8] <> VAL) extends Struct + ``` +- **Method/design-def parameters:** + ```scala + def increment(x: UInt[8] <> VAL): UInt[8] <> DFRET = x + 1 + ``` + +`VAL` values cannot be assigned or connected — they are inputs to the computation. + +### Design Defs and `DFRET` + +Design defs are functional helpers. Arguments use `<> VAL`, return types use `<> DFRET` (or `RTRET`/`EDRET` for domain-specific defs): + +```scala +// DF domain design def +def double(value: Bits[Int] <> VAL): Bits[Int] <> DFRET = (value, value) + +// Opaque type extension method +extension (c: Counter <> VAL) + def increment: Counter <> DFRET = (c.actual + 1).as(Counter) +``` + +### Bounded and Unbounded Types {#bounded-unbounded} + +DFHDL types carry their size (width or length) as a Scala type parameter. There are three levels of size specificity: + +**Bounded** — the size is a literal singleton known at compile time. All type checks happen statically: + +```scala +val a: UInt[8] <> CONST = d"255" +val b: Bits[4] <> CONST = h"A" +val v: Bits[8] X 4 <> CONST = all(all(0)) +``` + +**Parameterized bounded** — the size is the singleton type of a named parameter. The compiler can track the relationship, even though the concrete value isn't known until instantiation: + +```scala +class Foo(val w: Int <> CONST) extends RTDesign: + val x: Bits[w.type] <> CONST = all(0) // width tied to parameter w + val y = UInt[w.type] <> VAR init 0 // same + val v: UInt[4] X w.type <> CONST = all(0) // vector length tied to w +``` + +**Unbounded** — the size is bare `Int`, with no compile-time size information. Used when the type is too complex to express at the Scala type level (e.g., results of operations on parameterized types). The DFHDL compiler still has the required size information available during elaboration, where it is checked: + +```scala +val cu: UInt[Int] <> VAL = 1 +val cs: SInt[Int] <> VAL = -1 +val bv: Bits[8] X Int <> CONST = Vector(h"12", h"34") +def twice(value: Bits[Int] <> VAL): Bits[Int] <> DFRET = (value, value) +``` + +/// admonition | Struct fields must be bounded + type: warning +Struct field types cannot be unbounded. Each field must have a concrete or parameterized-bounded type: +```scala +// CORRECT: bounded fields +case class Pkt(header: Bits[8] <> VAL, data: UInt[32] <> VAL) extends Struct + +// ERROR: unbounded fields are not allowed +// case class Bad(data: Bits[Int] <> VAL) extends Struct +``` +/// + ## DFHDL Value Types ### `Bit`/`Boolean` {#DFBitOrBool} @@ -544,6 +646,9 @@ val c_bit: Bit <> CONST = 1 val c_bool: Boolean <> CONST = false ``` +#### Type Signatures +`Bit` and `Boolean` have no size parameter. Type signatures: `Bit <> CONST`, `Bit <> VAL`, `Boolean <> VAL`, etc. + #### Candidates * DFHDL `Bit` values. @@ -656,6 +761,11 @@ val b6: Bits[6] <> CONST = all(0) * __Additional constructors:__ DFHDL provides additional constructs to simplify some common VHDL bit vector declaration. For example, instead of declaring `signal addr: std_logic_vector(clog2(DEPTH)-1 downto 0)` in VHDL, in DFHDL simply declare `val addr = Bits.until(DEPTH) <> VAR`. /// +#### Type Signatures +- Bounded: `Bits[8]`, `Bits[4]` +- Parameterized bounded: `Bits[w.type]` (where `w: Int <> CONST`) +- Unbounded: `Bits[Int]` + #### Literal (Constant) Value Generation Literal (constant) DFHDL `Bits` value generation is carried out through [binary][b-interp] and [hexadecimal][h-interp] string interpolation, a core [Scala feature](https://docs.scala-lang.org/scala3/book/string-interpolation.html){target="_blank"} that was customized for DFHDL's exact use-case. There are also bit-accurate [decimal][d-interp] and [signed decimal][sd-interp] interpolations available that produce `UInt` and `SInt` DFHDL values. If needed, those values can be cast to `Bits`. No octal interpolation is currently available or planned. @@ -859,6 +969,12 @@ DFHDL provides three decimal numeric types: | `Int`| Construct a constant integer DFType. Used mainly for parameters. | None | `Int` DFType | /// +#### Type Signatures +- Bounded: `UInt[8]`, `SInt[16]` +- Parameterized bounded: `UInt[w.type]`, `SInt[w.type]` (where `w: Int <> CONST`) +- Unbounded: `UInt[Int]`, `SInt[Int]` +- `Int` has no size parameter: `Int <> CONST`, `Int <> VAL` + #### Candidates * DFHDL decimal values of the same type * DFHDL `Bits` values (via `.uint` or `.sint` casting) @@ -950,6 +1066,9 @@ enum MyEnum extends Encoded: case A, B, C, D ``` +#### Type Signatures +`MyEnum <> VAL`, `MyEnum <> CONST`. The enum name itself is the type — no size parameter. + #### Encoding Types DFHDL supports several encoding schemes for enums: @@ -1041,6 +1160,12 @@ val vec2 = Bit X 8 X 8 <> VAR // 2D 8x8 vector of bits val vec3 = MyEnum X 16 <> VAR // Vector of 16 enum values ``` +#### Type Signatures +- Bounded: `UInt[8] X 4`, `Bits[8] X 4 X 4` +- Parameterized bounded: `UInt[4] X len.type` (where `len: Int <> CONST`) +- Unbounded: `Bits[8] X Int` +- Both element type and dimensions can be parameterized independently + #### Initialization Vectors can be initialized in several ways: @@ -1114,6 +1239,9 @@ case class MyStruct( ) extends Struct ``` +#### Type Signatures +`MyStruct <> VAL`, `MyStruct <> CONST`. The struct name is the type. Struct fields must use bounded types (no `UInt[Int]` in fields). + #### Field Access and Assignment Fields are accessed using dot notation and can be assigned individually: @@ -1181,6 +1309,9 @@ DFHDL tuples provide a way to group multiple DFHDL values together without defin val tuple = (Type1, Type2, ..., TypeN) <> Modifier ``` +#### Type Signatures +`(UInt[8], Bit) <> VAL`, `(Bits[Int], Bit) <> CONST`. Elements can be individually bounded or unbounded. + #### Examples ```scala @@ -1224,6 +1355,9 @@ case class Counter() extends Opaque(UInt(32)): (c.actual + 1).as(Counter) ``` +#### Type Signatures +`MyOpaque <> VAL`, `MyOpaque <> CONST`. The opaque name is the type. + #### Usage ```scala @@ -1259,6 +1393,9 @@ DFHDL Double values represent IEEE-754 double-precision floating-point numbers. val d = Double <> Modifier ``` +#### Type Signatures +`Double <> VAL`, `Double <> CONST`. No size parameter (always 64 bits). + ### Time/Freq {#DFPhysical} DFHDL provides special types for representing time and frequency values in hardware designs through physical units. These types help ensure correct timing specifications and frequency calculations. From c7668af5b5e1190b79de9c2d0edfe3bafd729cbf Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 12:52:03 +0300 Subject: [PATCH 105/115] docs: document Selection `.sel` operation --- docs/transitioning/from-verilog/index.md | 20 ++++----- docs/user-guide/type-system/index.md | 56 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index dce08e862..2d5b9fe81 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -476,7 +476,7 @@ Note: `UInt` and `SInt` can be initialized or assigned with plain integers. /// admonition | Inline Conditional Expressions type: verilog -Verilog's ternary operator `cond ? a : b` maps to Scala's `if`/`else`, but in process blocks the inline form requires parentheses: +Verilog's ternary operator `cond ? a : b` has three DFHDL equivalents:
@@ -488,22 +488,20 @@ always @(*) ``` ```scala linenums="0" title="DFHDL" -// Continuous: use <> with inline if -out <> (if (sel) a else b) +// 1. Using .sel (closest to ternary) +out <> sel.sel(a, b) -// In process blocks: wrap in parentheses -process(all): - out := (if (sel) a else b) +// 2. Inline if/else (wrap in parentheses) +out <> (if (sel) a else b) -// Or use statement form: -process(all): - if (sel) out := a - else out := b +// 3. Statement form +if (sel) out := a +else out := b ```
-Without parentheses, `out := if (sel) a else b` causes a parse error. Either wrap the `if` in parentheses or use the statement form. +The `.sel` method compiles directly to Verilog's ternary operator. For complex nested conditions, prefer `if`/`else` or `match` over chaining `.sel` calls. See [Selection (.sel)][sel-ops] for details. /// /// admonition | Unsigned Literal Minus Signed Expression diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 4d5fe88db..708f0eea6 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1754,6 +1754,62 @@ Under the ED domain, the following operations are equivalent: | `!lhs` | `not lhs` | /// +### Selection (`.sel`) {#sel-ops} + +Condition: `Bit`, `Boolean`. Arguments: any DFHDL type. + +The `.sel` operation is a conditional selection — equivalent to Verilog's ternary operator `cond ? onTrue : onFalse`. It selects between two values based on a `Bit` or `Boolean` condition: + +/// html | div.operations +| Operation | Description | Returns | +| --------- | ----------- | ------- | +| `cond.sel(onTrue, onFalse)` | Select `onTrue` when `cond` is true/1, `onFalse` otherwise | Same type as the arguments | +/// + +The `onTrue` and `onFalse` arguments can be any DFHDL type — `UInt`, `SInt`, `Bits`, `Enum`, `Struct`, etc. They can also be Scala literals constant parameters. The result type is determined by whichever argument is a DFHDL value (the other is auto-converted via type conversion): + +```scala +val flag = Boolean <> VAR +val u8 = UInt(8) <> VAR + +// Select between two literals +val r1 = flag.sel(11, d"4'12") // UInt[4]: 11 if true, 12 if false + +// Select between DFHDL values +val r2 = flag.sel(u8, d"8'0") // UInt[8]: u8 if true, 0 if false + +// Select with Int parameters +val c1: Int <> CONST = 1 +val c2: Int <> CONST = 2 +val r3 = flag.sel(c1, c2) // Int: c1 if true, c2 if false + +// Select with other types +val e = flag.sel(MyEnum.A, MyEnum.B) // MyEnum +``` + +/// admonition | Prefer `if`/`match` for complex conditions + type: tip +For simple one-level selections, `.sel` is concise and maps directly to Verilog's ternary. However, nesting or chaining `.sel` operations (e.g., `a.sel(b.sel(x, y), z)`) quickly becomes unreadable. For complex conditional logic, use `if`/`else` or `match` expressions instead — they are clearer and produce equivalent hardware. +/// + +/// details | Transitioning from Verilog + type: verilog +The `.sel` operation compiles to Verilog's ternary operator: + +| DFHDL | Verilog | +|-------|---------| +| `cond.sel(a, b)` | `cond ? a : b` | +/// + +/// details | Transitioning from VHDL + type: vhdl +VHDL has no equivalent to Verilog's ternary expression. The DFHDL-generated VHDL package includes `bool_sel` functions that implement this behavior, with dedicated overloads generated for each type as required. + +| DFHDL | Generated VHDL | +|-------|----------------| +| `cond.sel(a, b)` | `bool_sel(cond, a, b)` | +/// + ### Arithmetic Operations (`+`, `-`, `*`, `/`, `%`) {#arithmetic-ops} Applies to: `UInt`, `SInt`, `Bits` (via implicit conversion to `UInt`), `Int`, `Double` (`%` not available for `Double`) From 8e85c2d7da8fc53b1e869d2bc6c6a9b79a87de5d Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 12:58:35 +0300 Subject: [PATCH 106/115] docs: fix confusing example --- docs/transitioning/from-verilog/index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 2d5b9fe81..ca0da0f69 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -481,21 +481,21 @@ Verilog's ternary operator `cond ? a : b` has three DFHDL equivalents:
```sv linenums="0" title="Verilog" -assign out = sel ? a : b; +assign out = cond ? a : b; always @(*) - out = sel ? a : b; + out = cond ? a : b; ``` ```scala linenums="0" title="DFHDL" // 1. Using .sel (closest to ternary) -out <> sel.sel(a, b) +out <> cond.sel(a, b) // 2. Inline if/else (wrap in parentheses) -out <> (if (sel) a else b) +out <> (if (cond) a else b) // 3. Statement form -if (sel) out := a +if (cond) out := a else out := b ``` From c198db3d397bdc0969ce87633c37f36ff1df056c Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 14:10:05 +0300 Subject: [PATCH 107/115] fix: parameterized bit selection --- .../scala/StagesSpec/PrintCodeStringSpec.scala | 16 ++++++++++++++++ core/src/main/scala/dfhdl/core/DFVal.scala | 9 +++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index baed9341f..aefc3a242 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -1702,4 +1702,20 @@ class PrintCodeStringSpec extends StageSpec: |end Foo""".stripMargin ) } + + test("parameterized selection") { + class Foo extends DFDesign: + val LEN: Int <> CONST = 8 + val v = Bits(LEN) <> VAR + val o = v(LEN - 2, 0) + val top = (new Foo).getCodeString + assertNoDiff( + top, + """|class Foo extends DFDesign: + | val LEN: Int <> CONST = 8 + | val v = Bits(LEN) <> VAR + | val o = v(LEN - 2, 0) + |end Foo""".stripMargin + ) + } end PrintCodeStringSpec diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index 6fe9830cb..29e030417 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -775,8 +775,13 @@ object DFVal extends DFValLP: if (prevLHSArg.isAnonymous) Some(prevLHSArg.setMeta(_ => dfc.getMeta)) else Some(prevLHSArg) else - dfc.mutableDB.setMember(prevRHSArg, _.copy(data = Some(newRHSData))) - Some(dfc.mutableDB.setMember(prevFunc, _.copy(meta = dfc.getMeta))) + // Clone prevFunc to avoid destructively modifying shared IR nodes + val clonedFunc = prevFunc.cloneAnonValueAndDepsHere + .asInstanceOf[ir.DFVal.Func] + val clonedRHSArg = clonedFunc.args.last.get + .asInstanceOf[ir.DFVal.Const] + dfc.mutableDB.setMember(clonedRHSArg, _.copy(data = Some(newRHSData))) + Some(dfc.mutableDB.setMember(clonedFunc, _.copy(meta = dfc.getMeta))) case _ => None end match case _ => None From af57708ee91f02eb41153e644bab794fb662f2cb Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 14:38:27 +0300 Subject: [PATCH 108/115] docs: provide unbounded bits and parametric dependency example --- docs/transitioning/from-verilog/index.md | 70 +++++++++--------------- 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index ca0da0f69..7c32889af 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -60,32 +60,41 @@ parameter [7:0] p = 8’b1011; ```scala linenums="0" title="DFHDL" val p: Bits[8] <> CONST = b"8'1011" ``` +
+ +Inter-dependent parameters and ports example: + +
```sv linenums="0" title="Verilog" -module Concat #( - parameter int len1; - parameter int len2; - localparam int outlen = len1 + len2 -) ( - input [len1-1:0] i1; - input [len2-1:0] i2; - output [outlen-1:0] o +module Concat#( + parameter int len1 = 8, + parameter int len2 = 8, + parameter logic [7:0] midVec = 8'h55 +)( + input wire logic [len1 - 1:0] i1, + input wire logic [len2 - 1:0] i2, + output logic [outlen - 1:0] o ); - assign o = {i1, i2}; + localparam int midLen = 8; + localparam int outlen = len1 + midLen + len2; + assign o = {i1, midVec, i2}; endmodule ``` ```scala linenums="0" title="DFHDL" class Concat( - val len1: Int <> CONST - val len2: Int <> CONST + val len1: Int <> CONST = 8, + val len2: Int <> CONST = 8, + val midVec: Bits[Int] <> CONST = h"55" ) extends EDDesign: - val outlen = len1 + len2 - val i1 = Bits(len1) <> IN - val i2 = Bits(len2) <> IN - val o = Bits(outlen) <> OUT - - o <> (i1, i2) + val midLen = midVec.width + val outlen = len1 + midLen + len2 + val i1 = Bits(len1) <> IN + val i2 = Bits(len2) <> IN + val o = Bits(outlen) <> OUT + + o <> (i1, midVec, i2) end Concat ``` @@ -530,30 +539,3 @@ err :== (-(r0_wide + r0_wide) + sd"2").truncate See [Arithmetic type constraints][arithmetic-ops] for the sign and width rules. /// - -/// admonition | Parametric Bit-Vector Constants - type: verilog -DFHDL does not support `Bits` CONST parameters whose width depends on another parameter. Use `Int <> CONST` and extract bits internally: - -
- -```sv linenums="0" title="Verilog" -parameter LEN=8; -parameter TAPS=8'b10111000; -// TAPS width depends on LEN -``` - -```scala linenums="0" title="DFHDL" -val LEN: Int <> CONST = 8 -val TAPS: Int <> CONST = 0xB8 - -// Extract bits internally -val taps_b = Bits(LEN) <> VAR -taps_b <> TAPS.bits(LEN-1, 0) -``` - -
- -Note: `.bits(hi, lo)` is an extension method on `Int <> CONST` DFHDL values. It does **not** work on plain Scala `Int`. If you have a compile-time constant, pass it as an `Int <> CONST` parameter, or use a hex/binary literal directly: `h"21'140000"`. -/// - From fc2d57831cd7f169006b27ff389f7170f30b8295 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 14:48:41 +0300 Subject: [PATCH 109/115] docs: improve shift example --- docs/transitioning/from-verilog/index.md | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 7c32889af..7b0399d75 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -332,19 +332,25 @@ Verilog has separate `>>` (logical) and `>>>` (arithmetic) right shift operators
```sv linenums="0" title="Verilog" -// Logical right shift (unsigned) -out = data >> 2; - -// Arithmetic right shift (signed) -out = $signed(data) >>> 2; +module RightShifter( + input wire logic [7:0] data, + output logic [7:0] logical, + output logic [7:0] arith +); + assign logical = data >> 1; + assign arith = data >>> 1; +endmodule ``` ```scala linenums="0" title="DFHDL" -// Logical right shift (UInt or Bits) -out := data >> 2 - -// Arithmetic right shift (SInt) -out := data_signed >> 2 +class RightShifter extends EDDesign: + val data = Bits(8) <> IN + val logical = Bits(8) <> OUT + val arith = Bits(8) <> OUT + + logical <> data >> 1 + arith <> (data.sint >> 1).bits +end RightShifter ```
From 98153eb8cf2b0ff67400b320ff7d75afec826246 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 14:53:27 +0300 Subject: [PATCH 110/115] docs: removed problematic type examples, since there is much more information in type-system docs --- docs/transitioning/from-verilog/index.md | 60 ------------------------ 1 file changed, 60 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 7b0399d75..9ded7fe1f 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -358,40 +358,6 @@ end RightShifter There is no `>>>` operator in DFHDL. The type of the LHS determines the shift semantics: `>>` on `UInt`/`Bits` zero-fills, `>>` on `SInt` sign-extends. See [Shift Operations][shift-ops] for details. /// -/// admonition | UInt/SInt Conversion - type: verilog -Verilog implicitly converts between signed and unsigned in mixed expressions. DFHDL requires explicit conversion through `Bits`: - -
- -```sv linenums="0" title="Verilog" -reg [2:0] counter; // unsigned -reg signed [7:0] offset; - -// Verilog auto-converts -offset = 180 - 5 * counter; -``` - -```scala linenums="0" title="DFHDL" -val counter = UInt(3) <> VAR -val offset = SInt(8) <> VAR - -// Must convert UInt -> SInt explicitly -// UInt -> .bits -> .sint -> .resize -val cnt_s = counter.bits.sint.resize(8) -offset := sd"8'180" - cnt_s * 5 -``` - -
- -Conversion options: - -- **UInt → SInt (width+1):** `uint_val.signed` — adds a sign bit, widening by 1. Preferred when you can accept the extra bit. -- **UInt → SInt (same width):** `uint_val.bits.sint` — reinterprets the bit pattern without widening. -- **SInt → UInt (same width):** `sint_val.bits.uint` — reinterprets the bit pattern. - -See [Conversions and Casts][type-conversion] for the full conversion reference. -/// /// admonition | Bit/Boolean Operators: `|`/`&` and `||`/`&&` type: verilog @@ -519,29 +485,3 @@ else out := b The `.sel` method compiles directly to Verilog's ternary operator. For complex nested conditions, prefer `if`/`else` or `match` over chaining `.sel` calls. See [Selection (.sel)][sel-ops] for details. /// -/// admonition | Unsigned Literal Minus Signed Expression - type: verilog -In Verilog, `2 - signed_expr` works because integer literals are implicitly 32-bit signed. In DFHDL, a plain `2` is unsigned, so `2 - signed_val` is a compile error (unsigned LHS cannot accept signed RHS). - -The simplest fix is to use a signed literal with `sd"..."`: - -
- -```sv linenums="0" title="Verilog" -// Works: 2 is 32-bit integer -err <= 2 - (2 * r0); -``` - -```scala linenums="0" title="DFHDL" -// Use a signed literal wide enough for the result: -val two = sd"${CORDW+2}'2" -err :== (two - (r0_wide + r0_wide)).truncate - -// Or restructure as negation + addition: -err :== (-(r0_wide + r0_wide) + sd"2").truncate -``` - -
- -See [Arithmetic type constraints][arithmetic-ops] for the sign and width rules. -/// From 88cf299ce6e790e4487284e94511ad1ee3380396 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 3 Apr 2026 15:52:58 +0300 Subject: [PATCH 111/115] docs: parametric Bits constants, dynamic index truncation, Int bits slicing, inline-if parentheses Addresses knowledge gaps reported by learner agents: - Parametric-width Bits constants: use Bits[Int] <> CONST (closes #15, #20) - Dynamic bit indexing: .truncate for wider-than-needed index (closes #12) - Int <> CONST .bits(hi, lo) slicing note (closes #24) - Inline if parenthesization with := and <> examples (closes #21) Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/transitioning/from-verilog/index.md | 51 ++++++++++++++++++++++++ docs/user-guide/type-system/index.md | 12 ++++++ 2 files changed, 63 insertions(+) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 9ded7fe1f..d10acec33 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -385,6 +385,40 @@ o3 <> a.bool || b || c See [Logical Operations][logical-ops] for the full reference and Verilog/VHDL mapping tables. /// +## Parametric Constants + +/// admonition | Parametric-Width Bits Constants + type: verilog +Verilog parameters can be bit-vector constants whose width depends on another parameter. In DFHDL, use `Bits[Int] <> CONST` (unbounded width) for the parameter. The width is inferred from the default literal value, and other local values can derive their widths from it: + +
+ +```sv linenums="0" title="Verilog" +module MyDesign #( + parameter WIDTH = 8, + parameter [WIDTH-1:0] MASK = 8'hB8 +)( + output logic [WIDTH-1:0] data +); + // ... +endmodule +``` + +```scala linenums="0" title="DFHDL" +class MyDesign( + val WIDTH: Int <> CONST = 8, + val MASK: Bits[Int] <> CONST = h"B8" +) extends EDDesign: + // MASK.width gives the actual width + val data = Bits(MASK.width) <> OUT + // ... +``` + +
+ +See the [Parameter Declarations](#parameter-declarations) section above for a complete inter-dependent parameters example. +/// + ## Common Pitfalls /// admonition | Scala Reserved Keywords as DFHDL Port or Variable Names @@ -483,5 +517,22 @@ else out := b
The `.sel` method compiles directly to Verilog's ternary operator. For complex nested conditions, prefer `if`/`else` or `match` over chaining `.sel` calls. See [Selection (.sel)][sel-ops] for details. + +When using inline `if`/`else` as the RHS of `:=` or `:==`, **parentheses are required**. Without them, Scala 3 parses the `if` as a statement, not an expression: + +```scala +// CORRECT: parenthesized inline if with := and <> +out := (if (cond) a else b) +out <> (if (cond) a else b) + +// PARSE ERROR: bare inline if on RHS of := or <> +// out := if (cond) a else b // "end of statement expected" + +// CORRECT: statement form (no parentheses needed) +if (cond) out := a +else out := b +``` + +This applies to all assignment operators (`:=`, `:==`, `<>`). Use `.sel` or the parenthesized form for inline conditionals; use the statement form for multi-assignment branches. /// diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 708f0eea6..f9cde09f7 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1635,6 +1635,13 @@ process(clk): if (clk.rising) data(pos) :== din // dynamic write ``` + +When the index variable is wider than needed, use `.truncate` to automatically narrow it to the required width: +```scala +val data = Bits(8) <> VAR init all(0) +val pos = UInt(4) <> VAR init 0 // 4-bit, but Bits(8) needs UInt(3) +val bit_out = data(pos.truncate) // .truncate narrows to UInt(3) automatically +``` /// ### Width Adjustment {#width-adjustment} @@ -2170,6 +2177,11 @@ See the [DFType Constructors][DFDecimal] and [Bits constructors][DFBits] section Non-constant DFHDL `Int` values (e.g., `Int <> VAR`) are possible and support the same arithmetic operations (`+`, `-`, `*`, `/`, `%`). However, they are discouraged for synthesizable designs because they map to a fixed 32-bit signed representation -- use `SInt[32]` instead for explicit control over the hardware. For simulation purposes, non-constant `Int` values are acceptable as long as the 32-bit width limitation is understood. /// +/// admonition | Slicing bits from a DFHDL `Int` + type: note +To extract a partial bit range from a DFHDL `Int` value, first convert it to `Bits` using `.bits`, then apply the slice: `myInt.bits(hi, lo)`. This is a `.bits` conversion followed by `(hi, lo)` slicing. The `.bits` conversion is a DFHDL extension method available on DFHDL `Int <> CONST` values, not on plain Scala `Int`. +/// + ### History Operations {#history-ops} Applies to: `Bit` (`.rising`, `.falling`) From c8d6a788dee6a1b45f5b7af1f38e62355e86545a Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 3 Apr 2026 18:09:19 +0300 Subject: [PATCH 112/115] docs: reduction ops, Bits comparisons, part-select, signed arithmetic, Encoded.Manual, integer case, .truncate warning, UInt.to vs .until Closes #25 #26 #27 #29 #30 #32 #33 #34 #35 #36 #37 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/transitioning/from-verilog/index.md | 132 ++++++++++++++++++++++- docs/user-guide/type-system/index.md | 67 +++++++++++- 2 files changed, 197 insertions(+), 2 deletions(-) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index d10acec33..c53154e7d 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -178,6 +178,11 @@ val cnt = UInt.to(SCALE) <> VAR init 0 +/// admonition | Choosing `.until(N)` vs `.to(N)` for counters + type: warning +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]. /// @@ -320,7 +325,49 @@ process(clk): -If the encoded Verilog state values have no standard pattern (incremental, gray, one-hot), use `Encoded.Manual` (see [Enumeration][DFEnum]). Avoid modeling FSM states as `Bits` or `UInt` constants, it's an anti-pattern. When compiling to SystemVerilog (SV), the SV enums are being utilized as well. +If the encoded Verilog state values follow a standard pattern (incremental, gray, one-hot), use the corresponding `Encoded` variant. For non-standard encodings, use `Encoded.Manual` with a constructor parameter: + +```scala +// Verilog: parameter INIT=3'b000, RUN=3'b011, DONE=3'b101, ERR=3'b110; +enum Phase(val value: UInt[3] <> CONST) extends Encoded.Manual(3): + case Init extends Phase(0) + case Run extends Phase(3) + case Done extends Phase(5) + case Err extends Phase(6) +``` + +The constructor parameter `(val value: UInt[N] <> CONST)` is required for `Encoded.Manual` and the bit width `N` must match the argument to `Encoded.Manual(N)`. Omitting it causes a compile error. See [Enumeration][DFEnum] for all encoding options. + +Avoid modeling FSM states as `Bits` or `UInt` constants -- it is an anti-pattern. When compiling to SystemVerilog (SV), the SV enums are being utilized as well. +/// + +/// admonition | Integer `case` Statements (non-enum) + type: verilog +When the Verilog `case` selector is a plain integer counter (not an FSM), use `match` with integer literal cases directly: + +
+ +```sv linenums="0" title="Verilog" +case (sel) + 'd0: out <= 60; + 'd1: out <= 110; + 'd2 | 'd3: out <= 200; + default: out <= 0; +endcase +``` + +```scala linenums="0" title="DFHDL" +sel match + case 0 => out :== 60 + case 1 => out :== 110 + case 2 | 3 => out :== 200 + case _ => out :== 0 +end match +``` + +
+ +Integer literal pattern matching works with `UInt` and `SInt` selectors. Guard conditions (`case _ if sel == N`) also work but are less idiomatic. See [Match Expressions][match-expressions] for full details. /// ## Operations @@ -385,6 +432,89 @@ o3 <> a.bool || b || c See [Logical Operations][logical-ops] for the full reference and Verilog/VHDL mapping tables. /// +/// admonition | Reduction Operators (`&v`, `|v`, `^v`) + type: verilog +Verilog's unary reduction operators have direct DFHDL equivalents using postfix `.&`, `.|`, `.^` on `Bits` values: + +
+ +```sv linenums="0" title="Verilog" +logic [7:0] v; +logic all_set = &v; // AND reduce +logic any_set = |v; // OR reduce +logic parity = ^v; // XOR reduce +logic not_all = ~&v; // NAND reduce +logic none_set = ~|v; // NOR reduce +``` + +```scala linenums="0" title="DFHDL" +val v = Bits(8) <> VAR +val all_set = v.& // Bit: AND reduce +val any_set = v.| // Bit: OR reduce +val parity = v.^ // Bit: XOR reduce +val not_all = !v.& // Bit: NAND reduce +val none_set = !v.| // Bit: NOR reduce +``` + +
+ +See [Bit Reduction Operations][reduction-ops] for full details. +/// + +/// 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)`: + +| Verilog | DFHDL | Notes | +|---------|-------|-------| +| `sig[base -: W]` | `sig(base, base - W + 1)` | Descending slice from `base`, `W` bits | +| `sig[base +: W]` | `sig(base + W - 1, base)` | Ascending slice from `base`, `W` bits | +| `sig[N-1 -: W]` | `sig.msbits(W)` or `sig(N-1, N-W)` | Top `W` bits | +| `sig[0 +: W]` | `sig.lsbits(W)` or `sig(W-1, 0)` | Bottom `W` bits | +| `sig[idx]` | `sig(idx)` | Single bit access | + +
+ +```sv linenums="0" title="Verilog" +logic [15:0] data; +logic [3:0] top4 = data[15 -: 4]; +logic [3:0] bot4 = data[0 +: 4]; +logic bit5 = data[5]; +``` + +```scala linenums="0" title="DFHDL" +val data = Bits(16) <> VAR +val top4 = data.msbits(4) // top 4 bits +val bot4 = data.lsbits(4) // bottom 4 bits +val bit5 = data(5) // single bit +``` + +
+/// + +/// 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: + +```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) +``` + +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 diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index f9cde09f7..8d766c44b 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1669,6 +1669,19 @@ s8 := s4.extend // sign-extend to match s8's width s4 := s8.resize(4) // truncate to 4 bits ``` +/// admonition | `.truncate` is not `.resize(N)` -- do not pass an argument to `.truncate` + type: warning +`.truncate` (no argument) narrows the width to match the assignment context. There is no `.truncate(N)` method. Writing `expr.truncate(N)` is parsed by Scala as `expr.truncate` followed by `(N)`, which applies bit selection on the truncated result -- selecting a single bit at index `N`. This causes confusing errors like "argument must be smaller than the upper-bound". To keep the lowest `N` bits, use `.resize(N)`: + +```scala +val b8 = Bits(8) <> VAR +val b4 = Bits(4) <> VAR +b4 := b8.truncate // OK: auto-narrow from 8 to 4 bits +b4 := b8.resize(4) // OK: explicit narrow to 4 bits +// b8.truncate(4) // MISLEADING: this is b8.truncate followed by bit-select (4) +``` +/// + ### Bit Concatenation {#bit-concat} Applies to: `Bits`, `UInt`, `SInt` @@ -1761,6 +1774,41 @@ Under the ED domain, the following operations are equivalent: | `!lhs` | `not lhs` | /// +### Bit Reduction Operations (`.&`, `.|`, `.^`) {#reduction-ops} + +Applies to: `Bits`, `UInt` (via implicit conversion to `Bits`) + +Reduction operators fold all bits of a `Bits` vector into a single `Bit` value. They are the DFHDL equivalents of Verilog's unary reduction operators (`&v`, `|v`, `^v`): + +/// html | div.operations +| Operation | Description | Returns | +| --------- | ----------- | ------- | +| `bits.&` | AND reduction -- `1` if all bits are `1` | `Bit` | +| `bits.|` | OR reduction -- `1` if any bit is `1` | `Bit` | +| `bits.^` | XOR reduction -- `1` if an odd number of bits are `1` (parity) | `Bit` | +/// + +```scala +val b8 = Bits(8) <> VAR +val allSet = b8.& // Bit: 1 when all bits are 1 +val anySet = b8.| // Bit: 1 when at least one bit is 1 +val parity = b8.^ // Bit: 1 when odd number of bits are 1 +``` + +/// details | Transitioning from Verilog + type: verilog + +| Verilog | DFHDL | Notes | +|---------|-------|-------| +| `&v` (AND reduce) | `v.&` | All bits must be `1` | +| `|v` (OR reduce) | `v.|` | At least one bit is `1` | +| `^v` (XOR reduce) | `v.^` | Parity (odd number of `1`s) | +| `~&v` (NAND reduce) | `!v.&` | Not all bits are `1` | +| `~|v` (NOR reduce) | `!v.|` | No bits are `1` | +| `~^v` (XNOR reduce) | `!v.^` | Even parity | + +/// + ### Selection (`.sel`) {#sel-ops} Condition: `Bit`, `Boolean`. Arguments: any DFHDL type. @@ -1968,7 +2016,7 @@ Unlike standard arithmetic where `SInt op UInt` is allowed (the unsigned RHS is ### Comparison Operations (`==`, `!=`, `<`, `>`, `<=`, `>=`) {#comparison-ops} -Applies to: `UInt`, `SInt`, `Int`, `Double` (all comparisons); `Enum`, `Struct`, `Tuple` (`==`/`!=` only) +Applies to: `UInt`, `SInt`, `Int`, `Double` (all comparisons); `Bits`, `Enum`, `Struct`, `Tuple` (`==`/`!=` only) #### Decimal Comparisons @@ -2026,6 +2074,23 @@ if (counter == LIMIT - 1) // Int <> CONST compared with UInt -- works directly ``` /// +#### Bits Comparisons + +`Bits` values support `==` and `!=` with other `Bits` values of the same width, with `all(0)`, `all(1)`, or with sized literals (`d"..."`, `h"..."`, `b"..."`). Plain Scala `Int` literals cannot be compared directly with `Bits` -- use a sized literal or convert to `.uint` first: + +```scala +val b8 = Bits(8) <> VAR +val isAllOnes = b8 == all(1) // Boolean: all bits are 1 +val isAllZeros = b8 == all(0) // Boolean: all bits are 0 +val isMatch = b8 == h"B0" // Boolean: exact match with hex literal +val isDec = b8 == d"8'12" // Boolean: match with sized decimal + +// ERROR: An integer value cannot be a candidate for a Bits type. +// val bad = b8 == 0 +// FIX: use all(0), a sized literal, or convert to UInt first: +// b8 == all(0) OR b8 == d"8'0" OR b8.uint == 0 +``` + #### Enum, Struct, and Tuple Comparisons Enums, structs, and tuples support equality comparisons (`==` and `!=`) between values of the same type: From dd04f98475ee9fac31dc4272c0cf13c6c8d28cc3 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Fri, 3 Apr 2026 18:18:26 +0300 Subject: [PATCH 113/115] docs: emphasize state machine with reset --- docs/transitioning/from-verilog/index.md | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index c53154e7d..779cb3749 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -338,6 +338,40 @@ enum Phase(val value: UInt[3] <> CONST) extends Encoded.Manual(3): The constructor parameter `(val value: UInt[N] <> CONST)` is required for `Encoded.Manual` and the bit width `N` must match the argument to `Encoded.Manual(N)`. Omitting it causes a compile error. See [Enumeration][DFEnum] for all encoding options. +When the Verilog FSM uses an explicit reset instead of `initial`, omit the `init` and handle reset inside the clocked process: + +
+ +```sv linenums="0" title="Verilog" +always @(posedge clk) + if (rst) + state <= READY; + else + case (state) + READY: if (go) state <= AIM; + AIM: state <= FIRE; + FIRE: state <= READY; + default: state <= READY; + endcase +``` + +```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 +``` + +
+ Avoid modeling FSM states as `Bits` or `UInt` constants -- it is an anti-pattern. When compiling to SystemVerilog (SV), the SV enums are being utilized as well. /// From 4a2bb290f6e5b14415c4d33263c17c50da9007d8 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sat, 4 Apr 2026 04:13:35 +0300 Subject: [PATCH 114/115] fix: prevent wrong alias reduction. fixes https://github.com/DFiantHDL/DFHDL/issues/364 --- .../scala/StagesSpec/PrintCodeStringSpec.scala | 15 +++++++++++++++ core/src/main/scala/dfhdl/core/DFVal.scala | 7 +++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index aefc3a242..beeebd8dd 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -1718,4 +1718,19 @@ class PrintCodeStringSpec extends StageSpec: |end Foo""".stripMargin ) } + + test("cascade aliasing regression") { + class Foo() extends DFDesign: + val x = UInt(3) <> IN + val y = x.resize(16).bits.sint + end Foo + val top = (new Foo).getCodeString + assertNoDiff( + top, + """|class Foo extends DFDesign: + | val x = UInt(3) <> IN + | val y = x.resize(16).bits.sint + |end Foo""".stripMargin + ) + } end PrintCodeStringSpec diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index 29e030417..5984877c7 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -836,9 +836,12 @@ object DFVal extends DFValLP: ) ).asVal[AT, M] // remove redundant intermediate casting when the final result needs to be `.bits` anyways - case asIs: ir.DFVal.Alias.AsIs + // as long as the alias is anonymous and has the same width as the related value, + // to avoid modifying the semantics of named values that can be referenced in multiple places. + case asIs @ ir.DFVal.Alias.AsIs(relValRef = ir.DFRef(relValIR)) if aliasType.asIR.isInstanceOf[ir.DFBits] && asIs.isAnonymous && - dfc.isAnonymous && !forceNewAlias && asIs.tags.isEmpty => + dfc.isAnonymous && !forceNewAlias && asIs.tags.isEmpty && + relValIR.width == asIs.width => asIs.relValRef.get.asVal[AT, M] // remove redundant intermediate casting converting from BoolOrBit to Bits/UInt/SInt + resize case asIs @ ir.DFVal.Alias.AsIs( From 099f99a0b582bc0d722e5e12f20803766d2b7fb0 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sat, 4 Apr 2026 05:10:57 +0300 Subject: [PATCH 115/115] fix: proper signed parameterized literal generation in verilog. fixes https://github.com/DFiantHDL/DFHDL/issues/366 --- .../stages/verilog/VerilogDataPrinter.scala | 6 ++++-- .../StagesSpec/PrintVerilogCodeSpec.scala | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogDataPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogDataPrinter.scala index 6f4b5b875..2e27a6ed0 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogDataPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogDataPrinter.scala @@ -42,9 +42,11 @@ protected trait VerilogDataPrinter extends AbstractDataPrinter: def csDFSIntFormatBig(value: BigInt, width: IntParamRef): String = val csWidth = width.refCodeString.applyBrackets() if (width.isRef) - if (value.isValidInt && allowWidthCastSyntax) s"""${csWidth}'($value)""" + val actualWidth = value.bitsWidth(true) + if (value.isValidInt && allowWidthCastSyntax) + if (value >= 0) s"""${csWidth}'($actualWidth'sd$value)""" + else s"${csWidth}'(-$actualWidth'sd${-value})" else - val actualWidth = value.bitsWidth(true) if (allowWidthCastSyntax && printer.allowSignedKeywordAndOps) if (value >= 0) s"""${csWidth}'($actualWidth'sd$value)""" else s"""${csWidth}'(-$actualWidth'sd${-value})""" diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 16a1eecd4..4cf1700c3 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -1598,4 +1598,22 @@ class PrintVerilogCodeSpec extends StageSpec: |endmodule""".stripMargin ) } + test("signed literal with parametric width") { + class Foo( + val WIDTH: Int <> CONST = 8 + ) extends EDDesign: + val arg = sd"${WIDTH}'2" + end Foo + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module Foo#(parameter int WIDTH = 8); + | `include "dfhdl_defs.svh" + | localparam logic signed [WIDTH - 1:0] arg = WIDTH'(3'sd2); + |endmodule""".stripMargin + ) + } end PrintVerilogCodeSpec