From e47f07d071ad52fbc478bfa9d0cee2fa481356c6 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 12 Aug 2026 21:43:43 +0300 Subject: [PATCH 01/57] compiler/stages: an async reset whose `else` is a chain gets a clock edge branch of its own `VerilogProcToVHDL` Rule 2 only recognized an if-reset-else-clock process whose `else` was a single guard-less branch, so it could hand that branch the clock edge in place. An `else` holding nothing but a conditional is not that shape: it reads as `if (rst) ... else if (c) ...`, leaving no branch to carry the edge. The rule fell through, the process kept its edge-style sensitivity list, and the backend printed `process (rising_edge(clk), rising_edge(rst))`, which is not legal VHDL. Nothing reported it, and the Verilog backend was unaffected, so it only surfaced downstream. That shape is the ordinary way to write an FSM under an async reset, so the rule now covers the whole chain the reset heads. A guard-less `else` still takes the edge in place; otherwise a fresh `else if (clk.)` branch is chained after the reset branch and the rest of the chain is nested inside it under a header of its own. Restricted to a conditional statement, since restructuring the chain of a conditional expression would detach its branches from the header whose value they produce. Re-homing the tail needs both its ownership and its chain link redirected, which two reference patches on one member cannot express. The chain link is redirected by replacing what it points at instead, scoped with a `RefFilter` to the chain head alone, keeping it to a single patch list. Co-Authored-By: Claude Opus 5 (1M context) --- .../compiler/stages/VerilogProcToVHDL.scala | 126 ++++++++++++++---- .../StagesSpec/VerilogProcToVHDLSpec.scala | 63 +++++++++ 2 files changed, 162 insertions(+), 27 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/VerilogProcToVHDL.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/VerilogProcToVHDL.scala index 8b79a025f..40ebb5376 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/VerilogProcToVHDL.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/VerilogProcToVHDL.scala @@ -48,6 +48,31 @@ import dfhdl.compiler.printing.* * y := x * }}} * + * When the `else` branch holds nothing but a conditional, it is not a guard-less branch at all: + * the chain reads as `if (rst) ... else if (c) ... else ...`, and there is no branch left to + * carry the clock edge. A fresh `else if (clk.)` branch is chained after the reset branch + * instead, and everything below the reset branch is nested inside it as a chain of its own: + * {{{ + * // Before + * process(clk.rising, rst.rising): + * if (rst) + * y := 0 + * else if (c) + * y := x + * else + * y := z + * + * // After + * process(clk, rst): + * if (rst) + * y := 0 + * else if (clk.rising) + * if (c) + * y := x + * else + * y := z + * }}} + * * ==Rule 3: Async reset, reset-at-the-end== * When the reset condition is a separate `if` statement placed last in the process (overriding * the clocked assignments), the clocked statements are wrapped by a clock edge guard while the @@ -125,11 +150,13 @@ case object VerilogProcToVHDL extends HierarchyStage: sensRemoveList :+ dsn.patch case List(e1, e2) if SensSignal(e1._2) != SensSignal(e2._2) => pb.members(MemberView.Folded).collect { case b: DFIfElseBlock => b } match - // Rule 2: async reset in an if-reset-else-clock structure => the plain - // `else` branch becomes an `else if (clk.)` branch - case rstIfBlock :: elseBlock :: Nil - if elseBlock.getFirstCB == rstIfBlock && - elseBlock.guardRef.get == DFMember.Empty => + // Rule 2: async reset in an if-reset-else-clock structure => everything below + // the reset branch is placed under the clock edge. The reset `if` must head a + // chain that covers every conditional block at the process level, so that the + // clock edge accounts for all the process does when the reset is inactive. + case blocks @ (rstIfBlock :: tailBlocks) + if tailBlocks.nonEmpty && rstIfBlock.isFirstCB && + blocks.forall(_.getFirstCB == rstIfBlock) => rstIfBlock.guardRef.get match case RstActive(rstGuardSig, _) => splitClkRst(rstGuardSig) match @@ -142,28 +169,73 @@ case object VerilogProcToVHDL extends HierarchyStage: rstSig.cloneAnonValueAndDepsHere.asValAny ) )(using dfc.setMeta(pb.meta)) - // the clock edge guard for the `else` branch, physically placed - // between the reset branch and the `else` branch (its ownership - // reference resolves to `newPB` since `dsn` replaces `pb` first - // in the patch list) - val guardDsn = - new MetaDesign(rstIfBlock, Patch.Add.Config.After, domainType = ED): - import dfhdl.core.refTW - val clkEdgeSig = clkEdge match - case ClkCfg.Edge.Rising => - clkSig.cloneAnonValueAndDepsHere.asValOf[Bit].rising - case ClkCfg.Edge.Falling => - clkSig.cloneAnonValueAndDepsHere.asValOf[Bit].falling - val newGuardRef: DFConditional.Block.GuardRef = - clkEdgeSig.asIR.refTW[DFIfElseBlock] - sensRemoveList ++ List( - dsn.patch, - guardDsn.patch, - elseBlock -> Patch.Replace( - elseBlock.copy(guardRef = guardDsn.newGuardRef), - Patch.Replace.Config.FullReplacement - ) - ) + tailBlocks match + // Rule 2a: a plain `else` branch carries the clock edge itself + case elseBlock :: Nil if elseBlock.guardRef.get == DFMember.Empty => + // the clock edge guard for the `else` branch, physically placed + // between the reset branch and the `else` branch (its ownership + // reference resolves to `newPB` since `dsn` replaces `pb` first + // in the patch list) + val guardDsn = + new MetaDesign(rstIfBlock, Patch.Add.Config.After, domainType = ED): + import dfhdl.core.refTW + val clkEdgeSig = clkEdge match + case ClkCfg.Edge.Rising => + clkSig.cloneAnonValueAndDepsHere.asValOf[Bit].rising + case ClkCfg.Edge.Falling => + clkSig.cloneAnonValueAndDepsHere.asValOf[Bit].falling + val newGuardRef: DFConditional.Block.GuardRef = + clkEdgeSig.asIR.refTW[DFIfElseBlock] + sensRemoveList ++ List( + dsn.patch, + guardDsn.patch, + elseBlock -> Patch.Replace( + elseBlock.copy(guardRef = guardDsn.newGuardRef), + Patch.Replace.Config.FullReplacement + ) + ) + // Rule 2b: the branches below the reset form a chain of their own, so + // there is no guard-less branch to carry the clock edge. A new + // `else if (clk.)` branch is chained after the reset branch and + // the tail chain is re-homed inside it, under a header of its own. + // Restricted to a conditional *statement*: restructuring the chain of + // a conditional expression would detach its branches from the header + // whose value they produce. + case tailHead :: _ if rstIfBlock.getHeaderCB.dfType == DFUnit => + // placed like the Rule 2a guard, between the reset branch and the + // tail chain, so the flat member list stays a pre-order traversal + // (the tail chain follows the branch that now owns it) + val clkDsn = + new MetaDesign(rstIfBlock, Patch.Add.Config.After, domainType = ED): + import dfhdl.core.{DFIf, DFUnit} + import dfhdl.core.DFOwner.asFE + val clkEdgeSig = clkEdge match + case ClkCfg.Edge.Rising => + clkSig.cloneAnonValueAndDepsHere.asValOf[Bit].rising + case ClkCfg.Edge.Falling => + clkSig.cloneAnonValueAndDepsHere.asValOf[Bit].falling + val clkBlock = DFIf.Block(Some(clkEdgeSig), rstIfBlock.asFE) + dfc.enterOwner(clkBlock) + val nestedHeader = DFIf.Header(DFUnit) + dfc.exitOwner() + val clkBlockIR = clkBlock.asIR + val nestedHeaderIR = nestedHeader.asIR + sensRemoveList ++ List( + dsn.patch, + clkDsn.patch, + // the tail's head now heads the chain nested in the clock edge + // branch. Its owner reference is handled below, so the chain + // reference is redirected by replacing what it points at, scoped + // to the head alone (the reset branch keeps every other + // reference, including its own members' ownership) + rstIfBlock -> Patch.Replace( + clkDsn.nestedHeaderIR, + Patch.Replace.Config.ChangeRefOnly, + Patch.Replace.RefFilter.OfMembers(Set(tailHead)) + ) + ) ++ tailBlocks.map(_ -> Patch.ChangeOwner(clkDsn.clkBlockIR)) + case _ => None + end match case None => None case _ => None // Rule 3: async reset as a final `if (rst)` statement => the clocked diff --git a/compiler/stages/src/test/scala/StagesSpec/VerilogProcToVHDLSpec.scala b/compiler/stages/src/test/scala/StagesSpec/VerilogProcToVHDLSpec.scala index fe9d0323c..c45298532 100644 --- a/compiler/stages/src/test/scala/StagesSpec/VerilogProcToVHDLSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/VerilogProcToVHDLSpec.scala @@ -169,6 +169,69 @@ class VerilogProcToVHDLSpec extends StageSpec: |""".stripMargin ) } + test("clock and reset, else branch is a conditional chain") { + class ID extends EDDesign: + val clk = Bit <> IN + val rst = Bit <> IN + val a = Bit <> IN + val b = Bit <> IN + val x1 = SInt(16) <> IN + val y1 = SInt(16) <> OUT + val y2 = SInt(16) <> OUT + val y3 = SInt(16) <> OUT + // an `else` holding nothing but an if/else reads as an `else if` chain, so there is no + // guard-less branch left to carry the clock edge + val proc1 = process(clk.rising, rst.rising): + if (rst) y1 := 0 + else + if (a) y1 := x1 + else y1 := 1 + // a longer chain, and a chain with no trailing `else` at all + val proc2 = process(clk.rising, rst.rising): + if (rst) y2 := 0 + else + if (a) y2 := x1 + else if (b) y2 := 1 + else y2 := 2 + val proc3 = process(clk.falling, rst.falling): + if (rst == 0) y3 := 0 + else + if (a) y3 := x1 + end ID + val id = (new ID).verilogProcToVHDL + assertCodeString( + id, + """|class ID extends EDDesign: + | val clk = Bit <> IN + | val rst = Bit <> IN + | val a = Bit <> IN + | val b = Bit <> IN + | val x1 = SInt(16) <> IN + | val y1 = SInt(16) <> OUT + | val y2 = SInt(16) <> OUT + | val y3 = SInt(16) <> OUT + | val proc1 = process(clk, rst): + | if (rst) y1 := sd"16'0" + | else if (clk.rising) + | if (a) y1 := x1 + | else y1 := sd"16'1" + | end if + | val proc2 = process(clk, rst): + | if (rst) y2 := sd"16'0" + | else if (clk.rising) + | if (a) y2 := x1 + | else if (b) y2 := sd"16'1" + | else y2 := sd"16'2" + | end if + | val proc3 = process(clk, rst): + | if (rst == 0) y3 := sd"16'0" + | else if (clk.falling) + | if (a) y3 := x1 + | end if + |end ID + |""".stripMargin + ) + } test("clock and reset at the end") { class ClkRstGen extends EDDesign: val clk = Bit <> OUT From 847a1a1017ea1e51a2ab3cfa7642e3c9052ddc1f Mon Sep 17 00:00:00 2001 From: Oron Port Date: Wed, 12 Aug 2026 21:50:14 +0300 Subject: [PATCH 02/57] skills+core: two reference changes on one member want a ref filter, not a second patch phase Banks what the `VerilogProcToVHDL` fix turned up, all of it general enough to catch the next stage author rather than specific to that stage. The `/new-stage` skill gains a recipe for the collision that makes a second `db.patch()` phase look inevitable: a member needing two of its references redirected at once. `Patch.ChangeRef` reaches the member-list patch table like any other patch, so two on one member throw, and `Replace + ChangeRef` does not merge either. The way out is to redirect one of them by replacing what the reference points at, keyed on the old target with `ChangeRefOnly` and a `RefFilter` narrowing it to the holder. `ChangeRefOnly` is dropped from the patch table outright, so it cannot collide even with an Add already keyed on that target. Two mistakes join the list. A `Patch.Replace` cannot carry a new `ownerRef`, since `replaceMember` keeps only `repMember.getRefs` and `getRefs` excludes ownership: the minted reference is purged and the next `getOwner` dies with an unrecognizable `key not found`, usually inside a later stage. And matching a conditional chain by arity skips the most common spelling, because an `else` holding nothing but a conditional flattens into an `else if` chain. `RefFilter.OfMembers` was documented as matching references *to* the given members, which is backwards: `originMember` is the member holding the reference, as its `Outside`/`Inside` siblings already say. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/new-stage.md | 60 ++++++++++++++++++- .../scala/dfhdl/compiler/patching/Patch.scala | 3 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index 8cf02daf8..6a4ee3274 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -350,6 +350,41 @@ original's ref objects. `NamedAliases` uses exactly this to name a value and lift it out of a conditional expression branch atomically, which it must, since `SanityCheck` would reject the intermediate DB. +### Recipe: TWO reference changes on one member, in one patch + +Re-homing a member usually means redirecting more than one of its references at once (its +`ownerRef` plus a structural link, say). `Patch.ChangeRef` reaches the member-list patch table like +any other patch (the `case x => Some(x)` fall-through), so two of them on one member throw +`Received two different patches for the same member`, and `Replace + ChangeRef` is not in the merge +table either. **This is not a reason to add a second `db.patch()` phase.** + +Redirect one of them by replacing **what the reference points at**, keyed on the OLD TARGET and +scoped to the holder, so the two patches never share a key: + +```scala +List( + // ownerRef: the supported mechanism, keyed on the member + member -> Patch.ChangeOwner(newOwner), + // the other reference: keyed on what it currently points at, narrowed to this holder alone + oldTarget -> Patch.Replace( + newTarget, + Patch.Replace.Config.ChangeRefOnly, + Patch.Replace.RefFilter.OfMembers(Set(member)) + ) +) +``` + +Two properties make it safe. `ChangeRefOnly` is dropped from the member-list patch table outright +(`case (_, Patch.Replace(config = ChangeRefOnly)) => None`), so it cannot collide even when another +patch — a `MetaDesign` Add, say — is already keyed on `oldTarget`. And `RefFilter` narrows the +redirect to the references you mean: **`OfMembers` matches on `r.originMember`, the member HOLDING +the reference, not the member referenced** (`Outside`/`Inside` filter the same side). So every other +reference to `oldTarget` survives untouched, including its own members' `ownerRef`s. + +`VerilogProcToVHDL` Rule 2b uses this to re-home a conditional chain into a newly created branch: +`ChangeOwner` moves the blocks, while the chain head's `prevBlockOrHeaderRef` is re-pointed at a +fresh nested header by replacing the reset block it used to follow, scoped to that head. + ### `Patch.Add` via `MetaDesign` Use `MetaDesign` when you need to construct new IR members using the DFHDL frontend DSL: @@ -1368,7 +1403,10 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) stage's *own output legal*, a separate stage is not an option either: `SanityCheck` runs after every stage, so the DB in between would be invalid. It has to be the same patch. Check the merge table before concluding that is impossible, and see the - *replace AND relocate in one patch* recipe for the case that looks unmergeable but is not. + *replace AND relocate in one patch* and *TWO reference changes on one member* recipes for the + cases that look unmergeable but are not. Both cover a same-member collision that the merge + table genuinely rejects, which is exactly the point where the second phase starts to look + inevitable and is not. 27. **Substituting into a cloned expression tree AFTER cloning it inverts the member order** — `cloneAnonValueAndDepsHere` builds each dependency before the value that reads it, which is the only order the flat member list accepts. If you then walk the finished clone and `newRefFor` a @@ -1408,6 +1446,26 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) stages: extract it to a shared analysis class (`RTDomainAnalysis`) and have BOTH consume it, so they cannot drift. The bugfix skill's "twin helpers drift" warning applies doubly when the twins live in different stages. +31. **`Patch.Replace` cannot carry a new `ownerRef`** — `replaceMember` is called with + `keepRefs = repMember.getRefs`, and `getRefs` deliberately excludes `ownerRef`, so an owner + reference freshly minted in a `MetaDesign` (via `dfc.ownerOrEmptyRef` or `.ref`) and attached + to `member.copy(ownerRef = ...)` is purged from the ref table. The failure surfaces far away + and unrecognizably, as `NoSuchElementException: key not found: "OW_…"` from the next + `getOwner` — often inside a later stage such as `OrderMembers`. Change ownership with + `Patch.ChangeOwner` (or let a `ReplaceWithLast` bulk redirect cover it), and see the + *TWO reference changes on one member* recipe when that leaves you needing a second reference + change on the same member. +32. **Matching a conditional chain by arity is fragile** — an `else` branch whose entire body is a + single conditional does not stay a guard-less `else`: it flattens into an `else if` chain, so + `pb.members(Folded).collect { case b: DFIfElseBlock => b }` yields three blocks for + `if (a) … else { if (b) … else … }`, and two *guarded* blocks when the inner `if` has no + `else`. A pattern like `case first :: second :: Nil` therefore silently skips the most common + real-world spelling (any FSM under an async reset), and a stage that silently skips prints its + input unconverted. Match the whole chain (`case blocks @ (head :: tail)` plus + `blocks.forall(_.getFirstCB == head)` to confirm they are one chain and nothing else at that + level), then branch on the tail's shape. Guard any rewrite that restructures the chain with + `head.getHeaderCB.dfType == DFUnit`: the same block shapes serve conditional *expressions*, + whose branches must keep feeding the header that owns their value. --- diff --git a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala index 53a14d332..4edc052ee 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala @@ -51,7 +51,8 @@ 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 + // Only references held by the given members are replaced (`originMember` is the referencing + // member, matching the `Outside`/`Inside` filters above) 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 } From 86144ad5317b9042dc125509eb6783dfb0c1568b Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 13 Aug 2026 01:19:14 +0300 Subject: [PATCH 03/57] core+compiler_ir: a comparison against a `max`/`min` branch decides that branch away A `max` is at least each of its own branches and a `min` at most each of its, whatever those branches are, so a comparison between such a chain and one of its own branches is either an answer outright or a comparison with what is left of the chain. That shape is not exotic: it is what a width taken as the COMMON width of two operands meets when it comes back to one of them, which every binary operation over unrelated parametric widths does. The identity lands in two places, because it is asked at two levels. As an expression rewrite, so a design that has to hold `x(W1) + y(W2)` in `W1` bits requires `W1 >= W2` and says so, rather than restating the common width it went through. And as a fallback in the width-fit decision, so `max(W1, W2) >= W1` is proven rather than left undecided. The decision keeps its existing answers first: the max/min elimination reads a mixed chain by its constants and is deliberately lenient, so the identity only ever turns an undecided answer into a decided one. Proving it is what lets a stacked resize through a common width fold away, once the fold asks whether the inner resize loses anything rather than whether it strictly widens: a value resized to a width that is at least its own and back again recovers itself, so only the operand whose width really changes carries a resize. A condition can now fold to a constant where the width proof could not decide it, so a constraint whose condition folded to `true` requires nothing and is no longer recorded. One that folded to `false` is kept: an assumption that cannot hold is worth the noise. Co-Authored-By: Claude Opus 5 (1M context) --- .../scala/dfhdl/compiler/ir/IntExprCalc.scala | 19 +++ .../StagesSpec/PrintCodeStringSpec.scala | 27 +++++ .../scala/dfhdl/core/AutoConstraint.scala | 11 +- .../src/main/scala/dfhdl/core/DFDecimal.scala | 19 +-- .../main/scala/dfhdl/core/SimplifyFunc.scala | 113 ++++++++++++++++++ .../scala/CoreSpec/SameWidthArithSpec.scala | 67 +++++++++++ 6 files changed, 246 insertions(+), 10 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala index 32c5d9208..8a77c0d54 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala @@ -69,6 +69,9 @@ object IntExprCalc: // the negative direction: `b - a - 1 >= 0` proves `b > a`, deciding `a >= b` as false val negDiffM1 = Linear(diff.terms.map((c, b) => (-c, b)), -diff.offset - 1) if (calc.proveNonNeg(negDiffM1, facts)) Some(false) + // last, so that it only ever turns an undecided answer into a decided one and never + // overrides the max/min elimination above, which reads a mixed chain by its constants + else if (calc.dominatesByBranch(a, b)) Some(true) else None end widthFitCompare @@ -352,6 +355,22 @@ object IntExprCalc: val lb = linear(b) Option.when(sameTerms(la, lb))(la.offset - lb.offset) + /** Whether `a >= b` holds by CONSTRUCTION rather than by arithmetic: a `max` is at least each + * of its own branches and a `min` at most each of its, whatever those branches are. That + * decides a comparison the linear calculus cannot touch, two unrelated symbolic branches never + * cancelling under subtraction, and it is what makes a value resized to a common width and + * back again recover itself. + * + * Only these two orientations. The mirrored ones (`b >= max(b, c)`) genuinely depend on the + * other branch and stay undecided, which is exactly the assumption a design states. + */ + def dominatesByBranch(a: DFVal, b: DFVal): Boolean = + def hasBranch(chain: DFVal, branch: DFVal, op: FuncOp): Boolean = + strip(chain) match + case f: DFVal.Func if f.op == op => f.args.exists(r => strip(r.get) =~ strip(branch)) + case _ => false + hasBranch(a, b, FuncOp.max) || hasBranch(b, a, FuncOp.min) + /** Proves `e >= 0` for every valid parameter assignment, where each fact in `facts` is a linear * form known to be `>= 1` on the valid domain. Two proof rules: a constant `e` decides * directly, and a single-fact proportional bound: if `e == λ*f + c` with rational `λ >= 0`, diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index df2076a56..446498f6d 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -3517,6 +3517,33 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + test("a common width is seen through, in the operands and in the constraint") { + // Two operands of unrelated parametric widths align at their COMMON width, which is then the + // one the target has to hold. Both of those meet a `max` against one of its own branches: + // the operand resized to the common width and back recovers itself, so only the operand that + // really changes width carries a resize, and the fit the design states is between the two + // widths themselves rather than through the width it went through. + class CommonWidth(val W1: Int <> CONST = 8, val W2: Int <> CONST = 8) extends RTDesign: + val x = UInt(W1) <> IN + val y = UInt(W2) <> IN + val z = UInt(W1) <> OUT + z := x + y + end CommonWidth + assertCodeString( + CommonWidth(), + """|class CommonWidth( + | val W1: Int <> CONST = 8, + | val W2: Int <> CONST = 8 + |) extends RTDesign: + | val x = UInt(W1) <> IN + | val y = UInt(W2) <> IN + | val z = UInt(W1) <> OUT + | z := x + y.resize(W1) + | val constraint_0 = assert(W1 >= W2, s"Design parameter violation found. Expected: W1 >= W2", Severity.Fatal) + |end CommonWidth + |""".stripMargin + ) + } test("auto constraint from an LHS-dominant operation") { // `-`, `/` and `%` take the LHS width and convert the RHS to it, so each needs the RHS to // fit. `-` used to answer the undecided case with an outright rejection while its two diff --git a/core/src/main/scala/dfhdl/core/AutoConstraint.scala b/core/src/main/scala/dfhdl/core/AutoConstraint.scala index 184e54359..374cab70b 100644 --- a/core/src/main/scala/dfhdl/core/AutoConstraint.scala +++ b/core/src/main/scala/dfhdl/core/AutoConstraint.scala @@ -83,10 +83,17 @@ object AutoConstraint: * of the operation that assumed it. */ def raise(guard: Guard)(using dfc: DFC): Option[ir.DFVal] = + import dfc.getSet + // A condition can fold to a constant on its way here, a relation between a `max`/`min` and + // one of its own branches being decidable by simplification where the width proof cannot + // decide it. One that folded to `true` requires nothing of the design and states nothing; + // one that folded to `false` is kept, an assumption that cannot hold being worth the noise. + val decided = guard.asIR.getConstData[Option[Boolean]] match + case ir.ConstData.KnownConst(Some(true)) => true + case _ => false // nothing states a constraint outside a design: global scope has no body to put it in, and a // stage's meta design transforms an already-elaborated one and assumes nothing of its own - if (!dfc.inMetaProgramming && dfc.ownerOption.isDefined) - import dfc.getSet + if (!decided && !dfc.inMetaProgramming && dfc.ownerOption.isDefined) Some(guard.asIR.setTags(_.tag(ir.AutoConstraint))) else None diff --git a/core/src/main/scala/dfhdl/core/DFDecimal.scala b/core/src/main/scala/dfhdl/core/DFDecimal.scala index d9918ab53..b99cb21d8 100644 --- a/core/src/main/scala/dfhdl/core/DFDecimal.scala +++ b/core/src/main/scala/dfhdl/core/DFDecimal.scala @@ -1619,20 +1619,23 @@ object DFXInt: // a leaf below val lhsConverted: DFValOf[DFSInt[Int]] = CarryPromote.widenedOpt(lhs.asIR, dfType).getOrElse { - // Fold stacked widenings: an anonymous same-kind widening resize alias - // is transparent to a further conversion (both are value-preserving - // extensions), so when the width fix below would resize anyway, it - // applies to the alias's base directly instead of stacking. + // Fold stacked widenings: an anonymous same-kind resize alias that loses + // nothing is transparent to a further conversion, so when the width fix + // below would resize anyway, it applies to the alias's base directly + // instead of stacking. Losing nothing is `to >= from`, the same width-fit + // decision made everywhere else, which is what sees through a resize to a + // COMMON width: `max(W1, W2)` is at least each of the widths it was taken + // from, so a value resized to it and back recovers itself. def unstack(v: ir.DFVal): ir.DFVal = v match case alias: ir.DFVal.Alias.AsIs if alias.isAnonymous => val relVal = alias.relValRef.get - val widening = (alias.dfType, relVal.dfType) match + val lossless = (alias.dfType, relVal.dfType) match case (ir.DFUInt(toW), ir.DFUInt(fromW)) => - toW.compare(fromW)(_ > _).getOrElse(false) + toW.widthFitGE(fromW).getOrElse(false) case (ir.DFSInt(toW), ir.DFSInt(fromW)) => - toW.compare(fromW)(_ > _).getOrElse(false) + toW.widthFitGE(fromW).getOrElse(false) case _ => false - if (widening) unstack(relVal) else v + if (lossless) unstack(relVal) else v case _ => v val widthChanges = !dfType.asIR.magnitudeWidthParamRef .isSimilarTo(lhs.dfType.asIR.magnitudeWidthParamRef) diff --git a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala index 6df6a02a0..4f90cdc30 100644 --- a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala +++ b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala @@ -24,6 +24,7 @@ private object SimplifyFunc: case IdentityOps(v) => Some(v) case SelfCancelling(v) => Some(v) case MaxMinWithOffset(v) => Some(v) + case CompareAgainstMaxMin(v) => Some(v) case AdditiveCancellation(v) => Some(v) case _ => None @@ -53,6 +54,21 @@ private object SimplifyFunc: dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags ).addMember + // Creates a fresh Func with the current DFC meta, for a simplification that rewrites the + // operation rather than answering with a value that already exists. + private def mkFunc(dfType: ir.DFType, op: FuncOp, args: List[ir.DFVal])(using + dfc: DFC + ): ir.DFVal = + import dfc.getSet + ir.DFVal.Func( + dfType, + op, + args.map(_.refTW[ir.DFVal](knownReachable = true)), + dfc.ownerOrEmptyRef, + dfc.getMeta, + dfc.tags + ).addMember + // Naming without mutation: a simplification returns an EXISTING value, so a `val` binding's // name is applied by wrapping the value in a named Ident rather than by restamping its meta // (an anonymous member is never revised; issue #449). With an anonymous context the value is @@ -180,6 +196,103 @@ private object SimplifyFunc: end unapply end MaxMinChainAbsorb + // A comparison between a `max`/`min` and one of its OWN branches decides that branch away. + // Writing the chain as `max(a, B)` for the branch `a` being compared and `B` for whatever is + // left of it, every such comparison is either an answer or a comparison of `B` with `a`: + // + // max(a, B) >= a true min(a, B) <= a true + // max(a, B) < a false min(a, B) > a false + // max(a, B) > a B > a min(a, B) < a B < a + // max(a, B) <= a B <= a min(a, B) >= a B >= a + // max(a, B) === a B <= a min(a, B) === a B >= a + // max(a, B) =!= a B > a min(a, B) =!= a B < a + // + // with the branch on the left the same table read through the reversed operation. The shape + // arises wherever a width taken as the COMMON width of two operands meets one of them again, + // so a design that has to hold `x(W1) + y(W2)` in `W1` bits requires `W1 >= W2` and says so, + // rather than restating the common width it went through. + private object CompareAgainstMaxMin: + private def mkBool(value: Boolean)(using dfc: DFC): ir.DFVal = + import dfc.getSet + ir.DFVal.Const( + ir.DFBool, Some(value), + dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags + ).addMember + + // the same relation read from the other side + private def reversed(op: FuncOp): FuncOp = op match + case FuncOp.>= => FuncOp.<= + case FuncOp.<= => FuncOp.>= + case FuncOp.> => FuncOp.< + case FuncOp.< => FuncOp.> + case symmetric => symmetric // `===` and `=!=` read alike from either side + + // What is left of `chain` once the branch that is `self` is dropped, when `chain` is a + // `maxMin` having it as a branch. `None` when it is not one, or does not. + private def withoutBranch(chain: ir.DFVal, self: ir.DFVal, maxMin: FuncOp)(using + dfc: DFC + ): Option[ir.DFVal] = + import dfc.getSet + // ident-transparent, as the max/min chain absorption above is: either side may be a + // (named) ident of the expression it stands for + chain.stripTypePreservingAliases match + case f: ir.DFVal.Func if f.dfType == ir.DFInt32 && f.op == maxMin => + val selfStripped = self.stripTypePreservingAliases + val branches = f.args.map(_.get) + val rest = branches.filterNot(_.stripTypePreservingAliases =~ selfStripped) + if (rest.sizeIs == branches.size) None // `self` is not one of the branches + else + rest match + // nothing but `self`, so the chain IS `self`; the chain absorption above is what + // reduces that, and it does so before any comparison sees it + case Nil => None + case only :: Nil => Some(only) + case several => Some(mkFunc(ir.DFInt32, maxMin, several)) + case _ => None + end withoutBranch + + // the table above, for `maxMin(self, rest) op self`: an answer, or the operation to apply + // between `rest` and `self` + private def reduction(maxMin: FuncOp, op: FuncOp): Either[Boolean, FuncOp] = + val isMax = maxMin == FuncOp.max + op match + case FuncOp.>= => if (isMax) Left(true) else Right(FuncOp.>=) + case FuncOp.<= => if (isMax) Right(FuncOp.<=) else Left(true) + case FuncOp.> => if (isMax) Right(FuncOp.>) else Left(false) + case FuncOp.< => if (isMax) Left(false) else Right(FuncOp.<) + case FuncOp.=== => if (isMax) Right(FuncOp.<=) else Right(FuncOp.>=) + case _ => if (isMax) Right(FuncOp.>) else Right(FuncOp.<) + + def unapply(opArgs: (ir.DFType, FuncOp, List[ir.DFVal]))(using dfc: DFC): Option[ir.DFVal] = + opArgs match + case ( + ir.DFBool, + op @ (FuncOp.>= | FuncOp.<= | FuncOp.> | FuncOp.< | FuncOp.=== | FuncOp.=!=), + List(lhs, rhs) + ) => + // read with the chain on the left, which is the orientation the table is written in, + // and put the answer back the way it was written + def attempt( + chain: ir.DFVal, + self: ir.DFVal, + chainOp: FuncOp, + chainOnLeft: Boolean + ): Option[ir.DFVal] = + List(FuncOp.max, FuncOp.min).view.flatMap { maxMin => + withoutBranch(chain, self, maxMin).map { rest => + reduction(maxMin, chainOp) match + case Left(answer) => mkBool(answer) + case Right(restOp) => + if (chainOnLeft) mkFunc(ir.DFBool, restOp, List(rest, self)) + else mkFunc(ir.DFBool, reversed(restOp), List(self, rest)) + } + }.headOption + attempt(lhs, rhs, op, chainOnLeft = true) + .orElse(attempt(rhs, lhs, reversed(op), chainOnLeft = false)) + case _ => None + end unapply + end CompareAgainstMaxMin + // Merge consecutive same-op anonymous Funcs for associative operations. // E.g., `a + b + c` becomes Func(+, [a, b, c]) instead of nested binary Funcs. // For left-associative chains, only the first arg can be an absorbed Func. diff --git a/core/src/test/scala/CoreSpec/SameWidthArithSpec.scala b/core/src/test/scala/CoreSpec/SameWidthArithSpec.scala index 4bc2c79e1..3e9b28e8f 100644 --- a/core/src/test/scala/CoreSpec/SameWidthArithSpec.scala +++ b/core/src/test/scala/CoreSpec/SameWidthArithSpec.scala @@ -59,6 +59,73 @@ class SameWidthArithSpec extends NoDFCSpec: ) } + // A `max` is at least each of its own branches and a `min` at most each of its, so every + // comparison between a chain and one of its own branches is either an answer or a comparison + // with what is left of the chain. The shape is how a width taken as the COMMON width of two + // operands meets one of them again. + test("a comparison against a max/min branch decides that branch away") { + class Top(val A: Int <> CONST = 1, val B: Int <> CONST = 2) extends DFDesign: + // the chain on the right + val rGE = A >= (A max B) + val rLE = A <= (A max B) + val rGT = A > (A max B) + val rLT = A < (A max B) + val rEQ = A == (A max B) + val rNE = A != (A max B) + val nGE = A >= (A min B) + val nLE = A <= (A min B) + val nGT = A > (A min B) + val nLT = A < (A min B) + val nEQ = A == (A min B) + val nNE = A != (A min B) + // the chain on the left + val lGE = (A max B) >= A + val lLE = (A max B) <= A + val lGT = (A max B) > A + val lLT = (A max B) < A + val lEQ = (A max B) == A + val lNE = (A max B) != A + val mGE = (A min B) >= A + val mLE = (A min B) <= A + val mGT = (A min B) > A + val mLT = (A min B) < A + val mEQ = (A min B) == A + val mNE = (A min B) != A + end Top + assertNoDiff( + codeString(Top()), + """|class Top( + | val A: Int <> CONST = 1, + | val B: Int <> CONST = 2 + |) extends DFDesign: + | val rGE: Boolean <> CONST = A >= B + | val rLE: Boolean <> CONST = true + | val rGT: Boolean <> CONST = false + | val rLT: Boolean <> CONST = A < B + | val rEQ: Boolean <> CONST = A >= B + | val rNE: Boolean <> CONST = A < B + | val nGE: Boolean <> CONST = true + | val nLE: Boolean <> CONST = A <= B + | val nGT: Boolean <> CONST = A > B + | val nLT: Boolean <> CONST = false + | val nEQ: Boolean <> CONST = A <= B + | val nNE: Boolean <> CONST = A > B + | val lGE: Boolean <> CONST = true + | val lLE: Boolean <> CONST = B <= A + | val lGT: Boolean <> CONST = B > A + | val lLT: Boolean <> CONST = false + | val lEQ: Boolean <> CONST = B <= A + | val lNE: Boolean <> CONST = B > A + | val mGE: Boolean <> CONST = B >= A + | val mLE: Boolean <> CONST = true + | val mGT: Boolean <> CONST = false + | val mLT: Boolean <> CONST = B < A + | val mEQ: Boolean <> CONST = B >= A + | val mNE: Boolean <> CONST = B < A + |end Top""".stripMargin + ) + } + test("a repeated max/min chain over a design parameter is absorbed") { class Top(val W: Int <> CONST = 11) extends DFDesign: val v = Int <> VAR From e02cfc3cabf842224fff6641e5782c3b4dbfa584 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 13 Aug 2026 02:34:58 +0300 Subject: [PATCH 04/57] core+lib+plugin: the `@top` annotation moves out of the `dfhdl` wildcard's reach Fixes #482 `dfhdl.top` was a top-level member of the `dfhdl` package, so every `import dfhdl.*` bound the name `top`. A user's own design class named `top` (the standard Verilog top-module name) then lost name resolution to it from any OTHER compilation unit: a package member referenced across files is the lowest-precedence binding, below a wildcard import. `new top(WIDTH = 8)` was checked against the annotation's constructor and reported "dfhdl.top does not have a parameter WIDTH", naming neither the collision nor what the identifier had resolved to. #465 had fixed only the declaration-site half (#458), by qualifying the plugin's injected annotation. The annotation now lives at `dfhdl.hw.annotation.top`, beside the other user-facing hardware annotations, and reaches user code only through an explicit import or the `@hw.annotation.top` spelling. Nothing named `top` enters scope through `import dfhdl.*`. `dfhdl.hw.annotation` becomes a PACKAGE rather than an object to host it: `top` needs five option sets that live downstream in `lib` and cannot be compiled into core's object, while a package is open across subprojects. `constraints` stays in the same file, since `HWAnnotation` is sealed. The plugin's auto-injection is spelled `_root_.dfhdl.hw.annotation.top`, so an auto-topped design still needs no import; only a hand-written `@top` does. `rightmostName`-based detection keeps matching every spelling. `ElaborationChecksSpec` takes its import on line 1 rather than a new line, because 46 of its assertions pin absolute line numbers. A test-scope `type top = dfhdl.hw.annotation.top` alias would have spared the test files their import, and was rejected: it reproduces this very bug inside lib's test scope, where the regression fixture lives. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/bugfix.md | 18 ++++++++++++++---- .claude/commands/verilog-to-dfhdl.md | 4 +++- core/src/main/scala/dfhdl/hw/annotation.scala | 6 +++++- devdocs/dfapp.md | 11 ++++++++++- lib/src/main/scala/dfhdl/app/DFApp.scala | 2 +- .../scala/dfhdl/{ => hw/annotation}/top.scala | 4 ++-- lib/src/test/scala/AES/Cipher.scala | 1 + lib/src/test/scala/AES/CipherSpec.scala | 1 + lib/src/test/scala/ArithSpec/PrioEncSpec.scala | 1 + lib/src/test/scala/ContextWidenSpec.scala | 1 + lib/src/test/scala/ElaborationChecksSpec.scala | 2 +- lib/src/test/scala/app/TestCLINested.scala | 1 + lib/src/test/scala/issues/i116.scala | 1 + lib/src/test/scala/issues/i118.scala | 1 + lib/src/test/scala/issues/i126.scala | 1 + lib/src/test/scala/issues/i128.scala | 1 + lib/src/test/scala/issues/i129.scala | 1 + lib/src/test/scala/issues/i131.scala | 3 ++- lib/src/test/scala/issues/i133.scala | 1 + lib/src/test/scala/issues/i135.scala | 1 + lib/src/test/scala/issues/i141.scala | 3 ++- lib/src/test/scala/issues/i142.scala | 3 ++- lib/src/test/scala/issues/i146.scala | 1 + lib/src/test/scala/issues/i147.scala | 1 + lib/src/test/scala/issues/i375.scala | 1 + lib/src/test/scala/issues/i450.scala | 1 + lib/src/test/scala/issues/i458.scala | 2 +- lib/src/test/scala/issues/i482.scala | 15 +++++++++++++++ lib/src/test/scala/issues/i482_wrapper.scala | 17 +++++++++++++++++ lib/src/test/scala/util/EmptyDesign.scala | 1 + .../scala/plugin/MetaContextPlacerPhase.scala | 2 +- .../src/main/scala/plugin/PreTyperPhase.scala | 16 +++++++++++----- .../src/main/scala/plugin/TopAnnotPhase.scala | 2 +- 33 files changed, 105 insertions(+), 22 deletions(-) rename lib/src/main/scala/dfhdl/{ => hw/annotation}/top.scala (92%) create mode 100644 lib/src/test/scala/issues/i482.scala create mode 100644 lib/src/test/scala/issues/i482_wrapper.scala diff --git a/.claude/commands/bugfix.md b/.claude/commands/bugfix.md index e6ac7a585..71fc92d5e 100644 --- a/.claude/commands/bugfix.md +++ b/.claude/commands/bugfix.md @@ -81,9 +81,17 @@ auto-`@top` injection spelled the annotation `Ident("top")`, and a design class class being annotated, and scalac reported `Cyclic reference involving class top` at the class definition, with nothing pointing at the plugin (issue #458). Anchor every synthesized library reference at the root (`Select(Select(Ident(nme.ROOTPKG), "dfhdl"), ...)`, i.e. -`_root_.dfhdl.top`) — a bare `Ident("dfhdl")` can itself be captured by a user package or object -named `dfhdl`. Rightmost-name-based detection helpers (`rightmostName`) keep matching the -qualified spelling, so only the construction site changes. +`_root_.dfhdl.hw.annotation.top`) — a bare `Ident("dfhdl")` can itself be captured by a user +package or object named `dfhdl`. Rightmost-name-based detection helpers (`rightmostName`) keep +matching the qualified spelling, so only the construction site changes. + +Qualifying the injection fixes only the injection. The same name still collided at the USE site, +because `import dfhdl.*` re-exported the library's own `top` and it outranked a same-named +top-level user class, so `new top(WIDTH = 8)` was checked against the annotation's constructor +(issue #482). Anything the frontend wildcard re-exports is a name a user can no longer define: +`@top` therefore lives at `dfhdl.hw.annotation.top`, alongside the other user-facing hardware +annotations, and reaches user code only through an explicit import or the `@hw.annotation.top` +spelling. Weigh that before adding a short name to `__hdl` or to the `dfhdl` package. The tell for this species: a resolution-flavored error (cyclic reference, ambiguity, "not found") positioned on ordinary user code that appears or vanishes with the *name* of a @@ -636,7 +644,9 @@ non-warning twins, not by re-reading the predicate. Probing designs outside the app runner has its own traps: a lib design class with all-defaulted parameters is auto-`@top`ed, and a bare `Design()` of a topped class returns a STAGED handle -that never elaborates (no warnings, empty DB) — mark probe designs `@top(false)`. Read warnings +that never elaborates (no warnings, empty DB) — mark probe designs `@top(false)`, which needs +`import dfhdl.hw.annotation.top` on top of `import dfhdl.*` (the auto-injection is qualified and +needs no import; only a hand-written `@top` does). Read warnings via `dsn.dfc.getWarnings`; prefer `getCodeString` over `getDB` for IR inspection in a lib @main. ### Two habits that pay off diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index 9f9d20c34..d311e3640 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -41,7 +41,9 @@ to write. Reserve `EDDesign`/`process` for genuinely event-driven or multi-edge port names, **which registers the reset actually targets** (a "MINI"/partial reset resets only some), the parameters, and any `generate`-gated variants. 2. **One module per design, in a same-named file** (case-sensitive: `serv_alu` in `serv_alu.scala`). - Match port names exactly, `i_`/`o_` prefixes included. + Match port names exactly, `i_`/`o_` prefixes included. A module named `top` needs no + workaround: nothing named `top` enters scope through `import dfhdl.*`, so `class top` both + declares and instantiates normally, and the emitted module name stays `top`. 3. **Compile it standalone and read the emitted HDL:** ```bash sbtn.bat ";clearSandbox ;/runMain . compile" diff --git a/core/src/main/scala/dfhdl/hw/annotation.scala b/core/src/main/scala/dfhdl/hw/annotation.scala index d7d7fb6ce..8e69bc187 100644 --- a/core/src/main/scala/dfhdl/hw/annotation.scala +++ b/core/src/main/scala/dfhdl/hw/annotation.scala @@ -9,7 +9,11 @@ import scala.annotation.Annotation import dfhdl.compiler.ir import dfhdl.core.* -object annotation: +// `annotation` is a PACKAGE rather than an object so that `top` (which needs the tool option +// sets defined downstream in `lib`) can join it from there. `@top` used to live at `dfhdl.top`, +// where the package-level `export __hdl.*` put it in scope for every `import dfhdl.*` and let it +// shadow a user's own design class named `top` (#482). +package annotation: sealed abstract class HWAnnotation extends StaticAnnotation: val isActive: Boolean val asIR: ir.annotation.HWAnnotation diff --git a/devdocs/dfapp.md b/devdocs/dfapp.md index 93bb1779a..779358845 100644 --- a/devdocs/dfapp.md +++ b/devdocs/dfapp.md @@ -35,7 +35,9 @@ generation (the plugin's `select("...".toTermName)` fails at the generated call Users rarely write `@top` themselves: `PreTyperPhase` injects it onto every concrete class that looks like a design (a `Design` parent, a `<> CONST` parameter, or `<>` in the body), skipping traits, case -and enum classes, interfaces, and anything with more than one parameter block. It injects the +and enum classes, interfaces, and anything with more than one parameter block. The injection is +spelled `_root_.dfhdl.hw.annotation.top`, so it needs no import in the user's file; writing `@top` +by hand does (`import dfhdl.hw.annotation.top`, or the `@hw.annotation.top` spelling). It injects the explicit `@top(true)` form on purpose, since that is the lenient variant: `TopAnnotPhase` silently skips entry-point generation when such a class turns out not to be a `Design`, whereas a bare `@top` written by hand is strict and reports a compile error. `@top(false)` opts out entirely. @@ -73,10 +75,17 @@ The `@top` annotation carries the eight option sets in its third parameter list, design's DECLARATION site: ```scala +// lib/src/main/scala/dfhdl/hw/annotation/top.scala final case class top(genMain: Boolean = true)(using annot: AnnotatedWith[top, Any])(using val elaborationOptions: ElaborationOptions.Defaults[annot.Out], ...) ``` +It lives in `lib` because five of those eight option sets do, and in `dfhdl.hw.annotation` rather +than `dfhdl` because the frontend's package-level `export __hdl.*` would otherwise put the name +`top` in scope for every `import dfhdl.*` and shadow a user's own design class named `top` +(issue #482). `dfhdl.hw.annotation` is a PACKAGE, not an object, purely so this one member can +join the core-side annotations from a downstream subproject. + That is how a user's `given options.CompilerOptions.Backend = _.vhdl` reaches the app. `setInitials` copies each set into a mutable field, applies the `-Werror` scalac flag to the tool option sets, and picks the default `AppMode`: diff --git a/lib/src/main/scala/dfhdl/app/DFApp.scala b/lib/src/main/scala/dfhdl/app/DFApp.scala index cd604cb14..e15183bde 100644 --- a/lib/src/main/scala/dfhdl/app/DFApp.scala +++ b/lib/src/main/scala/dfhdl/app/DFApp.scala @@ -109,7 +109,7 @@ class DFApp: topClass: Class[?], designName: String, topScalaPath: String, - top: dfhdl.top, + top: dfhdl.hw.annotation.top, argNames: List[String], argValues: List[Any], argDescs: List[String], diff --git a/lib/src/main/scala/dfhdl/top.scala b/lib/src/main/scala/dfhdl/hw/annotation/top.scala similarity index 92% rename from lib/src/main/scala/dfhdl/top.scala rename to lib/src/main/scala/dfhdl/hw/annotation/top.scala index 8ea2477c5..9d56f08d5 100644 --- a/lib/src/main/scala/dfhdl/top.scala +++ b/lib/src/main/scala/dfhdl/hw/annotation/top.scala @@ -1,5 +1,5 @@ -package dfhdl -import internals.AnnotatedWith +package dfhdl.hw.annotation +import dfhdl.internals.AnnotatedWith final case class top(genMain: Boolean = true)(using private[dfhdl] val annot: AnnotatedWith[top, Any] diff --git a/lib/src/test/scala/AES/Cipher.scala b/lib/src/test/scala/AES/Cipher.scala index b7b4989d8..a26287fed 100644 --- a/lib/src/test/scala/AES/Cipher.scala +++ b/lib/src/test/scala/AES/Cipher.scala @@ -1,5 +1,6 @@ package dfhdl.AES import dfhdl.* +import dfhdl.hw.annotation.top @top(false) class Cipher extends DFDesign: val key = AESKey <> IN diff --git a/lib/src/test/scala/AES/CipherSpec.scala b/lib/src/test/scala/AES/CipherSpec.scala index 4c2c365e4..df71aed62 100644 --- a/lib/src/test/scala/AES/CipherSpec.scala +++ b/lib/src/test/scala/AES/CipherSpec.scala @@ -1,6 +1,7 @@ package dfhdl.AES import munit.* import dfhdl.* +import dfhdl.hw.annotation.top import tools.linters.iverilog import dfhdl.options.LinterOptions._VerilogLinter import dfhdl.options.CompilerOptions diff --git a/lib/src/test/scala/ArithSpec/PrioEncSpec.scala b/lib/src/test/scala/ArithSpec/PrioEncSpec.scala index f95697a74..25df25243 100644 --- a/lib/src/test/scala/ArithSpec/PrioEncSpec.scala +++ b/lib/src/test/scala/ArithSpec/PrioEncSpec.scala @@ -1,5 +1,6 @@ package ArithSpec import dfhdl.* +import dfhdl.hw.annotation.top import munit.* import lib.arith.prioEnc diff --git a/lib/src/test/scala/ContextWidenSpec.scala b/lib/src/test/scala/ContextWidenSpec.scala index 523d780b3..57d28465c 100644 --- a/lib/src/test/scala/ContextWidenSpec.scala +++ b/lib/src/test/scala/ContextWidenSpec.scala @@ -1,4 +1,5 @@ package dfhdl +import dfhdl.hw.annotation.top /** Target-context widening of anonymous arithmetic (issue dfhdl_by_agents#119): an anonymous `+`, * `-`, `*` cone assigned or connected to a wider value evaluates at the target's width and sign, diff --git a/lib/src/test/scala/ElaborationChecksSpec.scala b/lib/src/test/scala/ElaborationChecksSpec.scala index 4307f2401..85a6aabb2 100644 --- a/lib/src/test/scala/ElaborationChecksSpec.scala +++ b/lib/src/test/scala/ElaborationChecksSpec.scala @@ -1,4 +1,4 @@ -import dfhdl.* +import dfhdl.*, dfhdl.hw.annotation.top // one line: the assertions below pin absolute line numbers import munit.* import java.io.File.separatorChar as S given options.ElaborationOptions.OnError = _.Exception diff --git a/lib/src/test/scala/app/TestCLINested.scala b/lib/src/test/scala/app/TestCLINested.scala index bec7e7c4e..9b4475d7a 100644 --- a/lib/src/test/scala/app/TestCLINested.scala +++ b/lib/src/test/scala/app/TestCLINested.scala @@ -1,5 +1,6 @@ package app import dfhdl.* +import dfhdl.hw.annotation.top // Fixture used by `DesignArgsCLISpec` to cover a `@top` design nested inside an // object. A nested companion cannot serve as a runnable entry point, so the diff --git a/lib/src/test/scala/issues/i116.scala b/lib/src/test/scala/issues/i116.scala index c8582df52..f9b738a0a 100644 --- a/lib/src/test/scala/issues/i116.scala +++ b/lib/src/test/scala/issues/i116.scala @@ -2,6 +2,7 @@ package issues.i116 import dfhdl.* +import dfhdl.hw.annotation.top case class Test ( a : Bit <> VAL, diff --git a/lib/src/test/scala/issues/i118.scala b/lib/src/test/scala/issues/i118.scala index be62d71f5..2045c2f5e 100644 --- a/lib/src/test/scala/issues/i118.scala +++ b/lib/src/test/scala/issues/i118.scala @@ -2,6 +2,7 @@ package issues.i118 import dfhdl.* +import dfhdl.hw.annotation.top @top(false) class ShiftIssue() extends RTDesign: val bitvec = Bits(10) <> VAR diff --git a/lib/src/test/scala/issues/i126.scala b/lib/src/test/scala/issues/i126.scala index fc87f281a..a2d13cb62 100644 --- a/lib/src/test/scala/issues/i126.scala +++ b/lib/src/test/scala/issues/i126.scala @@ -2,6 +2,7 @@ package issues.i126 import dfhdl.* +import dfhdl.hw.annotation.top @top(false) class TypeConvertIssue() extends RTDesign: val a = Bit <> IN diff --git a/lib/src/test/scala/issues/i128.scala b/lib/src/test/scala/issues/i128.scala index 673fb9942..c3376fe52 100644 --- a/lib/src/test/scala/issues/i128.scala +++ b/lib/src/test/scala/issues/i128.scala @@ -2,6 +2,7 @@ package issues.i128 import dfhdl.* +import dfhdl.hw.annotation.top @top(false) class ArrayIssue() extends RTDesign: val a = Bit <> IN diff --git a/lib/src/test/scala/issues/i129.scala b/lib/src/test/scala/issues/i129.scala index 3a7e9296d..79313cb87 100644 --- a/lib/src/test/scala/issues/i129.scala +++ b/lib/src/test/scala/issues/i129.scala @@ -2,6 +2,7 @@ package issues.i129 import dfhdl.* +import dfhdl.hw.annotation.top @top(false) class StdLogicConvIssue() extends RTDesign: val a = Bits(10) <> IN diff --git a/lib/src/test/scala/issues/i131.scala b/lib/src/test/scala/issues/i131.scala index fe9601592..95b8f8100 100644 --- a/lib/src/test/scala/issues/i131.scala +++ b/lib/src/test/scala/issues/i131.scala @@ -2,6 +2,7 @@ package issues.i131 import dfhdl.* +import dfhdl.hw.annotation.top import hw.flag.scalaRanges @top(false) class DictControl( val fetch_count : Int <> CONST, // set to 2 @@ -23,4 +24,4 @@ import hw.flag.scalaRanges for (i <- 0 until fetch_count.toScalaInt) if ((dict_in(dict_entry_size * (i+1) - 1, dict_entry_size * i) == (idx_r, sym_r)) && (addr_r + d"$i" < entry_count - d"1")) - matching(i) := 1 \ No newline at end of file + matching(i) := 1 diff --git a/lib/src/test/scala/issues/i133.scala b/lib/src/test/scala/issues/i133.scala index 4d94f8138..c95095c03 100644 --- a/lib/src/test/scala/issues/i133.scala +++ b/lib/src/test/scala/issues/i133.scala @@ -2,6 +2,7 @@ package issues.i133 import dfhdl.* +import dfhdl.hw.annotation.top @top(false) class Width0Issue(val width : Int <> CONST) extends RTDesign: val d = Bit <> IN diff --git a/lib/src/test/scala/issues/i135.scala b/lib/src/test/scala/issues/i135.scala index 82ab8739d..e420b5e46 100644 --- a/lib/src/test/scala/issues/i135.scala +++ b/lib/src/test/scala/issues/i135.scala @@ -2,6 +2,7 @@ package issues.i135 import dfhdl._ +import dfhdl.hw.annotation.top @top(false) class VerilogSRA() extends RTDesign: val a = SInt(10) <> IN val b = SInt(10) <> VAR diff --git a/lib/src/test/scala/issues/i141.scala b/lib/src/test/scala/issues/i141.scala index b39425a67..c224b9459 100644 --- a/lib/src/test/scala/issues/i141.scala +++ b/lib/src/test/scala/issues/i141.scala @@ -2,6 +2,7 @@ package issues.i141 import dfhdl._ +import dfhdl.hw.annotation.top case class EmbeddedArray ( a : Bits[8]X(3) <> VAL @@ -10,4 +11,4 @@ case class EmbeddedArray ( @top(false) class StructArrayIssue() extends RTDesign: val a = Bit <> IN val e = EmbeddedArray <> VAR - e.a(0)(0) := a \ No newline at end of file + e.a(0)(0) := a diff --git a/lib/src/test/scala/issues/i142.scala b/lib/src/test/scala/issues/i142.scala index 0a697e956..72bd1ed08 100644 --- a/lib/src/test/scala/issues/i142.scala +++ b/lib/src/test/scala/issues/i142.scala @@ -2,10 +2,11 @@ package issues.i142 import dfhdl._ +import dfhdl.hw.annotation.top @top(false) class IntegerIndexingIssue() extends RTDesign: val a = Bits(4) <> IN val b = Bits(12) X 16 <> VAR.REG init all(all(0)) val c = Bits(12) <> OUT - c := b(a) \ No newline at end of file + c := b(a) diff --git a/lib/src/test/scala/issues/i146.scala b/lib/src/test/scala/issues/i146.scala index dca1211b1..26436fc1f 100644 --- a/lib/src/test/scala/issues/i146.scala +++ b/lib/src/test/scala/issues/i146.scala @@ -2,6 +2,7 @@ package issues.i146 import dfhdl.* +import dfhdl.hw.annotation.top case class InStruct ( a : Bit <> VAL, diff --git a/lib/src/test/scala/issues/i147.scala b/lib/src/test/scala/issues/i147.scala index 60e394007..e979307a1 100644 --- a/lib/src/test/scala/issues/i147.scala +++ b/lib/src/test/scala/issues/i147.scala @@ -2,6 +2,7 @@ package issues.i147 import dfhdl.* +import dfhdl.hw.annotation.top class INV() extends RTDesign: val a = Bit <> IN diff --git a/lib/src/test/scala/issues/i375.scala b/lib/src/test/scala/issues/i375.scala index 0c02bf001..9d484b9a0 100644 --- a/lib/src/test/scala/issues/i375.scala +++ b/lib/src/test/scala/issues/i375.scala @@ -1,6 +1,7 @@ package issues.i375 import dfhdl.* +import dfhdl.hw.annotation.top @top(false) class draw_line(val CORDW: Int <> CONST = 16) extends EDDesign: val clk = Bit <> IN diff --git a/lib/src/test/scala/issues/i450.scala b/lib/src/test/scala/issues/i450.scala index 4fa1372ec..9b376de98 100644 --- a/lib/src/test/scala/issues/i450.scala +++ b/lib/src/test/scala/issues/i450.scala @@ -1,6 +1,7 @@ package issues.i450 import dfhdl.* +import dfhdl.hw.annotation.top // A `Bits` port whose width comes from a design parameter that is USED THROUGH ITS DEFAULT // (`new Consumer()`), tied to `all(0)` by the parent. A class parameter default is evaluated diff --git a/lib/src/test/scala/issues/i458.scala b/lib/src/test/scala/issues/i458.scala index 17fc07cfe..703d213da 100644 --- a/lib/src/test/scala/issues/i458.scala +++ b/lib/src/test/scala/issues/i458.scala @@ -6,7 +6,7 @@ import dfhdl.* // module). The class must carry NO explicit annotation: the auto-`@top` injection is the // path under test. An injection spelled with an unqualified `top` resolves to this very // class and fails compilation with "Cyclic reference involving class top", so the plugin -// must inject the fully qualified `@_root_.dfhdl.top` instead. +// must inject the fully qualified `@_root_.dfhdl.hw.annotation.top` instead. class top extends EDDesign: val a = Bit <> IN val b = Bit <> IN diff --git a/lib/src/test/scala/issues/i482.scala b/lib/src/test/scala/issues/i482.scala new file mode 100644 index 000000000..37bf966aa --- /dev/null +++ b/lib/src/test/scala/issues/i482.scala @@ -0,0 +1,15 @@ +package issues.i482 + +import dfhdl.* + +// The declaration half of the use-site `class top` collision (the pure declaration-site case is +// `i458`). The instantiating sibling lives in `i482_wrapper.scala` ON PURPOSE: a package member +// referenced from ANOTHER compilation unit ranks BELOW a wildcard import, so only a cross-file +// reference reproduces the bug. Keeping both in one file would make this fixture pass either way. +class top( + val WIDTH: Int <> CONST = 8 +) extends EDDesign: + val din = Bits(WIDTH) <> IN + val dout = Bits(WIDTH) <> OUT + dout <> din +end top diff --git a/lib/src/test/scala/issues/i482_wrapper.scala b/lib/src/test/scala/issues/i482_wrapper.scala new file mode 100644 index 000000000..efbeba6f6 --- /dev/null +++ b/lib/src/test/scala/issues/i482_wrapper.scala @@ -0,0 +1,17 @@ +package issues.i482 + +import dfhdl.* + +// The use half of the `class top` collision. While the built-in annotation lived at `dfhdl.top`, +// the wildcard import above outranked the sibling `top` declared in `i482.scala` (a package member +// from another compilation unit is the LOWEST-precedence binding, below a wildcard import), so +// `new top(WIDTH = 8)` was checked against the annotation's own constructor and reported +// "dfhdl.top does not have a parameter WIDTH" without ever naming what it had resolved to. The +// annotation now lives at `dfhdl.hw.annotation.top`, so `import dfhdl.*` binds no `top` at all. +class Wrapper extends EDDesign: + val din = Bits(8) <> IN + val dout = Bits(8) <> OUT + val u_top = new top(WIDTH = 8) + u_top.din <> din + u_top.dout <> dout +end Wrapper diff --git a/lib/src/test/scala/util/EmptyDesign.scala b/lib/src/test/scala/util/EmptyDesign.scala index 981aa98d7..a505d4f5d 100644 --- a/lib/src/test/scala/util/EmptyDesign.scala +++ b/lib/src/test/scala/util/EmptyDesign.scala @@ -1,4 +1,5 @@ package util import dfhdl.* +import dfhdl.hw.annotation.top //used to create an empty design app for testing @top class EmptyDesign extends DFDesign diff --git a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala index a8a1a6f9f..0b66988ed 100644 --- a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala @@ -504,7 +504,7 @@ class MetaContextPlacerPhase(setting: Setting) extends CapturePhase, IdentityDen designTpe = requiredClassRef("dfhdl.core.Design") metaTpe = requiredClassRef("dfhdl.compiler.ir.Meta") interfaceTpe = requiredClassRef("dfhdl.core.Interface") - topAnnotSym = requiredClass("dfhdl.top") + topAnnotSym = requiredClass("dfhdl.hw.annotation.top") appTpe = requiredClassRef("dfhdl.app.DFApp") noTopAnnotIsRequired = requiredClassRef("dfhdl.internals.NoTopAnnotIsRequired") listMapEmptySym = requiredMethod("scala.collection.immutable.ListMap.empty") diff --git a/plugin/src/main/scala/plugin/PreTyperPhase.scala b/plugin/src/main/scala/plugin/PreTyperPhase.scala index 52a44f7d2..7ff087100 100644 --- a/plugin/src/main/scala/plugin/PreTyperPhase.scala +++ b/plugin/src/main/scala/plugin/PreTyperPhase.scala @@ -644,15 +644,21 @@ class PreTyperPhase(setting: Setting) extends CommonPhase: // lenient variant — TopAnnotPhase silently skips entry-point generation when the // annotated class turns out not to be a Design, whereas bare `@top` is strict // and would surface a compile error on a false positive. - // The annotation is fully qualified as `_root_.dfhdl.top`: an unqualified `top` - // resolves to the annotated class itself when the class is named `top` (a common + // The annotation is fully qualified as `_root_.dfhdl.hw.annotation.top`: an unqualified + // `top` resolves to the annotated class itself when the class is named `top` (a common // Verilog top-module convention), yielding a baffling "Cyclic reference involving // class top" error (#458). untpd.Apply( untpd.Select( untpd.New( untpd.Select( - untpd.Select(untpd.Ident(nme.ROOTPKG), "dfhdl".toTermName), + untpd.Select( + untpd.Select( + untpd.Select(untpd.Ident(nme.ROOTPKG), "dfhdl".toTermName), + "hw".toTermName + ), + "annotation".toTermName + ), "top".toTypeName ) ), @@ -837,9 +843,9 @@ class PreTyperPhase(setting: Setting) extends CommonPhase: override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = val parsed = super.runOn(units) - // `dfhdl.top` lives in the `lib` subproject — only apply the auto-@top + // `dfhdl.hw.annotation.top` lives in the `lib` subproject — only apply the auto-@top // rewrite when it's reachable on the classpath of this compilation. - val topAvailable = getClassIfDefined("dfhdl.top").exists + val topAvailable = getClassIfDefined("dfhdl.hw.annotation.top").exists parsed.foreach { cu => debugFlag = cu.source.file.path.contains("Playground.scala") cu.untpdTree = rewriteParsed(cu.untpdTree) diff --git a/plugin/src/main/scala/plugin/TopAnnotPhase.scala b/plugin/src/main/scala/plugin/TopAnnotPhase.scala index 88ba1531b..af57907f4 100644 --- a/plugin/src/main/scala/plugin/TopAnnotPhase.scala +++ b/plugin/src/main/scala/plugin/TopAnnotPhase.scala @@ -501,7 +501,7 @@ class TopAnnotPhase(setting: Setting) extends CommonPhase: override def prepareForUnit(tree: Tree)(using Context): Context = super.prepareForUnit(tree) - topAnnotSym = requiredClass("dfhdl.top") + topAnnotSym = requiredClass("dfhdl.hw.annotation.top") appTpe = requiredClassRef("dfhdl.app.DFApp") dfcTpe = requiredClassRef("dfhdl.core.DFC") designTpe = requiredClassRef("dfhdl.core.Design") From 42c2f3ae61e940826c5377b9572b63f259994b57 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 13 Aug 2026 02:35:21 +0300 Subject: [PATCH 05/57] benchmarks: pick up the `@top` import the annotation's move requires Points at the one submodule commit that adds `import dfhdl.hw.annotation.top` to the three `serv` entry points, so the benchmarks project still compiles after #482. Co-Authored-By: Claude Opus 5 (1M context) --- benchmarks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks b/benchmarks index a97522630..5f53840f1 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit a97522630f4960adabe6b4241b6dc0b124700c6b +Subproject commit 5f53840f1929721cc0c01a0b3a75eebb36c7d07b From 0e6323cb5b442830d281f944977e0e169068e37a Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 13 Aug 2026 02:42:39 +0300 Subject: [PATCH 06/57] devdocs: automatic constraints, as implemented What DFHDL does when a width relation is neither provably held nor provably violated, which is the normal state of affairs once widths are design parameters: it accepts the operation and states the relation it assumed as a static assertion in the generated design. Covers the three-way answer and what makes an undecidable relation load-bearing enough to state, the static assertion as a structurally derived species and what `ToED`, `OrderMembers` and the elaboration check do with it, the guard as its own record along with retraction and materialization, the minimization that reads the user's own assertions as facts, the checks that generate and the ones that deliberately do not, and the per-dialect printing. Co-Authored-By: Claude Opus 5 (1M context) --- devdocs/auto-constraints.md | 330 ++++++++++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 devdocs/auto-constraints.md diff --git a/devdocs/auto-constraints.md b/devdocs/auto-constraints.md new file mode 100644 index 000000000..ea155db8e --- /dev/null +++ b/devdocs/auto-constraints.md @@ -0,0 +1,330 @@ +# Automatic Constraints + +What DFHDL does when a width relation is neither provably held nor provably violated, which is the +normal state of affairs once widths are design parameters. It accepts the operation and states the +relation it assumed as a static assertion in the generated design, so every instantiation checks +the contract that this elaboration could not. + +This describes what is implemented, in +[AutoConstraint.scala](../core/src/main/scala/dfhdl/core/AutoConstraint.scala) for the mechanism +and in [DFDecimal.scala](../core/src/main/scala/dfhdl/core/DFDecimal.scala) for the checks that +feed it. The generated assertions are ordinary members of the elaborated design, so they print in +the DFHDL code string and in every backend. Coverage lives in +[PrintCodeStringSpec](../compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala) (the +generated forms and their minimization), +[PrintVerilogCodeSpec](../compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala) and +[PrintVHDLCodeSpec](../compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala) (the +per-dialect printing), and +[ElaborationChecksSpec](../lib/src/test/scala/ElaborationChecksSpec.scala) (the rejections that +stay rejections). + +Related: [scoping.md](scoping.md) for the capability that lets a body hold an assertion at all, and +[initial-blocks.md](initial-blocks.md) for the other construct that reaches a Verilog `initial` +block. + +## 1. The three-way + +Every width relation has three answers, and the middle one is what this document is about. + +| Answer | What happens | +|---|---| +| provably held, for every valid parameter assignment | nothing, the operation stands | +| provably violated, for every valid assignment | an error, at compile time over resolved widths and at elaboration over parametric ones | +| undecidable | the operation stands, and the relation becomes a constraint of the design | + +The first two are unchanged by anything here: `u8 := u16` is a compile error and `x(W) := y(2 * W)` +an elaboration error, because both are violated whatever the parameters turn out to be. Only the +third answer is new, and it replaces two older behaviours that disagreed with each other: some +sites rejected an undecidable relation outright, demanding a `.resize` of code that is very +probably correct, while most assumed it silently. + +The rule for whether an undecidable relation generates anything is that it must be +**load-bearing**: the construction elaboration chose has to be correct only under it. + +- A leaf resize consumes an assumption. `z(16) := x(W)` extends only if `16 >= W`, and truncates + otherwise, which is the data loss the language exists to catch. +- A target-context-widened `+`, `-`, `*` cone consumes none. Truncation commutes with those, so + re-evaluating at the target agrees with the narrow evaluation whether the target turns out wider + or narrower. A `<<` is a multiplication and commutes too. +- A widened `>>` consumes one: `(x mod 2^t) >> k` is not `(x >> k) mod 2^t`, so the agreement rests + on the target being at least as wide as the operand. +- A type bound consumes one as well (`UInt(N)` is a legal type only under `N >= 1`), and generates + nothing all the same. See §7. + +Deriving what to assert from what was assumed, rather than from what was undecidable, is what keeps +the count low before any minimization. + +## 2. Static assertions + +A generated constraint is an assertion whose condition and message are constant, and that species +is first-class rather than a private form. + +**Definition**, derived structurally in `TextOut.isStaticAssert` +([DFValAnalysis.scala](../compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala)): +a `TextOut` whose op is `Assert`, that is **directly owned by a `DFDomainOwner`** (a design or +domain body, an HDL method's block excluded, that being procedural), and whose assertion guard and +every message argument is `isConst`. Nothing is stored: no IR op of its own, no marker tag. A +user-written `assert` with a constant condition is a static assertion by construction, with no API +of its own, and its printed form stays `assert(cond, msg)`. + +The position half of the definition is what makes the species printable. The elaboration-time forms +of §6 exist only in concurrent position; an assert meeting the constant criteria inside a process, +a conditional block or a loop is procedural content and keeps its procedural printing. + +Three consequences downstream: + +- **`ToED`** exempts static asserts and their constant cones from the process sweep + ([ToED.scala](../compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala)), so an RT or + DF body's contract survives lowering as a body member instead of firing per evaluation. +- **`DB.textOutCheck`** rejects any other concurrent text output under an ED domain. A runtime + statement needs a runtime, and a concurrent ED position has none to offer, the sweep that gives + an RT body statement one not applying to a body that is already ED. So the user is told at + elaboration rather than discovering it in illegal generated HDL. +- **`OrderMembers`** ranks static asserts, and the anonymous members computing their conditions, + immediately after the constant declarations, so the emitted output reads them as a contract + header. + +**DFacsimile** evaluates a static assert ONCE at time zero (`once = t.isStaticAssert` in +`buildTextOut`), the simulation analogue of elaboration time, rather than per committed cycle. + +## 3. The mechanism + +### The guard is the record + +A pending constraint is not a side table. It is **the condition value itself, tagged** +`ir.AutoConstraint`, a marker with no payload. Everything else a constraint needs the guard already +carries: + +- **its origin** is the guard's own `meta.position`, the guard being built under the DFC of the + operation that assumed it, so nothing can disagree with where it came from; +- **collection** is a scan of the design context's member list in member order, which is the + elaboration order of the operations, so the assertions come out in source order with no sorting; +- **its lifetime** is already managed, a guard nothing reads being an unread anonymous value that + the end-of-design sweep collects. + +Two invariants keep the tag honest. It goes on a FRESH anonymous value, tagging something already +read being both a leak past the sweep and a tag carried into the snapshot. And it never survives +materialization, so no member of a finished design has one. + +This is why it is neither of the two things a tag must not be. It is not a structural property +that belongs in a `Modifier`, and it is not a stage marker that a later stage reads: it is created +and consumed inside one design's elaboration, before that design's member snapshot exists. + +`raise` records nothing under meta-programming (a stage transforms an already-elaborated design and +assumes nothing of its own) or at global scope (no body to state it in), and nothing for a +condition that folded to a constant `true`, which requires nothing. One that folded to `false` is +kept: an assumption that cannot hold is worth the noise. + +### Retraction + +`raiseFor` ties a constraint to the VALUE that makes it, recorded in +`DesignContext.autoConstraintOf`, and `retract` drops it. An assumption is normally the design's +for good, the operation that made it being a statement of the body. An anonymous operand is not: +target-context widening re-evaluates a whole expression at the target's width +([CarryPromote.scala](../core/src/main/scala/dfhdl/core/CarryPromote.scala)), discarding the narrow +form of every operand in it, and an assumption only that narrow form needed goes with it. + +### Materialization + +At the end of the design body, under the body's own DFC and before `dfc.exitOwner()` +([Design.scala](../core/src/main/scala/dfhdl/core/Design.scala)), `materialize` collects the tagged +guards, minimizes them (§4), and for each survivor plants a static assert at the tail of the body. + +It does not reference the guard it was handed. The check fires wherever the user wrote the +operation, so a guard built inside an `if` is OWNED by that block: perfectly constant, and simply +not accessible from where the assertion belongs, reading a block-owned value from the body being +what `DB.blockScopeCheck` rejects. So the guard's cone is CLONED into the body +(`cloneAnonValueAndDepsHere`) and the assertion made over the clone. Three things follow: the +original is then read by nothing and the sweep collects it, which is also what makes a +minimized-away constraint free; deduplication must compare the constraints rather than the members, +two identical constraints raised in different blocks being distinct cones; and a cone reading a +NAMED block-local declaration is the one shape that does not lift, which `blockScopeCheck` already +reports as the error it is. + +**Message**, derived from the condition and nothing else: + +``` +Design parameter violation found. Expected: 16 >= W +``` + +The check that raised it has a message of its own, and using it is wrong on both counts. It +describes ONE operation, while the assertion describes the design's INTERFACE, minimization having +merged the assumptions of several operations into one statement. And its reader is different: an +elaboration error is read by whoever wrote the assignment, at a position Scala pins exactly, while +this is read by whoever instantiates the generated module, where naming a DFHDL assignment says +nothing actionable. What that reader needs is the relation to satisfy. + +**Severity** is `Fatal`. A violated width contract invalidates everything downstream, and the +elaboration-time forms abort cheaply. + +**Naming**: `constraint_0`, `constraint_1`, and so on, enumerated at materialization rather than +left to `UniqueNames`, the printed DFHDL being source and two `val constraint = ...` bindings in one +body not re-elaborating. The enumeration starts at the first constraint even when a design has only +one, because the bare `constraint` is a SystemVerilog keyword that cannot label a generate block. + +**Design parameters stay unfolded** in the emitted condition. The generated HDL keeps parameters +overridable, which is the entire point of checking at the instantiation. + +### Only the elaboration root's parameters are free + +A width constraint is generated where a width does not resolve, and inside a SUB-design every width +does, its parameters being fixed by the instantiation that is elaborating it. So width constraints +land on the top design, which is the same scope as the problem they solve. A VALUE constraint (§5) +is different: a parameter stays opaque to `getConstData` even where it was applied, so a sub-design +states its own, which is right, the generated module keeping that parameter overridable in HDL. + +## 4. Minimization + +Every comparison normalizes onto ONE canonical form, `IntExprCalc.linearDiff(lhs, rhs) >= 0`, so +`W + W >= 8` and `2 * W >= 8` are one relation, and a user's `W <= 8` is comparable with a generated +`16 >= W` without either being rewritten. A strict comparison is the non-strict one over integers, +one tighter. Never textual comparison. + +Two relations are comparable exactly when their symbolic terms cancel (`constOffsetDiff`), and then +the one with the smaller constant is the stronger: `x - y >= 0` means `y >= 0` implies `x >= 0`. So +`8 >= W` subsumes `16 >= W`, and `W >= 6` subsumes `W >= 2`. Nonlinear widths participate as opaque +bases, so same-base constraints minimize identically. + +Materialization keeps a constraint only when nothing already kept implies it, and drops anything +kept that IT implies. Deduplication falls out as the case where two constraints imply each other. + +A constraint with no comparable form takes no part beyond structural deduplication. A user's +assertion may be anything at all, and a generated multi-part requirement is a conjunction rather +than a relation. + +### The user's own assertions participate + +A user-written static assertion of severity `Error` or `Fatal` is a design contract like a +generated one, so minimization reads its condition as an input. `Info` and `Warning` do not: they +report, they do not constrain. + +The relation is one-way, and that asymmetry is the point. A user assertion is NEVER removed, +subsumed or rewritten: the user wrote it, so it stays as written, in its own position, with its own +message and severity. An AUTO constraint IS removed when a user assertion implies it. Having +written `assert(W >= 8, ...)`, the user should not then read a generated `W >= 1` next to it. + +## 5. Which checks generate + +An undecided `Check` is the source. A `Check1`/`Check2` instance is applied over `Int`s, so a check +whose arguments do not fold to literals never runs, and its undecided arm is where the assumption +is made. One elaboration-half helper per check family sits beside the check the family already has, +so a family is wired once and every site reaching its undecided arm goes through it. + +| Site | Helper | States | +|---|---|---| +| assignment, connection, and the LHS-dominant `-`, `/`, `%` | `widthFitCheck` | `LW >= RW'` | +| a wildcard `Int` with a known minimum width adapting | `wildcardFitCheck` | `baWidth >= wcWidth` | +| a wildcard `Int` whose VALUE does not resolve | `wildcardValueFitCheck` | see below | +| a width-adjustment permission (`.extend`, `.truncate`) | `permitsWidthAdjust` | the direction the permission covers | +| a widened `>>` | `AutoConstraint.raiseUndecidedFit` | `target >= operand` | + +**The LHS-dominant three** take the LHS width and convert the RHS to it, so all three need the same +fit and take the same three answers. `-` used to reject the undecided one outright, which answered +"cannot tell" with "no" for one operation in three. + +**A wildcard whose value does not resolve** is every manifestation of an overridable parameter: +neither its width nor its sign is known, for this elaboration or any other, so there is nothing to +compare and the bound is on the VALUE. It is the same relation, stated as the width that value +needs, which is `clog2(v + 1)` bits for an unsigned `v` and `clog2(max(v + 1, -v)) + 1` for a signed +one, both exactly and for every `v`. Through `clog2` rather than as `v <= 2 ** width - 1` +deliberately: a 64-bit target would overflow the 32-bit integer arithmetic the generated HDL +evaluates the contract in. The sign being unknown too, an unsigned target adds `v >= 0` in the same +constraint, the halves being what one adaptation needs together. Nothing is stated for a target that +is itself a wildcard, which adapts to nothing, nor for a wildcard the body cannot read, a `for` +iterator having no value in the finished design either. + +**A permission covers the WIDTH relation and only that.** Signedness is not a permission's to give, +so it is checked either way. `.extend` and `.truncate` each cover one direction and state it where +it is undecided; the carte-blanche `.resize` covers both and states nothing, which makes it the one +spelling under which a widened `>>` has no other guard. + +**A `.truncate` also decides against widening.** It states that the target is narrower, the exact +contradiction of what an undecided comparison optimistically assumes, so where the widths cannot be +compared the author's statement is what decides and the value keeps its own width. Only there: a +permission whose direction does not apply contributes nothing, so a provably wider target widens as +it always did. + +**A comparison** has two undecided cases and only one generates. A wildcard `Int` argument adapts to +the receiver, so it states the fit it needs. Two bit-accurate operands are held to EQUAL widths, and +an unprovable pair is REJECTED: a comparison has no adaptation semantics to assume anything for, so +the resize it would otherwise emit is not a semantics worth asserting, it is the silent truncation +the equality rule exists to prevent. + +### What a `max` decides away + +A `max` is at least each of its own branches and a `min` at most each of its, so a comparison +between such a chain and one of its own branches is either an answer or a comparison with what is +left of the chain. The shape is what a width taken as the COMMON width of two operands meets when it +comes back to one of them, which every binary operation over unrelated parametric widths does. + +The identity is asked at two levels and implemented at both. `SimplifyFunc.CompareAgainstMaxMin` +rewrites the expression, so a design holding `x(W1) + y(W2)` in `W1` bits states `W1 >= W2` rather +than restating the common width it went through. `IntExprCalc`'s `dominatesByBranch` is a fallback +in `widthFitCompare`, so `max(W1, W2) >= W1` is proven rather than left undecided; it runs last, so +it only ever turns an undecided answer into a decided one and never overrides the max/min +elimination, which reads a mixed chain by its constants and is deliberately lenient. + +Proving it is also what lets a stacked resize through a common width fold away, `toDFXIntOf`'s +`unstack` asking whether the inner resize loses anything rather than whether it strictly widens. + +## 6. Printing + +The key is position: a static assert in CONCURRENT position prints as an elaboration-time +construct, and in procedural position as it always did. + +| Backend | Concurrent form | +|---|---| +| VHDL v93 / v2008 / v2019 | concurrent `assert COND report MSG severity S;`, a static expression, so it fires at elaboration | +| SystemVerilog sv2009 and later | `if (!(COND)) $fatal(1, MSG);` at module scope: a generate-`if` with an elaboration system task (IEEE 1800-2009 par. 20.11), so synthesis and simulation both catch it at elaboration | +| sv2005 | `initial assert (COND) else $error(MSG);`, immediate asserts and severity tasks being procedural in 1800-2005, checked at simulation time zero | +| v95 / v2001 | `initial if (!(COND)) begin $display(...); $finish; end`, an `initial` block being a module item where a bare statement is not | + +Verilog names a block, not a statement, so a named assertion becomes a named block: the generate +block of the elaboration form, or the `initial` block of the others. Naming the generate block is +also what keeps a linter from complaining about the implicit `genblk` the LRM would otherwise +assign. + +## 7. Adding a check + +1. Find the undecided arm. It is the `case _ =>` where a `Check` over `Int`s could not run because + a width did not fold. +2. Decide whether the relation is LOAD-BEARING there (§1). If the construction is correct whatever + the relation turns out to be, state nothing. +3. Discharge before stating: `AutoConstraint.widthFitGE` decides the two decidable answers, and + only `None` becomes a constraint. A provably violated relation stays the check's own hard error, + with the check's own message. +4. State it with `raise`, or with `raiseFor` if the value it constrains is an anonymous operand + something else can supersede. +5. Nothing on this path may mint an `IntParamRef` for a width it is holding. A reference no member + holds has no origin, and the printer resolves a name relative to the origin's owner, so + rendering through a freshly minted one dies with a missing-ref lookup. Both the rendering and the + discharge work from the width VALUE (`IntParam.errorString`, `IntExprCalc.widthFitCompare`). +6. Add the generated form to + [PrintCodeStringSpec](../compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala), + and the surviving rejection to + [ElaborationChecksSpec](../lib/src/test/scala/ElaborationChecksSpec.scala). Expect reference HDL + churn under `lib/src/test/resources/ref/` and review it deliberately: a new constraint on a + documented example is a user-visible change. + +## 8. Known gaps + +- **Type-construction bounds generate nothing.** The `toScalaIntOpt.foreach(check(_))` sites across + `DFBits.scala`, `DFDecimal.scala` and `DFVector.scala` have exactly the same shape of undecided + arm and are the widest source of unprovable predicates: width positivity, signed width, + `.until`/`.to` bounds, `repeat`/`eby` positivity, sel widths, vector cell dims, `UBArg` bounds. + `SInt(W)` appears in essentially every parametric declaration, so asserting each construction + would put a wall of `W >= 2`-class contracts in front of the ones that say something about the + design, and those bounds are also the likeliest to be implied by a constraint generated elsewhere. + Nothing about the mechanism needs to change if that judgement is revisited. +- **A signed width restates its own type bound.** `widthFitCompare`'s proof knows only that a width + is `>= 1`, so `a + b + 1` over `SInt(W)` states `W >= 2`, which `SInt(W)` already guarantees. + Discharging those means giving the proof the operand's signedness, which the shared + `widthFitCompare(a, b)` does not take. +- **`Bits` strictness stays a hard reject.** `Bits` is the strict type by design and has no + adaptation semantics to assert an assumption for. If ever relaxed, the mechanism extends + trivially, but that is a separate decision. +- **No suppression option.** There is no elaboration option or annotation to turn the constraints + off for a user who wants lean output. The assertions are the feature. +- **No user documentation.** Nothing under `docs/` describes any of this, even though the generated + assertions appear both in the elaborated design and in the emitted HDL, `Blinker` and `UART_Tx` + included. From b1a646a94acebb542977b824dd21bc5e7230117d Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 13 Aug 2026 03:12:56 +0300 Subject: [PATCH 07/57] compiler_ir: a design parameter's default is not what the parameter is, and never decides anything The width algebra substituted a non-top design parameter with its applied OR DEFAULT value. While a design elaborates its own body there is no instance to take an applied value from, so it took the default, and every width decision inside a parametric sub-design was made on a value the design may well not have. The elaboration root escaped only by being guarded out of the substitution entirely, which is why the same class elaborated cleanly standalone and failed the moment it was nested. It went wrong in both directions. An operation the applied value made perfectly legal was rejected, reporting the width symbolically while having decided it numerically, which is the shape of the report: nothing can prove `9 > W` for an opaque `W`. And an operation the applied value made illegal was accepted, stating no contract at all, so a module whose parameter is overridable truncated silently at every value its default did not cover. A parameter is now substituted only where an instantiation actually supplies a value. A design's own body never does, so its parameters stay the free variables they are and it states the same contract whether it is elaborated standalone or as a child. A decision made in the PARENT still resolves, and should: there the applied value is what the operation is about. Fixes #479 Co-Authored-By: Claude Opus 5 (1M context) --- .../scala/dfhdl/compiler/ir/IntExprCalc.scala | 34 ++++++++++------- .../StagesSpec/PrintCodeStringSpec.scala | 37 +++++++++++++++++++ devdocs/auto-constraints.md | 24 ++++++++---- .../test/scala/ElaborationChecksSpec.scala | 13 ++++--- 4 files changed, 81 insertions(+), 27 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala index 8a77c0d54..e3022cea1 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala @@ -103,10 +103,11 @@ object IntExprCalc: */ case Opaque - /** Substituted by the applied/default value EXPRESSION for non-top designs - * (`appliedOrDefaultVal`). Correct only under a getSet where the instantiation site is - * resolvable (the flat DB); used by post-elaboration width equivalence - * (`IntParamRef.compare`). + /** Substituted by the APPLIED value expression, and only where an instantiation actually + * supplies one. A parameter with none stays an opaque base, so a decision made about it holds + * for every assignment: while its own design is still elaborating there is no instance yet, + * and the elaboration root never has one. Used by width equivalence (`IntParamRef.compare`) + * and the width-fit proof ([[widthFitCompare]]). */ case AppliedExpr @@ -187,19 +188,24 @@ object IntExprCalc: private final class Calc(mode: ParamResolve, elimSymbolicMaxMin: Boolean = false)(using getSet: MemberGetSet ): - // Strip type-preserving AsIs wrappers and, under `AppliedExpr`, DesignParams - // whose owner design has a parent (i.e., is not the top design). For non-top - // designs, the parameter was provided by the instantiating parent, so - // resolve it via `appliedOrDefaultVal`. Params on a top design have no - // parent and stay opaque: they are the symbolic free variables exposed to - // the user at elaboration time. Elaboration-time folding (SimplifyFunc) - // disables the resolution (`Opaque`) so its decisions hold for any parameter - // assignment and designs stay parametric. `AppliedData` resolves in `linear` - // at the data level instead (see ParamResolve). + // Strip type-preserving AsIs wrappers and, under `AppliedExpr`, DesignParams that an + // instantiation actually supplies a value for. + // + // A parameter's DEFAULT is never that value. It is what the parameter is when nothing says + // otherwise, and substituting it decides a relation on a value the design may well not + // have: the generated module keeps the parameter overridable, from a DFHDL parent or from + // hand-written HDL, so a decision about it either holds symbolically or is not a decision + // about the design at all. So a parameter with no applied value stays an opaque base, which + // covers both the design that is still elaborating its own body (no instance exists yet) and + // the elaboration root (whose parameters are the free variables of the compilation). + // + // Elaboration-time folding (SimplifyFunc) disables the resolution entirely (`Opaque`) so its + // decisions hold for any assignment and designs stay parametric. `AppliedData` resolves in + // `linear` at the data level instead (see ParamResolve). private def strip(v: DFVal): DFVal = v.stripTypePreservingAliases match case dp: DFVal.DesignParam if mode == ParamResolve.AppliedExpr && !dp.getOwnerDesign.isTop => - strip(dp.appliedOrDefaultVal) + dp.appliedValOpt.map(strip).getOrElse(dp) case stripped => stripped // Ops whose operand order is irrelevant when comparing opaque bases. diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index 446498f6d..a40644cc8 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -3544,6 +3544,43 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + test("a sub-design states its own contract, not the one its instantiation happens to satisfy") { + // A design's parameter stays overridable in the module it is emitted as, so what its body + // assumes has to hold for whatever that parameter turns out to be. The value an instantiation + // supplies decides nothing about it, and neither does its default, which is what the + // parameter is only when nothing says otherwise. So a child elaborates to exactly what it + // would standalone: the literal takes the target width and the design states the fit it needs. + class Mul(val W: Int <> CONST = 8) extends EDDesign: + val gx = UInt(W) <> IN + val prod = UInt(21) <> OUT + prod <> gx * 373 + end Mul + class MulParent extends EDDesign: + val gx = UInt(8) <> IN + val prod = UInt(21) <> OUT + val leaf = Mul() + leaf.gx <> gx + leaf.prod <> prod + end MulParent + assertCodeString( + MulParent(), + """|class Mul(val W: Int <> CONST = 8) extends EDDesign: + | val gx = UInt(W) <> IN + | val prod = UInt(21) <> OUT + | prod <> (gx.resize(21) * d"21'373") + | val constraint_0 = assert(21 >= W, s"Design parameter violation found. Expected: 21 >= W", Severity.Fatal) + |end Mul + | + |class MulParent extends EDDesign: + | val gx = UInt(8) <> IN + | val prod = UInt(21) <> OUT + | val leaf = Mul(W = 8) + | leaf.gx <> gx + | prod <> leaf.prod + |end MulParent + |""".stripMargin + ) + } test("auto constraint from an LHS-dominant operation") { // `-`, `/` and `%` take the LHS width and convert the RHS to it, so each needs the RHS to // fit. `-` used to answer the undecided case with an outright rejection while its two diff --git a/devdocs/auto-constraints.md b/devdocs/auto-constraints.md index ea155db8e..f13d262f0 100644 --- a/devdocs/auto-constraints.md +++ b/devdocs/auto-constraints.md @@ -165,13 +165,23 @@ one, because the bare `constraint` is a SystemVerilog keyword that cannot label **Design parameters stay unfolded** in the emitted condition. The generated HDL keeps parameters overridable, which is the entire point of checking at the instantiation. -### Only the elaboration root's parameters are free - -A width constraint is generated where a width does not resolve, and inside a SUB-design every width -does, its parameters being fixed by the instantiation that is elaborating it. So width constraints -land on the top design, which is the same scope as the problem they solve. A VALUE constraint (§5) -is different: a parameter stays opaque to `getConstData` even where it was applied, so a sub-design -states its own, which is right, the generated module keeping that parameter overridable in HDL. +### Every design states its own contract + +A design parameter is never resolved to decide a relation about the design that declares it, and +least of all to its DEFAULT, which is what the parameter is when nothing says otherwise rather than +what it is. The generated module keeps the parameter overridable, from a DFHDL parent or from +hand-written HDL, so what a body assumes has to hold for whatever that parameter turns out to be. A +decision made on one instantiation's value is not a decision about the design. + +So a sub-design's parameters stay symbolic while its body elaborates, exactly as the elaboration +root's do, and a child elaborates to the same thing it would standalone. `IntExprCalc`'s +`AppliedExpr` mode substitutes a parameter only where an instantiation actually supplies a value, +which during the design's own body is never, no instance existing yet, and for the root is never at +all. Substituting the default in its place decided relations on a value the design did not have, in +both directions: rejecting an operation the applied value made legal, and accepting one it did not. + +A decision made in the PARENT is a different matter. There the applied value is what the connection +or the operation is really about, and the instantiation is resolvable, so it resolves. ## 4. Minimization diff --git a/lib/src/test/scala/ElaborationChecksSpec.scala b/lib/src/test/scala/ElaborationChecksSpec.scala index 85a6aabb2..13bd46d38 100644 --- a/lib/src/test/scala/ElaborationChecksSpec.scala +++ b/lib/src/test/scala/ElaborationChecksSpec.scala @@ -1777,15 +1777,16 @@ class ElaborationChecksSpec extends DesignSpec: import dfhdl.compiler.stages.db val designDB = Outer().db val allMembers = (designDB :: designDB.subDBs.values.toList).flatMap(_.members) - // one constraint, from `Outer`. `Inner`'s own fit needs no constraint: a sub-design's - // parameters are fixed by the instantiation elaborating it, so its widths resolve and the - // relation is decided here and now. Only the elaboration ROOT's parameters stay free. + // one constraint each, from `Outer` and from `Inner`. A sub-design states its own contract: + // it is emitted as a module whose parameter stays overridable, so what its body assumes has + // to hold for whatever that parameter turns out to be, and the value this instantiation + // happens to supply decides nothing. assertEquals( allMembers.count { case textOut: compiler.ir.TextOut => !textOut.isAnonymous case _ => false }, - 1 + 2 ) assert( allMembers.forall(!_.hasTagOf[compiler.ir.AutoConstraint]), @@ -1819,7 +1820,7 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(Unprovable())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1805:9 - 1805:27 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1806:9 - 1806:27 |Hierarchy: Unprovable |Operation: `apply` |Message: Cannot apply this operation between a value of W bits width (LHS) and a value of 8 bits width (RHS). @@ -1845,7 +1846,7 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(ProvablyNarrowSub())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1838:17 - 1838:22 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1839:17 - 1839:22 |Hierarchy: ProvablyNarrowSub |Operation: `-` |Message: The applied RHS value width (2 * W) is larger than the LHS variable width (W).""".stripMargin From 91cf1e965348a69c1923d0f29fec3043ce0c00c9 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 13 Aug 2026 09:00:49 +0300 Subject: [PATCH 08/57] core+lib: an elaboration error is named by the operation the user wrote `dout <> din` between mismatched widths reported ``Operation: `apply` ``, twice, once per direction. The name an error carries is the `CTName` of whichever `trydf` traps it, and `CTName` resolves to the enclosing method's name unless the site passes one. `<>` did pass one. It was never the trap that fired: `DFVal.TC.apply` wrapped `conv` in a `trydf` of its own, so the width mismatch was caught one level below the operator, stamped with that method's name, and logged as an error the `<>` trap could no longer rename. What reached the operator was a `DFError.Derived`, which the report filters out. The mirrored pair came from the same swallowing: the connect retries the flipped direction when the first throws, and both directions logged their own error. A conversion is not an operation. It is the receiving half of the `<>`, `:=`, `init` or `sel` the user wrote, and that operation's trap is the one that names it, so `TC.apply` no longer traps at all. Every path reaching it already sits under an operator's `trydf`, which the `:=` route, going through `Exact1.apply` straight to `conv`, had been demonstrating all along by reporting `:=` correctly. `Compare.apply` and its `DFXInt` override had the same shape and lose their traps for the same reason; dropping the override's also restores the flipped-direction retry a swallowed exception was suppressing. The same reasoning applies to the shared `DFDecimal` builder every integer and fixed-point constructor delegates to, which was where their width checks were being named; `DFSInt.apply` had been relying on it and now traps for itself. The rest is naming. Seven operator givens took the enclosing `apply` while holding the `ValueOf` of the very operator they implement, so `^`, `&`, `|`, `++`, `>>`, `<<` and `**` say so now. A type constructor names its type: `Bits constructor`, `UInt.to constructor`, `SFix constructor`. Domain blocks join ports and variables in naming what is being constructed. Co-Authored-By: Claude Opus 5 (1M context) --- core/src/main/scala/dfhdl/core/DFBits.scala | 23 ++++--- .../main/scala/dfhdl/core/DFBoolOrBit.scala | 2 +- .../src/main/scala/dfhdl/core/DFDecimal.scala | 39 ++++++----- core/src/main/scala/dfhdl/core/DFOpaque.scala | 3 +- core/src/main/scala/dfhdl/core/DFVal.scala | 5 +- core/src/main/scala/dfhdl/core/DFVector.scala | 3 +- core/src/main/scala/dfhdl/core/Domain.scala | 3 +- .../test/scala/ElaborationChecksSpec.scala | 68 ++++++++----------- 8 files changed, 72 insertions(+), 74 deletions(-) diff --git a/core/src/main/scala/dfhdl/core/DFBits.scala b/core/src/main/scala/dfhdl/core/DFBits.scala index c0f1daeea..c43d800d1 100644 --- a/core/src/main/scala/dfhdl/core/DFBits.scala +++ b/core/src/main/scala/dfhdl/core/DFBits.scala @@ -13,35 +13,40 @@ object DFBits: def apply[W <: IntP](width: IntParam[W])(using dfc: DFCG, check: Arg.Width.CheckNUB[W] - ): DFBits[W] = trydf: + ): DFBits[W] = trydf { width.toScalaIntOpt.foreach(check(_)) ir.DFBits(width.ref).asFE[DFBits[W]] + }(using dfc, CTName("Bits constructor")) def forced[W <: IntP](width: Int): DFBits[W] = val check = summon[Arg.Width.Check[Int]] check(width) ir.DFBits(width).asFE[DFBits[W]] - def apply[W <: IntP](using dfc: DFCG, dfType: => DFBits[W]): DFBits[W] = trydf { dfType } + def apply[W <: IntP](using dfc: DFCG, dfType: => DFBits[W]): DFBits[W] = + trydf { dfType }(using dfc, CTName("Bits constructor")) def until[V <: IntP](sup: IntParam[V])(using dfc: DFCG, check: Arg.LargerThan1.CheckNUB[V] - ): DFBits[IntP.CLog2[V]] = trydf: + ): DFBits[IntP.CLog2[V]] = trydf { sup.toScalaIntOpt.foreach(check(_)) ir.DFBits(sup.clog2.ref).asFE[DFBits[IntP.CLog2[V]]] + }(using dfc, CTName("Bits.until constructor")) def to[V <: IntP](max: IntParam[V])(using dfc: DFCG, check: Arg.Positive.CheckNUB[V] - ): DFBits[IntP.CLog2P1[V]] = trydf: + ): DFBits[IntP.CLog2P1[V]] = trydf { max.toScalaIntOpt.foreach(check(_)) ir.DFBits((max + 1).clog2.ref).asFE[DFBits[IntP.CLog2P1[V]]] + }(using dfc, CTName("Bits.to constructor")) given [W <: IntP & Singleton](using dfc: DFCG, v: ValueOf[W], check: Arg.Width.CheckNUB[W] - ): DFBits[W] = trydf: + ): DFBits[W] = trydf { val width = IntParam.forced(v) width.toScalaIntOpt.foreach(check(_)) ir.DFBits(width.ref).asFE[DFBits[W]] + }(using dfc, CTName("Bits constructor")) protected object `AW == TW` extends Check2[ @@ -667,7 +672,7 @@ object DFBits: case (Some(lw), Some(rw)) => check(lw, rw) case _ => equalWidthCheck(lhsVal.dfType, rhsVal.dfType) DFVal.Func(lhsVal.dfType, op.value, List(lhsVal, rhsVal)) - } + }(using dfc, CTName(op.value.toString)) end evOpLogicDFBits given evOpLogicReduceDFBits[ Op <: FuncOp.|.type | FuncOp.&.type | FuncOp.^.type, @@ -681,7 +686,7 @@ object DFBits: type Out = DFValTP[DFBit, LP] def apply(lhs: L)(using DFC): Out = trydf { DFVal.Func(DFBit, op.value, List(lhs)).asValTP[DFBit, LP] - } + }(using dfc, CTName(op.value.toString)) end evOpLogicReduceDFBits given evConcatOpDFBits[ Op <: FuncOp.++.type, @@ -703,7 +708,7 @@ object DFBits: val rhsVal = icR(rhs) val width = lhsVal.widthIntParam + rhsVal.widthIntParam DFVal.Func(DFBits(width), FuncOp.++, List(lhsVal, rhsVal)) - } + }(using dfc, CTName(op.value.toString)) end evConcatOpDFBits given evOpShift[ Op <: FuncOp.>>.type | FuncOp.<<.type, @@ -735,7 +740,7 @@ object DFBits: ) val shiftVal = ub(lhs.widthIntParam.asInstanceOf[IntParam[LW]], rhs) DFVal.Func(lhs.dfType, op.value, List(lhs, shiftVal)) - } + }(using dfc, CTName(op.value.toString)) end evOpShift extension [W <: IntP, P](lhs: DFValTP[DFBits[W], P]) diff --git a/core/src/main/scala/dfhdl/core/DFBoolOrBit.scala b/core/src/main/scala/dfhdl/core/DFBoolOrBit.scala index 32083c4c9..1cc924d47 100644 --- a/core/src/main/scala/dfhdl/core/DFBoolOrBit.scala +++ b/core/src/main/scala/dfhdl/core/DFBoolOrBit.scala @@ -131,7 +131,7 @@ object DFBoolOrBit: val lhsVal = icL(lhs) val rhsVal = b2b(lhsVal.dfType, icR(rhs)) DFVal.Func(lhsVal.dfType, op.value, List(lhsVal, rhsVal)) - } + }(using dfc, CTName(op.value.toString)) end evLogicOpDFBoolOrBit given evLogicOpDFBoolOrBit2[ Op <: FuncOp.|.type | FuncOp.&.type, diff --git a/core/src/main/scala/dfhdl/core/DFDecimal.scala b/core/src/main/scala/dfhdl/core/DFDecimal.scala index b99cb21d8..5f68dc01e 100644 --- a/core/src/main/scala/dfhdl/core/DFDecimal.scala +++ b/core/src/main/scala/dfhdl/core/DFDecimal.scala @@ -20,7 +20,7 @@ object DFDecimal: magnitudeWidth: IntParam[M], fractionWidth: Inlined[F], nativeType: N - )(using dfc: DFC, check: Width.CheckNUB[S, DecimalWidth[M, F]]): DFDecimal[S, M, F, N] = trydf: + )(using dfc: DFC, check: Width.CheckNUB[S, DecimalWidth[M, F]]): DFDecimal[S, M, F, N] = // the width constraints apply to the total bit width (magnitude + fraction) magnitudeWidth.toScalaIntOpt.foreach(m => check(signed, m + fractionWidth)) ir.DFDecimal(signed, magnitudeWidth.ref, fractionWidth, nativeType).asFE[DFDecimal[S, M, F, N]] @@ -41,8 +41,9 @@ object DFDecimal: ValueOf[M], ValueOf[F], ValueOf[N] - )(using DFCG, Width.CheckNUB[S, DecimalWidth[M, F]]): DFDecimal[S, M, F, N] = trydf: + )(using DFCG, Width.CheckNUB[S, DecimalWidth[M, F]]): DFDecimal[S, M, F, N] = trydf { DFDecimal(valueOf[S], IntParam[M](valueOf[M]), valueOf[F], valueOf[N]) + }(using dfc, CTName("Decimal constructor")) object Extensions: extension [S <: Boolean, M <: IntP, F <: Int, N <: NativeType](dfType: DFDecimal[S, M, F, N]) def signed: Inlined[S] = Inlined.forced[S](dfType.asIR.signed) @@ -1439,7 +1440,7 @@ object DFXInt: dfc: DFC, opv: ValueOf[Op], cv: ValueOf[C] - ): DFValTP[DFBool, P | RP] = trydf: + ): DFValTP[DFBool, P | RP] = // the operands are built anonymously, but the comparison itself is NOT: it takes the // enclosing context, which is what names it after the binding it feeds val anonDFC = dfc.anonymize @@ -1564,7 +1565,7 @@ object DFXInt: type Out = DFValTP[DFInt32, RP] def apply(lhs: L, rhs: R)(using DFC): Out = trydf { DFVal.Func(DFInt32, op.value, List(DFConstInt32(lhs), rhs)).asValTP[DFInt32, RP] - } + }(using dfc, CTName(op.value.toString)) end evOpShiftOrPowerInt given evOpLogicUInt[ Op <: FuncOp.|.type | FuncOp.&.type | FuncOp.^.type, @@ -1586,7 +1587,7 @@ object DFXInt: case (Some(lw), Some(rw)) => check(lw, rw) case _ => equalWidthCheck(lhs.dfType, rhs.dfType) DFVal.Func(lhs.dfType, op.value, List(lhs, rhs)) - } + }(using dfc, CTName(op.value.toString)) end evOpLogicUInt export dfhdl.internals.clog2 @@ -2274,17 +2275,18 @@ object DFUInt: def apply[W <: IntP](width: IntParam[W])(using DFCG, Width.CheckNUB[false, W]): DFUInt[W] = trydf { DFXInt(false, width, BitAccurate) - } + }(using dfc, CTName("UInt constructor")) def forced[W <: IntP](width: IntP)(using DFC): DFUInt[W] = DFUInt(IntParam[W](width.asInstanceOf[W])) - def apply[W <: IntP](using dfc: DFCG, dfType: => DFUInt[W]): DFUInt[W] = trydf { dfType } + def apply[W <: IntP](using dfc: DFCG, dfType: => DFUInt[W]): DFUInt[W] = + trydf { dfType }(using dfc, CTName("UInt constructor")) def until[V <: IntP](sup: IntParam[V])(using dfc: DFCG, check: Arg.LargerThan1.CheckNUB[V] ): DFUInt[IntP.CLog2[V]] = trydf { sup.toScalaIntOpt.foreach(check(_)) DFXInt(false, sup.clog2, BitAccurate) - } + }(using dfc, CTName("UInt.until constructor")) def to[V <: IntP](max: IntParam[V])(using dfc: DFCG, check: Arg.Positive.CheckNUB[V] @@ -2293,7 +2295,7 @@ object DFUInt: // the width value is `clog2(max + 1)`; the declared type says the same thing under a single // guard on `V`, which the composed spelling cannot (see `IntP.IsConstInt2`) DFXInt(false, (max + 1).clog2, BitAccurate).asInstanceOf[DFUInt[IntP.CLog2P1[V]]] - } + }(using dfc, CTName("UInt.to constructor")) protected object Unsigned extends Check1[ @@ -2503,24 +2505,27 @@ end DFUInt type DFSInt[W <: IntP] = DFXInt[true, W, BitAccurate] object DFSInt: def apply[W <: IntP](width: IntParam[W])(using DFCG, Width.CheckNUB[true, W]): DFSInt[W] = - DFXInt(true, width, BitAccurate) + trydf { + DFXInt(true, width, BitAccurate) + }(using dfc, CTName("SInt constructor")) def forced[W <: IntP](width: IntP)(using DFC): DFSInt[W] = DFSInt(IntParam[W](width.asInstanceOf[W])) - def apply[W <: IntP](using dfc: DFCG, dfType: => DFSInt[W]): DFSInt[W] = trydf { dfType } + def apply[W <: IntP](using dfc: DFCG, dfType: => DFSInt[W]): DFSInt[W] = + trydf { dfType }(using dfc, CTName("SInt constructor")) def untilAbs[V <: IntP](sup: IntParam[V])(using dfc: DFCG, check: Arg.LargerThan1.CheckNUB[V] ): DFSInt[IntP.CLog2Signed[V]] = trydf { sup.toScalaIntOpt.foreach(check(_)) DFXInt(true, sup.clog2 + 1, BitAccurate).asInstanceOf[DFSInt[IntP.CLog2Signed[V]]] - } + }(using dfc, CTName("SInt.untilAbs constructor")) def toAbs[V <: IntP](max: IntParam[V])(using dfc: DFCG, check: Arg.Positive.CheckNUB[V] ): DFSInt[IntP.CLog2P1Signed[V]] = trydf { max.toScalaIntOpt.foreach(check(_)) DFXInt(true, (max + 1).clog2 + 1, BitAccurate).asInstanceOf[DFSInt[IntP.CLog2P1Signed[V]]] - } + }(using dfc, CTName("SInt.toAbs constructor")) object Val: object Ops: @@ -2646,9 +2651,9 @@ object DFUFix: checkF(fractionWidth) magnitudeWidth.toScalaIntOpt.foreach(checkM(false, _)) DFDecimal(false, magnitudeWidth, fractionWidth, BitAccurate) - } + }(using dfc, CTName("UFix constructor")) def apply[M <: IntP, F <: Int](using dfc: DFCG, dfType: => DFUFix[M, F]): DFUFix[M, F] = - trydf { dfType } + trydf { dfType }(using dfc, CTName("UFix constructor")) end DFUFix type DFSFix[M <: IntP, F <: Int] = DFDecimal[true, M, F, BitAccurate] @@ -2662,9 +2667,9 @@ object DFSFix: checkF(fractionWidth) magnitudeWidth.toScalaIntOpt.foreach(checkM(true, _)) DFDecimal(true, magnitudeWidth, fractionWidth, BitAccurate) - } + }(using dfc, CTName("SFix constructor")) def apply[M <: IntP, F <: Int](using dfc: DFCG, dfType: => DFSFix[M, F]): DFSFix[M, F] = - trydf { dfType } + trydf { dfType }(using dfc, CTName("SFix constructor")) end DFSFix //a native Int32 decimal has no explicit Scala compile-time width, since the diff --git a/core/src/main/scala/dfhdl/core/DFOpaque.scala b/core/src/main/scala/dfhdl/core/DFOpaque.scala index 55dd04a06..722c7d762 100644 --- a/core/src/main/scala/dfhdl/core/DFOpaque.scala +++ b/core/src/main/scala/dfhdl/core/DFOpaque.scala @@ -43,7 +43,7 @@ object DFOpaque: def apply[TFE <: Abstract]( t: TFE - )(using dfc: DFCG): DFOpaque[TFE] = trydf: + )(using dfc: DFCG): DFOpaque[TFE] = trydf { val kind = t match case _: Clk => ir.DFOpaque.Kind.Clk case _: Rst => ir.DFOpaque.Kind.Rst @@ -63,6 +63,7 @@ object DFOpaque: id, t.actualType.asIR.dropUnreachableRefs(allowDesignParamRefs = false) ).asFE[DFOpaque[TFE]] + }(using dfc, CTName("Opaque constructor")) extension [A <: DFTypeAny, TFE <: Frontend[A]](dfType: DFOpaque[TFE]) def actualType: A = dfType.asIR.actualType.asFE[A] def opaqueType: TFE = dfType.asIR.id.asInstanceOf[TFE] diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index 295c425bb..5b261b217 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -1189,8 +1189,7 @@ object DFVal extends DFValLP: trait TC[T <: DFTypeAny, R] extends TCCommon[T, R, DFValAny]: type OutP type Out = DFValTP[T, OutP] - final def apply(dfType: T, value: R)(using DFC): Out = trydf: - conv(dfType, value) + final def apply(dfType: T, value: R)(using DFC): Out = conv(dfType, value) // This is a dummy instance for DFIf and DFMatch specialized Exact1 extractions object TCDummy extends TC[DFTypeAny, DFValOf[DFTypeAny]]: @@ -1319,7 +1318,7 @@ object DFVal extends DFValLP: DFC, ValueOf[Op], ValueOf[C] - ): DFValTP[DFBool, P | OutP] = trydf: + ): DFValTP[DFBool, P | OutP] = val dfValArg = conv(dfVal.dfType, arg)(using dfc.anonymize) func(dfVal, dfValArg) end Compare diff --git a/core/src/main/scala/dfhdl/core/DFVector.scala b/core/src/main/scala/dfhdl/core/DFVector.scala index 4a88e43cb..8ab310347 100644 --- a/core/src/main/scala/dfhdl/core/DFVector.scala +++ b/core/src/main/scala/dfhdl/core/DFVector.scala @@ -23,10 +23,11 @@ object DFVector: cellType: T, d: ValueOf[D], check: VectorLength.CheckNUB[D] - ): DFVector[T, Tuple1[D]] = trydf: + ): DFVector[T, Tuple1[D]] = trydf { val cellDim = IntParam.fromValue(d) cellDim.toScalaIntOpt.foreach(check(_)) DFVector(cellType, List(cellDim)) + }(using dfc, CTName("Vector constructor")) extension [T <: DFTypeAny, D <: NonEmptyTuple](dfType: DFVector[T, D]) def cellType: T = dfType.asIR.cellType.asFE[T] diff --git a/core/src/main/scala/dfhdl/core/Domain.scala b/core/src/main/scala/dfhdl/core/Domain.scala index 08fb9d0cb..26503e07c 100644 --- a/core/src/main/scala/dfhdl/core/Domain.scala +++ b/core/src/main/scala/dfhdl/core/Domain.scala @@ -15,7 +15,7 @@ private[dfhdl] trait Domain extends Container with scala.reflect.Selectable: object Domain: type Block = DFOwner[ir.DomainBlock] object Block: - def apply(domainType: ir.DomainType)(using DFC): Block = trydf: + def apply(domainType: ir.DomainType)(using DFC): Block = trydf { dfc.owner.asIR match case _: ir.DFDomainOwner => case _ => @@ -25,6 +25,7 @@ object Domain: val ownerRef: ir.DFOwner.Ref = dfc.ownerOption.map(_.asIR.ref).getOrElse(ir.DFMember.Empty.ref) ir.DomainBlock(domainType, ownerRef, dfc.getMeta, dfc.tags).addMember.asFE + }(using dfc, CTName("Domain constructor")) end Block extension [D <: Domain](domain: D) infix def tag[CT <: ir.DFTag: ClassTag](customTag: CT)(using dfc: DFC): D = diff --git a/lib/src/test/scala/ElaborationChecksSpec.scala b/lib/src/test/scala/ElaborationChecksSpec.scala index 13bd46d38..f376d996b 100644 --- a/lib/src/test/scala/ElaborationChecksSpec.scala +++ b/lib/src/test/scala/ElaborationChecksSpec.scala @@ -76,7 +76,7 @@ class ElaborationChecksSpec extends DesignSpec: |DFiant HDL elaboration error! |Position: ${currentFilePos}ElaborationChecksSpec.scala:72:25 - 72:33 |Hierarchy: Top.dmn - |Operation: `apply` + |Operation: `Domain constructor` |Message: A domain can only be directly owned by a design, an interface, or another domain. |""".stripMargin ) @@ -554,9 +554,9 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(Foo())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:548:42 - 548:60 - |Hierarchy: Foo - |Operation: `apply` + |Position: ${currentFilePos}ElaborationChecksSpec.scala:548:17 - 548:60 + |Hierarchy: Foo.y + |Operation: `init` |Message: The applied RHS value width (WIDTH1 + 2) is larger than the LHS variable width (WIDTH1).""".stripMargin ) test("DFBits parameter width checks"): @@ -574,16 +574,16 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(Foo())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:568:42 - 568:56 - |Hierarchy: Foo - |Operation: `apply` + |Position: ${currentFilePos}ElaborationChecksSpec.scala:568:17 - 568:56 + |Hierarchy: Foo.x + |Operation: `init` |Message: The argument width (WIDTH2) is different than the receiver width (WIDTH1). |Consider `.extend` or `.truncate` to adjust it to the receiver width, or `.resize(width)` to state the width explicitly. | |DFiant HDL elaboration error! |Position: ${currentFilePos}ElaborationChecksSpec.scala:571:17 - 571:23 |Hierarchy: Foo.w - |Operation: `apply` + |Operation: `===` |Message: Cannot apply this operation between a value of WIDTH1 bits width (LHS) and a value of WIDTH2 bits width (RHS). |An explicit conversion must be applied.""".stripMargin ) @@ -1118,7 +1118,7 @@ class ElaborationChecksSpec extends DesignSpec: |DFiant HDL elaboration error! |Position: ${currentFilePos}ElaborationChecksSpec.scala:1104:9 - 1104:27 |Hierarchy: SelFixed - |Operation: `apply` + |Operation: `sel` |Message: The applied RHS value width (10) is larger than the LHS variable width (8).""".stripMargin ) // the accumulated width is a `max` chain the repeated-operand absorption keeps @@ -1413,15 +1413,8 @@ class ElaborationChecksSpec extends DesignSpec: |DFiant HDL elaboration error! |Position: ${currentFilePos}ElaborationChecksSpec.scala:1408:9 - 1408:17 |Hierarchy: Parent - |Operation: `apply` + |Operation: `<>` |Message: The argument width (c.OUTPUT_WIDTH) is different than the receiver width (OUTPUT_WIDTH). - |Consider `.extend` or `.truncate` to adjust it to the receiver width, or `.resize(width)` to state the width explicitly. - | - |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1408:9 - 1408:17 - |Hierarchy: Parent - |Operation: `apply` - |Message: The argument width (OUTPUT_WIDTH) is different than the receiver width (c.OUTPUT_WIDTH). |Consider `.extend` or `.truncate` to adjust it to the receiver width, or `.resize(width)` to state the width explicitly.""".stripMargin ) @@ -1440,17 +1433,10 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(Parent())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1437:9 - 1437:17 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1430:9 - 1430:17 |Hierarchy: Parent - |Operation: `apply` + |Operation: `<>` |Message: The argument width (c.W) is different than the receiver width (W). - |Consider `.extend` or `.truncate` to adjust it to the receiver width, or `.resize(width)` to state the width explicitly. - | - |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1437:9 - 1437:17 - |Hierarchy: Parent - |Operation: `apply` - |Message: The argument width (W) is different than the receiver width (c.W). |Consider `.extend` or `.truncate` to adjust it to the receiver width, or `.resize(width)` to state the width explicitly.""".stripMargin ) @@ -1496,7 +1482,7 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(ProvablyNarrow())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1491:9 - 1491:20 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1477:9 - 1477:20 |Hierarchy: ProvablyNarrow |Operation: `:=` |Message: The applied RHS value width (2 * W) is larger than the LHS variable width (W).""".stripMargin @@ -1521,7 +1507,7 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(Top())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1518:14 - 1518:35 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1504:14 - 1504:35 |Hierarchy: Top |Operation: `setName` |Message: Cannot set a name for a port of an internal design. @@ -1599,12 +1585,12 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(ParamIdxCollide())( s"""|Elaboration errors found! |DFiant HDL connectivity error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1595:9 - 1595:18 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1581:9 - 1581:18 |Hierarchy: ParamIdxCollide |LHS: v(1) |RHS: a |Message: Found multiple connections write to the same variable/port `ParamIdxCollide.v`. - |The previous write occurred at ${currentFilePos}ElaborationChecksSpec.scala:1593:9 - 1593:22""".stripMargin + |The previous write occurred at ${currentFilePos}ElaborationChecksSpec.scala:1579:9 - 1579:22""".stripMargin ) // A variable already driven reads as a source, so a second driver reaches the analysis as a @@ -1624,12 +1610,12 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(VarRedrive())( s"""|Elaboration errors found! |DFiant HDL connectivity error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1619:9 - 1619:18 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1605:9 - 1605:18 |Hierarchy: VarRedrive |LHS: v(0) |RHS: a |Message: Found multiple connections write to the same variable/port `VarRedrive.v`. - |The previous write occurred at ${currentFilePos}ElaborationChecksSpec.scala:1618:9 - 1618:18""".stripMargin + |The previous write occurred at ${currentFilePos}ElaborationChecksSpec.scala:1604:9 - 1604:18""".stripMargin ) // A bitwise operation requires equal operand widths. When at least one width is a design @@ -1669,18 +1655,18 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(BitsXorParam())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1650:9 - 1650:23 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1636:9 - 1636:23 |Hierarchy: BitsXorParam - |Operation: `apply` + |Operation: `^` |Message: Cannot apply this operation between a value of LEN bits width (LHS) and a value of 8 bits width (RHS). |An explicit conversion must be applied.""".stripMargin ) assertElaborationErrors(UIntAndParam())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1658:9 - 1658:23 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1644:9 - 1644:23 |Hierarchy: UIntAndParam - |Operation: `apply` + |Operation: `&` |Message: Cannot apply this operation between a value of LEN bits width (LHS) and a value of 8 bits width (RHS). |An explicit conversion must be applied.""".stripMargin ) @@ -1733,7 +1719,7 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(EDPrint())( s"""|Elaboration errors found! |DFiant HDL text output error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1705:9 - 1705:28 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1691:9 - 1691:28 |Hierarchy: EDPrint |Message: Text output is not allowed as a concurrent statement under an event-driven (ED) domain. |Only a static assertion (an `assert` whose condition and message are constant) may reside directly in an ED domain body, as a design contract checked at elaboration. @@ -1742,7 +1728,7 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(EDDynAssert())( s"""|Elaboration errors found! |DFiant HDL text output error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1712:9 - 1712:49 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1698:9 - 1698:49 |Hierarchy: EDDynAssert |Message: Text output is not allowed as a concurrent statement under an event-driven (ED) domain. |Only a static assertion (an `assert` whose condition and message are constant) may reside directly in an ED domain body, as a design contract checked at elaboration. @@ -1820,9 +1806,9 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(Unprovable())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1806:9 - 1806:27 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1792:9 - 1792:27 |Hierarchy: Unprovable - |Operation: `apply` + |Operation: `<` |Message: Cannot apply this operation between a value of W bits width (LHS) and a value of 8 bits width (RHS). |An explicit conversion must be applied.""".stripMargin ) @@ -1846,7 +1832,7 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(ProvablyNarrowSub())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1839:17 - 1839:22 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1825:17 - 1825:22 |Hierarchy: ProvablyNarrowSub |Operation: `-` |Message: The applied RHS value width (2 * W) is larger than the LHS variable width (W).""".stripMargin From 9a107ef96de10c18b4a997e5d459dca8059ca7de Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 13 Aug 2026 11:21:44 +0300 Subject: [PATCH 09/57] core+compiler_ir: a parameter the elaboration reads is no longer free, and the design says so Reading a parameter as a Scala value specializes the body to it: the `if` branch not taken and the `for` iteration that never ran leave nothing behind, so the design that comes out is the one for that value and no other. The generated module went on exposing the parameter as overridable all the same, and the width algebra went on treating it as a free variable, which is why a relation the branch itself guarantees could not be seen. The record is the purity marking, not anything kept where the reading happens. `PureCheckPhase` already names a read parameter on the design's own `@pure(true, ...)`, and re-attributes every application of a marked parameter at the call site, so a parameter read deep in a child marks the parameter of every design that feeds it. A parent that reads nothing itself is specialized just as surely, and states its own value. So a data-impure parameter folds to its value in `IntExprCalc` under `AppliedExpr` (the one case where a parameter with no instantiation site folds, the elaboration root included) and the design states `param == value` at materialization, where it joins the same minimization as every other constraint. Nothing is stated where an overriding instantiation cannot exist: a method design, a blackbox, or a parametrically-typed parameter, whose value has no literal of its own type to be compared against. Additive cancellation now walks both sides of the tree rather than the left spine, so `x + (y - x)` states `y`: a relative width adjustment written as the distance to another width is that width said the long way round. Fixes #480 Co-Authored-By: Claude Opus 5 (1M context) --- .../scala/dfhdl/compiler/ir/DFMember.scala | 33 +++++++ .../scala/dfhdl/compiler/ir/IntExprCalc.scala | 16 +++- .../scala/StagesSpec/ClassDesignKeySpec.scala | 18 +++- .../StagesSpec/PrintCodeStringSpec.scala | 87 +++++++++++++++++++ .../scala/dfhdl/core/AutoConstraint.scala | 42 +++++++++ .../main/scala/dfhdl/core/SimplifyFunc.scala | 42 +++++---- .../scala/CoreSpec/SameWidthArithSpec.scala | 26 ++++++ devdocs/auto-constraints.md | 78 +++++++++++++++-- 8 files changed, 313 insertions(+), 29 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 0ec392afe..478d81b3f 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -558,6 +558,20 @@ object DFVal: def appliedOrDefaultVal(using MemberGetSet): DFVal = appliedValOpt.getOrElse(defaultValRef.get.asInstanceOf[DFVal]) + /** Whether the design's elaboration READ this parameter's data, as `PureCheckPhase` recorded it + * (see [[DFDesignBlock.dataImpureParamNames]]). + * + * Reading a parameter is what specializes a body to it: a Scala `if` over it keeps one branch, + * a Scala `for` unrolls to a count, and neither the branch that was dropped nor the iteration + * that never ran is recoverable from the design that came out. So such a parameter is no + * longer a free variable of its design, which is why the width algebra may read it as its + * value ([[IntExprCalc]]) and why the design states that value as a contract + * (`core.AutoConstraint`). + */ + def isDataImpure(using MemberGetSet): Boolean = + val names = getOwnerDesign.dataImpureParamNames + names.contains("*") || names.contains(meta.name) + // The applied constant data resolved ONLY through an instantiation site: the elaboration-time // cached instance, the DB's instance map, or the hierarchical parent sub-DB walk-up. The // instance map is queried directly and NOT via `appliedValRefOpt`, whose `isTop` gate reads @@ -1931,6 +1945,25 @@ object DFDesignBlock: case _ => false } + /** The names of the parameters whose applied DATA this design's elaboration reads, as + * `PureCheckPhase` records them on `@pure(true, )`; `"*"` stands for all of them. + * + * The marking is transitive by construction: a `toScalaXYZ` forcing is attributed to the + * parameter it is rooted at, and every application of a marked parameter re-attributes its + * applied argument at the call site, so a parameter read deep in a sub-design marks the + * parameter of every design that feeds it. That is what makes this the right question to ask + * about specialization, rather than anything recorded where the reading happens: the design + * that supplied the value was specialized to it just as surely as the one that read it. + * + * Read only from the annotation, so a vendor IP blackbox (whose applied parameters are baked + * into the emitted instance, and which `DesignLoadKey` therefore keys in full) is not covered: + * it has no body to have read anything in, nor one to state a contract in. + */ + def dataImpureParamNames: Set[String] = + dsn.dclMeta.annotations.collectFirst { + case annotation.Pure(true, names) if names.nonEmpty => names.toSet + }.getOrElse(Set.empty) + /** An ED method (HDL function/task — see devdocs/methods.md): a method under the ED domain. ED * methods are locally scoped — printed inside their owning design (as HDL methods) rather than * as standalone design files, and their name-uniqueness scope is the owning design. diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala index e3022cea1..2bd00b2f6 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala @@ -106,8 +106,10 @@ object IntExprCalc: /** Substituted by the APPLIED value expression, and only where an instantiation actually * supplies one. A parameter with none stays an opaque base, so a decision made about it holds * for every assignment: while its own design is still elaborating there is no instance yet, - * and the elaboration root never has one. Used by width equivalence (`IntParamRef.compare`) - * and the width-fit proof ([[widthFitCompare]]). + * and the elaboration root never has one. The exception is a parameter the design read + * (`ForcedParamTag`), which folds to the value it was read at, that design being specialized + * to it and stating so. Used by width equivalence (`IntParamRef.compare`) and the width-fit + * proof ([[widthFitCompare]]). */ case AppliedExpr @@ -425,6 +427,16 @@ object IntExprCalc: case f :: Nil => scale(linear(f), c) case _ if c == 0 => Linear(Nil, 0) case _ => Linear(List((c, sv)), 0) + // A parameter whose data the design's elaboration READS is fixed at that value for this + // design (see `DesignParam.isDataImpure`): the body is the one that value produced, and + // the design states the equality as a static assertion, so every instantiation is held to + // it. This is the one case where a parameter with no instantiation site still folds, the + // elaboration root included: the assertion travels with the generated module and is + // checked wherever it is instantiated from. + case dp: DFVal.DesignParam if mode == ParamResolve.AppliedExpr && dp.isDataImpure => + dp.getConstData[Any](using getSet, ConstData.CachePolicy.GoThroughDesignParams) match + case ConstData.KnownConst(Some(i: BigInt)) if i.isValidInt => Linear(Nil, i.toInt) + case _ => Linear(List((1, dp)), 0) // AppliedData: fold a design parameter to its applied constant data, resolved only // through an instantiation site, so an elaboration root's parameters (which have none) // and anything else unresolvable stay opaque bases diff --git a/compiler/stages/src/test/scala/StagesSpec/ClassDesignKeySpec.scala b/compiler/stages/src/test/scala/StagesSpec/ClassDesignKeySpec.scala index 16347e909..0c5f64057 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ClassDesignKeySpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ClassDesignKeySpec.scala @@ -160,7 +160,9 @@ class ClassDesignKeySpec extends StageSpec: // the forced data derives from the class param `amount`, so only that PARAM is // marked data-impure (recorded by name on the CLASS annotation) and the class stays // pure and keyable: different applied values elaborate separate designs with their - // folded constants, while a repeated value unifies (f3 joins f1's design) + // folded constants, while a repeated value unifies (f3 joins f1's design). Each design + // is the one its applied value produced and states that value as its contract, the + // generated module keeping the parameter overridable (issue #480). assertCodeString( new Top, """|@hw.annotation.pure(impureParams = "amount") @@ -168,6 +170,7 @@ class ClassDesignKeySpec extends StageSpec: | val x = UInt(32) <> IN | val y = UInt(32) <> OUT | y := x + d"32'1" + | val constraint_0 = assert(amount == d"8'1", s"Design parameter violation found. Expected: amount == d\"8'1\"", Severity.Fatal) |end Folder_0 | |@hw.annotation.pure(impureParams = "amount") @@ -175,6 +178,7 @@ class ClassDesignKeySpec extends StageSpec: | val x = UInt(32) <> IN | val y = UInt(32) <> OUT | y := x + d"32'10" + | val constraint_0 = assert(amount == d"8'10", s"Design parameter violation found. Expected: amount == d\"8'10\"", Severity.Fatal) |end Folder_1 | |class Top extends DFDesign: @@ -214,7 +218,9 @@ class ClassDesignKeySpec extends StageSpec: y <> s.y end Top // both expressions stay parametric where they are used as widths (`Bits(total)`, `Bits(sum)`) - // and fold where they are forced (`x(7)`, `Bits(6)`) + // and fold where they are forced (`x(7)`, `Bits(6)`). Forcing reaches both parameters through + // the expressions, so both are pinned and each states its own equality: a parameter that + // stays symbolic in the printed widths is still one this body was specialized to. assertCodeString( new Top, """|@hw.annotation.pure(impureParams = "width", "lanes") @@ -231,6 +237,8 @@ class ClassDesignKeySpec extends StageSpec: | val fixed = Bits(6) <> OUT | par := b"0".repeat(sum) | fixed := h"6'00" + | val constraint_0 = assert(width == 4, s"Design parameter violation found. Expected: width == 4", Severity.Fatal) + | val constraint_1 = assert(lanes == 2, s"Design parameter violation found. Expected: lanes == 2", Severity.Fatal) |end Slicer | |class Top extends DFDesign: @@ -269,7 +277,9 @@ class ClassDesignKeySpec extends StageSpec: // data (`toScalaBoolean`): the purity check sees that forcing and marks the param // data-impure exactly like an explicit toScalaXYZ call, so the applied value joins // the design key. Different applied values elaborate separate designs (with only the - // taken branch), while a repeated value unifies (c3 joins c1's design). + // taken branch), while a repeated value unifies (c3 joins c1's design). The branch that + // was dropped is not recoverable from the design that came out, which is why each states + // the guard's value as its contract. assertCodeString( new Top, """|@hw.annotation.pure(impureParams = "arg") @@ -277,6 +287,7 @@ class ClassDesignKeySpec extends StageSpec: | val x = UInt(32) <> IN | val y = UInt(32) <> OUT | y := x + d"32'1" + | val constraint_0 = assert(arg == true, s"Design parameter violation found. Expected: arg == true", Severity.Fatal) |end Cond_0 | |@hw.annotation.pure(impureParams = "arg") @@ -284,6 +295,7 @@ class ClassDesignKeySpec extends StageSpec: | val x = UInt(32) <> IN | val y = UInt(32) <> OUT | y := x + d"32'2" + | val constraint_0 = assert(arg == false, s"Design parameter violation found. Expected: arg == false", Severity.Fatal) |end Cond_1 | |class Top extends DFDesign: diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index a40644cc8..3f52fd00d 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -3581,6 +3581,93 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + test("a parameter the body reads is fixed at what it read, and states it") { + // Reading a parameter as a Scala value is what specializes a body to it: the branch that was + // not taken leaves nothing behind, so in the design that came out `OUT_W = D * B` and `D` are + // one width and the assignment between them is legal. The width algebra reads the parameter as + // the value the body read it at, which is the only way that equality can be seen, and the + // design states the value as its contract, the generated module keeping the parameter + // overridable (issue #480). The other branch is elaborated from the same source and states the + // value that selected IT; its `.eby(OUT_W - D)` asks for `D + (OUT_W - D)` bits, which is + // `OUT_W` said the long way round. + class Shifter(val D: Int <> CONST = 8, val B: Int <> CONST = 1) extends RTDesign: + val OUT_W = D * B + val in = Bits(D) <> IN + val out = Bits(OUT_W) <> OUT + if (B == 1) out := in + else out := in.eby(OUT_W - D) + end Shifter + assertCodeString( + Shifter(), + """|@hw.annotation.pure(impureParams = "B") + |class Shifter( + | val D: Int <> CONST = 8, + | val B: Int <> CONST = 1 + |) extends RTDesign: + | val OUT_W: Int <> CONST = D * B + | val in = Bits(D) <> IN + | val out = Bits(OUT_W) <> OUT + | out := in + | val constraint_0 = assert(B == 1, s"Design parameter violation found. Expected: B == 1", Severity.Fatal) + |end Shifter + |""".stripMargin + ) + assertCodeString( + Shifter(B = 3), + """|@hw.annotation.pure(impureParams = "B") + |class Shifter( + | val D: Int <> CONST = 8, + | val B: Int <> CONST = 3 + |) extends RTDesign: + | val OUT_W: Int <> CONST = D * B + | val in = Bits(D) <> IN + | val out = Bits(OUT_W) <> OUT + | out := in.resize(OUT_W) + | val constraint_0 = assert(B == 3, s"Design parameter violation found. Expected: B == 3", Severity.Fatal) + |end Shifter + |""".stripMargin + ) + } + test("a parameter that feeds a read one is read too, and states its own value") { + // Nothing in `Outer`'s body reads `M`, and `Outer` is specialized to it all the same: the + // value went into a child that read it, so the body `Outer` produced is the one for `M = 4`. + // The purity analysis already says so, every application of a data-impure parameter + // re-attributing its applied argument at the call site, which is why the contract is derived + // from that marking rather than from where the reading happened (issue #480). + class Inner(val N: Int <> CONST = 4) extends RTDesign: + val din = Bits(N * 2) <> IN + val dout = Bits(N) <> OUT + if (N == 4) dout := din(N - 1, 0) + else dout := din(2 * N - 1, N) + class Outer(val M: Int <> CONST = 4) extends RTDesign: + val din = Bits(M * 2) <> IN + val dout = Bits(M) <> OUT + val inner = Inner(M) + inner.din <> din + dout <> inner.dout + end Outer + assertCodeString( + Outer(), + """|@hw.annotation.pure(impureParams = "N") + |class Inner(val N: Int <> CONST = 4) extends RTDesign: + | val din = Bits(N * 2) <> IN + | val dout = Bits(N) <> OUT + | dout := din(N - 1, 0) + | val constraint_0 = assert(N == 4, s"Design parameter violation found. Expected: N == 4", Severity.Fatal) + |end Inner + | + |@hw.annotation.pure(impureParams = "M") + |class Outer(val M: Int <> CONST = 4) extends RTDesign: + | val din = Bits(M * 2) <> IN + | val dout = Bits(M) <> OUT + | val inner = Inner(N = M) + | inner.din <> din + | dout <> inner.dout + | val constraint_0 = assert(M == 4, s"Design parameter violation found. Expected: M == 4", Severity.Fatal) + |end Outer + |""".stripMargin + ) + } test("auto constraint from an LHS-dominant operation") { // `-`, `/` and `%` take the LHS width and convert the RHS to it, so each needs the RHS to // fit. `-` used to answer the undecided case with an outright rejection while its two diff --git a/core/src/main/scala/dfhdl/core/AutoConstraint.scala b/core/src/main/scala/dfhdl/core/AutoConstraint.scala index 374cab70b..24a785df6 100644 --- a/core/src/main/scala/dfhdl/core/AutoConstraint.scala +++ b/core/src/main/scala/dfhdl/core/AutoConstraint.scala @@ -128,6 +128,42 @@ object AutoConstraint: () } + /** [[raise]]s `param == ` for every parameter of the design whose DATA its elaboration + * reads (`DFVal.DesignParam.isDataImpure`). + * + * Reading a parameter is what specializes a body to it: the `if` branch that was not taken and + * the `for` iteration that did not run leave nothing behind, so the design that comes out is the + * one for that value and no other. Elaboration has always done this silently, and the generated + * module went on exposing the parameter as overridable; the assertion is what makes the + * specialization something the design says rather than something it merely is. It is the same + * fact the elaboration cache already keys on, said out loud. + * + * Nothing is stated where an overriding instantiation cannot exist. A method design is + * instantiated per call site, by DFHDL, with the value it was keyed on; a blackbox has no body + * that read anything. Nor for a parametrically-typed parameter (`INIT: Bits[W] <> CONST`), whose + * value would have to be compared against a literal of that same parametric type, which no + * constant can be built at; such a parameter is also one the width algebra never folds, so only + * the contract is left unsaid. + */ + private def raiseDataImpureParams(ctx: DesignContext)(using dfc: DFC): Unit = + import dfc.getSet + val design = dfc.owner.asIR.getThisOrOwnerDesign + val names = design.dataImpureParamNames + if (names.nonEmpty && !design.isBlackBox && design.instMode != ir.DFDesignBlock.InstMode.Def) + ctx.getImmutableMemberList.foreach { + case dp: ir.DFVal.DesignParam + if (dp.getOwnerDesign eq design) && dp.isDataImpure && + dp.dfType.getRefs.isEmpty => + dp.getConstDataThroughParams[ir.Data].foreach { data => + given DFC = dfc.anonymize + val value = DFVal.Const.forced(dp.dfType.asFE[DFTypeAny], data) + raise(DFVal.Func[DFBool, Any](DFBool, FuncOp.===, List(dp, value.asIR))) + () + } + case _ => + } + end raiseDataImpureParams + /** Whether `value` carries any width-adjustment permission at all, in either direction. */ def hasWidthAdjustPermission(value: DFValAny)(using DFC): Boolean = value.hasTag[ir.ResizeTag] || value.hasTag[ir.ExtendTag] || value.hasTag[ir.TruncateTag] @@ -265,11 +301,17 @@ object AutoConstraint: * * The tag is consumed here. It marks a PENDING constraint, and a materialized one is not * pending, so the clone is planted without it and no member of the finished design carries one. + * + * The body having run is also what makes the design's specialization to the parameters it read + * knowable, so those equalities are raised here ([[raiseDataImpureParams]]) and take part in the + * same minimization: an equality is the strongest statement there is about a parameter, and any + * width relation over one it fixes is already implied by it. */ private[core] def materialize()(using dfc: DFC): Unit = import dfc.getSet val ctx = dfc.mutableDB.DesignContext.current if (!dfc.inMetaProgramming) + raiseDataImpureParams(ctx) val pending = ctx.autoConstraintGuards.map(_.setTags(_.removeTagOf[ir.AutoConstraint])) val kept = mutable.ListBuffer.empty[(ir.DFVal, List[Requirement])] if (pending.nonEmpty) diff --git a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala index 4f90cdc30..081abd041 100644 --- a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala +++ b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala @@ -426,30 +426,34 @@ private object SimplifyFunc: end unapply end MaxMinWithOffset - // Cancels opposing +/- terms of the same non-constant DFVal across a - // left-associative DFInt32 additive chain. Handles e.g. `(x - 1) - x => -1`, - // which together with Const+Const folding handles `x - 1 - x + 5 => 4`. + // Cancels opposing +/- terms of the same non-constant DFVal across a DFInt32 additive + // TREE. Handles e.g. `(x - 1) - x => -1` (which together with Const+Const folding handles + // `x - 1 - x + 5 => 4`) and `x + (y - x) => y`, the shape a relative width adjustment + // takes: `.eby(k)` asks for `sourceWidth + k`, and a `k` written as the distance to + // another width states that width back. private object AdditiveCancellation: - // Walk a left-associative +/- chain rooted at `v` and return its terms - // as (sign, DFVal). Non-chain leaves become a single positive term. - private def collectChain(v: ir.DFVal)(using ir.MemberGetSet): List[(Int, ir.DFVal)] = - def loop(v: ir.DFVal, sign: Int, acc: List[(Int, ir.DFVal)]): List[(Int, ir.DFVal)] = - v match - case f: ir.DFVal.Func - if f.isAnonymous && f.dfType == ir.DFInt32 && - (f.op == FuncOp.+ || f.op == FuncOp.-) && f.args.size == 2 => - val List(lhs, rhs) = f.args.map(_.get): @unchecked - val rhsSign = if (f.op == FuncOp.+) sign else -sign - loop(lhs, sign, (rhsSign, rhs) :: acc) - case _ => (sign, v) :: acc - loop(v, 1, Nil) + // The terms of the additive tree rooted at `v`, as (sign, DFVal). Descends through + // ANONYMOUS `+`/`-` Funcs on EITHER side, associativity being no reason to prefer one: + // the same relation is spelled left-nested by a chain of operations and right-nested by + // one whose operand is a difference. A named Func is a value the user gave a name to and + // stays one term, as does anything that is not an additive Func. + private def collectTerms(v: ir.DFVal, sign: Int)(using + ir.MemberGetSet + ): List[(Int, ir.DFVal)] = + v match + case f: ir.DFVal.Func + if f.isAnonymous && f.dfType == ir.DFInt32 && + (f.op == FuncOp.+ || f.op == FuncOp.-) && f.args.size == 2 => + val List(lhs, rhs) = f.args.map(_.get): @unchecked + collectTerms(lhs, sign) ++ collectTerms(rhs, if (f.op == FuncOp.+) sign else -sign) + case _ => List((sign, v)) def unapply(opArgs: (ir.DFType, FuncOp, List[ir.DFVal]))(using dfc: DFC): Option[ir.DFVal] = import dfc.getSet opArgs match - case (ir.DFInt32, currentOp @ (FuncOp.+ | FuncOp.-), List(prev, curr)) - if prev.isAnonymous => - val chain = collectChain(prev) :+ ((if (currentOp == FuncOp.+) 1 else -1, curr)) + case (ir.DFInt32, currentOp @ (FuncOp.+ | FuncOp.-), List(prev, curr)) => + val chain = + collectTerms(prev, 1) ++ collectTerms(curr, if (currentOp == FuncOp.+) 1 else -1) if (chain.size < 2) None else // Find two terms with opposite signs whose DFVals are =~ (ident-transparent). diff --git a/core/src/test/scala/CoreSpec/SameWidthArithSpec.scala b/core/src/test/scala/CoreSpec/SameWidthArithSpec.scala index 3e9b28e8f..a33606e52 100644 --- a/core/src/test/scala/CoreSpec/SameWidthArithSpec.scala +++ b/core/src/test/scala/CoreSpec/SameWidthArithSpec.scala @@ -126,6 +126,32 @@ class SameWidthArithSpec extends NoDFCSpec: ) } + // Opposing terms of an additive expression cancel wherever they sit in it. Left-nesting is what + // a chain of operations builds; right-nesting is what an operand written as a DIFFERENCE gives, + // which is how a relative width adjustment reads: `.eby(TARGET - W)` asks for `W + (TARGET - W)` + // bits, and stating the target back is the whole of what that means. + test("opposing additive terms cancel wherever they sit") { + class Top(val A: Int <> CONST = 1, val B: Int <> CONST = 2) extends DFDesign: + val v = Int <> VAR + v := A + B - A // left-nested + v := A + (B - A) // right-nested + v := A - (A - B) // right-nested under a subtraction + v := A - 1 - A + 5 // the residue is a constant + assertNoDiff( + codeString(Top()), + """|class Top( + | val A: Int <> CONST = 1, + | val B: Int <> CONST = 2 + |) extends DFDesign: + | val v = Int <> VAR + | v := B + | v := B + | v := B + | v := 4 + |end Top""".stripMargin + ) + } + test("a repeated max/min chain over a design parameter is absorbed") { class Top(val W: Int <> CONST = 11) extends DFDesign: val v = Int <> VAR diff --git a/devdocs/auto-constraints.md b/devdocs/auto-constraints.md index f13d262f0..1d8d07e7f 100644 --- a/devdocs/auto-constraints.md +++ b/devdocs/auto-constraints.md @@ -5,6 +5,9 @@ normal state of affairs once widths are design parameters. It accepts the operat relation it assumed as a static assertion in the generated design, so every instantiation checks the contract that this elaboration could not. +The same mechanism carries the other thing an elaboration can assume about a parameter: the value +it READ one at, which specializes the body to that value (§6). + This describes what is implemented, in [AutoConstraint.scala](../core/src/main/scala/dfhdl/core/AutoConstraint.scala) for the mechanism and in [DFDecimal.scala](../core/src/main/scala/dfhdl/core/DFDecimal.scala) for the checks that @@ -49,7 +52,7 @@ The rule for whether an undecidable relation generates anything is that it must - A widened `>>` consumes one: `(x mod 2^t) >> k` is not `(x >> k) mod 2^t`, so the agreement rests on the target being at least as wide as the operand. - A type bound consumes one as well (`UInt(N)` is a legal type only under `N >= 1`), and generates - nothing all the same. See §7. + nothing all the same. See §9. Deriving what to assert from what was assumed, rather than from what was undecidable, is what keeps the count low before any minimization. @@ -68,7 +71,7 @@ user-written `assert` with a constant condition is a static assertion by constru of its own, and its printed form stays `assert(cond, msg)`. The position half of the definition is what makes the species printable. The elaboration-time forms -of §6 exist only in concurrent position; an assert meeting the constant criteria inside a process, +of §7 exist only in concurrent position; an assert meeting the constant criteria inside a process, a conditional block or a loop is procedural content and keeps its procedural printing. Three consequences downstream: @@ -183,6 +186,9 @@ both directions: rejecting an operation the applied value made legal, and accept A decision made in the PARENT is a different matter. There the applied value is what the connection or the operation is really about, and the instantiation is resolvable, so it resolves. +The one exception is a parameter the body itself READ, which is no longer a free variable of that +design and is stated as a contract of it (§6). + ## 4. Minimization Every comparison normalizes onto ONE canonical form, `IntExprCalc.linearDiff(lhs, rhs) >= 0`, so @@ -277,7 +283,69 @@ elimination, which reads a mixed chain by its constants and is deliberately leni Proving it is also what lets a stacked resize through a common width fold away, `toDFXIntOf`'s `unstack` asking whether the inner resize loses anything rather than whether it strictly widens. -## 6. Printing +## 6. Parameters the body reads + +§3 says a parameter is never resolved to decide a relation about its own design. Reading it as a +Scala value is the one thing that changes that, and it changes it completely. + +`toScalaValue` (and the `toScalaInt`/`toScalaBoolean`/... it backs) hands elaboration a value it +then builds the body out of. A Scala `if` over a parameter keeps one branch; a Scala `for` unrolls +to a count; a `Bits(N.toScalaInt)` is a literal width. Neither the branch that was dropped nor the +iteration that never ran is recoverable from the design that came out, so the parameter is no longer +a free variable of that design: it is a value the design was specialized to, while the generated +module goes on exposing it as overridable. + +### The marking is the record + +Nothing new records the reading, because the purity analysis already does. `PureCheckPhase` +attributes a `toScalaXYZ` forcing to the parameter it is rooted at and records it BY NAME on the +design's own `@pure(true, )` annotation, so `DFVal.DesignParam.isDataImpure` is the whole +question, asked of the annotation. + +Deriving it from the marking rather than from the forcing site is not a shortcut, it is more +correct. Every application of a data-impure parameter re-attributes its applied argument at the call +site, so a parameter read deep inside a child marks the parameter of every design that feeds it: + +```scala +class Inner(val N: Int <> CONST = 4) extends RTDesign: + if (N == 4) ... else ... // reads N +class Outer(val M: Int <> CONST = 4) extends RTDesign: + val inner = Inner(M) // reads nothing, and is specialized to M all the same +``` + +`Outer` states `M == 4`. Its body produced what it did because `M` was `4`, and a record kept where +the reading happened would have missed it entirely. + +It is also the same fact the elaboration cache is already keyed on: a data-impure parameter's applied +value joins the design load key, so each value elaborates its own design (see +[ClassDesignKeySpec](../compiler/stages/src/test/scala/StagesSpec/ClassDesignKeySpec.scala)). The +contract says out loud what the cache was already assuming. + +### What it buys + +- **The design states `param == value`**, raised at materialization by + `AutoConstraint.raiseDataImpureParams` and minimized with everything else. An equality is the + strongest statement there is about a parameter, so any width relation over one it fixes is implied + by it and drops out. +- **`IntExprCalc` reads the parameter as that value** under `AppliedExpr`. This is the one case + where a parameter with no instantiation site folds, the elaboration root included, and the + assertion is what earns it: the contract travels with the generated module. + +The folding is what makes a branch-conditioned width relation decidable at all +([issue #480](https://github.com/DFiantHDL/DFHDL/issues/480)). Given `OUT_W = D * B` and a body under +`if (B == 1)`, `out(OUT_W) := in(D)` is legal in the design that came out, and can be seen to be +only by reading `B` as the `1` the branch was selected by. Note where the folding is NOT done: the +elaboration-time simplifier (`Opaque`) leaves `D * B` alone, so the emitted design keeps the width +expressions the user wrote, and the constraint itself keeps its parameter unfolded. + +**Where nothing is stated**, an overriding instantiation being impossible or the equality +unstatable: a method design (instantiated per call site, by DFHDL, with the value it was keyed on), a +blackbox (no body read anything), and a parametrically-typed parameter (`INIT: Bits[W] <> CONST`), +whose value would have to be compared against a literal of that same parametric type, which no +constant can be built at. The last is also one the width algebra never folds, so only the contract is +left unsaid. + +## 7. Printing The key is position: a static assert in CONCURRENT position prints as an elaboration-time construct, and in procedural position as it always did. @@ -294,7 +362,7 @@ block of the elaboration form, or the `initial` block of the others. Naming the also what keeps a linter from complaining about the implicit `genblk` the LRM would otherwise assign. -## 7. Adding a check +## 8. Adding a check 1. Find the undecided arm. It is the `case _ =>` where a `Check` over `Int`s could not run because a width did not fold. @@ -316,7 +384,7 @@ assign. churn under `lib/src/test/resources/ref/` and review it deliberately: a new constraint on a documented example is a user-visible change. -## 8. Known gaps +## 9. Known gaps - **Type-construction bounds generate nothing.** The `toScalaIntOpt.foreach(check(_))` sites across `DFBits.scala`, `DFDecimal.scala` and `DFVector.scala` have exactly the same shape of undecided From 02a501f385f570e62deea72d13a838ee67b07f9e Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 14:41:56 +0300 Subject: [PATCH 10/57] benchmarks: the VeeR-EH1 constants layer, and a formatter that aligns declarations Advances the submodule to 7b658bf, which adds benchmarks/veer_eh1/ (provenance, upstream Apache-2.0 license, package clock/reset defaults, and the two constant headers) and gives the benchmarks repo its own .scalafmt.conf aligning `<>`, `=`, `=>` and `:=`. The constant headers take different Scala forms because the two Verilog headers they come from do not scope the same way: common_defines.vh is a global include, so its macros are top-level package definitions, while global.h is included inside 20 module bodies and is therefore an object that each design `export`s, which is what puts the names on the type as the baseline's localparams are. Co-Authored-By: Claude Opus 5 (1M context) --- benchmarks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks b/benchmarks index 5f53840f1..7b658bf1e 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit 5f53840f1929721cc0c01a0b3a75eebb36c7d07b +Subproject commit 7b658bf1e465cc1231fece541169d16c114cbc19 From 6f758fc43339f0efda17997630aba8c41b98df19 Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 14:45:30 +0300 Subject: [PATCH 11/57] git: submodules track main, so they stop landing detached None of the three submodules recorded a branch, so a clone or `submodule update` checks each one out detached. That is normal git behaviour and harmless until you commit inside one: `git push origin main` from a detached HEAD resolves `main` to the stale local branch ref, pushes nothing, and reports success. It cost two silent no-op pushes in the benchmarks repo this week, and the parent pointer referenced commits the remote did not have. Recording `branch = main` lets `git submodule update --remote` track main and makes the intended branch explicit at the point someone reads .gitmodules. Co-Authored-By: Claude Opus 5 (1M context) --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitmodules b/.gitmodules index f126f0b1e..6db3437ff 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,12 @@ [submodule "platforms"] path = platforms url = https://github.com/DFiantHDL/dfhdl-platforms + branch = main [submodule "ips"] path = ips url = https://github.com/DFiantHDL/dfhdl-ips + branch = main [submodule "benchmarks"] path = benchmarks url = https://github.com/DFiantHDL/benchmarks + branch = main From 9b313a7eba417f24c4cad54a58643f6dc953e4b8 Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 14:50:27 +0300 Subject: [PATCH 12/57] benchmarks: the VeeR-EH1 type package Advances the submodule to 8efac57, porting veer_types.sv. Co-Authored-By: Claude Opus 5 (1M context) --- benchmarks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks b/benchmarks index 7b658bf1e..8efac5741 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit 7b658bf1e465cc1231fece541169d16c114cbc19 +Subproject commit 8efac57412a950fdb53c40308006485b3491deb2 From b214113ad040745554c3907c4daba7ae61172514 Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 15:46:58 +0300 Subject: [PATCH 13/57] core: a reduction is not the binary operation that shares its symbol `&`, `|` and `^` each name two operations: the binary bitwise/logical one and the unary reduction. `MergeAssocFunc` keyed its associative merge on the `op` symbol alone, so it happily absorbed one form into the other and the reduction was simply lost: `a.^ ^ b.^` elaborated to `a ^ b.^`, and `(a ^ b).^` to `a ^ b`. Both backends then faithfully printed the corrupted IR, SystemVerilog silently (an 8-bit expression truncated into a 1-bit net) and VHDL as a type error. Only the multi-operand form of an associative op is associative at all, so the merge now requires both funcs to be in it. A chain of one form alone still merges, which is the whole of what the simplification was for. Fixes #483 Co-Authored-By: Claude Opus 5 (1M context) --- .../main/scala/dfhdl/core/SimplifyFunc.scala | 9 +++++ core/src/test/scala/CoreSpec/DFBitsSpec.scala | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala index 081abd041..25935dd90 100644 --- a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala +++ b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala @@ -305,6 +305,15 @@ private object SimplifyFunc: case (dfType, op, (prevFunc: ir.DFVal.Func) :: rest) if ir.DFVal.Func.Op.associativeSet.contains(op) && prevFunc.op == op + // `&`, `|` and `^` name TWO operations apiece: the binary bitwise/logical one + // and the unary reduction (`a.^`, one operand, a single-bit result). A matching + // `op` therefore does not imply a matching operation, and only the multi-operand + // form of an associative op is associative at all. Absorbing across the two forms + // splices a reduction's operand into a binary chain (or a binary chain's operands + // into a reduction) and the reduction is simply lost: `a.^ ^ b.^` became + // `a ^ b.^` and `(a ^ b).^` became `a ^ b` (issue #483). + && rest.nonEmpty + && prevFunc.args.sizeIs > 1 && prevFunc.isAnonymous && !rest.contains(prevFunc) && canMergeFunc(dfType, op, prevFunc) => diff --git a/core/src/test/scala/CoreSpec/DFBitsSpec.scala b/core/src/test/scala/CoreSpec/DFBitsSpec.scala index 2059f937b..e89b7dd97 100644 --- a/core/src/test/scala/CoreSpec/DFBitsSpec.scala +++ b/core/src/test/scala/CoreSpec/DFBitsSpec.scala @@ -449,4 +449,37 @@ class DFBitsSpec extends DFSpec: d8 := d4.truncate } } + // `&`, `|` and `^` each name both a binary logic operation and a unary reduction, so the + // associative merge of same-op anonymous funcs must not splice one form into the other: + // doing so dropped the reduction outright (issue #483). + test("Reduction meeting a same-symbol binary operation") { + val a = Bits(8) <> VAR + val b = Bits(8) <> VAR + val c = Bits(8) <> VAR + val o1 = Bit <> VAR + val o2 = Bits(8) <> VAR + assertCodeString { + """|o1 := a.^ ^ b.^ + |o1 := a.& && b.& + |o1 := a.| || b.| + |o1 := (a ^ b).^ + |o1 := (a & b).& + |o1 := (a | b).| + |o1 := a.^ ^ b.^ ^ c.^ + |o2 := a ^ b ^ c + |""".stripMargin + } { + // a reduction as the LHS of a binary operation with the same symbol + o1 := a.^ ^ b.^ + o1 := a.& & b.& + o1 := a.| | b.| + // a reduction OF a binary operation with the same symbol + o1 := (a ^ b).^ + o1 := (a & b).& + o1 := (a | b).| + // the associative merge itself still applies, to each form on its own + o1 := a.^ ^ b.^ ^ c.^ + o2 := a ^ b ^ c + } + } end DFBitsSpec From fac6794208c403e8b70205de6fbdb28d433838dd Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 15:47:33 +0300 Subject: [PATCH 14/57] bugfix skill: a Func.Op is a symbol, and one symbol can name two operations Banks the #483 species: an `op`-keyed predicate reasons about symbols, `&`/`|`/`^` name both a binary operation and a unary reduction, and arity is the only thing that tells them apart. Also records why the report arrives as a printer bug and why both nestings need probing. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/bugfix.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.claude/commands/bugfix.md b/.claude/commands/bugfix.md index 71fc92d5e..ee91e9312 100644 --- a/.claude/commands/bugfix.md +++ b/.claude/commands/bugfix.md @@ -675,6 +675,37 @@ direction that keeps unenumerated cases on today's behavior — here `!ident.get .isInstanceOf[DFDesignBlock]`, which changes the def-return case alone, rather than an allow-list of owners that would also change anything not yet thought of. +### A `Func.Op` is a symbol, not an operation, and one symbol can name two + +`&`, `|` and `^` each name the binary bitwise/logical operation AND the unary reduction (`a.^`, +one operand, a single-bit result). So anything keyed on `op` alone is reasoning about a *symbol*: +`MergeAssocFunc` merged an anonymous same-`op` Func into its parent for every member of +`associativeSet`, absorbed a reduction into a binary chain, and the reduction simply vanished +(`a.^ ^ b.^` elaborated to `a ^ b.^`; `(a ^ b).^` to `a ^ b`, issue #483). **Arity is what +separates the two forms**, so the guard is arity on both sides, not a type comparison: the +reduction of an 8-bit operand and the binary op over two reductions are both `DFBit`-typed, so +`prevFunc.dfType == dfType` sees nothing. When auditing an `op`-keyed predicate, ask which of the +`Func.Op` symbols are overloaded across arities before trusting that a matching `op` means a +matching operation. + +Two things about this species are worth knowing in advance: + +- **The reporter will call it a printer bug, and the backends will corroborate.** Both emitters + print funcs by arity, so a spliced Func renders as legal-looking HDL in one backend + (SystemVerilog `a ^ ^b`, silently truncated into a 1-bit net) and illegal HDL in the other + (VHDL `a xor (xor reduce b)`, a type error). Neither is the bug. Print the **DFHDL code string** + before either backend: `s1 := a ^ b.^` is elaboration output, and it ends the printer theory in + one probe. `SimplifyFunc` lives in `core`, so this whole family corrupts the IR at elaboration + and no `--log trace` stage dump will show a stage introducing it. +- **A report of one direction usually has a second.** #483 reported the reduction as the LHS of a + same-symbol binary op; the reduction *of* a same-symbol binary op was equally broken and + unreported. Probe both nestings of any operand-shape rule. + +The regression test belongs in `core/src/test/scala/CoreSpec/` (`DFBitsSpec` here) via +`assertCodeString`, which shows the corruption directly, and it must pin the legitimate merge +(`a ^ b ^ c`, `a.^ ^ b.^ ^ c.^`) alongside the broken shapes, since the guard's whole risk is +over-restricting the simplification it lives in. + ### Twin helpers drift, and only one of them gets fixed Two stages that lower the same construct at different points often carry near-identical recursive From e8b4fa687e096a4d9122d7d36b564f7d5cb7abb7 Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 17:00:56 +0300 Subject: [PATCH 15/57] verilog-to-dfhdl: the choices between equally-legal spellings, and what pins a parameter The skill covered the mechanics of translation but not the choices *between* equally-legal spellings, which is where the emitted HDL either tracks the gold or drifts from it. From the first two VeeR-EH1 helpers: A Verilog `assign` is a connection, so `<>` rather than `:=`. A named value beats a variable -- declare a VAR only where the baseline drives the bits separately, which is exactly when one named value cannot express it. Bit logic uses `&`, `|`, `~` for x-value equivalence, and the emitter picks `&` or `&&` from the operand types by itself. Concatenation is a tuple. `reduce`/`foldLeft` over DFHDL values need an explicit `[T <> VAL]`, and a seeded `foldLeft` emits the flat chain a `reduce` breaks into a paren group. Also a new subsection on parameters: what the elaboration *reads*, it pins. That is correct behaviour, but it turns one generic module into a specialised copy per instantiation, which is rarely what a port wants. `clog2` takes an `Int <> CONST` directly, slice bounds take one too, and a `generate`-style choice becomes a `.sel` on a constant condition rather than a Scala `if` -- so the design keeps both arms and stays generic. Plus the packed-struct section: field order is the baseline's, `: Int <> CONST` is what keeps a constant's name in the output, and an include's *scope* decides whether it maps to package-level definitions or an `export`ed object. Advances benchmarks to 9c52f7a. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/verilog-to-dfhdl.md | 88 ++++++++++++++++++++++++++++ benchmarks | 2 +- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index d311e3640..2d6298d00 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -141,11 +141,70 @@ Follow [from-verilog][from-verilog] for `Int <> CONST`/`String <> CONST` (they e - **No `generate` for structural params yet.** Parameters that change structure (bus width `W`, optional sub-blocks) cannot be made generic; hardwire them to the target configuration and note it. Standalone `runMain compile` needs a **default** for every CONST param. +- **What the elaboration *reads*, it pins.** `.toScalaInt` on a param, or a Scala `if` on one, makes + the design non-generic: DFHDL emits a `$fatal` design-parameter constraint and one *specialised* + module per distinct parameterisation (`rvrangecheck_0/_1/_2`). That is correct behaviour, not a + bug, but it is rarely what a port wants, because the baseline is one module instantiated N times. + Keep it generic by never reading the parameter: + - `clog2` takes an `Int <> CONST` directly, so `10 + clog2(SIZE)` emits the baseline's own + `localparam int MASK_BITS = 10 + $clog2(CCM_SIZE);`. **No `.toScalaInt`.** + - Slice bounds accept `Int <> CONST` and keep the name: `addr(31, MASK_BITS)` → `addr[31:MASK_BITS]`. + - A `generate`-style choice between two bodies becomes **`.sel` on a constant condition**, not a + Scala `if`: `x <> base & (SIZE == 48).sel(masked, 1)` folds at synthesis and covers both arms + while leaving `SIZE` free. Prefer this to dropping the dead arm. + - Reserve `.toScalaInt` for what genuinely needs a Scala `Int` (an `initFile` path, a `Vec` size + the frontend cannot take as a const). +- **`all(0)` for an explicitly-typed constant default** — `val CCM_SADR: Bits[32] <> CONST = all(0)` + rather than spelling out `h"32'00000000"`. - **DFacsimile rejects `String <> CONST`** (the minimum tier can't resolve a `DFString` const's param-dependent width). For an elaboration-only string (e.g. an `initFile` path), use a plain Scala `String` parameter, not `String <> CONST`, so it never enters the simulated IR. `Int <> CONST` widths do resolve. +## Writing the body - idioms that keep the baseline's shape + +The from-verilog guide covers the operators; these are the choices *between* equally-legal spellings, +and they decide how closely the emitted HDL tracks the gold. + +- **A Verilog `assign` is a connection: `<>`, not `:=`.** +- **Prefer a named value to a variable.** DFHDL does not need a variable to hold an expression: + `val x = ` beats `val x = T <> VAR` followed by an assignment. Declare a `VAR` only where the + baseline drives the bits **separately** (a per-bit `assign`, a `generate` of assigns), which is + exactly when a single named value cannot express it: + ```scala + val error_mask = Bits(39) <> VAR // 39 independent assigns: a VAR + for (i <- 1 until 40) error_mask(i - 1) <> (syndrome == i) + ``` + Intermediates that are pure renames upstream should just disappear. +- **Bit logic uses `&`, `|`, `~`** — not `&&`, `||`, `!`. It rarely changes 2-state behaviour but it + can for **x-value equivalence**. Write the bitwise form and let the emitter choose: it prints `&` + when the operands are `Bit` and `&&` when they are `Boolean`, matching whichever the baseline used. +- **Concatenate with a tuple**, not chained `++`: + ```scala + val x: Bits[39] <> VAL = (a, b, c) // ascribed, which also checks the total width + val y = (a, b, c).toBits // unascribed + port <> (a, b, c) // connecting straight out + ``` +- **`reduce`/`foldLeft` over DFHDL values need an explicit element type** — the result of an + operation is a plain value, which will not unify with the collection's element type. DFHDL's error + names the fix: + ```scala + group.map(din(_)).foldLeft[Bit <> VAL](ecc_in(i))(_ ^ _) // ecc_in[i] ^ din[..] ^ ... + group.map(din(_)).reduce[Bit <> VAL](_ ^ _) // also fine + ``` + Seeding a `foldLeft` with the baseline's own first term emits a **flat** chain; `reduce` adds a + paren group. Prefer `foldLeft` when the baseline starts the chain from a distinguished operand. + Neither works for a *widening* fold (`_ ++ _`), where no fixed element type exists. +- **A comparison yields `Boolean <> VAL`, not `Bit`.** `.bit` converts, and is needed before + concatenating a comparison result. +- **Convert once, at the definition.** `val syndrome = ecc_check(5, 0).uint` so every use reads + `syndrome == i`, rather than restating `.uint` at each use. +- **A Scala `var` accumulator is an ED-domain construct.** The elaboration-time + `var acc: Bits[Int] <> VAL = ...; acc = acc ++ x` idiom in the type-system guide is rejected by the + plugin inside an `RTDesign`; use a `VAR` there. The error says so explicitly. +- **A purely combinational design gets no clock or reset ports** — an `RTDesign` with no registers + emits a clean port list, so combinational leaf modules need no annotation at all. + ## Emitter gotchas not in the guide - **`buf`** (and other Verilog keywords) leak unescaped into emitted **port** names → syntax error. @@ -156,6 +215,35 @@ Follow [from-verilog][from-verilog] for `Int <> CONST`/`String <> CONST` (they e - **NTFS is case-insensitive:** writing `servant.scala` while `Servant.scala` exists writes *into* the old file. Delete old-cased files before renaming, and `clearSandbox` before regenerating renamed output. +- **Verilog ranges with a non-zero base do not survive.** `logic [31:1] prett` and `logic [18:2] x` + become `[30:0]` and `[16:0]`: the same width, packing identically inside a struct, but **indexed + differently**. Baseline `prett[j]` is `prett(j - 1)`. It compiles clean either way, so every slice + of such a field has to be translated deliberately; note it at the declaration. +- **A fully-assigned `VAR` read through a *parameter*-bounded slice is misreported as a latch** + (DFHDL#484). A local `Int <> CONST` bound is fine; only a design parameter trips it, and only for a + `VAR` (a port or parameter sliced the same way is fine). Where the variable is a pure rename, slice + the parameter directly instead. + +## Packed structs and the type package + +- **Field order is the baseline's, unreversed.** A SystemVerilog `struct packed` packs its + first-declared field at the MSB and DFHDL's `Struct` does the same, emitting a real + `typedef struct packed` into `_defs.svh` in declaration order. Worth confirming per port with + a two-field probe, since packets sliced as flat vectors would diverge silently. +- **Ascribe constants so the names reach the HDL.** A plain Scala `Int` folds into a literal and the + name is gone; `: Int <> CONST` emits a named `parameter int` and **preserves the definition chain** + (`parameter int DCCM_BITS = RV_DCCM_BITS;`), so derived widths print as `[DCCM_BITS - 1:0]` rather + than `[15:0]`. That is what makes the generated HDL diffable against the gold. Only constants the + elaborated design references reach the defs header, so declaring the full set costs nothing. +- **An include's *scope* decides its Scala form.** A globally-included macro header maps to + **top-level definitions in the package** (visible everywhere, no import). A header `` `include ``d + *inside module bodies* makes its localparams members of each module, which only **`export`** + reproduces: it puts the names on the type, where a plain `import` would not. Note that a package + cannot be an export target, so body-scoped headers must be an `object`. +- **Macros that are only `` `ifdef ``-tested** (and ones naming an SRAM cell) are plain Scala + `Boolean`s/`String`s: they select code at elaboration and must not reach the IR. +- **Scaladoc on a constant propagates into the emitted HDL** as a comment, so width derivations can + be explained in the generated header too. ## Non-synthesizable baseline constructs diff --git a/benchmarks b/benchmarks index 8efac5741..9c52f7ae7 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit 8efac57412a950fdb53c40308006485b3491deb2 +Subproject commit 9c52f7ae7095393c64c8f8abbc0fc5e896a6452d From 37eccee802aec57b85b4dcb627b5805f831a5840 Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 17:06:40 +0300 Subject: [PATCH 16/57] benchmarks: rvecc_encode and rvsyncss Advances the submodule to 2e599af. Co-Authored-By: Claude Opus 5 (1M context) --- benchmarks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks b/benchmarks index 9c52f7ae7..2e599afd6 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit 9c52f7ae7095393c64c8f8abbc0fc5e896a6452d +Subproject commit 2e599afd696c4fe2c9e065d780afaaf303279a0b From df6c17f0b67a24abab780bdb64560a4440454e56 Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 17:19:44 +0300 Subject: [PATCH 17/57] benchmarks: rvsyncss workaround for DFHDL#485 Advances the submodule to 27b42c5. Co-Authored-By: Claude Opus 5 (1M context) --- benchmarks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks b/benchmarks index 2e599afd6..27b42c56e 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit 2e599afd696c4fe2c9e065d780afaaf303279a0b +Subproject commit 27b42c56e4b43c57f6390f88030fd526056e7ba0 From 4bbc37fe8315a56da8cd095b4feacb6ddb5fbb04 Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 17:51:25 +0300 Subject: [PATCH 18/57] core: a cloned value must own its type references `cloneAnonValueAndDepsHere` reused the original's `DFType` instance, so the clone shared the original's `TypeRef` object. Type references are reference-counted before a patch purges them, but the count comes from the pre-patch member list and cannot see a member the same batch adds: a stage that clones a value and removes the original in one patch (`NameRegAliases` with a reg init) drops the count to zero and leaves the clone holding a dangling reference. Only parametric widths carry a type reference at all, which is why literal-width designs were unaffected. The clone now mints its own type references through the new `copyWithNewRefsHere`, so the added meta-design DB no longer depends on the original member surviving. Fixes DFHDL#485. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/bugfix.md | 33 +++++++++++++++ .claude/commands/new-stage.md | 15 ++++++- .../scala/StagesSpec/NameRegAliasesSpec.scala | 40 +++++++++++++++++++ core/src/main/scala/dfhdl/core/DFType.scala | 16 ++++++++ core/src/main/scala/dfhdl/core/DFVal.scala | 7 +++- 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/.claude/commands/bugfix.md b/.claude/commands/bugfix.md index ee91e9312..edffd3476 100644 --- a/.claude/commands/bugfix.md +++ b/.claude/commands/bugfix.md @@ -630,6 +630,39 @@ the loading run. Lessons that generalize: the dclName enumeration: the AES `FullCompileSpec` file-NAME comparison failed with `mulByte_0/1/2` renamed to `_1/2/3`, which reads like an enumeration bug and is cache debris. +### A dangling ref is the mirror image of a ghost, and it is a SHARING bug + +`NoSuchElementException: Missing member of reference "TR_..."` (from `DB._originMemberTable`, or +`Missing ref ... for the member` from `SanityCheck.refCheck`) is the inverse defect: a live member +holding a reference that no longer resolves. Two facts localize it fast: + +- **A `TR_` token is a TYPE reference** (`IntParamRef`), so the shape only exists when a width or + length comes from a **parameter**. The same design with a literal width has no type ref at all + and compiles — which is why issue #485 read as "`.reg(step, init = ...)` breaks on parametric + widths" and had nothing to do with `.reg`. +- **The ref token's `grpId` prefix differs from its holder's other refs** when the holder did not + mint it. In #485 the `repeat` func's arg refs were `TW_607c62db_*` and its type ref + `TR_67c03fa7_*`: a member built in one context carrying a reference minted in another, i.e. + sharing. + +Localize the *purge*, not the crash: the crash fires wherever `originMemberTable` is first forced, +which under `--log trace` is the next `SanityCheck` and without it some later stage +(`DropUnreferencedAnons`). A one-line `println` in `ReplacementContext.getUpdatedTypeRefCount` on +the refs it actually drops names the offending patch batch in one run. + +Type references are deliberately **reference-counted** before being purged +(`ReplacementContext.typeRefRepeats`), because a `member.copy(...)` legitimately shares its +original's `DFType` instance. The count is taken from the pre-patch member list, so it cannot see a +member the same batch is about to ADD — and `cloneAnonValueAndDepsHere` reused the original's +`dfType` verbatim, so `NameRegAliases` (clone the reg init into a `MetaDesign`, remove the original +init in the same patch) dropped the count to zero and purged a reference the clone still held. +Teaching the counter about the Add-DB members fixes the symptom; **the fix belongs at the clone** +(`dfType.copyWithNewRefsHere`, minting fresh type refs bound in the cloning context), because that +is what makes the added DB self-contained rather than dependent on the original's survival. The +general rule: when a member is copied into another context, it must not inherit the reference +identity of the member it was copied from — reference counting is a tolerance for sharing, not a +license to create it. + ### A missed diagnostic can have several independent gates When the bug is "a warning/error SHOULD have fired and did not", the predicate that suppressed diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index 6a4ee3274..fbabf5b55 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -1466,6 +1466,16 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) level), then branch on the tail's shape. Guard any rewrite that restructures the chain with `head.getHeaderCB.dfType == DFUnit`: the same block shapes serve conditional *expressions*, whose branches must keep feeding the header that owns their value. +33. **Cloning a member and removing its original in the SAME patch is safe only if the clone owns + its references** — type references (`IntParamRef`, i.e. a parametric width or length) are + reference-counted from the *pre-patch* member list, so a member the batch is about to ADD is + invisible to the count and the removal purges a reference the clone still holds + (`NoSuchElementException: Missing member of reference "TR_..."`, issue #485). Since + `cloneAnonValueAndDepsHere` now mints fresh type refs, this is handled for the clone path; + a stage that hand-builds a member from another member's `dfType` (Pattern 14 note 3) and + removes that member in the same patch still has to. Literal widths carry no type ref at all, + so this only ever shows up on parameter-width designs — write the spec test with a + `val W: Int <> CONST` design parameter, not a literal. --- @@ -1510,7 +1520,10 @@ non-obvious parts: `dfc.mutableDB.newRefFor(dfc.refGen.genTwoWay[M, O], member)`. 3. **Do not reuse an existing member's `dfType` instance in new members** — refs are identity objects; clone with `dfType.copyWithNewRefs` and bind each fresh type ref via - `newRefFor` to the original target (lazyZip old/new `getRefs`). + `newRefFor` to the original target (lazyZip old/new `getRefs`), or call the packaged + `dfType.copyWithNewRefsHere` which does exactly that in the current context. + `cloneAnonValueAndDepsHere` applies it for you (issue #485); anything that builds a member + from another member's `dfType` by hand still has to. 4. **Self-containment**: a def-design member must not reference design-local values of the host (the `directRefCheck` rejects cross-design refs). Captured design-local constants become `PhantomTag`-tagged IN-port formals (redirect body refs to them; pass the diff --git a/compiler/stages/src/test/scala/StagesSpec/NameRegAliasesSpec.scala b/compiler/stages/src/test/scala/StagesSpec/NameRegAliasesSpec.scala index 313f17b4d..7b9aca3af 100644 --- a/compiler/stages/src/test/scala/StagesSpec/NameRegAliasesSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/NameRegAliasesSpec.scala @@ -309,6 +309,46 @@ class NameRegAliasesSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + // The reg init is cloned into a meta design while the very same patch removes the original + // init, so the clone must own its type references (the parametric width one here) rather than + // share the original's, which the removal purges (issue #485). + test("parametric width reg alias init") { + class Foo(val WQ: Int <> CONST = 9) extends RTDesign: + val d_in = Bits(WQ) <> IN + val d_out = Bits(WQ) <> OUT + d_out <> d_in.reg(2, init = all(0)) + val top = (new Foo).nameRegAliases + assertCodeString( + top, + """|class Foo(val WQ: Int <> CONST = 9) extends RTDesign: + | val d_in = Bits(WQ) <> IN + | val d_out = Bits(WQ) <> OUT + | val d_in_reg1 = Bits(WQ) <> VAR.REG init b"0".repeat(WQ) + | val d_in_reg2 = Bits(WQ) <> VAR.REG init b"0".repeat(WQ) + | d_in_reg1.din := d_in + | d_in_reg2.din := d_in_reg1 + | d_out <> d_in_reg2 + |end Foo + |""".stripMargin + ) + } + // same as the above, through the properly-named single-step alias path + test("parametric width proper reg alias init") { + class Foo(val WQ: Int <> CONST = 9) extends RTDesign: + val d_in = Bits(WQ) <> IN + val d_out = Bits(WQ) <> OUT + d_out := d_in.reg(1, init = all(0)) + val top = (new Foo).nameRegAliases + assertCodeString( + top, + """|class Foo(val WQ: Int <> CONST = 9) extends RTDesign: + | val d_in = Bits(WQ) <> IN + | val d_out = Bits(WQ) <> OUT.REG init b"0".repeat(WQ) + | d_out.din := d_in + |end Foo + |""".stripMargin + ) + } // TODO: versioning is all wrong! // test("Reg alias inside conditionals with feedback") { // class ID extends RTDesign: diff --git a/core/src/main/scala/dfhdl/core/DFType.scala b/core/src/main/scala/dfhdl/core/DFType.scala index e04529863..4b02f83e0 100644 --- a/core/src/main/scala/dfhdl/core/DFType.scala +++ b/core/src/main/scala/dfhdl/core/DFType.scala @@ -368,4 +368,20 @@ extension (dfType: ir.DFType) else dfType end dropUnreachableRefs def dropUnreachableRefs(using DFC): ir.DFType = dropUnreachableRefs(true) + // copies the type with freshly generated type references, registered in the current context and + // pointing at the same values. A value cloned into another context (see + // `cloneAnonValueAndDepsHere`) must not share type references with the value it was cloned from: + // references are identity objects, and removing the original member purges the references it + // holds, which would leave the clone's type dangling (issue #485). + def copyWithNewRefsHere(using dfc: DFC): ir.DFType = + import dfc.getSet + given ir.RefGen = dfc.refGen + if (dfType.getRefs.isEmpty) dfType + else + val updatedDFType = dfType.copyWithNewRefs + dfType.getRefs.lazyZip(updatedDFType.getRefs).foreach { (oldRef, newRef) => + dfc.mutableDB.newRefFor(newRef, oldRef.get) + } + updatedDFType + end copyWithNewRefsHere end extension diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index 5b261b217..f227afa61 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -2270,7 +2270,10 @@ extension (dfVal: ir.DFVal) import dfc.getSet if (dfVal.isAnonymous) val dfcForClone = dfc.setMeta(dfVal.meta).setTags(dfVal.tags) - val dfType = dfVal.dfType.asFE[DFTypeAny] + // the clone gets its own type references, so that it never depends on the original member + // surviving (see `copyWithNewRefsHere`) + val dfTypeIR = dfVal.dfType.copyWithNewRefsHere + val dfType = dfTypeIR.asFE[DFTypeAny] val cloned = dfVal match case const: ir.DFVal.Const => DFVal.Const.forced(dfType, const.data)(using dfcForClone) @@ -2302,7 +2305,7 @@ extension (dfVal: ir.DFVal) end match case pbns: ir.DFVal.PortByNameSelect => DFVal.PortByNameSelect( - pbns.dfType, + dfTypeIR, pbns.dir, pbns.designInstRef.get, pbns.portNamePath From 616d732d7dea6b9f37bc3cac49c4e17a67141b1a Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 17:54:11 +0300 Subject: [PATCH 19/57] verilog-to-dfhdl: `.reg` is a delay chain, and what it costs the formal proof `.reg(n, init = ...)` says a delay chain more directly than declaring and chaining registers, but it names its flops after the source signal, so they stop matching the baseline's net names. equiv_make pairs by identical wire name, so the internal anchors are lost -- measured on rvsyncss, where the paired-cell count drops from 12 to 6. Free on two flops, since the proof closes on the outputs alone; not free on a module big enough that induction needs the anchors. Advances benchmarks to eac5952. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/verilog-to-dfhdl.md | 8 ++++++++ benchmarks | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index 2d6298d00..c9cdb95aa 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -202,6 +202,14 @@ and they decide how closely the emitted HDL tracks the gold. - **A Scala `var` accumulator is an ED-domain construct.** The elaboration-time `var acc: Bits[Int] <> VAL = ...; acc = acc ++ x` idiom in the type-system guide is rejected by the plugin inside an `RTDesign`; use a `VAR` there. The error says so explicitly. +- **`.reg(n, init = ...)` for a plain delay chain**, rather than declaring and chaining registers: + `dout <> din.reg(2, init = all(0))` is the baseline's two chained `rvdff`s. But note what it costs + formally: `.reg` names its flops after the source signal (`din_reg1`, `din_reg2`), so they no + longer match the baseline's net names, and `equiv_make` -- which pairs by *identical wire name* -- + loses those internal anchors. On a 2-flop module that is free (the proof closes on the outputs + alone). On a large sequential module the anchors are what keeps induction tractable, so there + declare the register under the baseline's own net name. **The choice is a verification one, not a + style one.** - **A purely combinational design gets no clock or reset ports** — an `RTDesign` with no registers emits a clean port list, so combinational leaf modules need no annotation at all. diff --git a/benchmarks b/benchmarks index 27b42c56e..eac595299 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit 27b42c56e4b43c57f6390f88030fd526056e7ba0 +Subproject commit eac595299ebf642663a5628de24c656a4511eaa4 From d2c68b6b36aea86e21a077a75e178121da1912ef Mon Sep 17 00:00:00 2001 From: Oron Date: Thu, 13 Aug 2026 18:27:40 +0300 Subject: [PATCH 20/57] compiler_ir: a parameter-bounded slice is a region the coverage can decide, not an unknown The state analysis re-seeded a slice from `idxLowRef.getIntOpt`, so a bound that depends on a design parameter collapsed to `Slice.Unknown`, and the collapse was wrong in both directions at once. Reading, it was conservative to the point of being false: `Slice.Unknown` never proves containment, so `v(31, MB)` read out of a `v` assigned in full answered `Tri.Unknown` and `v` was reported as a latch under RT (issue #484). Writing, the same collapse over-claimed. The seeded slice of a parametrically-sized selection is `Slice.Full` (its `widthIntOpt` is `None`), and shifting `Full` leaves it `Full`, so `v(MB - 1, 0) := x` banked the WHOLE of `v` as written and a genuinely partial assignment passed the check. Both halves are the same missing composition. `departial`'s per-step slice calculus moves to `DFVal.Alias.Partial.composeSlice`, which maps a slice into the selected value's coordinates as a linear form, and the state analysis uses it for `ApplyRange` and `SelectField` in the read and the write direction (`ApplyIdx` keeps its whole-value approximation). `departial` itself is unchanged in behaviour, now expressed through the shared helper. `Coverage` then keeps the symbolic regions instead of degrading them to "touched", and decides a containment query over them: a slice lies within its own value's bounds, so a coverage spanning the whole value contains every slice of it whatever the endpoints are, and anything genuinely partial goes to a sweep that extends the covered prefix by a region provably starting at or before the cursor and ending after it. Two complementary parametric writes therefore cover the variable between them for every parameter assignment, while one that covers only part of it still reports the latch. The suite was green both before and after, which says the whole branch was untested rather than that the change is inert, so the tests pin the accepting shape and the rejecting one on each side: the elaboration check for RT and the `ExplicitState` output for DF. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/bugfix.md | 27 ++++ .../compiler/analysis/StateAnalysis.scala | 68 +++----- .../scala/dfhdl/compiler/ir/Coverage.scala | 146 ++++++++++++++---- .../scala/dfhdl/compiler/ir/DFMember.scala | 121 +++++++++------ .../scala/StagesSpec/ExplicitStateSpec.scala | 23 ++- .../test/scala/ElaborationChecksSpec.scala | 41 +++++ 6 files changed, 299 insertions(+), 127 deletions(-) diff --git a/.claude/commands/bugfix.md b/.claude/commands/bugfix.md index edffd3476..bb0b42aad 100644 --- a/.claude/commands/bugfix.md +++ b/.claude/commands/bugfix.md @@ -962,6 +962,33 @@ generalizes: as the tiebreak, then fail. That fallback is what keeps every previously-accepted shape accepted (a read of a bit whose only writer is parametric still resolves), so the change stays confined to the shapes the bug affected. +- **A conservative collapse has a mirror at the same site, and the mirror is unsound.** Degrading a + parameter-dependent slice to "unknown" is conservative when the slice is READ (an unprovable + coverage keeps the value consuming state) and an over-claim when it is WRITTEN (an unprovable + write is banked as coverage). Issue #484 was the read half: `StateAnalysis` re-seeded an + `ApplyRange` from `idxLowRef.getIntOpt`, so a `v(31, MB)` read of a fully-assigned `v` answered + `Tri.Unknown` and reported a latch. The write half was worse and silent: the seeded slice of a + parametrically-sized selection is `Slice.Full` (its `widthIntOpt` is `None`), and the old + composition SHIFTED that seed, so `v(MB - 1, 0) := x` shifted `Full` by a concrete `0`, stayed + `Full`, and claimed the whole declaration as written. Fix both directions in one change, and + test the accepting shape AND the shape that must still be rejected: a suite that stays green + either way (this one did) is measuring nothing. Only `Slice.compose` handles a `Full` seed + correctly, mapping it onto the selection's own extent; a shift cannot, which is why the naive + and the symbolic version of the same walk are not interchangeable. +- **A containment query over a value has one axiom for free: a slice lies within its own value's + bounds.** So a coverage spanning the whole value contains EVERY slice of it, whatever the + endpoints are, and that one line answers the unprovable-endpoint case without any proof + machinery. Reach for the sweep (prove the query's start is covered, extend the covered prefix by + a region that provably starts at or before the cursor and ends after it, repeat) only for what + the axiom does not cover, i.e. genuinely partial coverage such as two complementary parametric + writes. Keep the region list bounded and degrade to "touched" above the bound: dropping regions + loses proofs but never invents one. +- **A variant that passes is not a variant that works.** Of eight probe variants, the one that + looked like the interesting positive case (two complementary parametric writes, accepted before + the fix and after it) was passing for an unsound reason, and no amount of re-reading the source + showed it. Two `println`s (the composed slice at each alias step, and the query plus coverage at + the decision) settled it in one run. When a variant's verdict is right, check WHY before counting + it as a control. - **Symbolic elimination is a per-site semantic choice, not a smarter equivalence.** The width-fit checks accept `LHS >= RHS` after a mixed `max`/`min` drops its symbolic operands (`16 >= WIDTH max 16` decides as `16 >= 16`; `IntParamRef.compare(..., elimSymbolicMaxMin = diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/StateAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/StateAnalysis.scala index ca9e63c19..9bb171fb0 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/StateAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/StateAnalysis.scala @@ -30,42 +30,21 @@ object StateAnalysis: assignMap, currentSet ) - case applyRange @ DFVal.Alias.ApplyRange( - relValRef = relValRef, - idxHighRef = idxHighRef, - idxLowRef = idxLowRef - ) => - // Re-seed the slice to the ApplyRange's full extent in the parent's coordinates. - // This replicates the pre-existing behavior where the passed-in slice would be - // replaced by the ApplyRange's own span when encountered. - val newSlice: Slice = ( - idxHighRef.getIntOpt, - idxLowRef.getIntOpt, - applyRange.elementWidthIntOpt - ) match - case (Some(idxHigh), Some(idxLow), Some(eW)) => - val start = idxLow * eW - val len = (idxHigh - idxLow) * eW + 1 - Slice.Concrete(Range(start, start + len)) - case _ => Slice.Unknown - consumeFrom(relValRef.get, newSlice, assignMap, currentSet) + case applyRange: DFVal.Alias.ApplyRange => + // Map the slice into the parent's coordinates through the shared partial-selection + // calculus, so a parameter-dependent bound stays a symbolic linear form that the + // coverage query can still decide (`Slice.Unknown` never can). + val newSlice = applyRange.composeSlice(slice).getOrElse(Slice.Unknown) + consumeFrom(applyRange.relValRef.get, newSlice, assignMap, currentSet) case DFVal.Alias.ApplyIdx(relValRef = relValRef, relIdx = idxRef) => // For simplification, consuming the entirety of selection index and array val rvSet = consumeFrom(relValRef.get, assignMap, currentSet) val idxSet = consumeFrom(idxRef.get, assignMap, currentSet) (rvSet union idxSet) - case sf @ DFVal.Alias.SelectField(relValRef = relValRef, fieldName = fieldName) => - // Re-seed the slice to the field's extent in the parent struct's coordinates, - // mirroring the ApplyRange case above. - val relVal = relValRef.get - relVal.dfType match - case structType: DFStruct => - val low = structType.fieldRelBitLow(fieldName) - val newSlice: Slice = sf.dfType.widthIntOpt match - case Some(w) => Slice.Concrete(Range(low, low + w)) - case None => Slice.Unknown - consumeFrom(relVal, newSlice, assignMap, currentSet) - case _ => consumeFrom(relVal, slice, assignMap, currentSet) + case sf: DFVal.Alias.SelectField => + // shift the field-relative slice into the parent struct's coordinates + val newSlice = sf.composeSlice(slice).getOrElse(Slice.Unknown) + consumeFrom(sf.relValRef.get, newSlice, assignMap, currentSet) case IteratorDcl() => currentSet // out ports of child designs are not consuming state within the current design case dcl @ DclOut() @@ -110,23 +89,20 @@ object StateAnalysis: value match case DFVal.Alias.AsIs(relValRef = relValRef) => assignTo(relValRef.get, slice, assignMap) - case applyRange @ DFVal.Alias.ApplyRange( - relValRef = relValRef, - idxLowRef = idxLowRef - ) => - val newSlice: Slice = (idxLowRef.getIntOpt, applyRange.elementWidthIntOpt) match - case (Some(idxLow), Some(eW)) => slice.shift(idxLow * eW) - case _ => Slice.Unknown - assignTo(relValRef.get, newSlice, assignMap) + case applyRange: DFVal.Alias.ApplyRange => + // as in `consumeFrom`: composing keeps a parameter-dependent bound symbolic. Note the + // seeded slice of a parametrically-sized selection is `Slice.Full`, which the composition + // maps onto the selection's own extent. Shifting it used to leave it `Full`, claiming the + // WHOLE of the assigned declaration as covered. + val newSlice = applyRange.composeSlice(slice).getOrElse(Slice.Unknown) + assignTo(applyRange.relValRef.get, newSlice, assignMap) case DFVal.Alias.ApplyIdx(relValRef = relValRef, relIdx = idxRef) => // for simplification, assigning the entirety of the array assignTo(relValRef.get, assignMap) - case DFVal.Alias.SelectField(relValRef = relValRef, fieldName = fieldName) => + case sf: DFVal.Alias.SelectField => // shift the field-relative slice into the parent struct's coordinates - relValRef.get.dfType match - case structType: DFStruct => - assignTo(relValRef.get, slice.shift(structType.fieldRelBitLow(fieldName)), assignMap) - case _ => assignTo(relValRef.get, slice, assignMap) + val newSlice = sf.composeSlice(slice).getOrElse(Slice.Unknown) + assignTo(sf.relValRef.get, newSlice, assignMap) case x => assignMap.assignTo(x, slice) end match end assignTo @@ -260,14 +236,14 @@ object StateAnalysis: * provably covered; `Tri.No` or `Tri.Unknown` otherwise (both are treated as "still consuming * state" by callers). */ - def contains(slice: Slice, widthOpt: Option[Int]): Tri = + def contains(slice: Slice, widthOpt: Option[Int])(using MemberGetSet): Tri = getLatest.contains(slice, widthOpt) def assign(slice: Slice, widthOpt: Option[Int]): AssignedScope = copy(latest = latest.assign(slice, widthOpt), hasAssignments = true) def branchEntry(firstBranch: Boolean): AssignedScope = val parentScope = if (firstBranch) this.copy(branchHistory = Some(getLatest)) else this AssignedScope(Coverage.empty, None, Some(this), hasAssignments) - def branchExit(lastBranch: Boolean, exhaustive: Boolean): AssignedScope = + def branchExit(lastBranch: Boolean, exhaustive: Boolean)(using MemberGetSet): AssignedScope = parentScopeOption match case Some(parentScope) => val updatedHistory = parentScope.branchHistory match diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/Coverage.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/Coverage.scala index 91b6b44bb..6941a4cb5 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/Coverage.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/Coverage.scala @@ -94,31 +94,48 @@ object Tri: /** Accumulated write coverage over one DFVal. * * - `bits` holds the concretely-tracked bit positions that are proven assigned/connected. - * - `unknownTouched` is set when a write with a [[Slice.Unknown]], a [[Slice.Symbolic]], or a - * [[Slice.Full]] over an unknown width has been observed, meaning we know the value was - * touched but not precisely where. + * - `symbolics` holds the written regions whose endpoints are parameter-dependent, kept as + * linear forms so a query can still be decided over them (see [[Coverage.contains]]). + * - `unknownTouched` is set when a write with a [[Slice.Unknown]], or a [[Slice.Full]] over an + * unknown width, has been observed, meaning we know the value was touched but not precisely + * where. A symbolic write past [[Coverage.maxSymbolicRegions]] degrades into it too. * - `fullyCovered` is a latch flag set when we observe a write that covers the entire value, * even if the value's width is symbolic (so we cannot represent it as a concrete BitSet). Once * set, any coverage query returns `Yes` regardless of `bits`. */ final case class Coverage( bits: immutable.BitSet, + symbolics: List[Slice.Symbolic], unknownTouched: Boolean, fullyCovered: Boolean ) derives CanEqual: def |(that: Coverage): Coverage = Coverage( bits | that.bits, + Nil, unknownTouched || that.unknownTouched, fullyCovered || that.fullyCovered - ) - def &(that: Coverage): Coverage = + ).withSymbolics(symbolics ++ that.symbolics) + + /** Intersection, used to merge what all branches of a conditional assign. The symbolic half is an + * UNDER-approximation (only a region both sides carry survives), which can only weaken a + * containment proof, never strengthen one. + */ + def &(that: Coverage)(using MemberGetSet): Coverage = Coverage( bits & that.bits, + symbolics.filter(a => that.symbolics.exists(Coverage.sameRegion(a, _))), unknownTouched && that.unknownTouched, fullyCovered && that.fullyCovered ) + // Symbolic regions are kept as a bounded list, past which they all degrade into + // `unknownTouched`: dropping regions can only lose a proof, and it keeps the containment + // search below bounded. + private def withSymbolics(all: List[Slice.Symbolic]): Coverage = + if (all.sizeIs > Coverage.maxSymbolicRegions) copy(symbolics = Nil, unknownTouched = true) + else copy(symbolics = all) + def assign(slice: Slice, widthOpt: Option[Int]): Coverage = slice match case Slice.Concrete(r) => @@ -127,8 +144,9 @@ final case class Coverage( widthOpt match case Some(w) => copy(bits = bits ++ immutable.BitSet.fromSpecific(0 until w)) case None => copy(fullyCovered = true) - // a symbolic slice has no concrete bit positions to track, so it degrades to "touched" - case _: Slice.Symbolic | Slice.Unknown => copy(unknownTouched = true) + case s: Slice.Symbolic => withSymbolics(symbolics :+ s) + // an unknown slice has nothing to track, so it degrades to "touched" + case Slice.Unknown => copy(unknownTouched = true) /** Does this coverage touch any bit of `slice`? */ def overlaps(slice: Slice, widthOpt: Option[Int]): Tri = @@ -137,51 +155,119 @@ final case class Coverage( case Slice.Concrete(r) if r.isEmpty => Tri.No case _ => Tri.Yes else + val maybe = unknownTouched || symbolics.nonEmpty slice match case Slice.Concrete(r) => val sliceBits = immutable.BitSet.fromSpecific(r) if ((bits & sliceBits).nonEmpty) Tri.Yes - else if (unknownTouched) Tri.Unknown + else if (maybe) Tri.Unknown else Tri.No case Slice.Full => if (bits.nonEmpty) Tri.Yes - else if (unknownTouched) Tri.Unknown + else if (maybe) Tri.Unknown else Tri.No case _: Slice.Symbolic | Slice.Unknown => - if (bits.nonEmpty || unknownTouched) Tri.Unknown + if (bits.nonEmpty || maybe) Tri.Unknown else Tri.No + end if + end overlaps - /** Does this coverage fully cover `slice`? */ - def contains(slice: Slice, widthOpt: Option[Int]): Tri = + /** Does this coverage fully cover `slice`? `Tri.Yes` only when proven, so a query the symbolic + * proofs cannot decide answers `Tri.Unknown` rather than `Tri.No`. + */ + def contains(slice: Slice, widthOpt: Option[Int])(using MemberGetSet): Tri = + import IntExprCalc.DataCalc.const if (fullyCovered) Tri.Yes else - slice match + val proven = slice match case Slice.Concrete(r) => - val sliceBits = immutable.BitSet.fromSpecific(r) - if ((sliceBits &~ bits).isEmpty) Tri.Yes - else if (unknownTouched) Tri.Unknown - else Tri.No - case Slice.Full => - widthOpt match - case Some(w) => - val fullBits = immutable.BitSet.fromSpecific(0 until w) - if ((fullBits &~ bits).isEmpty) Tri.Yes - else if (unknownTouched) Tri.Unknown - else Tri.No - case None => - if (unknownTouched) Tri.Unknown else Tri.No - case _: Slice.Symbolic | Slice.Unknown => Tri.Unknown + r.isEmpty || (immutable.BitSet.fromSpecific(r) &~ bits).isEmpty || + proveCovered(const(r.start), const(r.length)) + case Slice.Full => coversWholeValue(widthOpt) + // a slice is by construction within the value's own bounds, so a fully covered value + // contains every slice of it, whatever its endpoints are + case s: Slice.Symbolic => coversWholeValue(widthOpt) || proveCovered(s.lo, s.width) + case Slice.Unknown => coversWholeValue(widthOpt) + if (proven) Tri.Yes + else if (unknownTouched || symbolics.nonEmpty) Tri.Unknown + else + slice match + // concrete coverage decides a concrete query outright + case Slice.Concrete(_) | Slice.Full => Tri.No + case _ => Tri.Unknown + end if + end contains + + private def coversWholeValue(widthOpt: Option[Int])(using MemberGetSet): Boolean = + import IntExprCalc.DataCalc.const + widthOpt.exists { w => + (immutable.BitSet.fromSpecific(0 until w) &~ bits).isEmpty || + proveCovered(const(0), const(w)) + } + + /** Proof that every position of `[qLo, qLo + qW)` is written by some accumulated region, run as a + * sweep: starting at `qLo`, extend the covered prefix by a region that provably starts at or + * before the cursor and provably ends after it, until the prefix provably reaches the end of the + * query. Each region's width is a `>= 1` fact for the inequality proofs (see + * [[IntExprCalc.DataCalc.proveNonNeg]]), a slice of non-positive width never being a valid + * elaboration. + */ + private def proveCovered(qLo: IntExprCalc.Linear, qW: IntExprCalc.Linear)(using + MemberGetSet + ): Boolean = + import IntExprCalc.DataCalc.* + if (symbolics.isEmpty) false // a concrete-only coverage is already decided by the BitSet paths + else + val regions: Vector[(IntExprCalc.Linear, IntExprCalc.Linear)] = + Coverage.bitRuns(bits).map(r => (const(r.start), const(r.length))).toVector ++ + symbolics.view.map(s => (s.lo, s.width)) + if (regions.sizeIs > Coverage.maxSymbolicRegions) false + else + val facts = qW :: regions.view.map(_._2).toList + def nonNeg(e: IntExprCalc.Linear): Boolean = proveNonNeg(e, facts) + def sweep(cursor: IntExprCalc.Linear, unused: Set[Int]): Boolean = + // the query ends at or before the covered prefix + nonNeg(sub(cursor, add(qLo, qW))) || + unused.exists { i => + val (lo, w) = regions(i) + // starts at or before the cursor, and ends after it + nonNeg(sub(cursor, lo)) && nonNeg(addConst(sub(add(lo, w), cursor), -1)) && + sweep(add(lo, w), unused - i) + } + sweep(qLo, regions.indices.toSet) + end if + end proveCovered /** Is this coverage full for the given (possibly unknown) width? */ - def isFull(widthOpt: Option[Int]): Tri = contains(Slice.Full, widthOpt) + def isFull(widthOpt: Option[Int])(using MemberGetSet): Tri = contains(Slice.Full, widthOpt) - def isEmpty: Boolean = bits.isEmpty && !unknownTouched && !fullyCovered + def isEmpty: Boolean = bits.isEmpty && symbolics.isEmpty && !unknownTouched && !fullyCovered def nonEmpty: Boolean = !isEmpty end Coverage object Coverage: + /** The bound on how many regions a containment proof sweeps over. Above it the coverage degrades + * to `unknownTouched`, which loses proofs but never invents one. + */ + private[ir] val maxSymbolicRegions: Int = 8 + val empty: Coverage = - Coverage(immutable.BitSet.empty, unknownTouched = false, fullyCovered = false) + Coverage(immutable.BitSet.empty, Nil, unknownTouched = false, fullyCovered = false) def full(widthOpt: Option[Int]): Coverage = empty.assign(Slice.Full, widthOpt) + + /** The two regions are the same region for every parameter assignment. */ + private def sameRegion(a: Slice.Symbolic, b: Slice.Symbolic)(using MemberGetSet): Boolean = + import IntExprCalc.DataCalc.* + def same(x: IntExprCalc.Linear, y: IntExprCalc.Linear): Boolean = + val d = sub(x, y) + d.terms.isEmpty && d.offset == 0 + same(a.lo, b.lo) && same(a.width, b.width) + + /** The maximal contiguous runs of set bits. */ + private def bitRuns(bits: immutable.BitSet): List[Range] = + bits.toList.foldLeft(List.empty[Range]) { + case (run :: rest, bit) if bit == run.end => Range(run.start, run.end + 1) :: rest + case (acc, bit) => Range(bit, bit + 1) :: acc + }.reverse end Coverage 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 478d81b3f..3ae02ba62 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -386,59 +386,19 @@ object DFVal: case alias: DFVal.Alias => alias.relValRef.get.dealias case _ => None @tailrec private def departial(slice: Slice)(using MemberGetSet): (DFVal, Slice) = - import IntExprCalc.DataCalc.* dfVal match case partial: DFVal.Alias.Partial => val relVal = partial.relValRef.get - partial match - case partial: DFVal.Alias.ApplyRange => - // the selection indices are in cell units for a vector range selection, - // in bit units otherwise - val unitWidthOpt = relVal.dfType match - case DFVector(cellType = cellType) => linearOfTypeWidth(cellType) - case _ => Some(const(1)) - val newSlice = unitWidthOpt match - case Some(unitWidth) => - val loUnits = linearOfParamRef(partial.idxLowRef) - val hiUnits = linearOfParamRef(partial.idxHighRef) - val selWidthUnits = addConst(sub(hiUnits, loUnits), 1) - (mulOpt(loUnits, unitWidth), mulOpt(selWidthUnits, unitWidth)) match - case (Some(loBits), Some(selWidthBits)) => - Slice.compose(slice, loBits, selWidthBits) - case _ => Slice.Unknown - case None => Slice.Unknown - relVal.departial(newSlice) - case partial: DFVal.Alias.ApplyIdx => - val idxLinear = linearOfVal(partial.relIdx.get) - // An index fixed at elaboration selects one cell, so it composes into the slice: a - // literal folds to a concrete range, and an index over design parameters stays a - // symbolic one (`v(N - 1)`). Any other index affects the entire value: a runtime - // value is not constant at all, and a loop iterator or a static-function formal is - // constant per evaluation yet varies across them. - val idxIsFixed = idxLinear.terms.forall((_, base) => base.isDesignParam) - val newSliceOpt = - if (idxIsFixed) - linearOfTypeWidth(partial.dfType).flatMap { cellWidth => - mulOpt(idxLinear, cellWidth).map(Slice.compose(slice, _, cellWidth)) - } - else None - (newSliceOpt, idxIsFixed) match - case (Some(newSlice), _) => relVal.departial(newSlice) - // a fixed index whose bit coordinates are not expressible (a cell width that does - // not linearize, or a parametric index times a parametric cell width) - case (None, true) => relVal.departial(Slice.Unknown) - case (None, false) => - relVal.dealias match - case Some(dcl: DFVal.Dcl) => (dcl, Slice.fromWidthOpt(dcl.dfType.widthIntOpt)) - case _ => (relVal, Slice.fromWidthOpt(relVal.dfType.widthIntOpt)) - end match - case partial: DFVal.Alias.SelectField => - relVal.dfType match - case structType: DFStruct => - relVal.departial(slice.shift(structType.fieldRelBitLow(partial.fieldName))) - case _ => relVal.departial(slice) - case _ => relVal.departial(slice) - end match + partial.composeSlice(slice) match + case Some(newSlice) => relVal.departial(newSlice) + // a fixed selection whose bit coordinates are not expressible (a cell width that does + // not linearize, or a parametric index times a parametric cell width) + case None if partial.isFixedSelection => relVal.departial(Slice.Unknown) + // a selection that varies per evaluation affects the entire value + case None => + relVal.dealias match + case Some(dcl: DFVal.Dcl) => (dcl, Slice.fromWidthOpt(dcl.dfType.widthIntOpt)) + case _ => (relVal, Slice.fromWidthOpt(relVal.dfType.widthIntOpt)) case _ => (dfVal, slice) end match end departial @@ -463,6 +423,67 @@ object DFVal: case a: DFVal.Alias.Partial => a.relValRef.get.isBubble case _ => false end extension + + extension (partial: DFVal.Alias.Partial) + /** Whether the selected region is fixed at elaboration. A range selection and a field selection + * always are (their bounds are literals or design parameters), while an `ApplyIdx` index need + * not be: a runtime value is not constant at all, and a loop iterator or a static-function + * formal is constant per evaluation yet varies across them. + */ + def isFixedSelection(using MemberGetSet): Boolean = partial match + case applyIdx: DFVal.Alias.ApplyIdx => + IntExprCalc.DataCalc + .linearOfVal(applyIdx.relIdx.get).terms.forall((_, base) => base.isDesignParam) + case _ => true + + /** Maps `slice`, given in this selection's own bit coordinates, into the coordinates of the + * value it selects from. Parameter-dependent bounds stay [[Slice.Symbolic]] linear forms, so a + * slice over a design parameter remains decidable downstream instead of collapsing to + * [[Slice.Unknown]]. + * + * `None` when the selection's bit coordinates are not expressible (a cell width that does not + * linearize, or a parametric index times a parametric cell width) or when the selection is not + * fixed (see [[isFixedSelection]]). How conservative to be about that is the caller's call: + * [[DFVal.departial]] takes the whole value for a varying index, while the state analysis + * consumes the index alongside it. + */ + def composeSlice(slice: Slice)(using MemberGetSet): Option[Slice] = + import IntExprCalc.DataCalc.* + val relVal = partial.relValRef.get + partial match + case applyRange: DFVal.Alias.ApplyRange => + // the selection indices are in cell units for a vector range selection, + // in bit units otherwise + val unitWidthOpt = relVal.dfType match + case DFVector(cellType = cellType) => linearOfTypeWidth(cellType) + case _ => Some(const(1)) + unitWidthOpt.flatMap { unitWidth => + val loUnits = linearOfParamRef(applyRange.idxLowRef) + val hiUnits = linearOfParamRef(applyRange.idxHighRef) + val selWidthUnits = addConst(sub(hiUnits, loUnits), 1) + (mulOpt(loUnits, unitWidth), mulOpt(selWidthUnits, unitWidth)) match + case (Some(loBits), Some(selWidthBits)) => + Some(Slice.compose(slice, loBits, selWidthBits)) + case _ => None + } + case applyIdx: DFVal.Alias.ApplyIdx => + // a fixed index selects one cell, so it composes into the slice: a literal folds to a + // concrete range and an index over design parameters stays a symbolic one (`v(N - 1)`) + if (applyIdx.isFixedSelection) + val idxLinear = linearOfVal(applyIdx.relIdx.get) + linearOfTypeWidth(applyIdx.dfType).flatMap { cellWidth => + mulOpt(idxLinear, cellWidth).map(Slice.compose(slice, _, cellWidth)) + } + else None + case selectField: DFVal.Alias.SelectField => + relVal.dfType match + case structType: DFStruct => + Some(slice.shift(structType.fieldRelBitLow(selectField.fieldName))) + case _ => Some(slice) + case _ => Some(slice) + end match + end composeSlice + end extension // can be an expression sealed trait CanBeExpr extends DFVal diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitStateSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitStateSpec.scala index fa76a9c12..80eaa1335 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitStateSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitStateSpec.scala @@ -305,5 +305,26 @@ class ExplicitStateSpec extends StageSpec: |""".stripMargin ) } - + test("Parameter-bounded slice read of a fully assigned variable") { + // the unconditional assignment covers every slice of `v`, including one whose bound is a + // design parameter, so `v` holds no implicit state and gets no `prev` self-assignment + class ID(val MB: Int <> CONST = 17) extends DFDesign: + val addr = Bits(32) <> IN + val o = Bit <> OUT + val v = Bits(32) <> VAR + v := h"32'f0040000" + o := addr(31, MB) == v(31, MB) + val id = (new ID).explicitState + assertCodeString( + id, + """|class ID(val MB: Int <> CONST = 17) extends DFDesign: + | val addr = Bits(32) <> IN + | val o = Bit <> OUT + | val v = Bits(32) <> VAR + | v := h"f0040000" + | o := (addr(31, MB) == v(31, MB)).bit + |end ID + |""".stripMargin + ) + } end ExplicitStateSpec diff --git a/lib/src/test/scala/ElaborationChecksSpec.scala b/lib/src/test/scala/ElaborationChecksSpec.scala index f376d996b..5ef7fcd17 100644 --- a/lib/src/test/scala/ElaborationChecksSpec.scala +++ b/lib/src/test/scala/ElaborationChecksSpec.scala @@ -1837,5 +1837,46 @@ class ElaborationChecksSpec extends DesignSpec: |Operation: `-` |Message: The applied RHS value width (2 * W) is larger than the LHS variable width (W).""".stripMargin ) + // A slice lies within its value's own bounds, so a coverage that spans the whole value contains + // it whatever its endpoints are, and complementary parameter-bounded writes are decided on their + // linear forms. What must stay rejected is a variable some of whose bits no write reaches, which + // a parameter-bounded write neither proves nor hides. + test("latch check over parameter-bounded slices"): + object Test: + @top(false) class FullAssign(val MB: Int <> CONST = 17) extends RTDesign: + val addr = Bits(32) <> IN + val o = Bit <> OUT + val v = Bits(32) <> VAR + v := h"32'f0040000" + o := addr(31, MB) == v(31, MB) + end FullAssign + @top(false) class SplitAssign(val MB: Int <> CONST = 17) extends RTDesign: + val hi, lo = Bits(32) <> IN + val o = Bits(32) <> OUT + val v = Bits(32) <> VAR + v(31, MB) := hi(31, MB) + v(MB - 1, 0) := lo(MB - 1, 0) + o := v + end SplitAssign + @top(false) class PartialAssign(val MB: Int <> CONST = 17) extends RTDesign: + val lo = Bits(32) <> IN + val o = Bits(32) <> OUT + val v = Bits(32) <> VAR + v(MB - 1, 0) := lo(MB - 1, 0) + o := v + end PartialAssign + end Test + import Test.* + // a whole-variable assignment covers the parameter-bounded read of it + val _ = FullAssign() + // the two writes cover the variable between them for every value of `MB` + val _ = SplitAssign() + assertElaborationErrors(PartialAssign())( + s"""|Elaboration errors found! + |DFiant HDL connectivity/assignment error! + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1864:17 - 1864:32 + |Hierarchy: PartialAssign + |Message: Found a latch variable `v`. Latches are not allowed under RT domains.""".stripMargin + ) end ElaborationChecksSpec From 74ff997d9fd015d5cf463397d92a56b7ce23a62b Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 13 Aug 2026 19:41:06 +0300 Subject: [PATCH 21/57] compiler_stages: a selection prefix is a plain reference in Verilog, and a name or a function call in VHDL The `ApplyRange` criteria of `NamedVerilogSelection` named the selected value only when the selection was partial, but prefix legality has nothing to do with the selection width: the printer emits `[hi:lo]` either way, so a full-width `.bits(19, 0)` over an operation result printed `(a + 20'd1)[19:0]`, a part-select on a parenthesized expression that no strict frontend reads. The same guard also let a select over a select (`a[15:0][15:0]`) and over a concatenation (`{...}[19:0]`) through. The width guard is gone; a selected value without a Verilog name is named unconditionally, while `hasVerilogName` keeps named prefixes exempt and `isAllowedMultipleReferences` keeps the legal `vec[i][hi:lo]` / `s.f[hi:lo]` chains inline. The VHDL backend had the same genus a level wider, because `NamedVHDLSelection` only ever ran for v93 pattern matching. A VHDL slice or index prefix must be a name or a function call, so a selection over an anonymous expression (`(unsigned(a) + 1)(19 downto 0)`, `(a or b)(3)`) and over the TYPE conversions `unsigned(...)`/`signed(...)`, even of a named value (`a.uint(15, 0)`), printed prefixes both GHDL and NVC reject. The stage now runs for every VHDL dialect (the v93 match-selector rule is gated inside the criteria), and a `hasVHDLName` predicate mirrors `VHDLValPrinter.csDFValAliasAsIs`, form by form: conversions that print as function calls are legal prefixes and stay inline (`to_slv(...)(19 downto 0)` is untouched), the three type-conversion renderings are not, and a selection chain is a name whenever its own root is, which the naming fixpoint repairs independently. Both backends also shared a consumer-side hole: the criteria scan visits anonymous members only, so a NAMED selection (`val s = (a | b)(7, 0)`, `val s = u.signed(20, 1)`) never surfaced its operand demand, and the very shape the stage handles inline slipped through the moment the user bound it to a val. The criteria entry point now derives the demand from the prefix value's side, by re-asking the reading selection's own criteria; duplicate demands merge in grouping, and named results fall to the existing filter. Every previously-illegal probe output now analyzes clean under iverilog, yosys, GHDL, and NVC, and the already-legal controls print byte-identically. The suite was green with no reference output changed before the fix, which says the whole branch was untested; the new `NamedSelectionSpec` tests pin each shape and each was verified to fail with its guard reverted. Fixes #486 Co-Authored-By: Claude Fable 5 --- .claude/commands/bugfix.md | 45 +++++++ .../dfhdl/compiler/stages/NamedAliases.scala | 100 ++++++++++++--- .../scala/StagesSpec/NamedSelectionSpec.scala | 118 +++++++++++++++++- 3 files changed, 247 insertions(+), 16 deletions(-) diff --git a/.claude/commands/bugfix.md b/.claude/commands/bugfix.md index bb0b42aad..fa816e60c 100644 --- a/.claude/commands/bugfix.md +++ b/.claude/commands/bugfix.md @@ -692,6 +692,22 @@ via `dsn.dfc.getWarnings`; prefer `getCodeString` over `getDB` for IR inspection that code path is untested, which is why the bug survived. That also tells you the fix needs a new reference test, not just a patched stage. +### A criteria stage that scans anonymous members misses every demand a NAMED consumer makes + +The `NamedAliases` family collects naming demands by scanning **anonymous** values and asking +each one's `criteria`. That reaches a consumer's demand ("name my operand") only while the +consumer itself is anonymous; the moment the user binds the consumer to a `val` +(`val s = (a | b)(7, 0)`, `val s = u.signed(20, 1)`), the consumer never enters the scan and +its operand demand is silently lost — the stage works for the expression form and emits +illegal HDL for the bound form of the very same shape (issue #486's second half). When a +criteria rule says "construct X requires its operand named", probe the X-bound-to-a-val twin +before trusting it, and implement the rule from BOTH sides: the consumer's case for the +anonymous form, and an entry-point guard on the operand's side ("am I read by a selection that +cannot take me as written?") that re-asks the consumer's own criteria for the named form. +Duplicated demands from the two sides merge in the grouping step, and named values returned by +the re-ask are dropped by the `isAllowedMultipleReferences` filter, so the two-sided form costs +nothing. + ### An exemption phrased by shape swallows every construct with that shape When a stage's criteria carry an exemption written as a pattern (`case Ident(_) => false`, "skip @@ -841,6 +857,35 @@ Run it with `sbtn.bat 'lib/Test/runMain probe'`, and do **not** add your own already declares one at top level in the same (root) package, and a second makes every `@top` in the file ambiguous, with 226 errors that never name the duplicate given as the cause. +To classify the same variants under several backends in one run, take the backend as a `go` +parameter and re-bind it as a local given; the option type is a function from the `backends` +object, so callers spell it as a lambda shorthand: + +```scala +def go(name: String, beName: String, be: options.CompilerOptions.Backend)( + dsn: => core.Design +): Unit = + given options.CompilerOptions.Backend = be + ... // as above; getCompiledCodeString picks the given up per call +goAll("sv2009", _.verilog.sv2009); goAll("v2001", _.verilog.v2001); goAll("vhdl08", _.vhdl.v2008) +``` + +Note `getCompiledCodeString` needs `import dfhdl.compiler.stages.getCompiledCodeString` — it is +not in the `dfhdl.*` export. + +### A legality table needs the strict tools, not the permissive ones + +When the rule under construction is "which HDL shapes does the target language allow", do not +settle it from memory of the LRM or from whichever tool is handy: put each shape in a five-line +file and run the STRICT frontends. For the select-prefix rule of issue #486, verilator accepted +every illegal shape (`(a + 1)[19:0]`, `{...}[19:0]`, `a[15:0][15:0]`); iverilog rejected all of +them and yosys all but the double part-select. On the VHDL side ghdl and nvc agreed everywhere +and their messages QUOTE the rule ("the prefix of a slice name must be a name or a function +call"), which is the sentence the criteria comment should carry. So: Verilog legality = iverilog ++ yosys, VHDL legality = ghdl + nvc, and verilator's acceptance proves nothing. On this machine +the oss-cad-suite binaries only launch reliably from PowerShell by full path +(`C:\oss-cad-suite\bin\iverilog.exe`); under the Bash tool they die with exit 127. + --- ## 4. Write the check first diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala index 39835716b..2e6f2481f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala @@ -158,6 +158,21 @@ case object NamedVerilogSelection extends NamedAliases: case _ => false end extension def criteria(dfVal: DFVal)(using getSet: MemberGetSet, co: CompilerOptions): List[DFVal] = + dfVal.getReadDeps.headOption match + // A NAMED selection never enters the anonymous-member scan, so its prefix demand is + // derived from the prefix value's side, by re-asking the selection's own criteria (an + // anonymous selection reaches the same demand directly through the cases below, and a + // duplicate demand merges in grouping). Without this, `val s = u.signed(20, 1)` keeps + // the sign conversion inline and prints an illegal `$signed({1'b0, u})[20:1]`. + case Some(sel: (DFVal.Alias.ApplyRange | DFVal.Alias.ApplyIdx)) + if !dfVal.hasVerilogName && !sel.isAnonymous => + criteriaOwn(sel) + case _ => criteriaOwn(dfVal) + end criteria + private def criteriaOwn(dfVal: DFVal)(using + getSet: MemberGetSet, + co: CompilerOptions + ): List[DFVal] = def isBasicVerilog = co.backend match case be: dfhdl.backends.verilog => be.dialect match @@ -166,8 +181,13 @@ case object NamedVerilogSelection extends NamedAliases: case _ => false dfVal match case alias: DFVal.Alias if alias.relValRef.get.hasVerilogName => Nil - case alias: DFVal.Alias.ApplyRange - if alias.compareWidths(alias.relValRef.get)(_ != _).getOrElse(true) => + // A part-select prefix must be a plain reference in Verilog: `(expr)[hi:lo]`, + // `{...}[hi:lo]`, and `x[a:b][c:d]` are all rejected, and a full-width selection + // still prints as `[hi:lo]`, so the selection width is irrelevant and the selected + // value is named unconditionally (issue #486). Legal selection chains over a named + // value (`vec[i][hi:lo]`, `s.f[hi:lo]`) are unaffected: their relVal is an anonymous + // ApplyIdx/SelectField, which `isAllowedMultipleReferences` exempts from naming. + case alias: DFVal.Alias.ApplyRange => List(alias.relValRef.get) case alias @ DFVal.Alias.AsIs( dfType = _: (DFDecimal | DFBits), @@ -218,7 +238,7 @@ case object NamedVerilogSelection extends NamedAliases: List(alias.relValRef.get) case func: DFVal.Func => func.getReadDeps.headOption match - case Some(dfVal: DFVal) => criteria(dfVal) + case Some(dfVal: DFVal) => criteriaOwn(dfVal) case _ => Nil // anonymous conditional expressions case ch: DFConditional.Header if ch.isAnonymous && ch.dfType != DFUnit => @@ -231,28 +251,78 @@ case object NamedVerilogSelection extends NamedAliases: case _ => List(ch) case _ => Nil end match - end criteria + end criteriaOwn end NamedVerilogSelection -// For vhdl patten matching of a selection is limited. +// For vhdl, the prefix of a slice or an index selection must be a name or a function call +// (IEEE 1076-2008 8.1), so a selected value that prints as anything else is named. For v93, +// pattern matching over a selection is additionally limited, so a match selector is named. case object NamedVHDLSelection extends NamedAliases: - override def runCondition(using co: CompilerOptions): Boolean = - co.backend match - case be: dfhdl.backends.vhdl => - be.dialect match - case VHDLDialect.v93 => true - case _ => false - case _ => false - def criteria(dfVal: DFVal)(using MemberGetSet, CompilerOptions): List[DFVal] = + override def runCondition(using co: CompilerOptions): Boolean = co.backend.isVHDL + extension (dfVal: DFVal)(using MemberGetSet) + // Whether the value prints as a legal slice/index prefix. This mirrors + // `VHDLValPrinter.csDFValAliasAsIs` and must agree with it: the Bits-to-decimal + // conversions and the unsigned-to-signed widening print as TYPE CONVERSIONS + // (`unsigned(...)`, `signed(...)`), which VHDL forbids as a selection prefix whatever + // their operand, while every other conversion prints as a function call, which is a + // legal prefix. Transparent renderings take their operand's answer. A selection chain + // is a name whenever its own prefix is one, and an illegal chain prefix is named + // independently through `criteria`, so chain links count as names here. + def hasVHDLName: Boolean = dfVal match + case dfVal if !dfVal.isAnonymous => true + // prints as the selected port's name + case _: DFVal.PortByNameSelect => true + case alias: DFVal.Alias.AsIs => + val relVal = alias.relValRef.get + (alias.dfType, relVal.dfType) match + // transparent renderings + case (t, f) if t == f => relVal.hasVHDLName + case (t, DFOpaque(actualType = at)) if at =~ t => relVal.hasVHDLName + case (_: DFOpaque, _) => relVal.hasVHDLName + // type conversions + case (DFUInt(_) | DFSInt(_), DFBits(_)) => false + case (DFSInt(_), DFUInt(_)) => false + // function calls + case _ => true + case _: (DFVal.Alias.ApplyRange | DFVal.Alias.ApplyIdx | DFVal.Alias.SelectField) => true + case _ => false + end hasVHDLName + end extension + def criteria(dfVal: DFVal)(using getSet: MemberGetSet, co: CompilerOptions): List[DFVal] = + def isV93 = co.backend match + case be: dfhdl.backends.vhdl => be.dialect == VHDLDialect.v93 + case _ => false dfVal.getReadDeps.headOption match - case Some(_: DFConditional.DFMatchHeader) => List(dfVal) - case _ => Nil + // A NAMED selection never enters the anonymous-member scan, so its prefix demand is + // derived from the prefix value's side, by re-asking the selection's own criteria (an + // anonymous selection reaches the same demand directly below, and a duplicate demand + // merges in grouping). Without this, `val s = (a | b)(7, 0)` keeps the operation + // inline and prints an illegal `(a or b)(7 downto 0)`. + case Some(sel: (DFVal.Alias.ApplyRange | DFVal.Alias.ApplyIdx)) + if !dfVal.hasVHDLName && !sel.isAnonymous => + criteria(sel) + case readDep => + dfVal match + // v93 pattern matching cannot take a selection expression, so the selector is named + case _ if isV93 && readDep.exists(_.isInstanceOf[DFConditional.DFMatchHeader]) => + List(dfVal) + // the selection prefix rule: name a selected value that cannot print as a prefix + case alias: (DFVal.Alias.ApplyRange | DFVal.Alias.ApplyIdx) + if !alias.relValRef.get.hasVHDLName => + List(alias.relValRef.get) + case _ => Nil + end match + end criteria end NamedVHDLSelection extension [T: HasDB](t: T) def verilogNamedSelection(using CompilerOptions): DB = StageRunner.run(NamedVerilogSelection)(t.db) +extension [T: HasDB](t: T) + def vhdlNamedSelection(using CompilerOptions): DB = + StageRunner.run(NamedVHDLSelection)(t.db) + // Creating a previous values of a value requires that value to be names to avoid random anonymous names in the // the backend case object NamedPrev extends NamedAliases: diff --git a/compiler/stages/src/test/scala/StagesSpec/NamedSelectionSpec.scala b/compiler/stages/src/test/scala/StagesSpec/NamedSelectionSpec.scala index a11ceca71..220d7f7cd 100644 --- a/compiler/stages/src/test/scala/StagesSpec/NamedSelectionSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/NamedSelectionSpec.scala @@ -1,7 +1,7 @@ package StagesSpec import dfhdl.* -import dfhdl.compiler.stages.verilogNamedSelection +import dfhdl.compiler.stages.{verilogNamedSelection, vhdlNamedSelection} // scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} class NamedSelectionSpec extends StageSpec(stageCreatesUnrefAnons = true): @@ -285,4 +285,120 @@ class NamedSelectionSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + // A Verilog part-select prefix must be a plain reference, so the selected value is named + // even when the selection is full-width: `(a + 20'd1)[19:0]`, `a[15:0][15:0]`, and + // `{...}[19:0]` are all illegal (issue #486). + test("Full-width selection over an anonymous expression is named") { + class ID extends RTDesign: + val a = Bits(20) <> IN + val o1 = Bits(20) <> OUT + val o2 = Bits(16) <> OUT + val o3 = Bits(20) <> OUT + o1 <> (a.uint + 1).bits(19, 0) + o2 <> a(15, 0)(15, 0) + o3 <> (a(9, 0) ++ a(19, 10))(19, 0) + + val id = (new ID).verilogNamedSelection + assertCodeString( + id, + """|class ID extends RTDesign: + | val a = Bits(20) <> IN + | val o1 = Bits(20) <> OUT + | val o2 = Bits(16) <> OUT + | val o3 = Bits(20) <> OUT + | val o1_part = (a.uint + d"20'1").bits + | o1 <> o1_part(19, 0) + | val o2_part = a(15, 0) + | o2 <> o2_part(15, 0) + | val o3_part = (a(9, 0), a(19, 10)).toBits + | o3 <> o3_part(19, 0) + |end ID + |""".stripMargin + ) + } + // A value with no Verilog name that a NAMED selection reads never enters the anonymous + // scan through the selection, so the demand is derived from the prefix value's side. + // Without it, `val s = u.signed(20, 1)` prints an illegal `$signed({1'b0, u})[20:1]`. + test("Verilog prefix of a named selection is named") { + class ID extends RTDesign: + val u = UInt(20) <> IN + val o = UInt(20) <> OUT + val s = u.signed(20, 1) + o <> s + + val id = (new ID).verilogNamedSelection + assertCodeString( + id, + """|class ID extends RTDesign: + | val u = UInt(20) <> IN + | val o = UInt(20) <> OUT + | val s_part = u.signed + | val s = s_part(20, 1) + | o <> s + |end ID + |""".stripMargin + ) + } + // A VHDL slice/index prefix must be a name or a function call: `(unsigned(a) + 1)(19 + // downto 0)`, `(a or b)(3)`, and the type conversion `unsigned(a)(15 downto 0)` are all + // rejected, while `to_slv(...)(19 downto 0)` (function call) and `a(15 downto 0)(15 + // downto 0)` (slice of a slice) are legal and stay inline. + test("VHDL selection prefix over an anonymous expression is named") { + given options.CompilerOptions.Backend = _.vhdl.v2008 + class ID extends RTDesign: + val a = Bits(20) <> IN + val o1 = UInt(20) <> OUT + val o2 = UInt(16) <> OUT + val o3 = Bit <> OUT + val o4 = Bits(20) <> OUT + val o5 = Bits(16) <> OUT + o1 <> (a.uint + 1)(19, 0) + o2 <> a.uint(15, 0) + o3 <> (a(9, 0) | a(19, 10))(3) + o4 <> (a.uint + 1).bits(19, 0) + o5 <> a(15, 0)(15, 0) + + val id = (new ID).vhdlNamedSelection + assertCodeString( + id, + """|class ID extends RTDesign: + | val a = Bits(20) <> IN + | val o1 = UInt(20) <> OUT + | val o2 = UInt(16) <> OUT + | val o3 = Bit <> OUT + | val o4 = Bits(20) <> OUT + | val o5 = Bits(16) <> OUT + | val o1_part = a.uint + d"20'1" + | o1 <> o1_part(19, 0) + | val o2_part = a.uint + | o2 <> o2_part(15, 0) + | val o3_part = a(9, 0) | a(19, 10) + | o3 <> o3_part(3) + | o4 <> (a.uint + d"20'1").bits(19, 0) + | o5 <> a(15, 0)(15, 0) + |end ID + |""".stripMargin + ) + } + test("VHDL prefix of a named selection is named") { + given options.CompilerOptions.Backend = _.vhdl.v2008 + class ID extends RTDesign: + val a = Bits(20) <> IN + val o = Bits(8) <> OUT + val s = (a(9, 0) | a(19, 10))(7, 0) + o <> s + + val id = (new ID).vhdlNamedSelection + assertCodeString( + id, + """|class ID extends RTDesign: + | val a = Bits(20) <> IN + | val o = Bits(8) <> OUT + | val s_part = a(9, 0) | a(19, 10) + | val s = s_part(7, 0) + | o <> s + |end ID + |""".stripMargin + ) + } end NamedSelectionSpec From e22ed00341139f6f5f37b454d4ceb58df8588260 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 02:22:14 +0300 Subject: [PATCH 22/57] wip: DFBitsWL/BitsHL, a bit vector with a user-defined low index IR: DFBits renamed to DFBitsWL with an added lowIdxRef; object DFBits remains as the zero-based apply/unapply view. Frontend: DFBits[W] = DFBitsWL[W, 0], user-facing BitsHL(hi, lo) constructor, width-only compatibility across low indices via the generalized Candidate/TC/Compare/ops machinery, and absolute index selection (with the new BitIndexLow/BitIndexHigh checks) on low-indexed values. Selection results always normalize to low 0; a nonzero low arises only from explicit BitsHL construction. Backends render [hi:lo] / (hi downto lo). Still open: DFacsimile nonzero-low data offsets, BitsHL selection tests and backend print-spec cases, testApps validation, struct/vector BitsHL cells. Co-Authored-By: Claude Fable 5 --- .../analysis/DFConditionalAnalysis.scala | 2 +- .../compiler/analysis/DFValAnalysis.scala | 11 +- .../scala/dfhdl/compiler/ir/DFMember.scala | 50 +++- .../main/scala/dfhdl/compiler/ir/DFType.scala | 35 ++- .../scala/dfhdl/compiler/ir/DataOps.scala | 26 +- .../scala/dfhdl/compiler/ir/IntExprCalc.scala | 4 +- .../compiler/printing/DFDataPrinter.scala | 2 +- .../compiler/printing/DFTypePrinter.scala | 18 +- .../compiler/printing/DFValPrinter.scala | 38 ++- .../stages/ApplyInvertConstraint.scala | 2 +- .../compiler/stages/DropStructsVecs.scala | 4 +- .../stages/GlobalizePortVectorParams.scala | 7 +- .../dfhdl/compiler/stages/NamedAliases.scala | 8 +- .../stages/verilog/VerilogTypePrinter.scala | 4 +- .../stages/verilog/VerilogValPrinter.scala | 22 +- .../stages/vhdl/VHDLTypePrinter.scala | 13 +- .../compiler/stages/vhdl/VHDLValPrinter.scala | 10 +- .../src/main/scala/dfhdl/sim/DFacsimile.scala | 14 +- .../main/scala/dfhdl/sim/SimulationAPI.scala | 3 +- core/src/main/scala/dfhdl/core/Arg.scala | 6 + .../scala/dfhdl/core/AutoConstraint.scala | 2 +- core/src/main/scala/dfhdl/core/Bubble.scala | 2 +- core/src/main/scala/dfhdl/core/DFBits.scala | 240 ++++++++++++++---- core/src/main/scala/dfhdl/core/DFType.scala | 4 +- core/src/main/scala/dfhdl/core/DFVal.scala | 51 +++- core/src/main/scala/dfhdl/core/IntParam.scala | 7 + core/src/main/scala/dfhdl/core/ShowType.scala | 15 +- .../main/scala/dfhdl/core/SimplifyFunc.scala | 2 +- core/src/main/scala/dfhdl/core/Width.scala | 17 +- .../main/scala/dfhdl/core/r__For_Plugin.scala | 4 +- core/src/main/scala/dfhdl/hdl.scala | 2 + core/src/test/scala/CoreSpec/DFBitsSpec.scala | 44 +++- lib/src/main/scala/dfhdl/app/DesignArgs.scala | 8 +- .../scala/plugin/CustomControlPhase.scala | 2 +- .../main/scala/plugin/DFHDLTypePrinter.scala | 14 +- .../src/main/scala/plugin/TopAnnotPhase.scala | 6 +- 36 files changed, 504 insertions(+), 195 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala index 9d1da7116..d3817bc3a 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala @@ -75,7 +75,7 @@ extension [CB <: DFConditional.Block](cb: CB)(using MemberGetSet) .toSet selectorVal.dfType match case _ if complexPattern => None - case dt: DFBits => + case dt: DFBitsWL => if (constSet.exists(_.isBubble)) None // currently not checking don't-care patterns else Some((1 << dt.widthIntOpt.get) == constSet.size) case dec: DFDecimal => 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 9d0611eb4..6a9b51dc2 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala @@ -43,7 +43,7 @@ object Eby: val deltaOpt = (alias.dfType, relVal.dfType) match case (DFUInt(toW), DFUInt(fromW)) => toW.constDiffFrom(fromW) case (DFSInt(toW), DFSInt(fromW)) => toW.constDiffFrom(fromW) - case (DFBits(toW), DFBits(fromW)) => toW.constDiffFrom(fromW) + case (to: DFBitsWL, from: DFBitsWL) => to.widthParamRef.constDiffFrom(from.widthParamRef) case _ => None deltaOpt.filter(_ > 0).map((relVal, _)) @@ -425,7 +425,7 @@ extension (dfVal: DFVal) case DFVal.Alias.ApplyIdx.ConstIdx(i) => val maxValueOpt = relVal.dfType match case vector: DFVector => vector.lengthIntOpt - case bits: DFBits => bits.widthIntOpt + case bits: DFBitsWL => bits.widthIntOpt case xInt: DFDecimal => xInt.widthIntOpt case _ => None val padMaxValue = maxValueOpt.getOrElse(100) - 1 @@ -433,7 +433,7 @@ extension (dfVal: DFVal) case _ => "_sel" case applyRange: DFVal.Alias.ApplyRange => applyRange.dfType.runtimeChecked match - case DFBits(_) | DFUInt(_) | DFSInt(_) => + case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) => val padMaxValue = applyRange.widthIntOpt.getOrElse(100) - 1 val idxHigh = applyRange.idxHighRef.getIntOpt.map(_.toPaddedString(padMaxValue)).getOrElse("hi") @@ -469,7 +469,8 @@ extension (dfVal: DFVal) // looking for what kind of type reference it is r.originMember.asInstanceOf[DFVal].dfType match case DFVector(_, (cellDimRef: TypeRef) :: _) if cellDimRef == r => Some("length") - case DFBits(widthRef: TypeRef) if widthRef == r => Some("width") + case dt: DFBitsWL if dt.widthParamRef.getRef.contains(r) => Some("width") + case dt: DFBitsWL if dt.lowIdxRef.getRef.contains(r) => Some("lowidx") case DFDecimal(magnitudeWidthParamRef = widthRef: TypeRef) if widthRef == r => Some("width") case _ => None @@ -711,7 +712,7 @@ extension (lhs: DFVal)(using MemberGetSet) // total-width ref: for integer decimals the magnitude ref is the total ref (and may be // parametric); fixed-point total widths are always constant def widthRef(v: DFVal): IntParamRef = (v.dfType: @unchecked) match - case dt: DFBits => dt.widthParamRef + case dt: DFBitsWL => dt.widthParamRef case dt: DFDecimal if dt.fractionWidth == 0 => dt.magnitudeWidthParamRef case dt: DFDecimal => IntParamRef(dt.widthUNSAFE) widthRef(lhs).compare(widthRef(rhs))(func) 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 3ae02ba62..d2692101f 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -458,9 +458,15 @@ object DFVal: case DFVector(cellType = cellType) => linearOfTypeWidth(cellType) case _ => Some(const(1)) unitWidthOpt.flatMap { unitWidth => - val loUnits = linearOfParamRef(applyRange.idxLowRef) + // selection indices are absolute, so over a low-indexed bit vector they + // translate to relative offsets by subtracting the source's low index + // (the selection width is a difference, so it needs no translation) + val loUnitsAbs = linearOfParamRef(applyRange.idxLowRef) + val loUnits = relVal.dfType match + case b: DFBitsWL => sub(loUnitsAbs, linearOfParamRef(b.lowIdxRef)) + case _ => loUnitsAbs val hiUnits = linearOfParamRef(applyRange.idxHighRef) - val selWidthUnits = addConst(sub(hiUnits, loUnits), 1) + val selWidthUnits = addConst(sub(hiUnits, loUnitsAbs), 1) (mulOpt(loUnits, unitWidth), mulOpt(selWidthUnits, unitWidth)) match case (Some(loBits), Some(selWidthBits)) => Some(Slice.compose(slice, loBits, selWidthBits)) @@ -470,7 +476,11 @@ object DFVal: // a fixed index selects one cell, so it composes into the slice: a literal folds to a // concrete range and an index over design parameters stays a symbolic one (`v(N - 1)`) if (applyIdx.isFixedSelection) - val idxLinear = linearOfVal(applyIdx.relIdx.get) + // same absolute-to-relative translation as the range selection above + val idxLinearAbs = linearOfVal(applyIdx.relIdx.get) + val idxLinear = relVal.dfType match + case b: DFBitsWL => sub(idxLinearAbs, linearOfParamRef(b.lowIdxRef)) + case _ => idxLinearAbs linearOfTypeWidth(applyIdx.dfType).flatMap { cellWidth => mulOpt(idxLinear, cellWidth).map(Slice.compose(slice, _, cellWidth)) } @@ -1137,30 +1147,43 @@ object DFVal: tags: DFTags ) extends Partial derives ReadWriter: def elementWidthUNSAFE(using MemberGetSet): Int = dfType.runtimeChecked match - case DFBits(_) | DFUInt(_) | DFSInt(_) => 1 + case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) => 1 case DFVector(cellType = cellType) => cellType.widthUNSAFE def elementWidthIntOpt(using MemberGetSet): Option[Int] = dfType.runtimeChecked match - case DFBits(_) | DFUInt(_) | DFSInt(_) => Some(1) + case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) => Some(1) case DFVector(cellType = cellType) => cellType.widthIntOpt case _ => None protected def protIsFullyAnonymous(using MemberGetSet): Boolean = relValRef.get.isFullyAnonymous protected def protGetConstData(using MemberGetSet, ConstData.CachePolicy): ConstData[Any] = val relVal = relValRef.get - (relVal.getConstData[Any], idxHighRef.getIntConstData, idxLowRef.getIntConstData) match + // selection indices are absolute; a low-indexed bit vector's data offsets are + // relative to its low index + val relLowConstData: ConstData[Int] = relVal.dfType match + case b: DFBitsWL => b.lowIdxRef.getIntConstData + case _ => ConstData.KnownConst(0) + ( + relVal.getConstData[Any], + idxHighRef.getIntConstData, + idxLowRef.getIntConstData, + relLowConstData + ) match case ( ConstData.KnownConst(relValData), ConstData.KnownConst(idxHigh), - ConstData.KnownConst(idxLow) + ConstData.KnownConst(idxLow), + ConstData.KnownConst(relLow) ) => ConstData.KnownConst( - selRangeData(relVal.dfType, relValData, idxHigh, idxLow) + selRangeData(relVal.dfType, relValData, idxHigh - relLow, idxLow - relLow) ) case ( ConstData.NotConst, _, + _, _ - ) | (_, ConstData.NotConst, _) | (_, _, ConstData.NotConst) => + ) | (_, ConstData.NotConst, _, _) | (_, _, ConstData.NotConst, _) | + (_, _, _, ConstData.NotConst) => ConstData.NotConst case _ => ConstData.UnknownConst(this) end match @@ -1209,10 +1232,13 @@ object DFVal: case (ConstData.KnownConst(relValData), ConstData.KnownConst(Some(idx: BigInt))) => val idxInt = idx.toInt val outData = relVal.dfType match - case DFBits(_) => + case b: DFBitsWL => + // an absolute index into a low-indexed bit vector translates to a + // relative data offset + val relIdxInt = idxInt - b.lowIdxRef.getIntOpt.getOrElse(0) val data = relValData.asInstanceOf[(BitVector, BitVector)] - if (data._2.bit(idxInt)) None - else Some(data._1.bit(idxInt)) + if (data._2.bit(relIdxInt)) None + else Some(data._1.bit(relIdxInt)) case DFUInt(_) | DFSInt(_) => relValData.asInstanceOf[Option[BigInt]].map(_.testBit(idxInt)) case DFVector(_, _) => diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala index 2e0588356..fb963b235 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala @@ -28,7 +28,7 @@ sealed trait DFType extends Product, Serializable, HasRefCompare[DFType] derives object DFType: given ReadWriter[DFType] = ReadWriter.merge( summon[ReadWriter[DFBoolOrBit]], - summon[ReadWriter[DFBits]], + summon[ReadWriter[DFBitsWL]], summon[ReadWriter[DFDecimal]], summon[ReadWriter[DFEnum]], summon[ReadWriter[DFVector]], @@ -135,30 +135,39 @@ case object DFBit extends DFBoolOrBit ///////////////////////////////////////////////////////////////////////////// // DFBits ///////////////////////////////////////////////////////////////////////////// -final case class DFBits(widthParamRef: IntParamRef) extends DFType derives ReadWriter: +final case class DFBitsWL(widthParamRef: IntParamRef, lowIdxRef: IntParamRef) extends DFType + derives ReadWriter: type Data = (BitVector, BitVector) def widthIntOpt(using MemberGetSet): Option[Int] = widthParamRef.getIntOpt + def lowIdxIntOpt(using MemberGetSet): Option[Int] = lowIdxRef.getIntOpt def createBubbleData(using MemberGetSet): Data = (BitVector.low(widthUNSAFE), BitVector.high(widthUNSAFE)) def isDataBubble(data: Data): Boolean = !data._2.isZeros def dataToBitsData(data: Data)(using MemberGetSet): (BitVector, BitVector) = data def bitsDataToData(data: (BitVector, BitVector))(using MemberGetSet): Data = data protected def `prot_=~`(that: DFType)(using MemberGetSet): Boolean = that match - case that: DFBits => - this.widthParamRef =~ that.widthParamRef + case that: DFBitsWL => + this.widthParamRef =~ that.widthParamRef && this.lowIdxRef =~ that.lowIdxRef case _ => false def isSimilarTo(that: DFType)(using MemberGetSet): Boolean = that match - case that: DFBits => - this.widthParamRef.isSimilarTo(that.widthParamRef) + case that: DFBitsWL => + this.widthParamRef.isSimilarTo(that.widthParamRef) && + this.lowIdxRef.isSimilarTo(that.lowIdxRef) case _ => false - lazy val getRefs: List[DFRef.TypeRef] = widthParamRef.getRef.toList - def copyWithNewRefs(using RefGen): this.type = - copy(widthParamRef.copyAsNewRef).asInstanceOf[this.type] + lazy val getRefs: List[DFRef.TypeRef] = widthParamRef.getRef.toList ++ lowIdxRef.getRef.toList + def copyWithNewRefs(using RefGen): this.type = copy( + widthParamRef = widthParamRef.copyAsNewRef, + lowIdxRef = lowIdxRef.copyAsNewRef + ).asInstanceOf[this.type] def defaultData(using MemberGetSet): Data = createBubbleData -end DFBits - -object DFBits extends DFType.Companion[DFBits, (BitVector, BitVector)]: - def apply(width: Int): DFBits = DFBits(IntParamRef(width)) +end DFBitsWL + +object DFBits extends DFType.Companion[DFBitsWL, (BitVector, BitVector)]: + def apply(widthParamRef: IntParamRef): DFBitsWL = DFBitsWL(widthParamRef, IntParamRef(0)) + def apply(width: Int): DFBitsWL = apply(IntParamRef(width)) + // matches only a zero-based (literal low index 0) bit vector + def unapply(dfType: DFBitsWL): Option[IntParamRef] = + if (dfType.lowIdxRef.equals(0)) Some(dfType.widthParamRef) else None def dataFromBinString( bin: String ): Either[String, (BitVector, BitVector)] = boundary { diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DataOps.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DataOps.scala index 4b69ddeff..dd122b61c 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DataOps.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DataOps.scala @@ -17,15 +17,15 @@ def dataConversion[TT <: DFType, FT <: DFType](toType: TT, fromType: FT)( assert(tWidth == fWidth - 1) fromData // Double to Bits conversion - case (DFBits(IntUNSAFE(tWidth)), DFDouble) => + case (DFBitsWL(IntUNSAFE(tWidth), _), DFDouble) => assert(tWidth == 64) fromType.dataToBitsData(fromData) // Bits to Double conversion - case (DFDouble, DFBits(IntUNSAFE(fWidth))) => + case (DFDouble, DFBitsWL(IntUNSAFE(fWidth), _)) => assert(fWidth == 64) toType.bitsDataToData(fromData.asInstanceOf[(BitVector, BitVector)]) // Bits resize - case (DFBits(IntUNSAFE(tWidth)), DFBits(_)) => + case (DFBitsWL(IntUNSAFE(tWidth), _), _: DFBitsWL) => import dfhdl.internals.{resize => resizeBV} val data = fromData.asInstanceOf[(BitVector, BitVector)] (data._1.resizeBV(tWidth), data._2.resizeBV(tWidth)) @@ -60,16 +60,16 @@ def dataConversion[TT <: DFType, FT <: DFType](toType: TT, fromType: FT)( assert(fWidth <= 31) fromData // Conversion from BoolOrBit to Bits - case (DFBits(IntUNSAFE(tWidth)), DFBit | DFBool) => + case (DFBitsWL(IntUNSAFE(tWidth), _), DFBit | DFBool) => fromData.asInstanceOf[Option[Boolean]] .map(x => (BitVector.bit(x).resize(tWidth), BitVector.low(tWidth))) .getOrElse((BitVector.low(tWidth), BitVector.high(tWidth))) // Casting from any data to Bits - case (DFBits(IntUNSAFE(tWidth)), _) => + case (DFBitsWL(IntUNSAFE(tWidth), _), _) => assert(tWidth == fromType.widthUNSAFE) fromType.dataToBitsData(fromData) // Casting from Bits to any data - case (_, DFBits(IntUNSAFE(fWidth))) => + case (_, DFBitsWL(IntUNSAFE(fWidth), _)) => assert(fWidth == toType.widthUNSAFE) toType.bitsDataToData(fromData.asInstanceOf[(BitVector, BitVector)]) // Casting from BoolOrBit to UInt/SInt @@ -98,7 +98,7 @@ def selRangeData( relBitHigh: Int, relBitLow: Int )(using MemberGetSet): Any = (dfType, fromData).runtimeChecked match - case (_: DFBits, (valueBits: BitVector, bubbleBits: BitVector)) => + case (_: DFBitsWL, (valueBits: BitVector, bubbleBits: BitVector)) => assert(relBitHigh >= 0 && relBitHigh < valueBits.length) assert(relBitLow >= 0 && relBitLow < valueBits.length) assert(relBitHigh >= relBitLow) @@ -140,7 +140,7 @@ def calcFuncData[OT <: DFType]( else outType match // bits operations are handled specially, because bubble is bit-accurate - case _: DFBits => + case _: DFBitsWL => val ret: (BitVector, BitVector) = (op, argTypes, argData) match // bits concatenation case (FuncOp.++, _, argData: List[(BitVector, BitVector)] @unchecked) => @@ -149,7 +149,7 @@ def calcFuncData[OT <: DFType]( // bits repeat case ( FuncOp.repeat, - DFBits(_) :: DFInt32 :: Nil, + (_: DFBitsWL) :: DFInt32 :: Nil, (argData: (BitVector, BitVector) @unchecked) :: Some(cnt: BigInt) :: Nil ) => val (values, bubbles) = List.fill(cnt.toInt)(argData).unzip @@ -157,7 +157,7 @@ def calcFuncData[OT <: DFType]( // bits shifting case ( op @ (FuncOp.<< | FuncOp.>>), - DFBits(_) :: DFInt32 :: Nil, + (_: DFBitsWL) :: DFInt32 :: Nil, (vec: (BitVector, BitVector) @unchecked) :: Some(shift: BigInt) :: Nil ) => op match @@ -170,7 +170,7 @@ def calcFuncData[OT <: DFType]( // bits logic operations case ( op @ (FuncOp.^ | FuncOp.& | FuncOp.|), - DFBits(_) :: DFBits(_) :: maybeMoreTypes, + (_: DFBitsWL) :: (_: DFBitsWL) :: maybeMoreTypes, argData: List[(BitVector, BitVector)] @unchecked ) => val (values, bubbles) = argData.unzip @@ -182,7 +182,7 @@ def calcFuncData[OT <: DFType]( case FuncOp.^ => (values.reduce(_ ^ _), outBubbles) case ( FuncOp.unary_~, - DFBits(_) :: Nil, + (_: DFBitsWL) :: Nil, (vec: (BitVector, BitVector) @unchecked) :: Nil ) => (vec._1.not, vec._2) @@ -306,7 +306,7 @@ def calcFuncData[OT <: DFType]( case ( DFBit, op @ (FuncOp.^ | FuncOp.& | FuncOp.|), - DFBits(_) :: Nil, + (_: DFBitsWL) :: Nil, (valueBits: BitVector, _: BitVector) :: Nil ) => // bubble bits are always or-ed diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala index 2bd00b2f6..d219dc0c8 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala @@ -288,7 +288,7 @@ object IntExprCalc: case None => (ref.getIntUNSAFE, Nil) def typeFactors(t: DFType): Option[(Int, List[DFVal])] = t match case _ if t.getRefs.isEmpty => t.widthIntOpt.map((_, Nil)) - case DFBits(widthParamRef) => Some(paramRefFactors(widthParamRef)) + case dt: DFBitsWL => Some(paramRefFactors(dt.widthParamRef)) case DFXInt(_, widthParamRef, _) => Some(paramRefFactors(widthParamRef)) case vec: DFVector => vec.cellDimParamRefs.foldLeft(typeFactors(vec.cellType)) { (accOpt, dim) => @@ -345,7 +345,7 @@ object IntExprCalc: def linearOfTypeWidth(t: DFType): Option[Linear] = t match case _ if t.getRefs.isEmpty => t.widthIntOpt.map(Linear(Nil, _)) - case DFBits(widthParamRef) => Some(linearOfParamRef(widthParamRef)) + case dt: DFBitsWL => Some(linearOfParamRef(dt.widthParamRef)) case dec: DFDecimal => Some(DataCalc.addConst(linearOfParamRef(dec.magnitudeWidthParamRef), dec.fractionWidth)) case vec: DFVector => diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFDataPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFDataPrinter.scala index a7f6bba37..a23fc9f8e 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFDataPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFDataPrinter.scala @@ -14,7 +14,7 @@ trait AbstractDataPrinter extends AbstractPrinter: def csDFBitsHexFormat(hexRep: String): String def csDFBitsHexFormat(hexRep: String, actualWidth: Int, width: IntParamRef): String final def csDFBitsData( - dfType: DFBits, + dfType: DFBitsWL, data: (BitVector, BitVector), inPattern: Boolean = false ): String = diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala index c08d04a42..f3b72c7ea 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala @@ -6,7 +6,7 @@ import scala.collection.mutable trait AbstractTypePrinter extends AbstractPrinter: def csDFBoolOrBit(dfType: DFBoolOrBit, typeCS: Boolean): String - def csDFBits(dfType: DFBits, typeCS: Boolean): String + def csDFBits(dfType: DFBitsWL, typeCS: Boolean): String def csDFDecimal(dfType: DFDecimal, typeCS: Boolean): String final def csNamedDFTypeDcl(dfType: NamedDFType, global: Boolean): String = dfType match @@ -106,7 +106,7 @@ trait AbstractTypePrinter extends AbstractPrinter: final def csDFType(dfType: DFType, typeCS: Boolean = false): String = dfType match case dt: DFBoolOrBit => csDFBoolOrBit(dt, typeCS) - case dt: DFBits => csDFBits(dt, typeCS) + case dt: DFBitsWL => csDFBits(dt, typeCS) case dt: DFDecimal => csDFDecimal(dt, typeCS) case dt: DFEnum => csDFEnum(dt, typeCS) case dt: DFVector => csDFVector(dt, typeCS) @@ -127,10 +127,16 @@ protected trait DFTypePrinter extends AbstractTypePrinter: def csDFBoolOrBit(dfType: DFBoolOrBit, typeCS: Boolean): String = dfType match case DFBool => "Boolean" case DFBit => "Bit" - def csDFBits(dfType: DFBits, typeCS: Boolean): String = - val csWidth = dfType.widthParamRef.refCodeString(typeCS) - if (typeCS) s"Bits[$csWidth]" - else s"Bits($csWidth)" + def csDFBits(dfType: DFBitsWL, typeCS: Boolean): String = + if (dfType.lowIdxRef.equals(0)) + val csWidth = dfType.widthParamRef.refCodeString(typeCS) + if (typeCS) s"Bits[$csWidth]" + else s"Bits($csWidth)" + else + val csHigh = dfType.widthParamRef.hboundCS(dfType.lowIdxRef, typeCS) + val csLow = dfType.lowIdxRef.refCodeString(typeCS) + if (typeCS) s"BitsHL[$csHigh, $csLow]" + else s"BitsHL($csHigh, $csLow)" def csDFDecimal(dfType: DFDecimal, typeCS: Boolean): String = import dfType.* // the magnitude-width code string is the total width for integer types (fractionWidth 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 8a73f2a41..0750752dc 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala @@ -85,6 +85,24 @@ extension (intParamRef: IntParamRef) // case _ => s"${printer.csRef(ref, false).applyBrackets()} - 1" case int: Int => (int - 1).toString + /** the high-bound expression `low + width - 1` of a bit-vector range (the receiver is the width), + * folded to a literal when possible; a literal low of 0 spells exactly like `uboundCS` + */ + def hboundCS(lowIdxRef: IntParamRef, typeCS: Boolean = false)(using + printer: AbstractValPrinter + ): String = + (intParamRef, lowIdxRef) match + case (w: Int, l: Int) => (w + l - 1).toString + case (_, l: Int) if l == 0 => + s"${intParamRef.refCodeString(typeCS).applyBrackets()} - 1" + case (w: Int, _) => + s"${lowIdxRef.refCodeString(typeCS).applyBrackets()} + ${w - 1}" + case (_, l: Int) => + s"${intParamRef.refCodeString(typeCS).applyBrackets()} + ${l - 1}" + case _ => + val csWidth = intParamRef.refCodeString(typeCS).applyBrackets() + val csLow = lowIdxRef.refCodeString(typeCS).applyBrackets() + s"$csWidth + $csLow - 1" end extension extension (alias: Alias) @@ -104,7 +122,7 @@ trait AbstractValPrinter extends AbstractPrinter: */ final def csInlinedWidth(dfType: DFType): String = dfType match case DFBool | DFBit => "1" - case dt: DFBits => dt.widthParamRef.refCodeString + case dt: DFBitsWL => dt.widthParamRef.refCodeString case dt: DFDecimal => if (dt.fractionWidth == 0) dt.magnitudeWidthParamRef.refCodeString else s"${dt.magnitudeWidthParamRef.refCodeString.applyBrackets()} + ${dt.fractionWidth}" @@ -367,15 +385,15 @@ protected trait DFValPrinter extends AbstractValPrinter: s"${relValStr}.signed" case (DFUInt(_), DFSInt(_)) => s"${relValStr}.unsigned" - case (DFUInt(tWidthRef), DFBits(fWidthRef)) => + case (DFUInt(tWidthRef), _: DFBitsWL) => s"${relValStr}.uint" - case (DFSInt(tWidthRef), DFBits(fWidthRef)) => + case (DFSInt(tWidthRef), _: DFBitsWL) => s"${relValStr}.sint" - case (DFBits(tWidthParamRef), DFBits(fWidthRef)) => - s"${relValStr}${csResizeOrEby(tWidthParamRef, fWidthRef)}" - case (DFBits(tWidthParamRef), DFBit | DFBool) => - s"${relValStr}.toBits(${tWidthParamRef.refCodeString})" - case (DFBits(_), _) => + case (to: DFBitsWL, from: DFBitsWL) => + s"${relValStr}${csResizeOrEby(to.widthParamRef, from.widthParamRef)}" + case (to: DFBitsWL, DFBit | DFBool) => + s"${relValStr}.toBits(${to.widthParamRef.refCodeString})" + case (_: DFBitsWL, _) => s"${relValStr}.bits" case (DFUInt(tWidthParamRef), DFUInt(fWidthRef)) => s"${relValStr}${csResizeOrEby(tWidthParamRef, fWidthRef)}" @@ -391,7 +409,7 @@ protected trait DFValPrinter extends AbstractValPrinter: s"${relValStr}.as(${printer.csDFType(toType)})" case (t, DFOpaque(actualType = ot)) if ot == t => s"${relValStr}.actual" - case (_, DFBits(_)) | (DFOpaque(_, _, _, _), _) => + case (_, _: DFBitsWL) | (DFOpaque(_, _, _, _), _) => s"${relValStr}.as(${printer.csDFType(toType)})" case (DFUInt(tWidthParamRef), DFInt32) => s"""d"${printer.csWidthInterp(tWidthParamRef)}'$${${relValStr}}"""" @@ -425,7 +443,7 @@ protected trait DFValPrinter extends AbstractValPrinter: end csDFValAliasAsIs def csDFValAliasApplyRange(dfVal: Alias.ApplyRange): String = dfVal.dfType match - case DFBits(_) | DFUInt(_) | DFSInt(_) => + case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) => s"${dfVal.relValCodeString}(${dfVal.idxHighRef.refCodeString}, ${dfVal.idxLowRef.refCodeString})" case _ => s"${dfVal.relValCodeString}(${dfVal.idxLowRef.refCodeString}, ${dfVal.idxHighRef.refCodeString})" diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ApplyInvertConstraint.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ApplyInvertConstraint.scala index 4aa898d56..5bd847061 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ApplyInvertConstraint.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ApplyInvertConstraint.scala @@ -88,7 +88,7 @@ case object ApplyInvertConstraint extends HierarchyStage: ) def invert(dfVal: DFValAny): DFValAny = dfVal.asIR.dfType match case _: DFBoolOrBit => !dfVal.asValOf[dfhdl.core.DFBit] - case dfType: DFBits => + case dfType: DFBitsWL => // we assume constrained ports have known widths val width = dfType.widthIntOpt.get // all bits are inverted diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala index 0b7f87c2e..c2f7d32d3 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala @@ -67,7 +67,7 @@ case object DropStructsVecs extends GlobalStage: ): def updateArg(arg: DFVal): DFValAny = arg.dfType match // Structs and Vectors will be replaced with Bits in a different patch - case _: (DFStruct | DFVector | DFBits) => arg.asValAny + case _: (DFStruct | DFVector | DFBitsWL) => arg.asValAny case _ if !arg.isAnonymous => arg.asValAny.bits case _ => arg.asValAny.bits def typeToBits(dfType: irDFType): DFTypeAny = @@ -251,7 +251,7 @@ case object DropStructsVecs extends GlobalStage: explore = false end while val requireCast = partial.dfType match - case _: DFBits => false + case _: DFBitsWL => false case _: DFVector => false case _: DFStruct => false case _ => true 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 6206953d3..cba57cc8f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala @@ -40,7 +40,8 @@ case object Duplicate4GlobalizePortVectorParams extends ReduplicateDesign: dt.cellDimParamRefs.exists(preCheckIntParamRef) || ( dt.cellType match - case DFBits(w) => preCheckIntParamRef(w) + case dt: DFBitsWL => + preCheckIntParamRef(dt.widthParamRef) || preCheckIntParamRef(dt.lowIdxRef) case DFUInt(w) => preCheckIntParamRef(w) case DFSInt(w) => preCheckIntParamRef(w) case dt: DFVector => preCheckVector(dt.cellType) @@ -134,7 +135,9 @@ case object GlobalizePortVectorParams extends HierarchyStage: case dt: DFVector => dt.cellDimParamRefs.foreach(walkIntParamRef) dt.cellType match - case DFBits(w) => walkIntParamRef(w) + case dt: DFBitsWL => + walkIntParamRef(dt.widthParamRef) + walkIntParamRef(dt.lowIdxRef) case DFUInt(w) => walkIntParamRef(w) case DFSInt(w) => walkIntParamRef(w) case dt: DFVector => walkVector(dt.cellType) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala index 2e6f2481f..f183b2005 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala @@ -148,8 +148,8 @@ case object NamedVerilogSelection extends NamedAliases: case alias: DFVal.Alias.AsIs => val relVal = alias.relValRef.get val transparentConversion = (alias.dfType, relVal.dfType) match - case (DFUInt(toWidthRef), DFBits(fromWidthRef)) => toWidthRef.isSimilarTo(fromWidthRef) - case (DFBits(toWidthRef), DFUInt(fromWidthRef)) => toWidthRef.isSimilarTo(fromWidthRef) + case (DFUInt(toWidthRef), from: DFBitsWL) => toWidthRef.isSimilarTo(from.widthParamRef) + case (to: DFBitsWL, DFUInt(fromWidthRef)) => to.widthParamRef.isSimilarTo(fromWidthRef) case (DFBit, DFBool) => true case (DFBool, DFBit) => true case _ => false @@ -190,7 +190,7 @@ case object NamedVerilogSelection extends NamedAliases: case alias: DFVal.Alias.ApplyRange => List(alias.relValRef.get) case alias @ DFVal.Alias.AsIs( - dfType = _: (DFDecimal | DFBits), + dfType = _: (DFDecimal | DFBitsWL), relValRef = DFRef(relVal @ (DFBits.Val(_) | DFDecimal.Val(_))) ) if alias.compareWidths(relVal)(_ < _).getOrElse(true) => @@ -212,7 +212,7 @@ case object NamedVerilogSelection extends NamedAliases: // zero-extension of the conversion's own operand (see `csDFValAliasAsIs`), so it needs // no name. case alias @ DFVal.Alias.AsIs( - dfType = _: (DFDecimal | DFBits), + dfType = _: (DFDecimal | DFBitsWL), relValRef = DFRef(relVal @ (DFBits.Val(_) | DFDecimal.Val(_))) ) if relVal.dfType != DFInt32 && alias.compareWidths(relVal)(_ > _).getOrElse(false) => 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 cca305fd0..adc8753c8 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 @@ -7,8 +7,8 @@ import dfhdl.internals.* protected trait VerilogTypePrinter extends AbstractTypePrinter: type TPrinter <: VerilogPrinter def csDFBoolOrBit(dfType: DFBoolOrBit, typeCS: Boolean): String = "logic" - def csDFBits(dfType: DFBits, typeCS: Boolean): String = - s"logic [${dfType.widthParamRef.uboundCS}:0]" + def csDFBits(dfType: DFBitsWL, typeCS: Boolean): String = + s"logic [${dfType.widthParamRef.hboundCS(dfType.lowIdxRef)}:${dfType.lowIdxRef.refCodeString}]" val intTypeIsSupported: Boolean = printer.dialect match case VerilogDialect.v95 | VerilogDialect.v2001 => false 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 316b7d5cf..0e987d7d1 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 @@ -314,16 +314,16 @@ protected trait VerilogValPrinter extends AbstractValPrinter: s"$relValStr[${toWidthRef.uboundCS}:0]" if (printer.allowSignedKeywordAndOps) s"$$unsigned($truncated)" else truncated - case (DFBit, DFBits(_)) => - s"$relValStr[0]" - case (DFUInt(_), DFBits(_)) => + case (DFBit, from: DFBitsWL) => + s"$relValStr[${from.lowIdxRef.refCodeString}]" + case (DFUInt(_), _: DFBitsWL) => relValStr - case (DFBits(_), DFUInt(_)) => + case (_: DFBitsWL, DFUInt(_)) => relValStr - case (DFSInt(_), DFBits(_)) => + case (DFSInt(_), _: DFBitsWL) => if (printer.allowSignedKeywordAndOps) s"$$signed($relValStr)" else relValStr - case (DFBits(toWidthRef), DFBits(fromWidthRef)) => + case (DFBitsWL(toWidthRef, _), DFBitsWL(fromWidthRef, _)) => toWidthRef.widenDeltaOpt(fromWidthRef) match // a widening whose delta folds to a literal prints as the relative, // width-free `EBY_U` form @@ -424,9 +424,9 @@ protected trait VerilogValPrinter extends AbstractValPrinter: if (printer.allowTypeDef) s"${printer.csDFEnumTypeName(enumType)}'($relValStr)" else relValStr - case (toStruct: DFStruct, _: DFBits) => + case (toStruct: DFStruct, _: DFBitsWL) => s"${toStruct.name}'($relValStr)" - case (toVector: DFVector, _: DFBits) => + case (toVector: DFVector, _: DFBitsWL) => def to_vector_conv(vectorType: DFVector, relHighIdx: Int): String = val vecLength = vectorType.lengthUNSAFE vectorType.cellType match @@ -434,7 +434,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: List.tabulate(vecLength)(i => to_vector_conv(cellType, relHighIdx - i * cellType.widthUNSAFE) ).csList(literalGroupOpen, ",", "}") - case cellType: DFBits => + case cellType: DFBitsWL => val cellWidth = cellType.widthUNSAFE List.tabulate(vecLength)(i => s"$relValStr[${relHighIdx - i * cellWidth}:${relHighIdx - (i + 1) * cellWidth + 1}]" @@ -456,7 +456,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: case cellType: DFVector => List.tabulate(vecLength)(i => from_vector_conv(cellType, s"[$i]")) .csList("{", ",", "}") - case cellType: DFBits => + case cellType: DFBitsWL => List.tabulate(vecLength)(i => s"$relValStr$prevSelect[$i]").csList("{", ",", "}") case _: DFBoolOrBit => List.tabulate(vecLength)(i => s"$relValStr$prevSelect[$i]").csList("{", ",", "}") @@ -501,7 +501,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: end csDFValAliasAsIs def csDFValAliasApplyRange(dfVal: Alias.ApplyRange): String = dfVal.dfType match - case DFBits(_) | DFUInt(_) | DFSInt(_) => + case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) => s"${dfVal.relValCodeString}[${dfVal.idxHighRef.refCodeString}:${dfVal.idxLowRef.refCodeString}]" case _ => s"${dfVal.relValCodeString}[${dfVal.idxLowRef.refCodeString}:${dfVal.idxHighRef.refCodeString}]" diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala index b4b62e2e6..1619df05e 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala @@ -16,9 +16,11 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: dfType match case DFBool => "boolean" case DFBit => "std_logic" - def csDFBits(dfType: DFBits, typeCS: Boolean): String = + def csDFBits(dfType: DFBitsWL, typeCS: Boolean): String = if (typeCS) "std_logic_vector" - else s"std_logic_vector(${dfType.widthParamRef.uboundCS} downto 0)" + else + val csHigh = dfType.widthParamRef.hboundCS(dfType.lowIdxRef) + s"std_logic_vector($csHigh downto ${dfType.lowIdxRef.refCodeString})" def csDFDecimal(dfType: DFDecimal, typeCS: Boolean): String = import dfType.* // fixed-point (fractionWidth != 0) types are the custom `ufix`/`sfix` arrays with the @@ -201,7 +203,9 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: dfType.cellType match case DFBit => "sl" case DFBool => "boolean" - case DFBits(widthParamRef) => s"slv${csIntParamRef(widthParamRef)}" + case dt: DFBitsWL => + val lowSuffix = if (dt.lowIdxRef.equals(0)) "" else s"_at${csIntParamRef(dt.lowIdxRef)}" + s"slv${csIntParamRef(dt.widthParamRef)}$lowSuffix" case DFUInt(widthParamRef) => s"unsigned${csIntParamRef(widthParamRef)}" case DFSInt(widthParamRef) => s"signed${csIntParamRef(widthParamRef)}" case dt: DFOpaque => csDFOpaqueTypeName(dt) @@ -385,7 +389,8 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: loopType = dfType.cellType case cellType => val finale = cellType match - case DFBits(width) => s"(${width.uboundCS} downto 0)" + case dt: DFBitsWL => + s"(${dt.widthParamRef.hboundCS(dt.lowIdxRef)} downto ${dt.lowIdxRef.refCodeString})" case DFUInt(width) => s"(${width.uboundCS} downto 0)" case DFSInt(width) => s"(${width.uboundCS} downto 0)" case _ => "" diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala index a525c8434..d1a996191 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala @@ -59,7 +59,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: // repeat func case argL :: argR :: Nil if dfVal.op == Func.Op.repeat => dfVal.dfType match - case dfType: DFBits => + case dfType: DFBitsWL => s"repeat(${argL.refCodeString}, ${dfType.widthParamRef.refCodeString})" case dfType: DFVector => s"(0 to ${dfType.cellDimParamRefs.head.uboundCS} => ${argL.refCodeString})" @@ -138,7 +138,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: // width too) case (Func.Op.length, _) => s"$argStrB'length" case (_, dt: DFDecimal) if !dt.isDFInt32 => s"$argStrB'length" - case (_, _: DFBits) => s"$argStrB'length" + case (_, _: DFBitsWL) => s"$argStrB'length" // every other rendering (integer, std_logic, boolean, enum, record, vector // array, opaque) is covered by the `bitWidth` overload family the printer // already emits (dfhdl_pkg + the per-named-type support functions) @@ -231,7 +231,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: def csToSLV(fromType: DFType, arg: String): String = fromType match - case dt: DFBits => arg + case dt: DFBitsWL => arg // opaques are subtypes, so they are transparent to `to_slv` operations case dt: DFOpaque => csToSLV(dt.actualType, arg) case _ => s"to_slv($arg)" @@ -253,7 +253,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: tWidthRef.widenDeltaOpt(fWidthRef) match case Some(k) => s"eby($relValStr, $k)" case _ => s"resize($relValStr, ${tWidthRef.refCodeString})" - case (toType: DFType, fromType: DFBits) => + case (toType: DFType, fromType: DFBitsWL) => csBitsToType(toType, relValStr) case (DFBits(tWidthRef), DFBit | DFBool) => s"to_slv($relValStr, ${tWidthRef.refCodeString})" @@ -304,7 +304,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: end csDFValAliasAsIs def csDFValAliasApplyRange(dfVal: Alias.ApplyRange): String = dfVal.dfType match - case DFBits(_) | DFUInt(_) | DFSInt(_) => + case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) => val slice = s"${dfVal.relValCodeString}(${dfVal.idxHighRef.refCodeString} downto ${dfVal.idxLowRef.refCodeString})" // SInt slice now produces DFUInt; wrap with `unsigned(...)` since diff --git a/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala b/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala index 86fd967b3..498b7262d 100644 --- a/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala +++ b/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala @@ -953,7 +953,7 @@ private final class Builder(rawDB: DB): */ private def perInstanceConstData(v: DFVal): Option[Any] = val packed = v.dfType match - case _: DFBits | _: DFDecimal | DFBool | DFBit | _: DFEnum | _: DFStruct | _: DFVector | + case _: DFBitsWL | _: DFDecimal | DFBool | DFBit | _: DFEnum | _: DFStruct | _: DFVector | _: DFOpaque => true case _ => false if !packed then None @@ -1157,7 +1157,7 @@ private final class Builder(rawDB: DB): val relWV = readWV(rel) val off = dynCellOffset(relWV.width / cellW, cellW, a.relIdx.get) wide.dynExtract(relWV, off, cellW) - case _: DFBits => + case _: DFBitsWL => constIdxOpt(a.relIdx.get) match case Some(i) if undrivenPartialSink(rel) => partialSinkRead(rel.asInstanceOf[DFVal.Dcl], i, 1) @@ -1172,9 +1172,9 @@ private final class Builder(rawDB: DB): val hi = a.idxHighRef.getIntOpt.getOrElse(unsupported("non-constant range", a)) val lo = a.idxLowRef.getIntOpt.getOrElse(unsupported("non-constant range", a)) rel.dfType match - case _: DFBits if undrivenPartialSink(rel) => + case _: DFBitsWL if undrivenPartialSink(rel) => partialSinkRead(rel.asInstanceOf[DFVal.Dcl], lo, hi - lo + 1) - case _: DFBits => wide.extract(readWV(rel), lo, hi - lo + 1) + case _: DFBitsWL => wide.extract(readWV(rel), lo, hi - lo + 1) case t => unsupported(s"range selection on $t", a) private def buildSelectField(sf: DFVal.Alias.SelectField): WV = @@ -1583,7 +1583,7 @@ private final class Builder(rawDB: DB): constIdxOpt(ai.relIdx.get) match case Some(i) => (dcl, lo0 + (len - 1 - i) * cellW, dyn) case None => (dcl, lo0, addDyn(dyn, dynCellOffset(len, cellW, ai.relIdx.get))) - case _: DFBits => + case _: DFBitsWL => constIdxOpt(ai.relIdx.get) match case Some(i) => (dcl, lo0 + i, dyn) case None => (dcl, lo0, addDyn(dyn, dynBitOffset(ai.relIdx.get))) @@ -1769,7 +1769,7 @@ private final class Builder(rawDB: DB): val cellW = widthOfType(vt.cellType, ai) val len = widthOfType(vt, ai) / cellW (dcl, lo0 + (len - 1 - constIdxOf(ai.relIdx.get)) * cellW) - case _: DFBits => (dcl, lo0 + constIdxOf(ai.relIdx.get)) + case _: DFBitsWL => (dcl, lo0 + constIdxOf(ai.relIdx.get)) case t => unsupported(s"initial assignment through indexing into $t", ai) case sf: DFVal.Alias.SelectField => val rel = sf.relValRef.get @@ -2992,7 +2992,7 @@ private final class Builder(rawDB: DB): private def widthThroughParams(t: DFType): Option[Int] = given ConstData.CachePolicy = ConstData.CachePolicy.NoCache t match - case b: DFBits => b.widthParamRef.getIntConstData.toOption + case b: DFBitsWL => b.widthParamRef.getIntConstData.toOption case d: DFDecimal => d.magnitudeWidthParamRef.getIntConstData.toOption.map(_ + d.fractionWidth) case v: DFVector => diff --git a/compiler/stages/src/main/scala/dfhdl/sim/SimulationAPI.scala b/compiler/stages/src/main/scala/dfhdl/sim/SimulationAPI.scala index 1a7077f59..2f0bce914 100644 --- a/compiler/stages/src/main/scala/dfhdl/sim/SimulationAPI.scala +++ b/compiler/stages/src/main/scala/dfhdl/sim/SimulationAPI.scala @@ -106,7 +106,8 @@ final class Simulation[D <: Design] private[sim] ( throw new IllegalArgumentException(s"cannot resolve a param-dependent width in type:\n$t") )) def rec(t: ir.DFType): ir.DFType = t match - case b: ir.DFBits => b.copy(widthParamRef = lit(b.widthParamRef)) + case b: ir.DFBitsWL => + b.copy(widthParamRef = lit(b.widthParamRef), lowIdxRef = lit(b.lowIdxRef)) case d: ir.DFDecimal => d.copy(magnitudeWidthParamRef = lit(d.magnitudeWidthParamRef)) case v: ir.DFVector => v.copy(cellType = rec(v.cellType), cellDimParamRefs = v.cellDimParamRefs.map(lit)) diff --git a/core/src/main/scala/dfhdl/core/Arg.scala b/core/src/main/scala/dfhdl/core/Arg.scala index 3ed6a0322..617122956 100644 --- a/core/src/main/scala/dfhdl/core/Arg.scala +++ b/core/src/main/scala/dfhdl/core/Arg.scala @@ -26,4 +26,10 @@ object Arg: [t <: Int] =>> t > 1, [t <: Int] =>> "Argument must be larger than 1, but found: " + t ] + object Natural + extends Check1[ + Int, + [t <: Int] =>> t >= 0, + [t <: Int] =>> "Argument must be non-negative, but found: " + t + ] end Arg diff --git a/core/src/main/scala/dfhdl/core/AutoConstraint.scala b/core/src/main/scala/dfhdl/core/AutoConstraint.scala index 24a785df6..e0a921e05 100644 --- a/core/src/main/scala/dfhdl/core/AutoConstraint.scala +++ b/core/src/main/scala/dfhdl/core/AutoConstraint.scala @@ -206,7 +206,7 @@ object AutoConstraint: // only the types that carry these permissions are answered for; an integer decimal keeps its // total width in the magnitude ref (fraction 0) val sourceWidthOpt: Option[IntParam[Int]] = value.dfType.asIR match - case ir.DFBits(widthRef) => Some(widthRef.get) + case dt: ir.DFBitsWL => Some(dt.widthParamRef.get) case dt: ir.DFDecimal if dt.fractionWidth == 0 => Some(dt.magnitudeWidthParamRef.get) case _ => None if (value.hasTag[ir.ResizeTag]) true diff --git a/core/src/main/scala/dfhdl/core/Bubble.scala b/core/src/main/scala/dfhdl/core/Bubble.scala index a36226216..17c912f84 100644 --- a/core/src/main/scala/dfhdl/core/Bubble.scala +++ b/core/src/main/scala/dfhdl/core/Bubble.scala @@ -20,7 +20,7 @@ object Bubble extends Bubble: singleBit.repeat(widthParamRef.get) val dfcArg = if (named) dfc else dfc.anonymize dfType.asIR match - case ir.DFBits(widthParamRef) if !widthParamRef.isInt => + case ir.DFBitsWL(widthParamRef, _) if !widthParamRef.isInt => bitsBubbleRepeat(widthParamRef)(using dfcArg).asConstOf[T] case ir.DFXInt(signed, widthParamRef, _) if !widthParamRef.isInt => import DFBits.Val.Ops.{uint, sint} diff --git a/core/src/main/scala/dfhdl/core/DFBits.scala b/core/src/main/scala/dfhdl/core/DFBits.scala index c43d800d1..9f8a3a156 100644 --- a/core/src/main/scala/dfhdl/core/DFBits.scala +++ b/core/src/main/scala/dfhdl/core/DFBits.scala @@ -8,7 +8,41 @@ import scala.quoted.* import scala.util.boundary, boundary.break import DFDecimal.Constraints.{`LW == RW`, equalWidthCheck} -type DFBits[W <: IntP] = DFType[ir.DFBits, Args1[W]] +type DFBitsWL[W <: IntP, L <: IntP] = DFType[ir.DFBitsWL, Args2[W, L]] +type DFBits[W <: IntP] = DFBitsWL[W, 0] + +// internal width+low constructor; the user-facing spelling is the high/low-indexed DFBitsHL +object DFBitsWL: + def apply[W <: IntP, L <: IntP](width: IntParam[W], lowIdx: IntParam[L])(using + dfc: DFCG, + widthCheck: Arg.Width.CheckNUB[W], + lowCheck: Arg.Natural.CheckNUB[L] + ): DFBitsWL[W, L] = trydf { + width.toScalaIntOpt.foreach(widthCheck(_)) + lowIdx.toScalaIntOpt.foreach(lowCheck(_)) + ir.DFBitsWL(width.ref, lowIdx.ref).asFE[DFBitsWL[W, L]] + }(using dfc, CTName("BitsWL constructor")) + def forced[W <: IntP, L <: IntP](width: Int, lowIdx: Int): DFBitsWL[W, L] = + summon[Arg.Width.Check[Int]](width) + summon[Arg.Natural.Check[Int]](lowIdx) + ir.DFBitsWL(ir.IntParamRef(width), ir.IntParamRef(lowIdx)).asFE[DFBitsWL[W, L]] +end DFBitsWL + +type DFBitsHL[H <: IntP, L <: IntP] = DFBitsWL[IntP.RangeWidth[H, L], L] +object DFBitsHL: + def apply[H <: IntP, L <: IntP](idxHigh: IntParam[H], idxLow: IntParam[L])(using + dfc: DFCG, + hiloCheck: DFBits.BitsHiLo.CheckNUB[H, L], + lowCheck: Arg.Natural.CheckNUB[L] + ): DFBitsHL[H, L] = trydf { + (idxHigh.toScalaIntOpt, idxLow.toScalaIntOpt) match + case (Some(idxHighInt), Some(idxLowInt)) => hiloCheck(idxHighInt, idxLowInt) + case _ => + idxLow.toScalaIntOpt.foreach(lowCheck(_)) + ir.DFBitsWL((idxHigh - idxLow + 1).ref, idxLow.ref).asFE[DFBitsHL[H, L]] + }(using dfc, CTName("BitsHL constructor")) +end DFBitsHL + object DFBits: def apply[W <: IntP](width: IntParam[W])(using dfc: DFCG, @@ -78,6 +112,24 @@ object DFBits: [H <: Int, L <: Int] =>> H >= L, [H <: Int, L <: Int] =>> "Low index " + L + " is bigger than High bit index " + H ] + // selection on a low-indexed bit vector uses ABSOLUTE indices, so the valid index + // range is [L, L+W-1] rather than [0, W-1] (the latter stays with BitIndex above) + protected[core] object BitIndexLow + extends Check2[ + Int, + Int, + [I <: Int, L <: Int] =>> I >= L, + [I <: Int, L <: Int] =>> "Index " + I + " is below the low index " + L + + " of the selected value" + ] + protected[core] object BitIndexHigh + extends Check2[ + Int, + Int, + [I <: Int, H <: Int] =>> I <= H, + [I <: Int, H <: Int] =>> "Index " + I + " is above the high index " + H + + " of the selected value" + ] trait CompareCheck[ ValW <: IntP, ArgW <: IntP, @@ -388,10 +440,10 @@ object DFBits: compiletime.error( "An integer value cannot be a candidate for a Bits type.\nTry explicitly using a decimal constant via the `d\"'\"` string interpolation." ).asInstanceOf[Dud[V]] - given fromDFBits[W <: IntP, P, R <: DFValTP[DFBits[W], P]]: Candidate[R] with + given fromDFBits[W <: IntP, L <: IntP, P, R <: DFValTP[DFBitsWL[W, L], P]]: Candidate[R] with type OutW = W type OutP = P - def apply(value: R)(using DFC): Out = value + def apply(value: R)(using DFC): Out = value.asValTP[DFBits[W], P] given fromDFBoolOrBit[P, R <: DFValTP[DFBoolOrBit, P]]: Candidate[R] with type OutW = 1 type OutP = P @@ -425,7 +477,7 @@ object DFBits: import DFVal.Ops.bits val dfValIR = dfVal.asIR dfValIR.dfType match - case _: ir.DFBits => dfValIR.asValOf[DFBits[Int]] + case _: ir.DFBitsWL => dfValIR.asValOf[DFBits[Int]] case _ => dfValIR.asValAny.bits(using dfc)(using Width.wide).asValOf[DFBits[Int]] end match @@ -480,17 +532,17 @@ object DFBits: ToString[LW] + ")` to state the width explicitly." ] ] - given DFBitsFromCandidate[LW <: IntP, V, RP, IC <: Candidate[V]](using + given DFBitsFromCandidate[LW <: IntP, LL <: IntP, V, RP, IC <: Candidate[V]](using ic: IC { type OutP = RP } )(using check: `LW == RW`.CheckNUB[LW, ic.OutW] - ): TC[DFBits[LW], V] with + ): TC[DFBitsWL[LW, LL], V] with type OutP = RP - def conv(dfType: DFBits[LW], value: V)(using dfc: DFC): Out = + def conv(dfType: DFBitsWL[LW, LL], value: V)(using dfc: DFC): Out = import Ops.resizeBits val dfVal = ic(value) if (AutoConstraint.permitsWidthAdjust(dfVal, dfType.widthIntParam)) - dfVal.resizeBits(dfType.widthIntParam).asValTP[DFBits[LW], RP] + dfVal.resizeBits(dfType.widthIntParam).asValTP[DFBitsWL[LW, LL], RP] else (dfType.widthIntOpt, dfVal.widthIntOpt) match case (Some(lw), Some(rw)) => check(lw, rw) @@ -500,16 +552,17 @@ object DFBits: s"""|The argument width (${dfVal.dfType.widthErrorString}) is different than the receiver width (${dfType.widthErrorString}). |Consider `.extend` or `.truncate` to adjust it to the receiver width, or `.resize(width)` to state the width explicitly.""".stripMargin ) - dfVal.nameInDFCPosition.asValTP[DFBits[LW], RP] + dfVal.nameInDFCPosition.asValTP[DFBitsWL[LW, LL], RP] end if end conv end DFBitsFromCandidate - given DFBitsFromSEV[LW <: IntP, T <: BitOrBool, V <: SameElementsVector[T]]: TC[DFBits[LW], V] + given DFBitsFromSEV[LW <: IntP, LL <: IntP, T <: BitOrBool, V <: SameElementsVector[T]] + : TC[DFBitsWL[LW, LL], V] with type OutP = CONST - def conv(dfType: DFBits[LW], value: V)(using DFC): Out = + def conv(dfType: DFBitsWL[LW, LL], value: V)(using DFC): Out = SameElementsVector.bitsValOf(dfType.widthIntParam, value, named = true) - .asConstOf[DFBits[LW]] + .asConstOf[DFBitsWL[LW, LL]] end TC object TCConv: @@ -526,6 +579,7 @@ object DFBits: import DFVal.Compare given DFBitsCompareCandidate[ LW <: IntP, + LL <: IntP, R, RP, IC <: Candidate[R], @@ -537,9 +591,9 @@ object DFBits: check: CompareCheck[LW, ic.OutW, C], op: ValueOf[Op], castling: ValueOf[C] - ): Compare[DFBits[LW], R, Op, C] with + ): Compare[DFBitsWL[LW, LL], R, Op, C] with type OutP = RP - def conv(dfType: DFBits[LW], arg: R)(using DFC): Out = + def conv(dfType: DFBitsWL[LW, LL], arg: R)(using DFC): Out = val dfValArg = ic(arg) (dfType.widthIntOpt, dfValArg.dfType.widthIntOpt) match case (Some(lw), Some(rw)) => check(lw, rw) @@ -553,11 +607,12 @@ object DFBits: s"""|Cannot apply this operation between a value of $lhsStr bits width (LHS) and a value of $rhsStr bits width (RHS). |An explicit conversion must be applied.""".stripMargin ) - dfValArg.asValTP[DFBits[LW], RP] + dfValArg.asValTP[DFBitsWL[LW, LL], RP] end conv end DFBitsCompareCandidate given DFBitsCompareSEV[ LW <: IntP, + LL <: IntP, Op <: FuncOp.===.type | FuncOp.=!=.type, C <: Boolean, T <: BitOrBool, @@ -565,11 +620,11 @@ object DFBits: ](using ValueOf[Op], ValueOf[C] - ): Compare[DFBits[LW], V, Op, C] with + ): Compare[DFBitsWL[LW, LL], V, Op, C] with type OutP = CONST - def conv(dfType: DFBits[LW], arg: V)(using DFC): Out = + def conv(dfType: DFBitsWL[LW, LL], arg: V)(using DFC): Out = SameElementsVector.bitsValOf(dfType.widthIntParam, arg, named = true) - .asConstOf[DFBits[LW]] + .asConstOf[DFBitsWL[LW, LL]] end DFBitsCompareSEV end Compare @@ -611,18 +666,52 @@ object DFBits: DFVal.Alias.ApplyIdx(DFBit, lhs, ub(lhs.widthIntParam, idx)(using dfc.anonymize)) }(using dfc, CTName("bit selection (apply)")) end evOpApplyDFBits + // a nonzero-low receiver selects with ABSOLUTE indices in [L, L+W-1]; the bound + // composition `W+L` does not survive the type-level const guards (see the IntP + // doc comment), so this variant checks at elaboration time instead + given evOpApplyDFBitsWL[ + W <: IntP, + L2 <: IntP, + A, + C, + I, + P, + L <: DFVal[DFBitsWL[W, L2], Modifier[A, C, I, P]], + R + ](using + notLow0: scala.util.NotGiven[L2 =:= 0], + ub: DFUInt.Val.UBArg[Int, R] + ): ExactOp2Aux["apply", DFC, DFValAny, L, R, DFVal[DFBit, Modifier[A, C, Any, P]]] = + new ExactOp2["apply", DFC, DFValAny, L, R]: + type Out = DFVal[DFBit, Modifier[A, C, Any, P]] + def apply(lhs: L, idx: R)(using DFC): Out = trydf { + import dfc.getSet + val lowRef = lhs.asIR.dfType.asInstanceOf[ir.DFBitsWL].lowIdxRef + val bound = (lhs.widthIntParam + lowRef.get).asInstanceOf[IntParam[Int]] + val idxVal = ub(bound, idx)(using dfc.anonymize) + // a constant index must also respect the lower bound + val idxIntOpt = idxVal.asIR match + case c: ir.DFVal.Const => c.data.asInstanceOf[Option[BigInt]].map(_.toInt) + case _ => None + (idxIntOpt, lowRef.getIntOpt) match + case (Some(idxInt), Some(lowInt)) => BitIndexLow(idxInt, lowInt) + case _ => + DFVal.Alias.ApplyIdx(DFBit, lhs, idxVal) + }(using dfc, CTName("bit selection (apply)")) + end evOpApplyDFBitsWL given evOpApplyRangeDFBits[ W <: IntP, + L2 <: IntP, A, C, I, P, - L <: DFVal[DFBits[W], Modifier[A, C, I, P]], + L <: DFVal[DFBitsWL[W, L2], Modifier[A, C, I, P]], HI <: IntP, LO <: IntP ](using - checkHigh: BitIndex.CheckNUB[HI, W], - checkLow: BitIndex.CheckNUB[LO, W], + checkHigh: BitIndexHigh.CheckNUB[HI, IntP.HighIdx[W, L2]], + checkLow: BitIndexLow.CheckNUB[LO, L2], checkHiLo: BitsHiLo.CheckNUB[HI, LO] ): ExactOp3Aux["apply", DFC, DFValAny, L, HI, LO, DFVal[ DFBits[IntP.RangeWidth[HI, LO]], @@ -631,17 +720,22 @@ object DFBits: new ExactOp3["apply", DFC, DFValAny, L, HI, LO]: type Out = DFVal[DFBits[IntP.RangeWidth[HI, LO]], Modifier[A, C, Any, P]] def apply(lhs: L, idxHigh: HI, idxLow: LO)(using DFC): Out = trydf { + import dfc.getSet val idxHighParam = IntParam(idxHigh) val idxLowParam = IntParam(idxLow) val idxHighIntOpt = idxHighParam.toScalaIntOpt val idxLowIntOpt = idxLowParam.toScalaIntOpt - val widthIntOpt = lhs.widthIntOpt - (idxHighIntOpt, widthIntOpt) match - case (Some(idxHighInt), Some(widthInt)) => checkHigh(idxHighInt, widthInt) - case _ => - (idxLowIntOpt, widthIntOpt) match - case (Some(idxLowInt), Some(widthInt)) => checkLow(idxLowInt, widthInt) + val dfTypeIR = lhs.asIR.dfType.asInstanceOf[ir.DFBitsWL] + val lowIntOpt = dfTypeIR.lowIdxIntOpt + val highIntOpt = (dfTypeIR.widthIntOpt, lowIntOpt) match + case (Some(widthInt), Some(lowInt)) => Some(lowInt + widthInt - 1) + case _ => None + (idxHighIntOpt, highIntOpt) match + case (Some(idxHighInt), Some(highInt)) => checkHigh(idxHighInt, highInt) case _ => + (idxLowIntOpt, lowIntOpt) match + case (Some(idxLowInt), Some(lowInt)) => checkLow(idxLowInt, lowInt) + case _ => (idxHighIntOpt, idxLowIntOpt) match case (Some(idxHighInt), Some(idxLowInt)) => checkHiLo(idxHighInt, idxLowInt) case _ => @@ -677,8 +771,9 @@ object DFBits: given evOpLogicReduceDFBits[ Op <: FuncOp.|.type | FuncOp.&.type | FuncOp.^.type, LW <: IntP, + LL <: IntP, LP, - L <: DFValTP[DFBits[LW], LP] | DFValTP[DFUInt[LW], LP] + L <: DFValTP[DFBitsWL[LW, LL], LP] | DFValTP[DFUInt[LW], LP] ](using op: ValueOf[Op] ): ExactOp1Aux[Op, DFC, DFValAny, L, DFValTP[DFBit, LP]] = @@ -713,8 +808,10 @@ object DFBits: given evOpShift[ Op <: FuncOp.>>.type | FuncOp.<<.type, LW <: IntP, + LL <: IntP, LP, - LT <: DFBits[LW] | DFSInt[LW] | DFUInt[LW] | DFInt32, + // a shift keeps its receiver's type, including a nonzero low index + LT <: DFBitsWL[LW, LL] | DFSInt[LW] | DFUInt[LW] | DFInt32, L <: DFValTP[LT, LP], R, RP @@ -743,7 +840,7 @@ object DFBits: }(using dfc, CTName(op.value.toString)) end evOpShift - extension [W <: IntP, P](lhs: DFValTP[DFBits[W], P]) + extension [W <: IntP, LX <: IntP, P](lhs: DFValTP[DFBitsWL[W, LX], P]) // TODO: IntP private[DFBits] def resizeBits[RW <: IntP](updatedWidth: IntParam[RW])(using DFC @@ -820,11 +917,12 @@ object DFBits: given evOpAsDFBits[ W <: IntP, + LX <: IntP, A, C, I, P, - L <: DFVal[DFBits[W], Modifier[A, C, I, P]], + L <: DFVal[DFBitsWL[W, LX], Modifier[A, C, I, P]], AT <: DFType.Supported, OT <: DFTypeAny, OW <: IntP @@ -846,15 +944,20 @@ object DFBits: }(using dfc, CTName("cast from bits")) end evOpAsDFBits - extension [W <: IntP, T <: DFBits[W] | DFUInt[W], P]( + extension [W <: IntP, LX <: IntP, T <: DFBitsWL[W, LX] | DFUInt[W], P]( lhs: DFValTP[T, P] ) def unary_~(using DFCG): DFValTP[T, P] = trydf { DFVal.Func(lhs.dfType, FuncOp.unary_~, List(lhs)) } - extension [W <: IntP, A, C, I, P]( - lhs: DFVal[DFBits[W], Modifier[A, C, I, P]] + extension [W <: IntP, LX <: IntP, A, C, I, P]( + lhs: DFVal[DFBitsWL[W, LX], Modifier[A, C, I, P]] ) + // the receiver's low-index ref; selections use absolute indices, so a nonzero + // low offsets the computed bounds (the literal-0 path keeps the exact spelling + // zero-based code has always printed) + private def lowIdxRefIR: ir.IntParamRef = + lhs.asIR.dfType.asInstanceOf[ir.DFBitsWL].lowIdxRef def uint(using DFCG): DFValTP[DFUInt[W], P] = trydf { DFVal.Alias.AsIs(DFUInt(lhs.widthIntParam), lhs) } @@ -868,10 +971,16 @@ object DFBits: } def msbit(using DFCG): DFVal[DFBit, Modifier[A, C, Any, P]] = import DFVal.Ops.apply as applyBits - lhs.applyBits((lhs.widthIntParam - 1).toDFConst).asVal[DFBit, Modifier[A, C, Any, P]] + val lowRef = lowIdxRefIR + val msbIdx = + (if (lowRef.equals(0)) lhs.widthIntParam - 1 + else lhs.widthIntParam + lowRef.get - 1).asInstanceOf[IntParam[Int]] + lhs.applyBits(msbIdx.toDFConst).asVal[DFBit, Modifier[A, C, Any, P]] def lsbit(using DFCG): DFVal[DFBit, Modifier[A, C, Any, P]] = import DFVal.Ops.apply as applyBits - lhs.applyBits(0).asVal[DFBit, Modifier[A, C, Any, P]] + val lowRef = lowIdxRefIR + if (lowRef.equals(0)) lhs.applyBits(0).asVal[DFBit, Modifier[A, C, Any, P]] + else lhs.applyBits(lowRef.get.toDFConst).asVal[DFBit, Modifier[A, C, Any, P]] def msbits[RW <: IntP](updatedWidth: IntParam[RW])(using check: `LW >= RW`.CheckNUB[W, RW], dfc: DFCG @@ -879,8 +988,14 @@ object DFBits: (lhs.widthIntOpt, updatedWidth.toScalaIntOpt) match case (Some(lhsWidthInt), Some(updatedWidthInt)) => check(lhsWidthInt, updatedWidthInt) case _ => - DFVal.Alias.ApplyRange(lhs, lhs.widthIntParam - 1, lhs.widthIntParam - updatedWidth) - .asValTP[DFBits[RW], P] + val lowRef = lowIdxRefIR + val (idxHigh, idxLow) = + (if (lowRef.equals(0)) (lhs.widthIntParam - 1, lhs.widthIntParam - updatedWidth) + else + val low = lowRef.get + (lhs.widthIntParam + low - 1, lhs.widthIntParam + low - updatedWidth) + ).asInstanceOf[(IntParam[Int], IntParam[Int])] + DFVal.Alias.ApplyRange(lhs, idxHigh, idxLow).asValTP[DFBits[RW], P] } def lsbits[RW <: IntP](updatedWidth: IntParam[RW])(using check: `LW >= RW`.CheckNUB[W, RW], @@ -889,24 +1004,36 @@ object DFBits: (lhs.widthIntOpt, updatedWidth.toScalaIntOpt) match case (Some(lhsWidthInt), Some(updatedWidthInt)) => check(lhsWidthInt, updatedWidthInt) case _ => - DFVal.Alias.ApplyRange(lhs, updatedWidth - 1, 0).asValTP[DFBits[RW], P] + val lowRef = lowIdxRefIR + val (idxHigh, idxLow) = + (if (lowRef.equals(0)) (updatedWidth - 1, IntParam.forced[Int](0)) + else + val low = lowRef.get + (updatedWidth + low - 1, low) + ).asInstanceOf[(IntParam[Int], IntParam[Int])] + DFVal.Alias.ApplyRange(lhs, idxHigh, idxLow).asValTP[DFBits[RW], P] } // ascending part-select (Verilog `lhs[baseIdx +: selWidth]`): // selWidth bits whose LSB is anchored at baseIdx def lsbitsAt[BI <: IntP, SW <: IntP](baseIdx: IntParam[BI], selWidth: IntParam[SW])(using dfc: DFCG, checkWidth: Arg.Width.CheckNUB[SW], - checkLow: BitIndex.CheckNUB[BI, W], - checkHigh: BitIndex.CheckNUB[IntP.PartSelectHigh[BI, SW], W] + checkLow: BitIndexLow.CheckNUB[BI, LX], + checkHigh: BitIndexHigh.CheckNUB[IntP.PartSelectHigh[BI, SW], IntP.HighIdx[W, LX]] ): DFVal[DFBits[SW], Modifier[A, C, Any, P]] = trydf { + import dfc.getSet selWidth.toScalaIntOpt.foreach(checkWidth(_)) val idxHigh = baseIdx + selWidth - 1 - (baseIdx.toScalaIntOpt, lhs.widthIntOpt) match - case (Some(baseIdxInt), Some(widthInt)) => checkLow(baseIdxInt, widthInt) - case _ => - (idxHigh.toScalaIntOpt, lhs.widthIntOpt) match - case (Some(idxHighInt), Some(widthInt)) => checkHigh(idxHighInt, widthInt) - case _ => + val lowIntOpt = lowIdxRefIR.getIntOpt + val highIntOpt = (lhs.widthIntOpt, lowIntOpt) match + case (Some(widthInt), Some(lowInt)) => Some(lowInt + widthInt - 1) + case _ => None + (baseIdx.toScalaIntOpt, lowIntOpt) match + case (Some(baseIdxInt), Some(lowInt)) => checkLow(baseIdxInt, lowInt) + case _ => + (idxHigh.toScalaIntOpt, highIntOpt) match + case (Some(idxHighInt), Some(highInt)) => checkHigh(idxHighInt, highInt) + case _ => DFVal.Alias.ApplyRange(lhs, idxHigh, baseIdx).asVal[DFBits[SW], Modifier[A, C, Any, P]] } // descending part-select (Verilog `lhs[baseIdx -: selWidth]`): @@ -914,17 +1041,22 @@ object DFBits: def msbitsAt[BI <: IntP, SW <: IntP](baseIdx: IntParam[BI], selWidth: IntParam[SW])(using dfc: DFCG, checkWidth: Arg.Width.CheckNUB[SW], - checkHigh: BitIndex.CheckNUB[BI, W], - checkLow: BitIndex.CheckNUB[IntP.PartSelectLow[BI, SW], W] + checkHigh: BitIndexHigh.CheckNUB[BI, IntP.HighIdx[W, LX]], + checkLow: BitIndexLow.CheckNUB[IntP.PartSelectLow[BI, SW], LX] ): DFVal[DFBits[SW], Modifier[A, C, Any, P]] = trydf { + import dfc.getSet selWidth.toScalaIntOpt.foreach(checkWidth(_)) val idxLow = baseIdx - selWidth + 1 - (baseIdx.toScalaIntOpt, lhs.widthIntOpt) match - case (Some(baseIdxInt), Some(widthInt)) => checkHigh(baseIdxInt, widthInt) - case _ => - (idxLow.toScalaIntOpt, lhs.widthIntOpt) match - case (Some(idxLowInt), Some(widthInt)) => checkLow(idxLowInt, widthInt) + val lowIntOpt = lowIdxRefIR.getIntOpt + val highIntOpt = (lhs.widthIntOpt, lowIntOpt) match + case (Some(widthInt), Some(lowInt)) => Some(lowInt + widthInt - 1) + case _ => None + (baseIdx.toScalaIntOpt, highIntOpt) match + case (Some(baseIdxInt), Some(highInt)) => checkHigh(baseIdxInt, highInt) case _ => + (idxLow.toScalaIntOpt, lowIntOpt) match + case (Some(idxLowInt), Some(lowInt)) => checkLow(idxLowInt, lowInt) + case _ => DFVal.Alias.ApplyRange(lhs, baseIdx, idxLow).asVal[DFBits[SW], Modifier[A, C, Any, P]] } end extension diff --git a/core/src/main/scala/dfhdl/core/DFType.scala b/core/src/main/scala/dfhdl/core/DFType.scala index 4b02f83e0..8b0f2ef20 100644 --- a/core/src/main/scala/dfhdl/core/DFType.scala +++ b/core/src/main/scala/dfhdl/core/DFType.scala @@ -264,7 +264,7 @@ object DFType: // total-width ref (and may be parametric) private def widthRef[W <: IntP](dfType: DFTypeW[W])(using ir.MemberGetSet): ir.IntParamRef = dfType.asIR.runtimeChecked match - case dt: ir.DFBits => dt.widthParamRef + case dt: ir.DFBitsWL => dt.widthParamRef case dt: ir.DFDecimal => dt.magnitudeWidthParamRef extension [LW <: IntP](lhs: DFTypeW[LW]) protected[core] def compareWidths[RW <: IntP]( @@ -306,7 +306,7 @@ object DFType: end DFType -type DFTypeW[W <: IntP] = DFBits[W] | DFUInt[W] | DFSInt[W] +type DFTypeW[W <: IntP] = DFType[ir.DFBitsWL, Args2[W, ? <: IntP]] | DFUInt[W] | DFSInt[W] extension [T](t: T)(using tc: DFType.TC[T]) @targetName("tcDFType") diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index f227afa61..fbb727530 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -211,12 +211,13 @@ sealed protected trait DFValLP: transparent inline implicit def DFBitsValConversion[ W <: IntP, + L <: IntP, P <: Boolean, R <: CommonR | SameElementsVector[?] | NonEmptyTuple ]( inline from: R - )(using dfc: DFCG): DFValTP[DFBits[W], ISCONST[P]] = ${ - DFValConversionMacro[DFBits[W], ISCONST[P], R]('from)('dfc) + )(using dfc: DFCG): DFValTP[DFBitsWL[W, L], ISCONST[P]] = ${ + DFValConversionMacro[DFBitsWL[W, L], ISCONST[P], R]('from)('dfc) } // TODO: candidate should be fixed to cause UInt[?]->SInt[Int] conversion // covers the entire decimal family: DFUInt/DFSInt (F == 0, with an `Int` wildcard) and @@ -693,7 +694,7 @@ object DFVal extends DFValLP: path, format, length, width, undefinedValue ) val initFileConst = vectorType.cellType.asIR match - case ir.DFBits(_) => DFVal.Const(vectorType, data) + case _: ir.DFBitsWL => DFVal.Const(vectorType, data) case cellType => DFVal.Const(vectorType, data.map(cellType.bitsDataToData)) @@ -965,8 +966,11 @@ object DFVal extends DFValLP: case asIs @ ir.DFVal.Alias.AsIs(relValRef = ir.DFRef(relValIR)) if asIs.isAnonymous && dfc.isAnonymous && !forceNewAlias && asIs.tags.isEmpty && (aliasTypeIR match - case ir.DFBits(targetWidthRef) => - targetWidthRef.get =~ asIs.asValAny.widthIntParam && + // elision requires a zero-based target (dropping a cast to a nonzero-low + // type would lose its low index), but the source's low is irrelevant + // since bit vectors are width-only compatible + case dt: ir.DFBitsWL if dt.lowIdxRef.equals(0) => + dt.widthParamRef.get =~ asIs.asValAny.widthIntParam && relValIR.asValAny.widthIntParam =~ asIs.asValAny.widthIntParam case _ => false) => asIs.relValRef.get.asVal[AT, M] @@ -1045,8 +1049,8 @@ object DFVal extends DFValLP: end RegDIN object ApplyRange: import IntP.{-, +} - def apply[W <: IntP, M <: ModifierAny, H <: IntP, L <: IntP]( - relVal: DFVal[DFBits[W], M], + def apply[W <: IntP, L2 <: IntP, M <: ModifierAny, H <: IntP, L <: IntP]( + relVal: DFVal[DFBitsWL[W, L2], M], idxHigh: IntParam[H], idxLow: IntParam[L] )(using DFC): DFVal[DFBits[IntP.RangeWidth[H, L]], M] = @@ -1070,7 +1074,9 @@ object DFVal extends DFValLP: )(using DFC): ir.DFVal = val selLength = idxHigh - idxLow + 1 val dfType = relVal.dfType.runtimeChecked match - case ir.DFBits(_) => ir.DFBits(selLength.ref) + // a bit-vector selection result is always zero-based; a nonzero low index + // arises only from an explicit BitsHL construction + case _: ir.DFBitsWL => ir.DFBits(selLength.ref) case ir.DFUInt(_) | ir.DFSInt(_) => ir.DFUInt(selLength.ref) case ir.DFVector(cellType = cellType) => ir.DFVector(cellType, List(selLength.ref)) @@ -1078,11 +1084,16 @@ object DFVal extends DFValLP: // anonymous constant are replace by a different constant // after its data value was converted according to the alias case const: ir.DFVal.Const if const.isAnonymous => + // selection indices are absolute; the data offsets are relative to the + // source's low index + val relLowInt = relVal.dfType match + case b: ir.DFBitsWL => b.lowIdxIntOpt(using dfc.getSet).getOrElse(0) + case _ => 0 val updatedData = ir.selRangeData( dfType, const.data, - idxHigh.toScalaIntOpt.get, - idxLow.toScalaIntOpt.get + idxHigh.toScalaIntOpt.get - relLowInt, + idxLow.toScalaIntOpt.get - relLowInt )(using dfc.getSet) Const.forced(dfType.asFE, updatedData).asIR // named constants or other non-constant values are referenced @@ -1210,7 +1221,7 @@ object DFVal extends DFValLP: dt: DomainType )(using AssertGiven[ - dt.type <:< DomainType.DF | T =:= DFBit | IRT =:= ir.DFBits, + dt.type <:< DomainType.DF | T =:= DFBit | IRT =:= ir.DFBitsWL, "`NOTHING` can only be assigned to either `Bits` or `Bit` DFHDL values outside of a dataflow (DF) domain." ] ): TC[T, NOTHING] with @@ -1412,6 +1423,7 @@ object DFVal extends DFValLP: // exporting evidence for common exact operations export DFBits.Val.Ops.{ evOpApplyDFBits, + evOpApplyDFBitsWL, evOpApplyRangeDFBits, evOpAsDFBits, evOpLogicReduceDFBits, @@ -1852,6 +1864,19 @@ object REG_DIN: given evREG_DIN_TC[T <: DFTypeAny, R <: REG_DIN[T]]: DFVal.TC[T, R] with type OutP = NOTCONST def conv(dfType: T, value: R)(using DFC): Out = value.dinVal.asValTP[T, NOTCONST] + // bit vectors are width-only compatible, so a DIN read of one may drive any equal-width + // receiver regardless of the low indices on either side + given evREG_DIN_TC_BitsWL[ + W <: IntP, + L1 <: IntP, + L2 <: IntP, + R <: REG_DIN[DFBitsWL[W, L1]] + ](using + util.NotGiven[L1 =:= L2] + ): DFVal.TC[DFBitsWL[W, L2], R] with + type OutP = NOTCONST + def conv(dfType: DFBitsWL[W, L2], value: R)(using DFC): Out = + value.dinVal.asValTP[DFBitsWL[W, L2], NOTCONST] end REG_DIN object DFVarOps: @@ -2010,7 +2035,7 @@ object DFVarOps: // non-bits variables need to be casted to val assignVal = dfVar.dfType match // no need to cast - case _: ir.DFBits => concatVal + case _: ir.DFBitsWL => concatVal // casting required case dfType => DFVal.Alias.AsIs.forced(dfType, concatVal.asIR).asValAny dfVar.asValAny.assign(assignVal) @@ -2050,7 +2075,7 @@ object DFVarOps: val argsIR = flattenConcatArgs(tc(DFBits(width), rhs).asIR) val argsBitsIR = argsIR.map { arg => arg.dfType match - case _: ir.DFBits => arg + case _: ir.DFBitsWL => arg case dfType => DFVal.Alias.AsIs.forced(ir.DFBits(dfType.widthUNSAFE), arg) } assignRecur(dfVarsIR, argsBitsIR, 0, Nil) diff --git a/core/src/main/scala/dfhdl/core/IntParam.scala b/core/src/main/scala/dfhdl/core/IntParam.scala index 86904ea96..5b251a626 100644 --- a/core/src/main/scala/dfhdl/core/IntParam.scala +++ b/core/src/main/scala/dfhdl/core/IntParam.scala @@ -147,6 +147,13 @@ object IntP: type RangeWidth[HI <: IntP, LO <: IntP] = FoldConst2[HI, LO, [X <: Int, Y <: Int] =>> int.+[int.-[X, Y], 1]] + /** `L + W - 1`, the high (absolute) index of a low-indexed bit vector. A single guarded + * fold, since a composition of the guarded operators collapses (see the doc comment at + * the top of this file). + */ + type HighIdx[W <: IntP, L <: IntP] = + FoldConst2[W, L, [X <: Int, Y <: Int] =>> int.-[int.+[X, Y], 1]] + /** `BI - SW + 1`, the low index of a descending part-select anchored at `BI`. */ type PartSelectLow[BI <: IntP, SW <: IntP] = RangeWidth[BI, SW] diff --git a/core/src/main/scala/dfhdl/core/ShowType.scala b/core/src/main/scala/dfhdl/core/ShowType.scala index 7951cb6dc..b527d6c41 100644 --- a/core/src/main/scala/dfhdl/core/ShowType.scala +++ b/core/src/main/scala/dfhdl/core/ShowType.scala @@ -18,10 +18,19 @@ extension [T](using quotes: Quotes)(tpe: quotes.reflect.TypeRepr) def showVecLength: String = d.asType match case '[Tuple1[d]] => TypeRepr.of[d].showType case _ => d.showType + // the user-facing spelling is high/low indexed, so the high index is the + // width and low index folded together when both are literal + def showBitsHL[W: Type, L: Type]: String = + (TypeRepr.of[W], TypeRepr.of[L]) match + case (ConstantType(IntConstant(w)), ConstantType(IntConstant(l))) => + s"BitsHL[${w + l - 1}, $l]" + case _ => s"BitsHL[${Type.show[W]} + ${Type.show[L]} - 1, ${Type.show[L]}]" tpe.asTypeOf[DFTypeAny] match - case '[DFBit] => "Bit" - case '[DFBool] => "Boolean" - case '[DFBits[w]] => s"Bits[${Type.show[w]}]" + case '[DFBit] => "Bit" + case '[DFBool] => "Boolean" + case '[DFBits[w]] => s"Bits[${Type.show[w]}]" + case '[DFBitsWL[w, l]] => showBitsHL[w, l] + case '[DFType[ir.DFBitsWL, Args2[w, l]]] => showBitsHL[w, l] case '[DFUInt[w]] => s"UInt[${Type.show[w]}]" case '[DFInt32] => "Int" case '[DFSInt[w]] => s"SInt[${Type.show[w]}]" diff --git a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala index 25935dd90..8f5f8323c 100644 --- a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala +++ b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala @@ -39,7 +39,7 @@ private object SimplifyFunc: )(using ir.MemberGetSet): Boolean = op match case FuncOp.++ => - resultType.isInstanceOf[ir.DFBits] && prevFunc.dfType.isInstanceOf[ir.DFBits] + resultType.isInstanceOf[ir.DFBitsWL] && prevFunc.dfType.isInstanceOf[ir.DFBitsWL] case FuncOp.+ | FuncOp.`*` => prevFunc.dfType == resultType case FuncOp.- => false diff --git a/core/src/main/scala/dfhdl/core/Width.scala b/core/src/main/scala/dfhdl/core/Width.scala index daa01c001..175b339b6 100644 --- a/core/src/main/scala/dfhdl/core/Width.scala +++ b/core/src/main/scala/dfhdl/core/Width.scala @@ -18,7 +18,7 @@ type DecimalWidthI[M <: Int, F <: Int] <: Int = F match case 0 => M case _ => scala.compiletime.ops.int.+[M, F] trait WidthLP: - given fromDFBitsIntP[W <: IntP]: Width[DFBits[W]] with + given fromDFBitsIntP[W <: IntP, L <: IntP]: Width[DFBitsWL[W, L]] with type Out = W type OutI = Int given fromDFDecimalIntP[S <: Boolean, M <: IntP, F <: Int, N <: ir.DFDecimal.NativeType] @@ -46,7 +46,7 @@ object Width extends WidthLP: given fromDoubleCompanion: Width[Double.type] with type Out = 64 type OutI = 64 - given fromDFBitsInt[W <: Int]: Width[DFBits[W]] with + given fromDFBitsInt[W <: Int, L <: IntP]: Width[DFBitsWL[W, L]] with type Out = W type OutI = W given fromDFDecimalInt[S <: Boolean, M <: Int, F <: Int, N <: ir.DFDecimal.NativeType] @@ -101,6 +101,15 @@ object Width extends WidthLP: case '[DFValAny] => TypeRepr.of[Int] case _ => TypeRepr.of[w].calcWidth + // like DFVector below, the `DFBits[w]` alias pattern is not always taken + // (e.g. for an abstract width within given instances), so the raw shape + // must be matched as well; the width is the first arg regardless of the + // low index + case '[DFType[ir.DFBitsWL, Args2[w, l]]] => + Type.of[w] match + case '[DFValAny] => TypeRepr.of[Int] + case _ => + TypeRepr.of[w].calcWidth case '[DFDecimal[s, m, f, n]] => // `m` is the magnitude width; the total width adds the fraction width Type.of[m] match @@ -249,7 +258,7 @@ object Width extends WidthLP: ref.widen.calcValWidth case x => report.errorAndAbort( - s"Unsupported argument value ${x.showType} for DFHDL receiver type DFBits" + s"Unsupported argument value ${x.showType} for DFHDL receiver type DFBitsWL" ) end match end calcValWidth @@ -287,7 +296,7 @@ extension [T](t: T)(using tc: DFType.TC[T]) def widthIntParam(using dfc: DFC, w: Width[tc.Type]): IntParam[w.Out] = import dfc.getSet def intParam(dfTypeIR: ir.DFType): IntParam[Int] = dfTypeIR match - case ir.DFBits(width) => width.get + case dt: ir.DFBitsWL => dt.widthParamRef.get case ir.DFXInt(_, width, _) => width.get case ir.DFVector(cellType, cellDimParamRefs) => intParam(cellType) * cellDimParamRefs.map(_.get).asInstanceOf[List[IntParam[Int]]].reduce( 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 1cfb38388..afa8c8d2c 100644 --- a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala +++ b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala @@ -40,7 +40,7 @@ object r__For_Plugin: DFVal.Const(dt.asFE[DFBoolOrBit], Some(v > 0)) case (dt: ir.DFBoolOrBit, v: Boolean) => DFVal.Const(dt.asFE[DFBoolOrBit], Some(v)) - case (dt: ir.DFBits, allBit: BitOrBool) => + case (dt: ir.DFBitsWL, allBit: BitOrBool) => val width = dt.widthIntOpt.getOrElse(throw new IllegalArgumentException( s"Cannot pattern match against parameterized `${selector.dfType.codeString}` type." )) @@ -97,7 +97,7 @@ object r__For_Plugin: given DFC = dfc.anonymize val dfType = selector.dfType.asIR val selectorBitsIR: ir.DFVal = dfType match - case _: ir.DFBits => selector.asIR + case _: ir.DFBitsWL => selector.asIR case _ => import DFVal.Ops.bits selector.bits(using dfc)(using Width.wide).asIR diff --git a/core/src/main/scala/dfhdl/hdl.scala b/core/src/main/scala/dfhdl/hdl.scala index c6c985259..fc7f17a71 100644 --- a/core/src/main/scala/dfhdl/hdl.scala +++ b/core/src/main/scala/dfhdl/hdl.scala @@ -55,6 +55,8 @@ object __hdl: type Bit = core.BitNumWrapper type Bits[W <: IntP] = core.DFBits[W] val Bits = core.DFBits + type BitsHL[H <: IntP, L <: IntP] = core.DFBitsHL[H, L] + val BitsHL = core.DFBitsHL type UInt[W <: IntP] = core.DFUInt[W] val UInt = core.DFUInt type SInt[W <: IntP] = core.DFSInt[W] diff --git a/core/src/test/scala/CoreSpec/DFBitsSpec.scala b/core/src/test/scala/CoreSpec/DFBitsSpec.scala index e89b7dd97..8f0b2cdf9 100644 --- a/core/src/test/scala/CoreSpec/DFBitsSpec.scala +++ b/core/src/test/scala/CoreSpec/DFBitsSpec.scala @@ -270,7 +270,7 @@ class DFBitsSpec extends DFSpec: b8.lsbitsAt(2, 4) := b8.msbitsAt(7, 4) } assertDSLErrorLog( - "Index 8 is out of range of width/length 8" + "Index 8 is above the high index 7 of the selected value" )( """b8.lsbitsAt(5, 4)""" ) { @@ -278,7 +278,7 @@ class DFBitsSpec extends DFSpec: b8.lsbitsAt(five, 4) } assertDSLErrorLog( - "Index -1 is out of range of width/length 8" + "Index -1 is below the low index 0 of the selected value" )( """b8.msbitsAt(2, 4)""" ) { @@ -482,4 +482,44 @@ class DFBitsSpec extends DFSpec: o2 := a ^ b ^ c } } + test("BitsHL inlined width") { + val b = BitsHL(9, 2) + b.verifyWidth(8) + } + test("BitsHL type construction errors") { + val nine = 9 + assertDSLErrorLog( + "Low index 9 is bigger than High bit index 2" + )( + """BitsHL(2, 9)""" + ) { + BitsHL(2, nine) + } + val minusOne = -1 + assertDSLErrorLog( + "Argument must be non-negative, but found: -1" + )( + """BitsHL(3, -1)""" + ) { + BitsHL(3, minusOne) + } + } + test("BitsHL declaration, assignment, and comparison") { + assertCodeString { + """|val x = BitsHL(9, 2) <> VAR + |val y = Bits(8) <> VAR + |x := h"00" + |x := y + |y := x + |val eq = x == y + |""".stripMargin + } { + val x = BitsHL(9, 2) <> VAR + val y = Bits(8) <> VAR + x := all(0) + x := y + y := x + val eq = x == y + } + } end DFBitsSpec diff --git a/lib/src/main/scala/dfhdl/app/DesignArgs.scala b/lib/src/main/scala/dfhdl/app/DesignArgs.scala index c9deb070b..268fc30b5 100644 --- a/lib/src/main/scala/dfhdl/app/DesignArgs.scala +++ b/lib/src/main/scala/dfhdl/app/DesignArgs.scala @@ -3,7 +3,7 @@ import dfhdl.* import dfhdl.compiler.ir import core.{ DFValAny, asValAny, injectGlobalCtx, asConstOf, DFBit, DFBool, DFInt32, DFDouble, DFString, - DFBits, DFUInt, DFSInt, DFConstOf + DFBitsWL, DFUInt, DFSInt, DFConstOf } import dfhdl.compiler.printing.{DefaultPrinter, Printer} import dfhdl.internals.* @@ -39,7 +39,7 @@ case class DesignArg(name: String, value: Any, desc: String)(using dfc: DFC): case ir.DFInt32 => "Int" case ir.DFDouble => "Double" case ir.DFString => "String" - case _: ir.DFBits => "Bits" + case _: ir.DFBitsWL => "Bits" case ir.DFUInt(_) => "UInt" case ir.DFSInt(_) => "SInt" case _ => "" @@ -122,7 +122,7 @@ case class DesignArg(name: String, value: Any, desc: String)(using dfc: DFC): case ir.DFBit => val b = parseBit(updatedScalaValue.toString) core.DFVal.Const.forced(dfType, Some(b)) - case _: ir.DFBits => + case _: ir.DFBitsWL => parseBitsLiteral(updatedScalaValue.toString, dfConst) case ir.DFUInt(_) => parseDecimalLiteral(updatedScalaValue.toString, dfConst, signedForced = false) @@ -167,7 +167,7 @@ case class DesignArg(name: String, value: Any, desc: String)(using dfc: DFC): val binOnly = raw.forall(c => c == '0' || c == '1' || c == '?' || c == '_' || c == ' ') (if (binOnly) "b" else "h", raw) val currentWidth = dfConst.asIR.dfType.runtimeChecked match - case dt: ir.DFBits => dt.widthIntOpt.getOrElse( + case dt: ir.DFBitsWL => dt.widthIntOpt.getOrElse( throw new IllegalArgumentException( s"Design argument $name has a parametric width and cannot be set from the CLI." ) diff --git a/plugin/src/main/scala/plugin/CustomControlPhase.scala b/plugin/src/main/scala/plugin/CustomControlPhase.scala index 29ea6d63a..13e33ef4a 100644 --- a/plugin/src/main/scala/plugin/CustomControlPhase.scala +++ b/plugin/src/main/scala/plugin/CustomControlPhase.scala @@ -206,7 +206,7 @@ class CustomControlPhase(setting: Setting) extends CommonPhase: object DFBits: def unapply(arg: Type)(using Context): Option[Type] = arg match - case DFType("DFBits", w :: Nil) => Some(w) + case DFType("DFBitsWL", w :: _) => Some(w) case _ => None object DFDecimal: def unapply(arg: Type)(using Context): Option[(Type, Type, Type)] = diff --git a/plugin/src/main/scala/plugin/DFHDLTypePrinter.scala b/plugin/src/main/scala/plugin/DFHDLTypePrinter.scala index 8ef727369..2d84bcec7 100644 --- a/plugin/src/main/scala/plugin/DFHDLTypePrinter.scala +++ b/plugin/src/main/scala/plugin/DFHDLTypePrinter.scala @@ -53,7 +53,7 @@ final class DFHDLSymbols(using Context): List( irModuleClass("DFBool") -> DFTypeKind.DFBool, irModuleClass("DFBit") -> DFTypeKind.DFBit, - irClass("DFBits") -> DFTypeKind.DFBits, + irClass("DFBitsWL") -> DFTypeKind.DFBits, irClass("DFDecimal") -> DFTypeKind.DFDecimal, irClass("DFEnum") -> DFTypeKind.DFEnum, irClass("DFVector") -> DFTypeKind.DFVector, @@ -162,7 +162,8 @@ class DFHDLTypePrinter(_ctx: Context, syms: DFHDLSymbols) extends RefinedPrinter def unapply(tp: Type)(using Context): Option[Text] = tp.dealias match case AppliedType(tycon, List(irTpe, argsTpe)) if tycon.typeSymbol == syms.dfType => - syms.kindOf(irTpe.typeSymbol).flatMap(dfTypeText(_, irTpe, argsTpe.dealias.argInfos)) + syms.kindOf(irTpe.typeSymbol).orElse(syms.kindOf(irTpe.dealias.typeSymbol)) + .flatMap(dfTypeText(_, irTpe, argsTpe.dealias.argInfos)) case _ => None private def dfTypeText(kind: DFTypeKind, irTpe: Type, args: List[Type])(using @@ -171,7 +172,14 @@ class DFHDLTypePrinter(_ctx: Context, syms: DFHDLSymbols) extends RefinedPrinter (kind, args) match case (DFBool, _) => Some("Boolean") case (DFBit, _) => Some("Bit") - case (DFBits, IntP(width) :: Nil) => Some("Bits[" ~ width ~ "]") + case (DFBits, widthTpe :: lowTpe :: Nil) => + (constInt(widthTpe), constInt(lowTpe)) match + case (_, Some(0)) => Some("Bits[" ~ intPText(widthTpe) ~ "]") + case (Some(w), Some(l)) => + Some("BitsHL[" ~ (w + l - 1).toString ~ ", " ~ l.toString ~ "]") + case _ => + val low = intPText(lowTpe) + Some("BitsHL[" ~ intPText(widthTpe) ~ " + " ~ low ~ " - 1, " ~ low ~ "]") case (DFDecimal, sign :: IntP(magnitude) :: fraction :: native :: Nil) => decimalText(sign, magnitude, fraction, native) case (DFEnum, encoding :: Nil) => Some(toText(encoding)) diff --git a/plugin/src/main/scala/plugin/TopAnnotPhase.scala b/plugin/src/main/scala/plugin/TopAnnotPhase.scala index af57907f4..9acc5f6a5 100644 --- a/plugin/src/main/scala/plugin/TopAnnotPhase.scala +++ b/plugin/src/main/scala/plugin/TopAnnotPhase.scala @@ -172,7 +172,9 @@ class TopAnnotPhase(setting: Setting) extends CommonPhase: else if (irCls == irDFDoubleClsSym) Some(nullaryDefault(defaultsDoubleSym)) else if (irCls == irDFBitsClsSym) argsTpe.dealias match - case AppliedType(_, widthTpe :: Nil) => + // synthetic defaults are zero-based bit vectors only + case AppliedType(_, widthTpe :: lowTpe :: Nil) + if literalIntOf(lowTpe).contains(0) => literalIntOf(widthTpe).map(w => bitsDefault(defaultsBitsSym, w)) case _ => None else if (irCls == irDFDecimalClsSym) decimalDefault(argsTpe) @@ -516,7 +518,7 @@ class TopAnnotPhase(setting: Setting) extends CommonPhase: // class (trait) symbol here. irDFStringClsSym = requiredClass("dfhdl.compiler.ir.DFString") irDFDoubleClsSym = requiredClass("dfhdl.compiler.ir.DFDouble") - irDFBitsClsSym = requiredClass("dfhdl.compiler.ir.DFBits") + irDFBitsClsSym = requiredClass("dfhdl.compiler.ir.DFBitsWL") irDFDecimalClsSym = requiredClass("dfhdl.compiler.ir.DFDecimal") defaultsBoolSym = requiredMethod("dfhdl.core.r__For_Plugin.defaults.bool") defaultsBitSym = requiredMethod("dfhdl.core.r__For_Plugin.defaults.bit") From 03f009c000bd19431a97289226fd91e1efa75c83 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 03:13:40 +0300 Subject: [PATCH 23/57] BitsHL completion: simulation offsets, match rebase, composites, docs DFacsimile translates absolute selection indices to relative data offsets (dynamic indexing of a low-indexed vector is refused), verified by a both-tiers simulation test. Match expressions rebase a nonzero-low selector to zero-based bits once at construction, so relative patterns and bind ranges hold across all backends; an equal-width low-drop cast prints as .bits and a cast into a nonzero-low type as .as(...). The remaining strict zero-based printer/stage arms are generalized, a DFBitsWL Singleton given supports type-only spellings (struct fields), superseding the removed DFBits given, and the natural-check message now says "natural". New pinned coverage: absolute selection and its diagnostics, backend [9:2] / (9 downto 2) rendering, a VHDL composite (record field, vector cells), and v2001 struct flattening offsets. User-guide BitsHL section (prefer Bits(width); no reversed direction) and IR reference updated. Known follow-ups (pre-existing, tracked separately): v2001 struct-field bit-select flattens to an illegal chained part-select; a literal range-select check over struct-field BitsHL values may fail to reduce at compile time. Co-Authored-By: Claude Fable 5 --- .claude/commands/ir-reference.md | 9 ++- .../compiler/printing/DFValPrinter.scala | 8 +- .../dfhdl/compiler/stages/NamedAliases.scala | 2 +- .../stages/verilog/VerilogValPrinter.scala | 8 +- .../stages/vhdl/VHDLOwnerPrinter.scala | 4 +- .../stages/vhdl/VHDLTypePrinter.scala | 4 +- .../compiler/stages/vhdl/VHDLValPrinter.scala | 10 +-- .../src/main/scala/dfhdl/sim/DFacsimile.scala | 48 +++++++---- .../scala/StagesSpec/PrintVHDLCodeSpec.scala | 79 +++++++++++++++++++ .../StagesSpec/PrintVerilogCodeSpec.scala | 55 +++++++++++++ .../scala/dfhdl/sim/SimulationApiSpec.scala | 30 +++++++ core/src/main/scala/dfhdl/core/Arg.scala | 2 +- core/src/main/scala/dfhdl/core/DFBits.scala | 24 +++--- core/src/main/scala/dfhdl/core/DFMatch.scala | 9 ++- core/src/main/scala/dfhdl/core/DFType.scala | 2 +- .../main/scala/dfhdl/core/r__For_Plugin.scala | 6 +- core/src/test/scala/CoreSpec/DFBitsSpec.scala | 49 +++++++++++- docs/user-guide/type-system/index.md | 40 +++++++++- 18 files changed, 337 insertions(+), 52 deletions(-) diff --git a/.claude/commands/ir-reference.md b/.claude/commands/ir-reference.md index 62f6bfd72..08b5e4373 100644 --- a/.claude/commands/ir-reference.md +++ b/.claude/commands/ir-reference.md @@ -719,7 +719,12 @@ DFType (sealed) ├── DFBoolOrBit (sealed) │ ├── DFBool — boolean, width = 1 │ └── DFBit — single hardware bit, width = 1 -├── DFBits(widthParamRef) — bit vector +├── DFBitsWL(widthParamRef, lowIdxRef) — bit vector with a low index (`type`-less `object DFBits` +│ is the zero-based view: apply/unapply assume/match a +│ LITERAL lowIdxRef of 0; `case dt: DFBitsWL` matches any). +│ Selection uses ABSOLUTE indices in [L, L+W-1]; selection +│ RESULTS are always zero-based. Nonzero low arises only +│ from the user-facing `BitsHL(hi, lo)` constructor. ├── DFDecimal(signed, widthParamRef, fractionWidth, nativeType) │ ├── DFUInt(w) — unsigned integer │ ├── DFSInt(w) — signed integer @@ -774,7 +779,7 @@ case object Magnet extends Magnet // generic magnet opaque.isMagnet // kind.isInstanceOf[Magnet] ``` -**`IntParamRef`** — used for widths and indices in DFBits, DFDecimal, DFVector, ApplyRange: +**`IntParamRef`** — used for widths and indices in DFBitsWL (width + low index), DFDecimal, DFVector, ApplyRange: ```scala opaque type IntParamRef = DFRef.TypeRef | Int paramRef.getInt // resolve to Int (using MemberGetSet) 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 0750752dc..86c597c88 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala @@ -390,7 +390,13 @@ protected trait DFValPrinter extends AbstractValPrinter: case (DFSInt(tWidthRef), _: DFBitsWL) => s"${relValStr}.sint" case (to: DFBitsWL, from: DFBitsWL) => - s"${relValStr}${csResizeOrEby(to.widthParamRef, from.widthParamRef)}" + if (to.lowIdxRef.equals(0)) + // an equal-width cast that only drops a nonzero low index is the `.bits` rebase + if (!from.lowIdxRef.equals(0) && to.widthParamRef.isProvablyEqualTo(from.widthParamRef)) + s"${relValStr}.bits" + else s"${relValStr}${csResizeOrEby(to.widthParamRef, from.widthParamRef)}" + // a cast INTO a nonzero-low type keeps the explicit `.as(...)` spelling + else s"${relValStr}.as(${printer.csDFType(toType)})" case (to: DFBitsWL, DFBit | DFBool) => s"${relValStr}.toBits(${to.widthParamRef.refCodeString})" case (_: DFBitsWL, _) => diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala index f183b2005..141eb4a4a 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala @@ -280,7 +280,7 @@ case object NamedVHDLSelection extends NamedAliases: case (t, DFOpaque(actualType = at)) if at =~ t => relVal.hasVHDLName case (_: DFOpaque, _) => relVal.hasVHDLName // type conversions - case (DFUInt(_) | DFSInt(_), DFBits(_)) => false + case (DFUInt(_) | DFSInt(_), _: DFBitsWL) => false case (DFSInt(_), DFUInt(_)) => false // function calls case _ => true 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 0e987d7d1..bdadbab27 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 @@ -420,7 +420,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: case (DFInt32, DFUInt(_) | DFSInt(_)) => relValStr case (DFBit, DFBool | DFEnum(widthParam = 1)) => relValStr case (DFBool, DFBit | DFEnum(widthParam = 1)) => relValStr - case (enumType: DFEnum, DFBit | DFBool | DFBits(_)) => + case (enumType: DFEnum, DFBit | DFBool | (_: DFBitsWL)) => if (printer.allowTypeDef) s"${printer.csDFEnumTypeName(enumType)}'($relValStr)" else relValStr @@ -449,7 +449,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: end match end to_vector_conv to_vector_conv(toVector, toVector.widthUNSAFE - 1) - case (DFBits(IntUNSAFE(tWidth)), fromVector: DFVector) => + case (DFBitsWL(IntUNSAFE(tWidth), _), fromVector: DFVector) => def from_vector_conv(vectorType: DFVector, prevSelect: String): String = val vecLength = vectorType.lengthUNSAFE vectorType.cellType match @@ -466,12 +466,12 @@ protected trait VerilogValPrinter extends AbstractValPrinter: end from_vector_conv assert(tWidth == fromType.widthUNSAFE) from_vector_conv(fromVector, "") - case (DFBits(tWidthRef), DFBit | DFBool) => + case (DFBitsWL(tWidthRef, _), DFBit | DFBool) => if (printer.allowWidthCastSyntax) s"${tWidthRef.refCodeString.applyBrackets()}'($relValStr)" else s"`EXTEND_U($relValStr, 1, ${tWidthRef.refCodeString})" - case (DFBits(_), _) => + case (_: DFBitsWL, _) => s"{$relValStr}" // fixed-point (target fraction != 0): `.signed`/`.unsigned` sign casts and `.resize` // reformats. Verilog is positional, so these are the vector operations: add/drop the 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 6495df788..b43a5f5e3 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 @@ -74,9 +74,9 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: case dt: (DFVector | NamedDFType) => dt } (alias.dfType, alias.relValRef.get.dfType) match - case (DFBits(_), fromDFType: (NamedDFType | ComposedDFType)) => + case (_: DFBitsWL, fromDFType: (NamedDFType | ComposedDFType)) => fromDFType.decompose(pf) - case (toDFType: (NamedDFType | ComposedDFType), DFBits(_)) => + case (toDFType: (NamedDFType | ComposedDFType), _: DFBitsWL) => toDFType.decompose(pf) case _ => None case _ => None diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala index 1619df05e..8f390b78b 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala @@ -289,7 +289,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: val toCellConv = act( vecType => vecType.cellType match - case DFBits(_) => argSel + case _: DFBitsWL => argSel case DFBit => s"to_sl($argSel)" case DFBool => s"to_bool($argSel)" case DFUInt(_) => s"unsigned($argSel)" @@ -421,7 +421,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: def csDFStructConvFuncsBody(dfType: DFStruct): String = val typeName = csDFStructTypeName(dfType) def to_slv(fromType: DFType, csArg: String): String = fromType match - case DFBits(_) => csArg + case _: DFBitsWL => csArg case _ => s"to_slv($csArg)" val fieldLengths = dfType.fieldMap.map { (n, t) => s"width := width + bitWidth(A.$n);" diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala index d1a996191..eb381e624 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala @@ -201,7 +201,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: if (requiresBoolConv) s"to_bool(${condRef.refCodeString})" else condRef.refCodeString def csBitsToType(toType: DFType, csArg: String): String = toType match - case DFBits(_) => csArg + case _: DFBitsWL => csArg case DFBool => s"to_bool($csArg)" case DFBit => s"to_sl($csArg)" case DFUInt(_) => s"unsigned($csArg)" @@ -249,15 +249,15 @@ protected trait VHDLValPrinter extends AbstractValPrinter: case _ => s"signed(resize($relValStr, ${tWidthRef.refCodeString}))" case (DFUInt(tWidthRef), DFSInt(_)) => s"resize(unsigned($relValStr), ${tWidthRef.refCodeString})" - case (DFBits(tWidthRef), DFBits(fWidthRef)) => + case (DFBitsWL(tWidthRef, _), DFBitsWL(fWidthRef, _)) => tWidthRef.widenDeltaOpt(fWidthRef) match case Some(k) => s"eby($relValStr, $k)" case _ => s"resize($relValStr, ${tWidthRef.refCodeString})" case (toType: DFType, fromType: DFBitsWL) => csBitsToType(toType, relValStr) - case (DFBits(tWidthRef), DFBit | DFBool) => + case (DFBitsWL(tWidthRef, _), DFBit | DFBool) => s"to_slv($relValStr, ${tWidthRef.refCodeString})" - case (DFBits(_), fromType: DFType) => + case (_: DFBitsWL, fromType: DFType) => csToSLV(fromType, relValStr) case (DFUInt(tWidthRef), DFUInt(fWidthRef)) => tWidthRef.widenDeltaOpt(fWidthRef) match @@ -336,7 +336,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: def csNOTHING(dfVal: Special): String = dfVal.dfType match case DFBit => "'Z'" - case DFBits(_) => "(others => 'Z')" + case _: DFBitsWL => "(others => 'Z')" case _ => printer.unsupported def csDFValNamed(dfVal: DFVal): String = dfVal match diff --git a/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala b/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala index 498b7262d..be2dbcc16 100644 --- a/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala +++ b/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala @@ -1139,6 +1139,13 @@ private final class Builder(rawDB: DB): ) case _ => buildApplyIdxNonMem(a, rel) + /** selection indices are absolute, so a low-indexed bit vector's data offsets are + * relative to its low index (nonzero only for explicit BitsHL-constructed types) + */ + private def bitsLowOf(t: DFType): Int = t match + case b: DFBitsWL => b.lowIdxRef.getIntOpt.getOrElse(0) + case _ => 0 + private def buildApplyIdxNonMem(a: DFVal.Alias.ApplyIdx, rel: DFVal): WV = rel.dfType match case vt: DFVector => @@ -1157,12 +1164,16 @@ private final class Builder(rawDB: DB): val relWV = readWV(rel) val off = dynCellOffset(relWV.width / cellW, cellW, a.relIdx.get) wide.dynExtract(relWV, off, cellW) - case _: DFBitsWL => + case bt: DFBitsWL => + val low = bitsLowOf(bt) constIdxOpt(a.relIdx.get) match case Some(i) if undrivenPartialSink(rel) => - partialSinkRead(rel.asInstanceOf[DFVal.Dcl], i, 1) - case Some(i) => wide.extract(readWV(rel), i, 1) - case None => wide.dynExtract(readWV(rel), dynBitOffset(a.relIdx.get), 1) + partialSinkRead(rel.asInstanceOf[DFVal.Dcl], i - low, 1) + case Some(i) => wide.extract(readWV(rel), i - low, 1) + case None if low == 0 => + wide.dynExtract(readWV(rel), dynBitOffset(a.relIdx.get), 1) + case None => + unsupported("dynamic indexing of a low-indexed bit vector", a) case t => unsupported(s"indexing into $t", a) end match end buildApplyIdxNonMem @@ -1172,10 +1183,10 @@ private final class Builder(rawDB: DB): val hi = a.idxHighRef.getIntOpt.getOrElse(unsupported("non-constant range", a)) val lo = a.idxLowRef.getIntOpt.getOrElse(unsupported("non-constant range", a)) rel.dfType match - case _: DFBitsWL if undrivenPartialSink(rel) => - partialSinkRead(rel.asInstanceOf[DFVal.Dcl], lo, hi - lo + 1) - case _: DFBitsWL => wide.extract(readWV(rel), lo, hi - lo + 1) - case t => unsupported(s"range selection on $t", a) + case bt: DFBitsWL if undrivenPartialSink(rel) => + partialSinkRead(rel.asInstanceOf[DFVal.Dcl], lo - bitsLowOf(bt), hi - lo + 1) + case bt: DFBitsWL => wide.extract(readWV(rel), lo - bitsLowOf(bt), hi - lo + 1) + case t => unsupported(s"range selection on $t", a) private def buildSelectField(sf: DFVal.Alias.SelectField): WV = val rel = sf.relValRef.get @@ -1290,12 +1301,12 @@ private final class Builder(rawDB: DB): cellWriteTarget(ai.relValRef.get, net).map { (dcl, addr, lo0) => val bit = constIdxOpt(ai.relIdx.get) .getOrElse(unsupported("dynamic bit index in a memory write", net)) - (dcl, addr, lo0 + bit) + (dcl, addr, lo0 + bit - bitsLowOf(ai.relValRef.get.dfType)) } case ar: DFVal.Alias.ApplyRange => cellWriteTarget(ar.relValRef.get, net).map { (dcl, addr, lo0) => val lo = ar.idxLowRef.getIntOpt.getOrElse(unsupported("non-constant range", net)) - (dcl, addr, lo0 + lo) + (dcl, addr, lo0 + lo - bitsLowOf(ar.relValRef.get.dfType)) } case sf: DFVal.Alias.SelectField => cellWriteTarget(sf.relValRef.get, net).map { (dcl, addr, lo0) => @@ -1572,7 +1583,7 @@ private final class Builder(rawDB: DB): case ar: DFVal.Alias.ApplyRange => val (dcl, lo0, dyn) = assignTarget(ar.relValRef.get, net) val lo = ar.idxLowRef.getIntOpt.getOrElse(unsupported("non-constant range", net)) - (dcl, lo0 + lo, dyn) + (dcl, lo0 + lo - bitsLowOf(ar.relValRef.get.dfType), dyn) case ai: DFVal.Alias.ApplyIdx => val rel = ai.relValRef.get val (dcl, lo0, dyn) = assignTarget(rel, net) @@ -1583,10 +1594,13 @@ private final class Builder(rawDB: DB): constIdxOpt(ai.relIdx.get) match case Some(i) => (dcl, lo0 + (len - 1 - i) * cellW, dyn) case None => (dcl, lo0, addDyn(dyn, dynCellOffset(len, cellW, ai.relIdx.get))) - case _: DFBitsWL => + case bt: DFBitsWL => + val low = bitsLowOf(bt) constIdxOpt(ai.relIdx.get) match - case Some(i) => (dcl, lo0 + i, dyn) - case None => (dcl, lo0, addDyn(dyn, dynBitOffset(ai.relIdx.get))) + case Some(i) => (dcl, lo0 + i - low, dyn) + case None if low == 0 => (dcl, lo0, addDyn(dyn, dynBitOffset(ai.relIdx.get))) + case None => + unsupported("dynamic indexing of a low-indexed bit vector", net) case t => unsupported(s"assignment through indexing into $t", net) case sf: DFVal.Alias.SelectField => val rel = sf.relValRef.get @@ -1760,7 +1774,7 @@ private final class Builder(rawDB: DB): case ar: DFVal.Alias.ApplyRange => val (dcl, lo0) = lhsTarget(ar.relValRef.get) val lo = ar.idxLowRef.getIntOpt.getOrElse(unsupported("a non-constant range", ar)) - (dcl, lo0 + lo) + (dcl, lo0 + lo - bitsLowOf(ar.relValRef.get.dfType)) case ai: DFVal.Alias.ApplyIdx => val rel = ai.relValRef.get val (dcl, lo0) = lhsTarget(rel) @@ -1769,8 +1783,8 @@ private final class Builder(rawDB: DB): val cellW = widthOfType(vt.cellType, ai) val len = widthOfType(vt, ai) / cellW (dcl, lo0 + (len - 1 - constIdxOf(ai.relIdx.get)) * cellW) - case _: DFBitsWL => (dcl, lo0 + constIdxOf(ai.relIdx.get)) - case t => unsupported(s"initial assignment through indexing into $t", ai) + case bt: DFBitsWL => (dcl, lo0 + constIdxOf(ai.relIdx.get) - bitsLowOf(bt)) + case t => unsupported(s"initial assignment through indexing into $t", ai) case sf: DFVal.Alias.SelectField => val rel = sf.relValRef.get val (dcl, lo0) = lhsTarget(rel) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index 39f3479ee..badb73f0d 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -3748,4 +3748,83 @@ class PrintVHDLCodeSpec extends StageSpec: |""".stripMargin ) } + test("nonzero-low bit vector struct fields and vector cells") { + given options.CompilerOptions.Backend = _.vhdl.v2008 + class BitsHLComposite extends RTDesign: + case class P(f: BitsHL[9, 2] <> VAL, g: Bit <> VAL) extends Struct + val p = P <> IN + val v = BitsHL(9, 2) X 2 <> IN + val f8 = Bits(8) <> OUT + val fb = Bit <> OUT + val c8 = Bits(8) <> OUT + f8 := p.f + fb := p.f(5) + c8 := v(0) + end BitsHLComposite + val top = BitsHLComposite().getCompiledCodeString + assertNoDiff( + top, + """|type t_struct_P is record + | f : std_logic_vector(9 downto 2); + | g : std_logic; + |end record; + | + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + |use work.BitsHLComposite_pkg.all; + | + |entity BitsHLComposite is + |port ( + | p : in t_struct_P; + | v : in t_arrX1_std_logic_vector(0 to 1)(9 downto 2); + | f8 : out std_logic_vector(7 downto 0); + | fb : out std_logic; + | c8 : out std_logic_vector(7 downto 0) + |); + |end BitsHLComposite; + | + |architecture BitsHLComposite_arch of BitsHLComposite is + |begin + | f8 <= p.f; + | fb <= p.f(5); + | c8 <= v(0); + |end BitsHLComposite_arch; + |""".stripMargin + ) + } + test("nonzero-low bit vector ports and selection") { + given options.CompilerOptions.Backend = _.vhdl.v2008 + class BitsHLTop extends RTDesign: + val x = BitsHL(9, 2) <> IN + val y = Bits(8) <> OUT + val b = Bit <> OUT + y := x + b := x(5) + end BitsHLTop + val top = BitsHLTop().getCompiledCodeString + assertNoDiff( + top, + """|library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + | + |entity BitsHLTop is + |port ( + | x : in std_logic_vector(9 downto 2); + | y : out std_logic_vector(7 downto 0); + | b : out std_logic + |); + |end BitsHLTop; + | + |architecture BitsHLTop_arch of BitsHLTop is + |begin + | y <= x; + | b <= x(5); + |end BitsHLTop_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 810b4d669..a23b886da 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -3672,4 +3672,59 @@ class PrintVerilogCodeSpec extends StageSpec: |""".stripMargin ) } + test("nonzero-low bit vector struct field flattening under v2001") { + given options.CompilerOptions.Backend = _.verilog.v2001 + class BitsHLFlatten extends RTDesign: + // NOTE: a bit selection into the field (`p.f(5)`) is excluded here: the flattening + // currently emits an illegal chained part-select (`p[8:1][5]`) under v2001, a + // pre-existing issue that equally affects zero-based fields (and for a nonzero-low + // field also keeps the absolute index where a relative one is needed) + case class P(f: BitsHL[9, 2] <> VAL, g: Bit <> VAL) extends Struct + val p = P <> IN + val f8 = Bits(8) <> OUT + f8 := p.f + end BitsHLFlatten + val top = BitsHLFlatten().getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module BitsHLFlatten( + | input wire [8:0] p, + | output wire [7:0] f8 + |); + | `include "dfhdl_defs.vh" + | assign f8 = p[8:1]; + |endmodule + |""".stripMargin + ) + } + test("nonzero-low bit vector ports and selection") { + given options.CompilerOptions.Backend = _.verilog.sv2009 + class BitsHLTop extends RTDesign: + val x = BitsHL(9, 2) <> IN + val y = Bits(8) <> OUT + val b = Bit <> OUT + y := x + b := x(5) + end BitsHLTop + val top = BitsHLTop().getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module BitsHLTop( + | input wire logic [9:2] x, + | output logic [7:0] y, + | output logic b + |); + | `include "dfhdl_defs.svh" + | assign y = x; + | assign b = x[5]; + |endmodule + |""".stripMargin + ) + } end PrintVerilogCodeSpec diff --git a/compiler/stages/src/test/scala/dfhdl/sim/SimulationApiSpec.scala b/compiler/stages/src/test/scala/dfhdl/sim/SimulationApiSpec.scala index 288c27151..a094fd650 100644 --- a/compiler/stages/src/test/scala/dfhdl/sim/SimulationApiSpec.scala +++ b/compiler/stages/src/test/scala/dfhdl/sim/SimulationApiSpec.scala @@ -10,6 +10,27 @@ class Foo(val WIDTH: Int <> CONST) extends RTDesign: val y = Bits(WIDTH) <> OUT y := x +/** a nonzero-low bit vector: selection and partial assignment use ABSOLUTE indices in + * [L, L+W-1], while the underlying data offsets are relative to the low index + */ +class BitsHLFoo extends RTDesign: + val i8 = Bits(8) <> IN + val i4 = Bits(4) <> IN + val y8 = Bits(8) <> OUT + val lo = Bits(4) <> OUT + val hb = Bit <> OUT + val yp = Bits(8) <> OUT + val v = BitsHL(9, 2) <> VAR + val vp = BitsHL(9, 2) <> VAR + v := i8 + y8 := v + lo := v(5, 2) + hb := v(9) + vp := i8 + vp(5, 2) := i4 + yp := vp +end BitsHLFoo + class SimulationApiSpec extends SimSpec: bothTiers("typed peek/poke wire-through, settle-on-peek"): tier => Foo(8).simulation { dut => @@ -20,4 +41,13 @@ class SimulationApiSpec extends SimSpec: simCtx.step() assertEquals(dut.y.peek, h"7f") // stable across a clock step (pure wire) }.withTier(tier).run() + bothTiers("nonzero-low bit vector selection and partial-assignment offsets"): tier => + BitsHLFoo().simulation { dut => + dut.i8.poke(h"a5") + dut.i4.poke(h"c") + assertEquals(dut.y8.peek, h"a5") + assertEquals(dut.lo.peek, h"5") + assertEquals(dut.hb.peek, 1) + assertEquals(dut.yp.peek, h"ac") + }.withTier(tier).run() end SimulationApiSpec diff --git a/core/src/main/scala/dfhdl/core/Arg.scala b/core/src/main/scala/dfhdl/core/Arg.scala index 617122956..064766bac 100644 --- a/core/src/main/scala/dfhdl/core/Arg.scala +++ b/core/src/main/scala/dfhdl/core/Arg.scala @@ -30,6 +30,6 @@ object Arg: extends Check1[ Int, [t <: Int] =>> t >= 0, - [t <: Int] =>> "Argument must be non-negative, but found: " + t + [t <: Int] =>> "Argument must be natural, but found: " + t ] end Arg diff --git a/core/src/main/scala/dfhdl/core/DFBits.scala b/core/src/main/scala/dfhdl/core/DFBits.scala index 9f8a3a156..5b578f8d0 100644 --- a/core/src/main/scala/dfhdl/core/DFBits.scala +++ b/core/src/main/scala/dfhdl/core/DFBits.scala @@ -26,6 +26,20 @@ object DFBitsWL: summon[Arg.Width.Check[Int]](width) summon[Arg.Natural.Check[Int]](lowIdx) ir.DFBitsWL(ir.IntParamRef(width), ir.IntParamRef(lowIdx)).asFE[DFBitsWL[W, L]] + // the type-only spelling (e.g. a `BitsHL[9, 2] <> VAL` struct field) + given [W <: IntP & Singleton, L <: IntP & Singleton](using + dfc: DFCG, + w: ValueOf[W], + l: ValueOf[L], + widthCheck: Arg.Width.CheckNUB[W], + lowCheck: Arg.Natural.CheckNUB[L] + ): DFBitsWL[W, L] = trydf { + val width = IntParam.forced(w) + val lowIdx = IntParam.forced(l) + width.toScalaIntOpt.foreach(widthCheck(_)) + lowIdx.toScalaIntOpt.foreach(lowCheck(_)) + ir.DFBitsWL(width.ref, lowIdx.ref).asFE[DFBitsWL[W, L]] + }(using dfc, CTName("BitsWL constructor")) end DFBitsWL type DFBitsHL[H <: IntP, L <: IntP] = DFBitsWL[IntP.RangeWidth[H, L], L] @@ -72,16 +86,6 @@ object DFBits: ir.DFBits((max + 1).clog2.ref).asFE[DFBits[IntP.CLog2P1[V]]] }(using dfc, CTName("Bits.to constructor")) - given [W <: IntP & Singleton](using - dfc: DFCG, - v: ValueOf[W], - check: Arg.Width.CheckNUB[W] - ): DFBits[W] = trydf { - val width = IntParam.forced(v) - width.toScalaIntOpt.foreach(check(_)) - ir.DFBits(width.ref).asFE[DFBits[W]] - }(using dfc, CTName("Bits constructor")) - protected object `AW == TW` extends Check2[ Int, diff --git a/core/src/main/scala/dfhdl/core/DFMatch.scala b/core/src/main/scala/dfhdl/core/DFMatch.scala index dd55a048b..51dd28b9f 100644 --- a/core/src/main/scala/dfhdl/core/DFMatch.scala +++ b/core/src/main/scala/dfhdl/core/DFMatch.scala @@ -57,8 +57,15 @@ object DFMatch: try import dfc.getSet val dfcAnon = summon[DFC].anonymize + // a nonzero-low bit-vector selector is rebased to a zero-based one, so the patterns + // and bind ranges (which are relative) stay valid across all backends + val fixedSelector = selector.asIR.dfType match + case bt: ir.DFBitsWL if !bt.lowIdxRef.equals(0) => + import DFVal.Ops.bits + selector.bits(using dfcAnon)(using Width.wide) + case _ => selector val header = - Header(DFUnit, selector)(using if (forceAnonymous) dfcAnon else dfc) + Header(DFUnit, fixedSelector)(using if (forceAnonymous) dfcAnon else dfc) // creating a hook to save the return value for the first branch run var firstCaseRet: Option[R] = None val firstCaseRun: () => R = () => diff --git a/core/src/main/scala/dfhdl/core/DFType.scala b/core/src/main/scala/dfhdl/core/DFType.scala index 8b0f2ef20..5b5e03e78 100644 --- a/core/src/main/scala/dfhdl/core/DFType.scala +++ b/core/src/main/scala/dfhdl/core/DFType.scala @@ -89,7 +89,7 @@ object DFType: ): DFTypeAny = tc(t) export DFDecimal.Extensions.* export DFBoolOrBit.given - export DFBits.given + export DFBitsWL.given export DFDecimal.given export DFEnum.given export DFVector.given 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 afa8c8d2c..f8fa7daf8 100644 --- a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala +++ b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala @@ -97,8 +97,10 @@ object r__For_Plugin: given DFC = dfc.anonymize val dfType = selector.dfType.asIR val selectorBitsIR: ir.DFVal = dfType match - case _: ir.DFBitsWL => selector.asIR - case _ => + // a nonzero-low selector is rebased through `.bits`, since the bind ranges + // computed by the plugin are relative (zero-based) + case bt: ir.DFBitsWL if bt.lowIdxRef.equals(0) => selector.asIR + case _ => import DFVal.Ops.bits selector.bits(using dfc)(using Width.wide).asIR val rangeAlias = DFVal.Alias.ApplyRange(selectorBitsIR.asValOf[DFBits[Int]], idxHigh, idxLow) diff --git a/core/src/test/scala/CoreSpec/DFBitsSpec.scala b/core/src/test/scala/CoreSpec/DFBitsSpec.scala index 8f0b2cdf9..907b90123 100644 --- a/core/src/test/scala/CoreSpec/DFBitsSpec.scala +++ b/core/src/test/scala/CoreSpec/DFBitsSpec.scala @@ -497,13 +497,60 @@ class DFBitsSpec extends DFSpec: } val minusOne = -1 assertDSLErrorLog( - "Argument must be non-negative, but found: -1" + "Argument must be natural, but found: -1" )( """BitsHL(3, -1)""" ) { BitsHL(3, minusOne) } } + test("BitsHL selection with absolute indices") { + val x = BitsHL(9, 2) <> VAR + assertCodeString { + """|val s = x(5, 2) + |val b = x(9) + |val m = x(9, 6) + |val l = x(5, 2) + |""".stripMargin + } { + val s = x(5, 2) + val b = x(9) + val m = x.msbits(4) + val l = x.lsbits(4) + } + assertDSLErrorLog( + "Index 10 is above the high index 9 of the selected value" + )( + """x(10, 2)""" + ) { + val ten = 10 + x(ten, 2) + } + assertDSLErrorLog( + "Index 1 is below the low index 2 of the selected value" + )( + """x(5, 1)""" + ) { + val one = 1 + x(5, one) + } + } + test("BitsHL match selector is rebased to zero-based bits") { + val hl = BitsHL(9, 2) <> VAR + assertCodeString( + """|hl.bits match + | case h"12" => + | case h"a${bind: B[4]}" => + | case _ => + |end match + |""".stripMargin + ) { + hl match + case h"12" => + case h"a${bind: B[4]}" => + case _ => + } + } test("BitsHL declaration, assignment, and comparison") { assertCodeString { """|val x = BitsHL(9, 2) <> VAR diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 455c8ccfd..b9b88963f 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -1019,16 +1019,52 @@ val b6: Bits[6] <> CONST = all(0) /// details | Transitioning from Verilog type: verilog -* __Specifying a width instead of an index range:__ In Verilog bit vectors are declared with an index range that enables outliers like non-zero index start, negative indexing or changing bit order. These use-cases are rare and they are better covered using different language constructs. Therefore, DFHDL simplifies things by only requiring a single width/length argument which yields a `[width-1:0]` sized vector (for [generic vectors][DFVector] the element order the opposite). +* __Specifying a width instead of an index range:__ In Verilog bit vectors are declared with an index range that enables outliers like non-zero index start, negative indexing or changing bit order. These use-cases are rare and they are better covered using different language constructs. Therefore, DFHDL simplifies things by only requiring a single width/length argument which yields a `[width-1:0]` sized vector (for [generic vectors][DFVector] the element order the opposite). For the rare designs that genuinely need a non-zero low index, DFHDL provides the dedicated [`BitsHL`][DFBitsHL] constructor. * __Additional constructors:__ DFHDL provides additional constructs to simplify some common Verilog bit vector declaration. For example, instead of declaring `reg [$clog2(DEPTH)-1:0] addr` in Verilog, in DFHDL simply declare `val addr = Bits.until(DEPTH) <> VAR`. /// /// details | Transitioning from VHDL type: vhdl -* __Specifying a width instead of an index range:__ In VHDL bit vectors are declared with an index range that enables outliers like non-zero index start, negative indexing or changing bit order. These use-cases are rare and they are better covered using different language constructs. Therefore, DFHDL simplifies things by only requiring a single width/length argument which yields a `(width-1 downto 0)` sized vector (for [generic vectors][DFVector] the element order the opposite). +* __Specifying a width instead of an index range:__ In VHDL bit vectors are declared with an index range that enables outliers like non-zero index start, negative indexing or changing bit order. These use-cases are rare and they are better covered using different language constructs. Therefore, DFHDL simplifies things by only requiring a single width/length argument which yields a `(width-1 downto 0)` sized vector (for [generic vectors][DFVector] the element order the opposite). For the rare designs that genuinely need a non-zero low index, DFHDL provides the dedicated [`BitsHL`][DFBitsHL] constructor. * __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`. /// +#### Low-Indexed Bit Vectors: `BitsHL` {#DFBitsHL} + +For the rare cases that genuinely require a non-zero low index, such as mirroring a memory-mapped +register field or an address range taken from an external specification, DFHDL provides the +`BitsHL` constructor. `BitsHL(idxHigh, idxLow)` declares a bit vector spanning the absolute +inclusive range `idxHigh downto idxLow` (width is `idxHigh - idxLow + 1`), and the generated HDL +preserves that range (`[idxHigh:idxLow]` in Verilog, `(idxHigh downto idxLow)` in VHDL). +Reversed bit direction is not supported: a `BitsHL` range is always descending, so there is no +equivalent of a Verilog `[low:high]` or a VHDL `(low to high)` declaration. + +/// admonition | Prefer `Bits` over `BitsHL` + type: note +`BitsHL` should be used scarcely. Always prefer `Bits(width)` over `BitsHL(width-1, 0)`: both +construct the same zero-based bit vector type, and the width-based spelling is the canonical one. +Reach for `BitsHL` only when a non-zero low index carries real meaning in your design. +/// + +/// html | div.operations +| Constructor | Description | Arg Constraints | Returns | +| ------------ | ----------- | ------------------- | ------- | +| `BitsHL(idxHigh, idxLow)` | Construct a bit vector DFType spanning the absolute inclusive range `idxHigh downto idxLow`. | `idxHigh` and `idxLow` are Scala `Int` or constant DFHDL `Int` values, with `idxHigh >= idxLow` and `idxLow >= 0` (natural). | `BitsHL[idxHigh.type, idxLow.type]` DFType | +| `BitsHL[H, L]` | Construct a bit vector DFType with the given `H` high index and `L` low index as Scala type arguments (for advanced users). | `H` and `L` are Scala `Int` or constant DFHDL `Int` Singleton types, with `H >= L` and `L >= 0`. | `BitsHL[H, L]` DFType | +/// + +Selection on a low-indexed bit vector uses absolute indices within `[idxLow, idxHigh]`, and +selection results are always zero-based `Bits` values. Assignment, connection, and comparison +between bit vectors are width-based, so equal-width `Bits` and `BitsHL` values are compatible +regardless of their low indices. + +```scala +val reg = BitsHL(9, 2) <> VAR +val fld = reg(5, 2) // absolute range selection, yields a zero-based Bits[4] value +val msb = reg(9) // absolute bit selection +reg := all(0) // width-based compatibility, like any Bits[8] value +``` + #### Type Signatures - Bounded: `Bits[8]`, `Bits[4]` - Parameterized bounded: `Bits[w.type]` (where `w: Int <> CONST`) From f7eb9a750170c69092af2216fe1f7ea387863e85 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 03:47:18 +0300 Subject: [PATCH 24/57] BitsHL selection givens: full compile-time safety on both type paths The generalized selection givens move to an OpsLP low-priority trait (the CandidateLP/WidthLP idiom), dropping the NotGiven dispatch. Range selection now has two statically-checked forms: the W-form (HighIdx[W, L] bound) covers term-constructed receivers whose width is a reduced literal, and a new H-form (DFBitsHL[H, L] receiver) covers annotation-path receivers (e.g. a BitsHL[9, 2] <> VAL struct field) by binding H structurally from the unreduced RangeWidth application, checking bounds on H directly with nothing to collapse. Given-candidate backtracking on a failed using-clause is what lets the W-form's stuck check fall through to the H-form, so struct-field range selection compiles again (pinned in the VHDL composite test) while out-of-range literals still fail at compile time on both paths. Co-Authored-By: Claude Fable 5 --- .../scala/StagesSpec/PrintVHDLCodeSpec.scala | 4 + core/src/main/scala/dfhdl/core/DFBits.scala | 137 +++++++++++++++--- core/src/main/scala/dfhdl/core/DFVal.scala | 2 + 3 files changed, 119 insertions(+), 24 deletions(-) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index badb73f0d..4e3870203 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -3755,9 +3755,11 @@ class PrintVHDLCodeSpec extends StageSpec: val p = P <> IN val v = BitsHL(9, 2) X 2 <> IN val f8 = Bits(8) <> OUT + val f4 = Bits(4) <> OUT val fb = Bit <> OUT val c8 = Bits(8) <> OUT f8 := p.f + f4 := p.f(5, 2) fb := p.f(5) c8 := v(0) end BitsHLComposite @@ -3780,6 +3782,7 @@ class PrintVHDLCodeSpec extends StageSpec: | p : in t_struct_P; | v : in t_arrX1_std_logic_vector(0 to 1)(9 downto 2); | f8 : out std_logic_vector(7 downto 0); + | f4 : out std_logic_vector(3 downto 0); | fb : out std_logic; | c8 : out std_logic_vector(7 downto 0) |); @@ -3788,6 +3791,7 @@ class PrintVHDLCodeSpec extends StageSpec: |architecture BitsHLComposite_arch of BitsHLComposite is |begin | f8 <= p.f; + | f4 <= p.f(5 downto 2); | fb <= p.f(5); | c8 <= v(0); |end BitsHLComposite_arch; diff --git a/core/src/main/scala/dfhdl/core/DFBits.scala b/core/src/main/scala/dfhdl/core/DFBits.scala index 5b578f8d0..9a9441cc8 100644 --- a/core/src/main/scala/dfhdl/core/DFBits.scala +++ b/core/src/main/scala/dfhdl/core/DFBits.scala @@ -651,28 +651,13 @@ object DFBits: } end TupleOps - object Ops: - import IntP.{-, +} - given evOpApplyDFBits[ - W <: IntP, - A, - C, - I, - P, - L <: DFVal[DFBits[W], Modifier[A, C, I, P]], - R - ](using - ub: DFUInt.Val.UBArg[W, R] - ): ExactOp2Aux["apply", DFC, DFValAny, L, R, DFVal[DFBit, Modifier[A, C, Any, P]]] = - new ExactOp2["apply", DFC, DFValAny, L, R]: - type Out = DFVal[DFBit, Modifier[A, C, Any, P]] - def apply(lhs: L, idx: R)(using DFC): Out = trydf { - DFVal.Alias.ApplyIdx(DFBit, lhs, ub(lhs.widthIntParam, idx)(using dfc.anonymize)) - }(using dfc, CTName("bit selection (apply)")) - end evOpApplyDFBits - // a nonzero-low receiver selects with ABSOLUTE indices in [L, L+W-1]; the bound - // composition `W+L` does not survive the type-level const guards (see the IntP - // doc comment), so this variant checks at elaboration time instead + // The generalized (any low index) selection givens, at a LOWER priority than the + // zero-based ones in `Ops` (the codebase's LP-trait idiom, like CandidateLP/WidthLP). + // These check at elaboration time only: their type-level bounds compose over the + // receiver's width (`W+L-1`), which does not survive the IntP const guards when the + // width is itself an unreduced fold, e.g. a `BitsHL[9, 2] <> VAL` struct field whose + // width slot is the unreduced `RangeWidth[9, 2]` (see the doc comment in IntParam.scala). + trait OpsLP: given evOpApplyDFBitsWL[ W <: IntP, L2 <: IntP, @@ -683,7 +668,6 @@ object DFBits: L <: DFVal[DFBitsWL[W, L2], Modifier[A, C, I, P]], R ](using - notLow0: scala.util.NotGiven[L2 =:= 0], ub: DFUInt.Val.UBArg[Int, R] ): ExactOp2Aux["apply", DFC, DFValAny, L, R, DFVal[DFBit, Modifier[A, C, Any, P]]] = new ExactOp2["apply", DFC, DFValAny, L, R]: @@ -703,7 +687,7 @@ object DFBits: DFVal.Alias.ApplyIdx(DFBit, lhs, idxVal) }(using dfc, CTName("bit selection (apply)")) end evOpApplyDFBitsWL - given evOpApplyRangeDFBits[ + given evOpApplyRangeDFBitsWL[ W <: IntP, L2 <: IntP, A, @@ -745,7 +729,112 @@ object DFBits: case _ => DFVal.Alias.ApplyRange(lhs, idxHighParam, idxLowParam) }(using dfc, CTName("bit range selection (apply)")) + end evOpApplyRangeDFBitsWL + end OpsLP + object Ops extends OpsLP: + import IntP.{-, +} + given evOpApplyDFBits[ + W <: IntP, + A, + C, + I, + P, + L <: DFVal[DFBits[W], Modifier[A, C, I, P]], + R + ](using + ub: DFUInt.Val.UBArg[W, R] + ): ExactOp2Aux["apply", DFC, DFValAny, L, R, DFVal[DFBit, Modifier[A, C, Any, P]]] = + new ExactOp2["apply", DFC, DFValAny, L, R]: + type Out = DFVal[DFBit, Modifier[A, C, Any, P]] + def apply(lhs: L, idx: R)(using DFC): Out = trydf { + DFVal.Alias.ApplyIdx(DFBit, lhs, ub(lhs.widthIntParam, idx)(using dfc.anonymize)) + }(using dfc, CTName("bit selection (apply)")) + end evOpApplyDFBits + given evOpApplyRangeDFBits[ + W <: IntP, + A, + C, + I, + P, + L <: DFVal[DFBits[W], Modifier[A, C, I, P]], + HI <: IntP, + LO <: IntP + ](using + checkHigh: BitIndex.CheckNUB[HI, W], + checkLow: BitIndex.CheckNUB[LO, W], + checkHiLo: BitsHiLo.CheckNUB[HI, LO] + ): ExactOp3Aux["apply", DFC, DFValAny, L, HI, LO, DFVal[ + DFBits[IntP.RangeWidth[HI, LO]], + Modifier[A, C, Any, P] + ]] = + new ExactOp3["apply", DFC, DFValAny, L, HI, LO]: + type Out = DFVal[DFBits[IntP.RangeWidth[HI, LO]], Modifier[A, C, Any, P]] + def apply(lhs: L, idxHigh: HI, idxLow: LO)(using DFC): Out = trydf { + val idxHighParam = IntParam(idxHigh) + val idxLowParam = IntParam(idxLow) + val idxHighIntOpt = idxHighParam.toScalaIntOpt + val idxLowIntOpt = idxLowParam.toScalaIntOpt + val widthIntOpt = lhs.widthIntOpt + (idxHighIntOpt, widthIntOpt) match + case (Some(idxHighInt), Some(widthInt)) => checkHigh(idxHighInt, widthInt) + case _ => + (idxLowIntOpt, widthIntOpt) match + case (Some(idxLowInt), Some(widthInt)) => checkLow(idxLowInt, widthInt) + case _ => + (idxHighIntOpt, idxLowIntOpt) match + case (Some(idxHighInt), Some(idxLowInt)) => checkHiLo(idxHighInt, idxLowInt) + case _ => + DFVal.Alias.ApplyRange(lhs, idxHighParam, idxLowParam) + }(using dfc, CTName("bit range selection (apply)")) end evOpApplyRangeDFBits + // the annotation path (a `BitsHL[9, 2] <> VAL` field or parameter) carries the width + // as the UNREDUCED `RangeWidth[H, L]` application, where the W-form's `HighIdx[W, L]` + // bound gets stuck (fold over a fold); binding `H` structurally checks on `H` directly. + // The term-construction path reduces the width to a literal, misses this pattern, and + // resolves to the W-form above instead. + given evOpApplyRangeDFBitsHL[ + H <: IntP, + L2 <: IntP, + A, + C, + I, + P, + L <: DFVal[DFBitsHL[H, L2], Modifier[A, C, I, P]], + HI <: IntP, + LO <: IntP + ](using + checkHigh: BitIndexHigh.CheckNUB[HI, H], + checkLow: BitIndexLow.CheckNUB[LO, L2], + checkHiLo: BitsHiLo.CheckNUB[HI, LO] + ): ExactOp3Aux["apply", DFC, DFValAny, L, HI, LO, DFVal[ + DFBits[IntP.RangeWidth[HI, LO]], + Modifier[A, C, Any, P] + ]] = + new ExactOp3["apply", DFC, DFValAny, L, HI, LO]: + type Out = DFVal[DFBits[IntP.RangeWidth[HI, LO]], Modifier[A, C, Any, P]] + def apply(lhs: L, idxHigh: HI, idxLow: LO)(using DFC): Out = trydf { + import dfc.getSet + val idxHighParam = IntParam(idxHigh) + val idxLowParam = IntParam(idxLow) + val idxHighIntOpt = idxHighParam.toScalaIntOpt + val idxLowIntOpt = idxLowParam.toScalaIntOpt + val dfTypeIR = lhs.asIR.dfType.asInstanceOf[ir.DFBitsWL] + val lowIntOpt = dfTypeIR.lowIdxIntOpt + val highIntOpt = (dfTypeIR.widthIntOpt, lowIntOpt) match + case (Some(widthInt), Some(lowInt)) => Some(lowInt + widthInt - 1) + case _ => None + (idxHighIntOpt, highIntOpt) match + case (Some(idxHighInt), Some(highInt)) => checkHigh(idxHighInt, highInt) + case _ => + (idxLowIntOpt, lowIntOpt) match + case (Some(idxLowInt), Some(lowInt)) => checkLow(idxLowInt, lowInt) + case _ => + (idxHighIntOpt, idxLowIntOpt) match + case (Some(idxHighInt), Some(idxLowInt)) => checkHiLo(idxHighInt, idxLowInt) + case _ => + DFVal.Alias.ApplyRange(lhs, idxHighParam, idxLowParam) + }(using dfc, CTName("bit range selection (apply)")) + end evOpApplyRangeDFBitsHL given evOpLogicDFBits[ Op <: FuncOp.|.type | FuncOp.&.type | FuncOp.^.type, L, diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index fbb727530..98b820414 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -1425,6 +1425,8 @@ object DFVal extends DFValLP: evOpApplyDFBits, evOpApplyDFBitsWL, evOpApplyRangeDFBits, + evOpApplyRangeDFBitsWL, + evOpApplyRangeDFBitsHL, evOpAsDFBits, evOpLogicReduceDFBits, evOpShift From c800fe56801881b37fc6abcc7c31249e8fa233d2 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 12:45:51 +0300 Subject: [PATCH 25/57] compiler_stages: a selection into a flattened chain's Bits link folds into the chain DropStructsVecs stage 2 folded a partial chain into one range selection over the flattened declaration, but only for links present in the replacement map. A selection INTO a Bits-typed link (a bits field select or a vector bits-cell select, never themselves replaced) dangled and selected into the folded range, emitting an illegal chained select under v95/v2001 (`p[8:1][5]`), silently. The chain extractor now matches transitively through anonymous links (a named link still legally breaks the chain as its own net), the walk translates a Bits link's absolute indices by the link's low index (the BitsHL correction), and a single-bit result folds to a bit selection rather than a one-bit part-select, keeping runtime indices legal where part-select bounds must be constant. Co-Authored-By: Claude Fable 5 --- .claude/commands/bugfix.md | 17 +++ .../compiler/stages/DropStructsVecs.scala | 109 +++++++++++++----- .../StagesSpec/PrintVerilogCodeSpec.scala | 55 +++++++-- 3 files changed, 143 insertions(+), 38 deletions(-) diff --git a/.claude/commands/bugfix.md b/.claude/commands/bugfix.md index fa816e60c..dd323b1f1 100644 --- a/.claude/commands/bugfix.md +++ b/.claude/commands/bugfix.md @@ -708,6 +708,23 @@ Duplicated demands from the two sides merge in the grouping step, and named valu the re-ask are dropped by the `isAllowedMultipleReferences` filter, so the two-sided form costs nothing. +### A stage running after the naming stages can re-create the shape they exist to prevent + +`NamedVerilogSelection`/`NamedVHDLSelection` enforce the select-prefix rule ("a select must +consume a declared dimension of a name") early in `BackendPrepStage`; a later stage that +rewrites a selection's PREFIX re-creates select-over-expression shapes with nothing downstream +to repair them, so it must keep its own output legal in the same patch (a "run the naming stage +again" cleanup is off the table per the `SanityCheck` rule). `DropStructsVecs` was the case: it +folded a partial chain into one range selection over the flattened declaration, but its chain +extractor was keyed on direct membership in the replacement map, so a selection INTO a +leaf-typed (Bits) chain link — a bits field select or a vector bits-cell select, never +themselves replaced — dangled and emitted `p[8:1][5]`. Two generalizable points: an extractor +keyed on direct membership misses TRANSITIVE chain participants (probe the select-into-the- +select twin, not just the chain the author had in mind); and when folding for v95/v2001, a +single-bit result must fold to a BIT select (`ApplyIdx`), never a one-bit part-select, because +a part-select requires constant bounds while a bit select legally takes a runtime index — that +one choice is what keeps the runtime-index variants (`p.f(i)`, `v(i)(5)`) legal at all. + ### An exemption phrased by shape swallows every construct with that shape When a stage's criteria carry an exemption written as a pattern (`case Ident(_) => false`, "skip diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala index c2f7d32d3..f37c284e8 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala @@ -55,7 +55,17 @@ case object DropStructsVecs extends GlobalStage: object PartialSel: import DFVal.Alias.* def unapply(partial: ApplyIdx | ApplyRange | SelectField)(using MemberGetSet): Boolean = - replacementMap.contains(partial.relValRef.get) + partial.relValRef.get match + case relVal if replacementMap.contains(relVal) => true + // a selection into a Bits-typed link of the chain (a bits field select or a + // vector bits-cell select, which are never themselves replaced) is part of the + // chain as well: left out, it would select into the folded range selection, + // an illegal chained select in Verilog (a select must consume a declared + // dimension of a name). a NAMED link legally breaks the chain, since it is + // emitted as its own net declaration + case chainLink: (ApplyIdx | ApplyRange | SelectField) if chainLink.isAnonymous => + unapply(chainLink) + case _ => false /////////////////////////////////////////////////////////////////////////////// // Stage 1: Replace structs and vectors with Bits @@ -68,8 +78,8 @@ case object DropStructsVecs extends GlobalStage: def updateArg(arg: DFVal): DFValAny = arg.dfType match // Structs and Vectors will be replaced with Bits in a different patch case _: (DFStruct | DFVector | DFBitsWL) => arg.asValAny - case _ if !arg.isAnonymous => arg.asValAny.bits - case _ => arg.asValAny.bits + case _ if !arg.isAnonymous => arg.asValAny.bits + case _ => arg.asValAny.bits def typeToBits(dfType: irDFType): DFTypeAny = val width = dfType.asFE[DFTypeAny].widthIntParam DFBits(width.ref).asFE[DFTypeAny] @@ -201,6 +211,12 @@ case object DropStructsVecs extends GlobalStage: partial, Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement) ): + // the low index of an unreplaced Bits-typed chain link (a bits field select or a + // vector bits-cell select), against which the selection's absolute indices are + // translated; a nonzero low arises only from an explicit BitsHL construction + def bitsLinkLow(relVal: DFVal): IntParam[Int] = relVal.dfType match + case bt: DFBitsWL => bt.lowIdxRef.get + case _ => 0 // looping through the partial references to find the outermost related value and its index var currentPartial = partial var relVal = currentPartial.relValRef.get @@ -214,19 +230,35 @@ case object DropStructsVecs extends GlobalStage: case Some(Some(idx: BigInt)) if elemIdxVal.isAnonymous => idx.toInt.asInstanceOf[IntParam[Int]] case _ => elemIdxVal.asValAny.asInstanceOf[IntParam[Int]] - val elemWidth = elemSel.asValAny.widthIntParam - val relValWidth = relVal.asValAny.widthIntParam - idxLow = (relValWidth - elemWidth * (elemIdx + 1)) + - idxLow - .asInstanceOf[IntParam[Int]] + if (replacementMap.contains(relVal)) + // vector cell selection: cells are packed MSB-first + val elemWidth = elemSel.asValAny.widthIntParam + val relValWidth = relVal.asValAny.widthIntParam + idxLow = (relValWidth - elemWidth * (elemIdx + 1)) + + idxLow + .asInstanceOf[IntParam[Int]] + else + // bit selection into a Bits-typed chain link: the index is absolute + // in the link's own [low, high] range, so translate by the link's low + idxLow = (elemIdx - bitsLinkLow(relVal)) + + idxLow + .asInstanceOf[IntParam[Int]] case rangeSel: DFVal.Alias.ApplyRange => - val elemWidth = - replacementMap(relVal).dfType.asInstanceOf[DFVector] - .cellType.asFE[DFTypeAny].widthIntParam - val relValWidth = relVal.asValAny.widthIntParam - idxLow = (relValWidth - elemWidth * (rangeSel.idxHighRef.get + 1)) + - idxLow - .asInstanceOf[IntParam[Int]] + if (replacementMap.contains(relVal)) + // vector cell range selection: indices are in cell units, MSB-first + val elemWidth = + replacementMap(relVal).dfType.asInstanceOf[DFVector] + .cellType.asFE[DFTypeAny].widthIntParam + val relValWidth = relVal.asValAny.widthIntParam + idxLow = (relValWidth - elemWidth * (rangeSel.idxHighRef.get + 1)) + + idxLow + .asInstanceOf[IntParam[Int]] + else + // range selection into a Bits-typed chain link: absolute indices, + // translated by the link's low + idxLow = (rangeSel.idxLowRef.get - bitsLinkLow(relVal)) + + idxLow + .asInstanceOf[IntParam[Int]] case fieldSel: DFVal.Alias.SelectField => var relBitLow: IntParam[Int] = idxLow val dfType = replacementMap(relVal).dfType.asInstanceOf[DFStruct] @@ -250,25 +282,40 @@ case object DropStructsVecs extends GlobalStage: case _ => explore = false end while + // a single-bit selection folds into a bit selection rather than a degenerate + // one-bit range selection: a bit selection stays legal in v95/v2001 even with + // a runtime index, where a part-select requires constant bounds + val bitSelFold = partial match + case _: DFVal.Alias.ApplyIdx => + partial.dfType match + case DFBit | DFBool => true + case _ => false + case _ => false val requireCast = partial.dfType match - case _: DFBitsWL => false - case _: DFVector => false - case _: DFStruct => false - case _ => true + case _: DFBitsWL => false + case _: DFVector => false + case _: DFStruct => false + case DFBit if bitSelFold => false + case _ => true val bitsMeta = if (requireCast) partial.meta.anonymize else partial.meta - val idxHigh: IntParam[ - Int - ] = (partial.asValAny.widthIntParam + idxLow - 1).asInstanceOf[IntParam[Int]] - val bitsVal = - dfhdl.core.DFVal.Alias.ApplyRange( - relVal.asValOf[Bits[Int]], - idxHigh.cloneAnonValueAndDepsHere, - idxLow.cloneAnonValueAndDepsHere - )(using - dfc.setMeta(bitsMeta) - ) + val bitsValIR: DFVal = + if (bitSelFold) + dfhdl.core.DFVal.Alias.ApplyIdx( + dfhdl.core.DFBit, + relVal.asValAny, + idxLow.cloneAnonValueAndDepsHere.toDFConst(using dfc.anonymize) + )(using dfc.setMeta(bitsMeta)).asIR + else + val idxHigh: IntParam[ + Int + ] = (partial.asValAny.widthIntParam + idxLow - 1).asInstanceOf[IntParam[Int]] + dfhdl.core.DFVal.Alias.ApplyRange( + relVal.asValOf[Bits[Int]], + idxHigh.cloneAnonValueAndDepsHere, + idxLow.cloneAnonValueAndDepsHere + )(using dfc.setMeta(bitsMeta)).asIR if (requireCast) - dfhdl.core.DFVal.Alias.AsIs.forced(partial.dfType, bitsVal.asIR)(using + dfhdl.core.DFVal.Alias.AsIs.forced(partial.dfType, bitsValIR)(using dfc.setMeta(partial.meta) ) dsn.patch diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index a23b886da..734e82d4c 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -1004,7 +1004,7 @@ class PrintVerilogCodeSpec extends StageSpec: | for (j = 0; j < 8; j = j + 1) begin | if ((j % 2) == 0) begin | for (k = 0; k < 10; k = k + 1) begin - | if ((k % 2) == 0) (matrix[(10 + ((640 - (80 * (i + 1))) + ((80 - (10 * (j + 1))) + 0))) - 1:(640 - (80 * (i + 1))) + ((80 - (10 * (j + 1))) + 0)])[k] <= 1'b1; + | if ((k % 2) == 0) matrix[(640 - (80 * (i + 1))) + ((80 - (10 * (j + 1))) + ((k - 0) + 0))] <= 1'b1; | end | end | end @@ -1015,7 +1015,7 @@ class PrintVerilogCodeSpec extends StageSpec: | for (j = 0; j < 8; j = j + 1) begin | if ((j % 2) == 1) begin | for (k = 0; k < 10; k = k + 1) begin - | if ((k % 2) == 1) (matrix[(10 + ((640 - (80 * (i + 1))) + ((80 - (10 * (j + 1))) + 0))) - 1:(640 - (80 * (i + 1))) + ((80 - (10 * (j + 1))) + 0)])[k] <= 1'b0; + | if ((k % 2) == 1) matrix[(640 - (80 * (i + 1))) + ((80 - (10 * (j + 1))) + ((k - 0) + 0))] <= 1'b0; | end | end | end @@ -3675,14 +3675,16 @@ class PrintVerilogCodeSpec extends StageSpec: test("nonzero-low bit vector struct field flattening under v2001") { given options.CompilerOptions.Backend = _.verilog.v2001 class BitsHLFlatten extends RTDesign: - // NOTE: a bit selection into the field (`p.f(5)`) is excluded here: the flattening - // currently emits an illegal chained part-select (`p[8:1][5]`) under v2001, a - // pre-existing issue that equally affects zero-based fields (and for a nonzero-low - // field also keeps the absolute index where a relative one is needed) + // selections into the field fold into a single select over the flattened struct, + // with the field's absolute (nonzero-low) indices translated to flattened positions case class P(f: BitsHL[9, 2] <> VAL, g: Bit <> VAL) extends Struct val p = P <> IN val f8 = Bits(8) <> OUT + val fb = Bit <> OUT + val f4 = Bits(4) <> OUT f8 := p.f + fb := p.f(5) + f4 := p.f(5, 2) end BitsHLFlatten val top = BitsHLFlatten().getCompiledCodeString assertNoDiff( @@ -3692,10 +3694,14 @@ class PrintVerilogCodeSpec extends StageSpec: | |module BitsHLFlatten( | input wire [8:0] p, - | output wire [7:0] f8 + | output wire [7:0] f8, + | output wire fb, + | output wire [3:0] f4 |); | `include "dfhdl_defs.vh" | assign f8 = p[8:1]; + | assign fb = p[4]; + | assign f4 = p[4:1]; |endmodule |""".stripMargin ) @@ -3727,4 +3733,39 @@ class PrintVerilogCodeSpec extends StageSpec: |""".stripMargin ) } + test("selection into a flattened vector cell under v2001") { + given options.CompilerOptions.Backend = _.verilog.v2001 + class VecCellSel extends RTDesign: + val v = Bits(8) X 4 <> IN + val i = UInt(2) <> IN + val b = Bit <> OUT + val o = Bits(4) <> OUT + val d = Bit <> OUT + b := v(2)(5) + o := v(2)(5, 2) + // a runtime cell index folds into a runtime bit selection (a part-select + // with runtime bounds is illegal in v95/v2001) + d := v(i)(5) + end VecCellSel + val top = VecCellSel().getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module VecCellSel( + | input wire [31:0] v, + | input wire [1:0] i, + | output wire b, + | output wire [3:0] o, + | output wire d + |); + | `include "dfhdl_defs.vh" + | assign b = v[13]; + | assign o = v[13:10]; + | assign d = v[(32 - (8 * (i + 1))) + 5]; + |endmodule + |""".stripMargin + ) + } end PrintVerilogCodeSpec From 48b9a0c0cd15882686da519338549cf10b27c7e2 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 15:33:55 +0300 Subject: [PATCH 26/57] compiler_ir: a printed bound expression cancels its additive terms as elaboration would The printer synthesizes the bound expressions `width - 1` and `width + low - 1`, which are never built as values, so elaboration's SimplifyFunc additive cancellation can never reach them: a `BitsHL(idxHigh, idxLow)` declaration (width stored as the cone `(idxHigh - idxLow) + 1`) emitted its high bound as the unreduced `((HI - LO) + 1) + LO - 1` instead of the `HI` the user wrote. The bound helpers now collect signed additive terms across anonymous DFInt32 `+`/`-` cones and cancel ident-transparent `=~`-equal opposite-sign pairs, the SimplifyFunc term calculus applied at print. Terms keep their references, so a surviving term renders with the plain spelling's relative naming; an anonymous constant folds into the offset while a named constant stays a symbolic term (IntExprCalc's linear form is unusable here: it folds named constants to their data, erasing the user's spelling). Non-cancelling cones print byte-identically to before; carry-widened widths improve from `(W + 1) - 1` to `W`. This also retires the associative-reduction TODO in `uboundCS`. Closes #489 Co-Authored-By: Claude Fable 5 --- .../compiler/printing/DFValPrinter.scala | 123 +++++++++++++----- .../StagesSpec/PrintCodeStringSpec.scala | 21 +++ .../scala/StagesSpec/PrintVHDLCodeSpec.scala | 58 +++++++-- .../StagesSpec/PrintVerilogCodeSpec.scala | 51 ++++++-- 4 files changed, 207 insertions(+), 46 deletions(-) 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 86c597c88..487a84182 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala @@ -65,26 +65,98 @@ extension (intParamRef: IntParamRef) case ref: DFRef.TwoWayAny => printer.csRef(ref, typeCS) case int: Int => int.toString def refCodeString(using printer: AbstractValPrinter): String = intParamRef.refCodeString(false) + + /** The signed additive terms of the expression behind this parameter reference, collected across + * anonymous two-arg DFInt32 `+`/`-` cones. This is the term collection of the elaboration-time + * `SimplifyFunc` additive cancellation, applied at print: a bound expression the PRINTER + * synthesizes (`width - 1`, `width + low - 1`) is never built as a value, so its cancellation + * can only happen here. Terms keep their references, so a surviving term renders through `csRef` + * exactly as the plain spelling would; an ANONYMOUS constant folds into the returned constant + * offset, while a named constant is a spelling the user chose and stays a symbolic term. + */ + private def boundTerms(using + printer: AbstractValPrinter + ): (List[(Int, DFRef.TwoWayAny)], Int) = + import printer.getSet + def stripAnonIdent(dfVal: DFVal): DFVal = dfVal match + case Ident(underlying) if dfVal.isAnonymous => stripAnonIdent(underlying) + case _ => dfVal + def collect(ref: DFRef.TwoWayAny, sign: Int): (List[(Int, DFRef.TwoWayAny)], Int) = + ref.get match + case f: Func + if f.isAnonymous && f.dfType == DFInt32 && + (f.op == FuncOp.+ || f.op == FuncOp.-) && f.args.size == 2 => + val List(lhsRef, rhsRef) = f.args: @unchecked + val (lhsTerms, lhsOffset) = collect(lhsRef, sign) + val (rhsTerms, rhsOffset) = collect(rhsRef, if (f.op == FuncOp.+) sign else -sign) + (lhsTerms ++ rhsTerms, lhsOffset + rhsOffset) + case dfVal: DFVal => + stripAnonIdent(dfVal) match + case c: Const if c.isAnonymous && c.dfType == DFInt32 => + c.data match + case Some(i: BigInt) => (Nil, sign * i.toInt) + case _ => (List((sign, ref)), 0) + case _ => (List((sign, ref)), 0) + case _ => (List((sign, ref)), 0) + intParamRef match + case ref: DFRef.TwoWayAny => collect(ref, 1) + case int: Int => (Nil, int) + end boundTerms + + /** the code string of `width + low + constOffset` (the receiver is the width), with opposing + * additive terms cancelled the way elaboration's `SimplifyFunc` would cancel them had the + * expression been built as a value. In particular, the width of an explicit + * `BitsHL(idxHigh, idxLow)` construction is the cone `(idxHigh - idxLow) + 1`, so its high bound + * `width + low - 1` cancels back exactly to the `idxHigh` the user wrote. + */ + private def reducedBoundCS( + lowIdxRefOpt: Option[IntParamRef], + constOffset: Int, + typeCS: Boolean + )(using printer: AbstractValPrinter): String = + import printer.getSet + val (widthTerms, widthOffset) = intParamRef.boundTerms + val (lowTerms, lowOffset) = lowIdxRefOpt match + case Some(lowIdxRef) => lowIdxRef.boundTerms + case None => (Nil, 0) + var terms = widthTerms ++ lowTerms + val offset = widthOffset + lowOffset + constOffset + // cancel opposing +/- terms of the same value (ident-transparent), one pair per round + def strippedValOf(ref: DFRef.TwoWayAny): Option[DFVal] = ref.get match + case dfVal: DFVal => Some(dfVal.stripTypePreservingAliases) + case _ => None + def cancelOnce(ts: List[(Int, DFRef.TwoWayAny)]): Option[List[(Int, DFRef.TwoWayAny)]] = + val indexed = ts.zipWithIndex + indexed.iterator.flatMap { case ((s1, r1), i) => + indexed.iterator.collectFirst { + case ((s2, r2), j) + if j > i && s1 == -s2 && + strippedValOf(r1).exists(v1 => strippedValOf(r2).exists(v1 =~ _)) => + ts.zipWithIndex.collect { case (t, k) if k != i && k != j => t } + } + }.nextOption() + var continue = true + while (continue) + cancelOnce(terms) match + case Some(reduced) => terms = reduced + case None => continue = false + if (terms.isEmpty) offset.toString + else + val csTerms = terms.zipWithIndex.map { case ((sign, ref), idx) => + val cs = printer.csRef(ref, typeCS).applyBrackets() + if (idx == 0) if (sign > 0) cs else s"-$cs" + else if (sign > 0) s" + $cs" + else s" - $cs" + }.mkString + if (offset > 0) s"$csTerms + $offset" + else if (offset < 0) s"$csTerms - ${-offset}" + else csTerms + end reducedBoundCS + def uboundCS(using printer: AbstractValPrinter): String = intParamRef match - case ref: DFRef.TwoWayAny => - // TODO: consider implementing an associative int operation reduction - // import printer.getSet - // ref.get match - // case func @ ir.DFVal.Func( - // ir.DFInt32, - // op @ (Func.Op.+ | Func.Op.-), - // List(argRef, ir.DFRef(const: ir.DFVal.Const)), - // _, - // _, - // _ - // ) => - // val int = const.data.asInstanceOf[Option[BigInt]].get.toInt - // val csArg = printer.csRef(argRef, false) - // if (int == 1) csArg - // else s"$csArg $op ${int - 1}" - // case _ => - s"${printer.csRef(ref, false).applyBrackets()} - 1" case int: Int => (int - 1).toString + case _ => reducedBoundCS(None, -1, false) + /** the high-bound expression `low + width - 1` of a bit-vector range (the receiver is the width), * folded to a literal when possible; a literal low of 0 spells exactly like `uboundCS` */ @@ -92,17 +164,8 @@ extension (intParamRef: IntParamRef) printer: AbstractValPrinter ): String = (intParamRef, lowIdxRef) match - case (w: Int, l: Int) => (w + l - 1).toString - case (_, l: Int) if l == 0 => - s"${intParamRef.refCodeString(typeCS).applyBrackets()} - 1" - case (w: Int, _) => - s"${lowIdxRef.refCodeString(typeCS).applyBrackets()} + ${w - 1}" - case (_, l: Int) => - s"${intParamRef.refCodeString(typeCS).applyBrackets()} + ${l - 1}" - case _ => - val csWidth = intParamRef.refCodeString(typeCS).applyBrackets() - val csLow = lowIdxRef.refCodeString(typeCS).applyBrackets() - s"$csWidth + $csLow - 1" + case (w: Int, l: Int) => (w + l - 1).toString + case _ => reducedBoundCS(Some(lowIdxRef), -1, typeCS) end extension extension (alias: Alias) @@ -122,7 +185,7 @@ trait AbstractValPrinter extends AbstractPrinter: */ final def csInlinedWidth(dfType: DFType): String = dfType match case DFBool | DFBit => "1" - case dt: DFBitsWL => dt.widthParamRef.refCodeString + case dt: DFBitsWL => dt.widthParamRef.refCodeString case dt: DFDecimal => if (dt.fractionWidth == 0) dt.magnitudeWidthParamRef.refCodeString else s"${dt.magnitudeWidthParamRef.refCodeString.applyBrackets()} + ${dt.fractionWidth}" diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index 3f52fd00d..fcf490b97 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -3811,4 +3811,25 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + test("BitsHL constant bounds print as written") { + class HLBounds(val HI: Int <> CONST = 5, val LO: Int <> CONST = 4) extends RTDesign: + val b = BitsHL(HI, LO) <> OUT + val d = BitsHL(9, LO) <> OUT + b <> all(0) + d <> all(0) + end HLBounds + assertCodeString( + HLBounds(), + """|class HLBounds( + | val HI: Int <> CONST = 5, + | val LO: Int <> CONST = 4 + |) extends RTDesign: + | val b = BitsHL(HI, LO) <> OUT + | val d = BitsHL(9, LO) <> OUT + | b <> b"0".repeat((HI - LO) + 1) + | d <> b"0".repeat((9 - LO) + 1) + |end HLBounds + |""".stripMargin + ) + } end PrintCodeStringSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index 4e3870203..8c8c19e10 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -3528,17 +3528,17 @@ class PrintVHDLCodeSpec extends StageSpec: | b : in signed(W - 1 downto 0); | ua : in unsigned(W - 1 downto 0); | ub : in unsigned(W - 1 downto 0); - | sum : out signed((W + 1) - 1 downto 0); - | usub : out unsigned((W + 1) - 1 downto 0); - | acc : out signed((W + 2) - 1 downto 0); - | uacc : out unsigned((W + 2) - 1 downto 0); + | sum : out signed(W downto 0); + | usub : out unsigned(W downto 0); + | acc : out signed(W + 1 downto 0); + | uacc : out unsigned(W + 1 downto 0); | prod : out signed((2 * W) - 1 downto 0); | uprod : out unsigned((2 * W) - 1 downto 0); | c : in std_logic; - | viaSel : out signed((W + 1) - 1 downto 0); - | viaIf : out signed((W + 1) - 1 downto 0); - | shr : out signed((W + 2) - 1 downto 0); - | neg : out signed((W + 2) - 1 downto 0) + | viaSel : out signed(W downto 0); + | viaIf : out signed(W downto 0); + | shr : out signed(W + 1 downto 0); + | neg : out signed(W + 1 downto 0) |); |end ParamWiden; | @@ -3831,4 +3831,46 @@ class PrintVHDLCodeSpec extends StageSpec: |""".stripMargin ) } + test("BitsHL constant bounds emit as written") { + class HLBounds(val HI: Int <> CONST = 5, val LO: Int <> CONST = 4) extends RTDesign: + val b = BitsHL(HI, LO) <> OUT + val c = BitsHL(HI, 0) <> OUT + val d = BitsHL(9, LO) <> OUT + val e = Bits(HI) <> OUT + b <> all(0) + c <> all(0) + d <> all(0) + e <> all(0) + end HLBounds + val top = HLBounds().getCompiledCodeString + assertNoDiff( + top, + """|library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + | + |entity HLBounds is + |generic ( + | HI : integer := 5; + | LO : integer := 4 + |); + |port ( + | b : out std_logic_vector(HI downto LO); + | c : out std_logic_vector(HI downto 0); + | d : out std_logic_vector(9 downto LO); + | e : out std_logic_vector(HI - 1 downto 0) + |); + |end HLBounds; + | + |architecture HLBounds_arch of HLBounds is + |begin + | b <= repeat("0", (HI - LO) + 1); + | c <= repeat("0", HI + 1); + | d <= repeat("0", (9 - LO) + 1); + | e <= repeat("0", HI); + |end HLBounds_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 734e82d4c..1ffafd88b 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -3348,17 +3348,17 @@ class PrintVerilogCodeSpec extends StageSpec: | input wire logic signed [W - 1:0] b, | input wire logic [W - 1:0] ua, | input wire logic [W - 1:0] ub, - | output logic signed [(W + 1) - 1:0] sum, - | output logic [(W + 1) - 1:0] usub, - | output logic signed [(W + 2) - 1:0] acc, - | output logic [(W + 2) - 1:0] uacc, + | output logic signed [W:0] sum, + | output logic [W:0] usub, + | output logic signed [W + 1:0] acc, + | output logic [W + 1:0] uacc, | output logic signed [(2 * W) - 1:0] prod, | output logic [(2 * W) - 1:0] uprod, | input wire logic c, - | output logic signed [(W + 1) - 1:0] viaSel, - | output logic signed [(W + 1) - 1:0] viaIf, - | output logic signed [(W + 2) - 1:0] shr, - | output logic signed [(W + 2) - 1:0] neg + | output logic signed [W:0] viaSel, + | output logic signed [W:0] viaIf, + | output logic signed [W + 1:0] shr, + | output logic signed [W + 1:0] neg |); | `include "dfhdl_defs.svh" | assign sum = a + b; @@ -3768,4 +3768,39 @@ class PrintVerilogCodeSpec extends StageSpec: |""".stripMargin ) } + test("BitsHL constant bounds emit as written") { + class HLBounds(val HI: Int <> CONST = 5, val LO: Int <> CONST = 4) extends RTDesign: + val b = BitsHL(HI, LO) <> OUT + val c = BitsHL(HI, 0) <> OUT + val d = BitsHL(9, LO) <> OUT + val e = Bits(HI) <> OUT + b <> all(0) + c <> all(0) + d <> all(0) + e <> all(0) + end HLBounds + val top = HLBounds().getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module HLBounds#( + | parameter int HI = 5, + | parameter int LO = 4 + |)( + | output logic [HI:LO] b, + | output logic [HI:0] c, + | output logic [9:LO] d, + | output logic [HI - 1:0] e + |); + | `include "dfhdl_defs.svh" + | assign b = {((HI - LO) + 1){1'b0}}; + | assign c = {(HI + 1){1'b0}}; + | assign d = {((9 - LO) + 1){1'b0}}; + | assign e = {HI{1'b0}}; + |endmodule + |""".stripMargin + ) + } end PrintVerilogCodeSpec From b9b5402635aff9ca91564a97dc7c8cfaf96930cd Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 16:05:24 +0300 Subject: [PATCH 27/57] verilog-to-dfhdl: write the direct form, and make the control fail first Two lessons from the VeeR-EH1 beh_lib helpers, neither of which is a DFHDL fact. The transcription rule was too narrow. It said a conversion the compiler did not ask for is a smell, which caught half the cases; the actual cause was predicting that DFHDL would reject the obvious spelling and pre-emptively working around an error never raised. "I think it will not typecheck" is not a reason until the compiler says so, and the diagnostics name the fix when it does. Nine real corrections are tabulated, with the point that none was caught by compiling, by reading the emitted HDL, or by formal equivalence. Worst of the three smells is an operator the baseline did not use: `==` against XNOR on one bit is equivalent, so it passes every check and is visible only to a reader holding both files. And a verification-discipline note, because seven distinct green signals in one port meant nothing -- a sed that did not match, a sed -i that rewrote the gold filename, a semantically null mutation, a grep matching the failure line as well as the success line, head masking an exit code, probe classes sharing a file, and an ANSI prefix defeating a `^\[error\]` anchor. Assert the mutation applied and the control fails before believing a pass. Also records BitsHL's declaration-vs-expression rules, the carry operators, macro-modules as `@inline def`s, when a VAR is warranted, and that `.toScalaInt` neither causes nor cures parameter pinning. Advances benchmarks to f37d70e. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/verilog-to-dfhdl.md | 93 ++++++++++++++++++++++++++-- benchmarks | 2 +- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index c9cdb95aa..eb78142b5 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -54,6 +54,25 @@ to write. Reserve `EDDesign`/`process` for genuinely event-driven or multi-edge 5. Move up. Compiling a parent pulls in every child, so the top-level compile is the integration check. Whole-package sanity: `/Test/compile`. +**Do not trust a green result until the check can go red.** Every one of these produced a passing +signal that meant nothing, in a single port: + +- a `sed` mutation that silently did not match, so the "control" tested the unmodified design; +- a `sed -i` on the proof script that rewrote the *gold* filename too, so the tool errored out and + the grep for a failure string found none; +- a mutation that applied but was **semantically null** (widening `pc[8:7]` to `pc[9:7]` truncates + back to the same bits), so it changed the text and not the function; +- `grep -c "proof finished"`, which matches the failure line as happily as the success line; +- `cmd | head` reporting success because `head` exited 0 while the command behind it failed; +- several probe classes in one file, where a compile error in one is reported against another that + never ran; +- `grep '^\[error\]'` finding nothing because the tool prefixes lines with an ANSI escape. + +The habits that catch them: **assert the mutation applied** (diff the line count) and **assert the +control fails** before believing any pass; grep for the exact success string, never a substring +shared with failure; put each probe in its own file; and check for the artifact the run should have +produced rather than an exit code. + ## Clock and reset - the magnet model Clocks/resets are **magnets**: not ordinary ports, and they **auto-connect across the hierarchy**. @@ -152,8 +171,12 @@ Follow [from-verilog][from-verilog] for `Int <> CONST`/`String <> CONST` (they e - A `generate`-style choice between two bodies becomes **`.sel` on a constant condition**, not a Scala `if`: `x <> base & (SIZE == 48).sel(masked, 1)` folds at synthesis and covers both arms while leaving `SIZE` free. Prefer this to dropping the dead arm. - - Reserve `.toScalaInt` for what genuinely needs a Scala `Int` (an `initFile` path, a `Vec` size - the frontend cannot take as a const). + - Reserve `.toScalaInt` for what genuinely needs a Scala `Int` (an `initFile` path). It is **not** + needed for a `Bits(W) X N` size, a slice bound, or a `for (i <- 0 until N)` loop bound — the + ascribed constant works directly in all three. + - Note that writing `.toScalaInt` is not what pins a parameter, and dropping it does not unpin + one: the *read* pins it, and an elaboration-time loop reads its bound either way. Removing a + redundant `.toScalaInt` is a readability fix, not a genericity fix. - **`all(0)` for an explicitly-typed constant default** — `val CCM_SADR: Bits[32] <> CONST = all(0)` rather than spelling out `h"32'00000000"`. - **DFacsimile rejects `String <> CONST`** (the minimum tier can't resolve a `DFString` const's @@ -176,6 +199,12 @@ and they decide how closely the emitted HDL tracks the gold. for (i <- 1 until 40) error_mask(i - 1) <> (syndrome == i) ``` Intermediates that are pure renames upstream should just disappear. +- **A `VAR` is for a value the baseline drives a bit (or a range) at a time.** A per-bit `assign`, + a `generate` of assigns, or two `assign`s to different ranges of the same signal — those are the + cases a single named value cannot express. Everything else is a named value. +- **A `genvar` loop over a parameterised width reads the parameter, so it pins it** (see the + parameters section). That is usually acceptable — check how many distinct widths the baseline + actually instantiates before trying to avoid it. - **Bit logic uses `&`, `|`, `~`** — not `&&`, `||`, `!`. It rarely changes 2-state behaviour but it can for **x-value equivalence**. Write the bitwise form and let the emitter choose: it prints `&` when the operands are `Bit` and `&&` when they are `Boolean`, matching whichever the baseline used. @@ -195,6 +224,36 @@ and they decide how closely the emitted HDL tracks the gold. Seeding a `foldLeft` with the baseline's own first term emits a **flat** chain; `reduce` adds a paren group. Prefer `foldLeft` when the baseline starts the chain from a distinguished operand. Neither works for a *widening* fold (`_ ++ _`), where no fixed element type exists. +- **Use the carry operators for a widening add.** Verilog catches a carry by zero-extending both + operands (`{cout,sum} = {1'b0,a} + {1'b0,b}`); DFHDL says that directly with `+^` (and `-^`, + `*^`), which is defined as exactly that widening. `a +^ b` emits `assign sum = a + b;` with `sum` + one bit wider, and the carry is just `sum(top)`. +- **Transcribe the baseline; do not improve it.** The baseline's structure *is* the specification + and its tricks are the design, not workarounds to be modernised. Every departure costs the thing + the port is built on: emitted HDL that diffs against the gold. +- **Write the direct form and let the compiler object.** Nearly every needless complication below + came from *predicting* that DFHDL would reject the obvious spelling and pre-emptively working + around an error that was never raised. "I think it will not typecheck" is not a reason until the + compiler says so; it costs seconds to find out, and the diagnostics are good — they name the fix + (`.foldLeft[Bit <> VAL](...)`, "declare a DFHDL variable and assign it with `:=`"). Every row here + is a real correction from one port, and **not one was caught by compiling, by reading the emitted + HDL, or by formal equivalence**: + | wrote first | actually needed | why the rewrite was wrong | + |---|---|---| + | `(b"0", a).toBits.uint + (b"0", b).toBits.uint` | `a +^ b` | the zero-extension is not part of the operation; it is Verilog's only way to keep a carry | + | `(x_hi.uint + 1).bits(19, 0)` | `x_hi + 1` | arithmetic on `Bits` already yields `UInt[W] <> VAL`, modular at that width, converting back implicitly; the `.uint` forced a widening the `.bits` undid, and that pair is what hit DFHDL#486 | + | `(a, b).toBits` in an expression | `(a, b)` | a tuple converts implicitly wherever a `Bits` is wanted | + | `for (i <- 0 until W.toScalaInt)` | `for (i <- 0 until W)` | an ascribed constant works directly as a loop bound, `Vec` size or slice bound | + | `enum … (val value: UInt[4] <> CONST) extends Encoded.Manual(4)` with 15 explicit values | `enum … extends Encoded` | the default binary encoding already numbers from 0 in declaration order | + | `~(mask(i) ^ data(i))` for `mask[i] == data[i]` | `mask(i) == data(i)` | **substituting an operator the baseline chose**, on an unverified guess that `Bit \| Boolean` would not typecheck | + | `c.sel(v, all(0))` for `{N{c}} & v` | `c.repeat(N) & v` | a mask is not a mux waiting to be discovered | + | build a concat, connect the whole port | connect the pieces the baseline drives (`dout(12, 1) <> …`, `dout(31, 13) <> …`) | an invented intermediate hides the two assignments the gold has | + | `Bits(31)` plus `-1` at every slice | `BitsHL(31, 1)` | the base belongs on the declaration | + Three smells, in rising order of seriousness: **a conversion the compiler did not ask for**; **an + intermediate value with no counterpart in the baseline**; and **an operator the baseline did not + use**. The last is the one to fear, because an operator that is merely *equivalent* — `==` against + XNOR on one bit — passes every check in the ladder and is visible only to a reader holding the two + files side by side. - **A comparison yields `Boolean <> VAL`, not `Bit`.** `.bit` converts, and is needed before concatenating a comparison result. - **Convert once, at the definition.** `val syndrome = ecc_check(5, 0).uint` so every use reads @@ -210,6 +269,10 @@ and they decide how closely the emitted HDL tracks the gold. alone). On a large sequential module the anchors are what keeps induction tractable, so there declare the register under the baseline's own net name. **The choice is a verification one, not a style one.** +- **A baseline module whose body is a single `assign` over macros is a method, not a design.** + `@inline def f(...): Bits[W] <> DFRET = ` inlines at the call site. It still proves against + the baseline module: wrap it in a design carrying that module's port list. Give the wrapper a + *different* name — a design class collides with a same-named method in the package. - **A purely combinational design gets no clock or reset ports** — an `RTDesign` with no registers emits a clean port list, so combinational leaf modules need no annotation at all. @@ -223,10 +286,28 @@ and they decide how closely the emitted HDL tracks the gold. - **NTFS is case-insensitive:** writing `servant.scala` while `Servant.scala` exists writes *into* the old file. Delete old-cased files before renaming, and `clearSandbox` before regenerating renamed output. -- **Verilog ranges with a non-zero base do not survive.** `logic [31:1] prett` and `logic [18:2] x` - become `[30:0]` and `[16:0]`: the same width, packing identically inside a struct, but **indexed - differently**. Baseline `prett[j]` is `prett(j - 1)`. It compiles clean either way, so every slice - of such a field has to be translated deliberately; note it at the declaration. +- **A Verilog range with a non-zero base is `BitsHL`, not `Bits`.** `logic [31:1] pc` is + `BitsHL(31, 1)` (type-only spelling `BitsHL[31, 1] <> VAL` for a struct field or parameter). + Selection then uses the **baseline's own absolute indices** and the emitted declaration keeps the + range, so the code and the HDL both read like the gold: + ```scala + val pc = BitsHL(31, 1) <> IN // input wire logic [31:1] pc + val hi = pc(31, 13) // absolute; the result is a zero-based Bits[19] + ``` + Selection results are always zero-based and assignment/connection/comparison stay width-based, so + a `BitsHL` and an equal-width `Bits` remain interchangeable. Do **not** hand-translate to + `Bits(31)` and subtract one at each use: that compiles clean, loses the declaration, and every + slice becomes an off-by-one that only formal equivalence will catch. Prefer plain `Bits(width)` + whenever the base *is* zero. + In a **method signature** that follows from the same "declaration, not value" property: + - a **return type is always plain `Bits`** — selections are zero-based, so `BitsHL` can never + appear on the right of `<> DFRET`; + - a **parameter needs `BitsHL` only if the method indexes it with the baseline's indices**; + anything merely combined (XOR, concat, compare) takes `Bits[W]`, and width-based compatibility + passes a `BitsHL` argument straight in; + - constant bounds do not currently unify between the `BitsHL(H, L)` constructor and a + `BitsHL[H.type, L.type]` parameter (DFHDL#490), so a helper indexing a config-ranged signal + needs literal bounds or a `Bits` parameter. - **A fully-assigned `VAR` read through a *parameter*-bounded slice is misreported as a latch** (DFHDL#484). A local `Int <> CONST` bound is fine; only a design parameter trips it, and only for a `VAR` (a port or parameter sliced the same way is fine). Where the variable is a pure rename, slice diff --git a/benchmarks b/benchmarks index eac595299..f37d70ebc 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit eac595299ebf642663a5628de24c656a4511eaa4 +Subproject commit f37d70ebceb77cefbb7f916b982ad0fef17ca7f7 From bf1dd14dcb9a2133a63db32f3f943da4897ec656 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 18:19:43 +0300 Subject: [PATCH 28/57] fix sbtn detection --- internals/src/main/scala/dfhdl/internals/helpers.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internals/src/main/scala/dfhdl/internals/helpers.scala b/internals/src/main/scala/dfhdl/internals/helpers.scala index 386c19975..bf5a63a92 100644 --- a/internals/src/main/scala/dfhdl/internals/helpers.scala +++ b/internals/src/main/scala/dfhdl/internals/helpers.scala @@ -394,9 +394,10 @@ lazy val getShellCommand: Option[String] = end getShellCommand lazy val sbtnIsRunning: Boolean = - sbtIsRunning && getShellCommand.exists(cmd => cmd.endsWith("--server")) + sbtIsRunning && getShellCommand.exists(cmd => cmd.endsWith("--server") || cmd.endsWith("--detach-stdio")) lazy val sbtShellIsRunning: Boolean = + println(s"getShellCommand: ${getShellCommand}") getShellCommand.exists(cmd => cmd.endsWith("xsbt.boot.Boot") || cmd.endsWith("sbt-launch.jar")) lazy val sbtTestIsRunning: Boolean = From b69b79f8a906f5f61a711e798684e16d111501a0 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 18:20:05 +0300 Subject: [PATCH 29/57] remove debug --- internals/src/main/scala/dfhdl/internals/helpers.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/internals/src/main/scala/dfhdl/internals/helpers.scala b/internals/src/main/scala/dfhdl/internals/helpers.scala index bf5a63a92..cb1b12013 100644 --- a/internals/src/main/scala/dfhdl/internals/helpers.scala +++ b/internals/src/main/scala/dfhdl/internals/helpers.scala @@ -397,7 +397,6 @@ lazy val sbtnIsRunning: Boolean = sbtIsRunning && getShellCommand.exists(cmd => cmd.endsWith("--server") || cmd.endsWith("--detach-stdio")) lazy val sbtShellIsRunning: Boolean = - println(s"getShellCommand: ${getShellCommand}") getShellCommand.exists(cmd => cmd.endsWith("xsbt.boot.Boot") || cmd.endsWith("sbt-launch.jar")) lazy val sbtTestIsRunning: Boolean = From 0ee77931ea35ae3c41633b467634481e5f85b496 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 23:20:46 +0300 Subject: [PATCH 30/57] add missing BitsHL print coloring --- .../ir/src/main/scala/dfhdl/compiler/printing/Printer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1e032e513..8e2d50080 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala @@ -950,7 +950,7 @@ class DFPrinter(using val getSet: MemberGetSet, val printerOptions: PrinterOptio val dfhdlTypes: Set[String] = Set( "Bit", "Boolean", "Int", "UInt", "SInt", "Bits", "X", "Encoded", "Struct", "Opaque", "StartAt", "OneHot", "Gray", "Unit", "Time", "Freq", "String", "Double", "fs", "ns", "ps", "us", - "ms", "sec", "min", "hr", "Hz", "KHz", "MHz", "GHz" + "ms", "sec", "min", "hr", "Hz", "KHz", "MHz", "GHz", "BitsHL" ) def colorCode(cs: String): String = cs From 1a132db1fdffa5d5e8aa9fe2139bd81f58f1d199 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 23:29:54 +0300 Subject: [PATCH 31/57] scalafmt correction --- .../analysis/DFConditionalAnalysis.scala | 2 +- .../compiler/analysis/DFValAnalysis.scala | 12 +++++------ .../scala/dfhdl/compiler/ir/DFMember.scala | 6 +++--- .../scala/dfhdl/compiler/ir/IntExprCalc.scala | 4 ++-- .../compiler/printing/DFTypePrinter.scala | 2 +- .../stages/ApplyInvertConstraint.scala | 2 +- .../stages/GlobalizePortVectorParams.scala | 4 ++-- .../dfhdl/compiler/stages/NamedAliases.scala | 8 ++++---- .../stages/verilog/VerilogValPrinter.scala | 6 +++--- .../stages/vhdl/VHDLTypePrinter.scala | 20 +++++++++---------- .../compiler/stages/vhdl/VHDLValPrinter.scala | 6 +++--- .../src/main/scala/dfhdl/sim/DFacsimile.scala | 17 ++++++++-------- .../main/scala/dfhdl/sim/SimulationAPI.scala | 2 +- .../scala/dfhdl/sim/SimulationApiSpec.scala | 5 +++-- core/src/main/scala/dfhdl/core/DFBits.scala | 6 +++--- core/src/main/scala/dfhdl/core/DFType.scala | 2 +- core/src/main/scala/dfhdl/core/DFVal.scala | 4 ++-- core/src/main/scala/dfhdl/core/IntParam.scala | 6 +++--- core/src/main/scala/dfhdl/core/ShowType.scala | 14 ++++++------- .../main/scala/dfhdl/internals/helpers.scala | 3 ++- lib/src/main/scala/dfhdl/app/DesignArgs.scala | 16 +++++++-------- lib/src/test/scala/issues/i131.scala | 1 + .../main/scala/plugin/DFHDLTypePrinter.scala | 6 +++--- 23 files changed, 79 insertions(+), 75 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala index d3817bc3a..b94781720 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala @@ -75,7 +75,7 @@ extension [CB <: DFConditional.Block](cb: CB)(using MemberGetSet) .toSet selectorVal.dfType match case _ if complexPattern => None - case dt: DFBitsWL => + case dt: DFBitsWL => if (constSet.exists(_.isBubble)) None // currently not checking don't-care patterns else Some((1 << dt.widthIntOpt.get) == constSet.size) case dec: DFDecimal => 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 6a9b51dc2..34d2769ba 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala @@ -41,10 +41,10 @@ object Eby: def unapply(alias: DFVal.Alias.AsIs)(using MemberGetSet): Option[(DFVal, Int)] = val relVal = alias.relValRef.get val deltaOpt = (alias.dfType, relVal.dfType) match - case (DFUInt(toW), DFUInt(fromW)) => toW.constDiffFrom(fromW) - case (DFSInt(toW), DFSInt(fromW)) => toW.constDiffFrom(fromW) - case (to: DFBitsWL, from: DFBitsWL) => to.widthParamRef.constDiffFrom(from.widthParamRef) - case _ => None + case (DFUInt(toW), DFUInt(fromW)) => toW.constDiffFrom(fromW) + case (DFSInt(toW), DFSInt(fromW)) => toW.constDiffFrom(fromW) + case (to: DFBitsWL, from: DFBitsWL) => to.widthParamRef.constDiffFrom(from.widthParamRef) + case _ => None deltaOpt.filter(_ > 0).map((relVal, _)) // A carry-spelled arithmetic func: a binary `+`/`-`/`*` over two anonymous same-kind widening @@ -425,7 +425,7 @@ extension (dfVal: DFVal) case DFVal.Alias.ApplyIdx.ConstIdx(i) => val maxValueOpt = relVal.dfType match case vector: DFVector => vector.lengthIntOpt - case bits: DFBitsWL => bits.widthIntOpt + case bits: DFBitsWL => bits.widthIntOpt case xInt: DFDecimal => xInt.widthIntOpt case _ => None val padMaxValue = maxValueOpt.getOrElse(100) - 1 @@ -712,7 +712,7 @@ extension (lhs: DFVal)(using MemberGetSet) // total-width ref: for integer decimals the magnitude ref is the total ref (and may be // parametric); fixed-point total widths are always constant def widthRef(v: DFVal): IntParamRef = (v.dfType: @unchecked) match - case dt: DFBitsWL => dt.widthParamRef + case dt: DFBitsWL => dt.widthParamRef case dt: DFDecimal if dt.fractionWidth == 0 => dt.magnitudeWidthParamRef case dt: DFDecimal => IntParamRef(dt.widthUNSAFE) widthRef(lhs).compare(widthRef(rhs))(func) 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 d2692101f..a550f9ef1 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -1148,11 +1148,11 @@ object DFVal: ) extends Partial derives ReadWriter: def elementWidthUNSAFE(using MemberGetSet): Int = dfType.runtimeChecked match case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) => 1 - case DFVector(cellType = cellType) => cellType.widthUNSAFE + case DFVector(cellType = cellType) => cellType.widthUNSAFE def elementWidthIntOpt(using MemberGetSet): Option[Int] = dfType.runtimeChecked match case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) => Some(1) - case DFVector(cellType = cellType) => cellType.widthIntOpt - case _ => None + case DFVector(cellType = cellType) => cellType.widthIntOpt + case _ => None protected def protIsFullyAnonymous(using MemberGetSet): Boolean = relValRef.get.isFullyAnonymous protected def protGetConstData(using MemberGetSet, ConstData.CachePolicy): ConstData[Any] = diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala index d219dc0c8..66b2068ea 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/IntExprCalc.scala @@ -288,7 +288,7 @@ object IntExprCalc: case None => (ref.getIntUNSAFE, Nil) def typeFactors(t: DFType): Option[(Int, List[DFVal])] = t match case _ if t.getRefs.isEmpty => t.widthIntOpt.map((_, Nil)) - case dt: DFBitsWL => Some(paramRefFactors(dt.widthParamRef)) + case dt: DFBitsWL => Some(paramRefFactors(dt.widthParamRef)) case DFXInt(_, widthParamRef, _) => Some(paramRefFactors(widthParamRef)) case vec: DFVector => vec.cellDimParamRefs.foldLeft(typeFactors(vec.cellType)) { (accOpt, dim) => @@ -345,7 +345,7 @@ object IntExprCalc: def linearOfTypeWidth(t: DFType): Option[Linear] = t match case _ if t.getRefs.isEmpty => t.widthIntOpt.map(Linear(Nil, _)) - case dt: DFBitsWL => Some(linearOfParamRef(dt.widthParamRef)) + case dt: DFBitsWL => Some(linearOfParamRef(dt.widthParamRef)) case dec: DFDecimal => Some(DataCalc.addConst(linearOfParamRef(dec.magnitudeWidthParamRef), dec.fractionWidth)) case vec: DFVector => diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala index f3b72c7ea..2b00fe05f 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala @@ -106,7 +106,7 @@ trait AbstractTypePrinter extends AbstractPrinter: final def csDFType(dfType: DFType, typeCS: Boolean = false): String = dfType match case dt: DFBoolOrBit => csDFBoolOrBit(dt, typeCS) - case dt: DFBitsWL => csDFBits(dt, typeCS) + case dt: DFBitsWL => csDFBits(dt, typeCS) case dt: DFDecimal => csDFDecimal(dt, typeCS) case dt: DFEnum => csDFEnum(dt, typeCS) case dt: DFVector => csDFVector(dt, typeCS) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ApplyInvertConstraint.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ApplyInvertConstraint.scala index 5bd847061..232ee5060 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ApplyInvertConstraint.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ApplyInvertConstraint.scala @@ -87,7 +87,7 @@ case object ApplyInvertConstraint extends HierarchyStage: dfc.setName(invertedVarName) ) def invert(dfVal: DFValAny): DFValAny = dfVal.asIR.dfType match - case _: DFBoolOrBit => !dfVal.asValOf[dfhdl.core.DFBit] + case _: DFBoolOrBit => !dfVal.asValOf[dfhdl.core.DFBit] case dfType: DFBitsWL => // we assume constrained ports have known widths val width = dfType.widthIntOpt.get 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 cba57cc8f..62f3e7db1 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala @@ -40,7 +40,7 @@ case object Duplicate4GlobalizePortVectorParams extends ReduplicateDesign: dt.cellDimParamRefs.exists(preCheckIntParamRef) || ( dt.cellType match - case dt: DFBitsWL => + case dt: DFBitsWL => preCheckIntParamRef(dt.widthParamRef) || preCheckIntParamRef(dt.lowIdxRef) case DFUInt(w) => preCheckIntParamRef(w) case DFSInt(w) => preCheckIntParamRef(w) @@ -135,7 +135,7 @@ case object GlobalizePortVectorParams extends HierarchyStage: case dt: DFVector => dt.cellDimParamRefs.foreach(walkIntParamRef) dt.cellType match - case dt: DFBitsWL => + case dt: DFBitsWL => walkIntParamRef(dt.widthParamRef) walkIntParamRef(dt.lowIdxRef) case DFUInt(w) => walkIntParamRef(w) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala index 141eb4a4a..bd0e39f8a 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala @@ -150,9 +150,9 @@ case object NamedVerilogSelection extends NamedAliases: val transparentConversion = (alias.dfType, relVal.dfType) match case (DFUInt(toWidthRef), from: DFBitsWL) => toWidthRef.isSimilarTo(from.widthParamRef) case (to: DFBitsWL, DFUInt(fromWidthRef)) => to.widthParamRef.isSimilarTo(fromWidthRef) - case (DFBit, DFBool) => true - case (DFBool, DFBit) => true - case _ => false + case (DFBit, DFBool) => true + case (DFBool, DFBit) => true + case _ => false if (transparentConversion) relVal.hasVerilogName else false case _ => false @@ -281,7 +281,7 @@ case object NamedVHDLSelection extends NamedAliases: case (_: DFOpaque, _) => relVal.hasVHDLName // type conversions case (DFUInt(_) | DFSInt(_), _: DFBitsWL) => false - case (DFSInt(_), DFUInt(_)) => false + case (DFSInt(_), DFUInt(_)) => false // function calls case _ => true case _: (DFVal.Alias.ApplyRange | DFVal.Alias.ApplyIdx | DFVal.Alias.SelectField) => true 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 bdadbab27..32ca61a9d 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 @@ -417,9 +417,9 @@ protected trait VerilogValPrinter extends AbstractValPrinter: if (printer.allowSignedKeywordAndOps) s"$$signed($extended)" else extended - case (DFInt32, DFUInt(_) | DFSInt(_)) => relValStr - case (DFBit, DFBool | DFEnum(widthParam = 1)) => relValStr - case (DFBool, DFBit | DFEnum(widthParam = 1)) => relValStr + case (DFInt32, DFUInt(_) | DFSInt(_)) => relValStr + case (DFBit, DFBool | DFEnum(widthParam = 1)) => relValStr + case (DFBool, DFBit | DFEnum(widthParam = 1)) => relValStr case (enumType: DFEnum, DFBit | DFBool | (_: DFBitsWL)) => if (printer.allowTypeDef) s"${printer.csDFEnumTypeName(enumType)}'($relValStr)" diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala index 8f390b78b..f0bf619a4 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala @@ -201,9 +201,9 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: def getCellTypeName(dfType: DFVector): String = dfType.cellType match - case DFBit => "sl" - case DFBool => "boolean" - case dt: DFBitsWL => + case DFBit => "sl" + case DFBool => "boolean" + case dt: DFBitsWL => val lowSuffix = if (dt.lowIdxRef.equals(0)) "" else s"_at${csIntParamRef(dt.lowIdxRef)}" s"slv${csIntParamRef(dt.widthParamRef)}$lowSuffix" case DFUInt(widthParamRef) => s"unsigned${csIntParamRef(widthParamRef)}" @@ -290,11 +290,11 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: vecType => vecType.cellType match case _: DFBitsWL => argSel - case DFBit => s"to_sl($argSel)" - case DFBool => s"to_bool($argSel)" - case DFUInt(_) => s"unsigned($argSel)" - case DFSInt(_) => s"signed($argSel)" - case _ => s"to_${getCellTypeName(vecType)}($argSel)", + case DFBit => s"to_sl($argSel)" + case DFBool => s"to_bool($argSel)" + case DFUInt(_) => s"unsigned($argSel)" + case DFSInt(_) => s"signed($argSel)" + case _ => s"to_${getCellTypeName(vecType)}($argSel)", depth => if (depth == 1) cellTypeName match @@ -389,7 +389,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: loopType = dfType.cellType case cellType => val finale = cellType match - case dt: DFBitsWL => + case dt: DFBitsWL => s"(${dt.widthParamRef.hboundCS(dt.lowIdxRef)} downto ${dt.lowIdxRef.refCodeString})" case DFUInt(width) => s"(${width.uboundCS} downto 0)" case DFSInt(width) => s"(${width.uboundCS} downto 0)" @@ -422,7 +422,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: val typeName = csDFStructTypeName(dfType) def to_slv(fromType: DFType, csArg: String): String = fromType match case _: DFBitsWL => csArg - case _ => s"to_slv($csArg)" + case _ => s"to_slv($csArg)" val fieldLengths = dfType.fieldMap.map { (n, t) => s"width := width + bitWidth(A.$n);" }.mkString("\n ") diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala index eb381e624..e07e780bd 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala @@ -138,7 +138,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: // width too) case (Func.Op.length, _) => s"$argStrB'length" case (_, dt: DFDecimal) if !dt.isDFInt32 => s"$argStrB'length" - case (_, _: DFBitsWL) => s"$argStrB'length" + case (_, _: DFBitsWL) => s"$argStrB'length" // every other rendering (integer, std_logic, boolean, enum, record, vector // array, opaque) is covered by the `bitWidth` overload family the printer // already emits (dfhdl_pkg + the per-named-type support functions) @@ -335,9 +335,9 @@ protected trait VHDLValPrinter extends AbstractValPrinter: // def csTimerIsActive(dfVal: Timer.IsActive): String = printer.unsupported def csNOTHING(dfVal: Special): String = dfVal.dfType match - case DFBit => "'Z'" + case DFBit => "'Z'" case _: DFBitsWL => "(others => 'Z')" - case _ => printer.unsupported + case _ => printer.unsupported def csDFValNamed(dfVal: DFVal): String = dfVal match case dcl: DFVal.Dcl => csDFValDcl(dcl) diff --git a/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala b/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala index be2dbcc16..2d4e181c2 100644 --- a/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala +++ b/compiler/stages/src/main/scala/dfhdl/sim/DFacsimile.scala @@ -1139,8 +1139,8 @@ private final class Builder(rawDB: DB): ) case _ => buildApplyIdxNonMem(a, rel) - /** selection indices are absolute, so a low-indexed bit vector's data offsets are - * relative to its low index (nonzero only for explicit BitsHL-constructed types) + /** selection indices are absolute, so a low-indexed bit vector's data offsets are relative to + * its low index (nonzero only for explicit BitsHL-constructed types) */ private def bitsLowOf(t: DFType): Int = t match case b: DFBitsWL => b.lowIdxRef.getIntOpt.getOrElse(0) @@ -1169,10 +1169,10 @@ private final class Builder(rawDB: DB): constIdxOpt(a.relIdx.get) match case Some(i) if undrivenPartialSink(rel) => partialSinkRead(rel.asInstanceOf[DFVal.Dcl], i - low, 1) - case Some(i) => wide.extract(readWV(rel), i - low, 1) - case None if low == 0 => + case Some(i) => wide.extract(readWV(rel), i - low, 1) + case None if low == 0 => wide.dynExtract(readWV(rel), dynBitOffset(a.relIdx.get), 1) - case None => + case None => unsupported("dynamic indexing of a low-indexed bit vector", a) case t => unsupported(s"indexing into $t", a) end match @@ -1186,7 +1186,7 @@ private final class Builder(rawDB: DB): case bt: DFBitsWL if undrivenPartialSink(rel) => partialSinkRead(rel.asInstanceOf[DFVal.Dcl], lo - bitsLowOf(bt), hi - lo + 1) case bt: DFBitsWL => wide.extract(readWV(rel), lo - bitsLowOf(bt), hi - lo + 1) - case t => unsupported(s"range selection on $t", a) + case t => unsupported(s"range selection on $t", a) private def buildSelectField(sf: DFVal.Alias.SelectField): WV = val rel = sf.relValRef.get @@ -1602,6 +1602,7 @@ private final class Builder(rawDB: DB): case None => unsupported("dynamic indexing of a low-indexed bit vector", net) case t => unsupported(s"assignment through indexing into $t", net) + end match case sf: DFVal.Alias.SelectField => val rel = sf.relValRef.get val (dcl, lo0, dyn) = assignTarget(rel, net) @@ -1784,7 +1785,7 @@ private final class Builder(rawDB: DB): val len = widthOfType(vt, ai) / cellW (dcl, lo0 + (len - 1 - constIdxOf(ai.relIdx.get)) * cellW) case bt: DFBitsWL => (dcl, lo0 + constIdxOf(ai.relIdx.get) - bitsLowOf(bt)) - case t => unsupported(s"initial assignment through indexing into $t", ai) + case t => unsupported(s"initial assignment through indexing into $t", ai) case sf: DFVal.Alias.SelectField => val rel = sf.relValRef.get val (dcl, lo0) = lhsTarget(rel) @@ -3006,7 +3007,7 @@ private final class Builder(rawDB: DB): private def widthThroughParams(t: DFType): Option[Int] = given ConstData.CachePolicy = ConstData.CachePolicy.NoCache t match - case b: DFBitsWL => b.widthParamRef.getIntConstData.toOption + case b: DFBitsWL => b.widthParamRef.getIntConstData.toOption case d: DFDecimal => d.magnitudeWidthParamRef.getIntConstData.toOption.map(_ + d.fractionWidth) case v: DFVector => diff --git a/compiler/stages/src/main/scala/dfhdl/sim/SimulationAPI.scala b/compiler/stages/src/main/scala/dfhdl/sim/SimulationAPI.scala index 2f0bce914..7553097e0 100644 --- a/compiler/stages/src/main/scala/dfhdl/sim/SimulationAPI.scala +++ b/compiler/stages/src/main/scala/dfhdl/sim/SimulationAPI.scala @@ -106,7 +106,7 @@ final class Simulation[D <: Design] private[sim] ( throw new IllegalArgumentException(s"cannot resolve a param-dependent width in type:\n$t") )) def rec(t: ir.DFType): ir.DFType = t match - case b: ir.DFBitsWL => + case b: ir.DFBitsWL => b.copy(widthParamRef = lit(b.widthParamRef), lowIdxRef = lit(b.lowIdxRef)) case d: ir.DFDecimal => d.copy(magnitudeWidthParamRef = lit(d.magnitudeWidthParamRef)) case v: ir.DFVector => diff --git a/compiler/stages/src/test/scala/dfhdl/sim/SimulationApiSpec.scala b/compiler/stages/src/test/scala/dfhdl/sim/SimulationApiSpec.scala index a094fd650..6c0948c89 100644 --- a/compiler/stages/src/test/scala/dfhdl/sim/SimulationApiSpec.scala +++ b/compiler/stages/src/test/scala/dfhdl/sim/SimulationApiSpec.scala @@ -1,5 +1,6 @@ package dfhdl.sim import dfhdl.* +// scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} /** The canonical typed-API example (locked decision 10): typed poke with a DFHDL constant, * const-vs-const assertEquals through the SimSpec Compare, and settle-on-peek semantics, i.e. @@ -10,8 +11,8 @@ class Foo(val WIDTH: Int <> CONST) extends RTDesign: val y = Bits(WIDTH) <> OUT y := x -/** a nonzero-low bit vector: selection and partial assignment use ABSOLUTE indices in - * [L, L+W-1], while the underlying data offsets are relative to the low index +/** a nonzero-low bit vector: selection and partial assignment use ABSOLUTE indices in [L, L+W-1], + * while the underlying data offsets are relative to the low index */ class BitsHLFoo extends RTDesign: val i8 = Bits(8) <> IN diff --git a/core/src/main/scala/dfhdl/core/DFBits.scala b/core/src/main/scala/dfhdl/core/DFBits.scala index 9a9441cc8..72dad08d8 100644 --- a/core/src/main/scala/dfhdl/core/DFBits.scala +++ b/core/src/main/scala/dfhdl/core/DFBits.scala @@ -482,7 +482,7 @@ object DFBits: val dfValIR = dfVal.asIR dfValIR.dfType match case _: ir.DFBitsWL => dfValIR.asValOf[DFBits[Int]] - case _ => + case _ => dfValIR.asValAny.bits(using dfc)(using Width.wide).asValOf[DFBits[Int]] end match end valueToBits @@ -1087,7 +1087,7 @@ object DFBits: else val low = lowRef.get (lhs.widthIntParam + low - 1, lhs.widthIntParam + low - updatedWidth) - ).asInstanceOf[(IntParam[Int], IntParam[Int])] + ) .asInstanceOf[(IntParam[Int], IntParam[Int])] DFVal.Alias.ApplyRange(lhs, idxHigh, idxLow).asValTP[DFBits[RW], P] } def lsbits[RW <: IntP](updatedWidth: IntParam[RW])(using @@ -1103,7 +1103,7 @@ object DFBits: else val low = lowRef.get (updatedWidth + low - 1, low) - ).asInstanceOf[(IntParam[Int], IntParam[Int])] + ) .asInstanceOf[(IntParam[Int], IntParam[Int])] DFVal.Alias.ApplyRange(lhs, idxHigh, idxLow).asValTP[DFBits[RW], P] } // ascending part-select (Verilog `lhs[baseIdx +: selWidth]`): diff --git a/core/src/main/scala/dfhdl/core/DFType.scala b/core/src/main/scala/dfhdl/core/DFType.scala index 5b5e03e78..f0037f859 100644 --- a/core/src/main/scala/dfhdl/core/DFType.scala +++ b/core/src/main/scala/dfhdl/core/DFType.scala @@ -264,7 +264,7 @@ object DFType: // total-width ref (and may be parametric) private def widthRef[W <: IntP](dfType: DFTypeW[W])(using ir.MemberGetSet): ir.IntParamRef = dfType.asIR.runtimeChecked match - case dt: ir.DFBitsWL => dt.widthParamRef + case dt: ir.DFBitsWL => dt.widthParamRef case dt: ir.DFDecimal => dt.magnitudeWidthParamRef extension [LW <: IntP](lhs: DFTypeW[LW]) protected[core] def compareWidths[RW <: IntP]( diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index 98b820414..be6b4c1b1 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -695,7 +695,7 @@ object DFVal extends DFValLP: ) val initFileConst = vectorType.cellType.asIR match case _: ir.DFBitsWL => DFVal.Const(vectorType, data) - case cellType => + case cellType => DFVal.Const(vectorType, data.map(cellType.bitsDataToData)) dfVal.initForced(List(initFileConst)) @@ -2078,7 +2078,7 @@ object DFVarOps: val argsBitsIR = argsIR.map { arg => arg.dfType match case _: ir.DFBitsWL => arg - case dfType => DFVal.Alias.AsIs.forced(ir.DFBits(dfType.widthUNSAFE), arg) + case dfType => DFVal.Alias.AsIs.forced(ir.DFBits(dfType.widthUNSAFE), arg) } assignRecur(dfVarsIR, argsBitsIR, 0, Nil) end extension diff --git a/core/src/main/scala/dfhdl/core/IntParam.scala b/core/src/main/scala/dfhdl/core/IntParam.scala index 5b251a626..9a0d5c2bd 100644 --- a/core/src/main/scala/dfhdl/core/IntParam.scala +++ b/core/src/main/scala/dfhdl/core/IntParam.scala @@ -147,9 +147,9 @@ object IntP: type RangeWidth[HI <: IntP, LO <: IntP] = FoldConst2[HI, LO, [X <: Int, Y <: Int] =>> int.+[int.-[X, Y], 1]] - /** `L + W - 1`, the high (absolute) index of a low-indexed bit vector. A single guarded - * fold, since a composition of the guarded operators collapses (see the doc comment at - * the top of this file). + /** `L + W - 1`, the high (absolute) index of a low-indexed bit vector. A single guarded fold, + * since a composition of the guarded operators collapses (see the doc comment at the top of this + * file). */ type HighIdx[W <: IntP, L <: IntP] = FoldConst2[W, L, [X <: Int, Y <: Int] =>> int.-[int.+[X, Y], 1]] diff --git a/core/src/main/scala/dfhdl/core/ShowType.scala b/core/src/main/scala/dfhdl/core/ShowType.scala index b527d6c41..309279b07 100644 --- a/core/src/main/scala/dfhdl/core/ShowType.scala +++ b/core/src/main/scala/dfhdl/core/ShowType.scala @@ -26,14 +26,14 @@ extension [T](using quotes: Quotes)(tpe: quotes.reflect.TypeRepr) s"BitsHL[${w + l - 1}, $l]" case _ => s"BitsHL[${Type.show[W]} + ${Type.show[L]} - 1, ${Type.show[L]}]" tpe.asTypeOf[DFTypeAny] match - case '[DFBit] => "Bit" - case '[DFBool] => "Boolean" - case '[DFBits[w]] => s"Bits[${Type.show[w]}]" - case '[DFBitsWL[w, l]] => showBitsHL[w, l] + case '[DFBit] => "Bit" + case '[DFBool] => "Boolean" + case '[DFBits[w]] => s"Bits[${Type.show[w]}]" + case '[DFBitsWL[w, l]] => showBitsHL[w, l] case '[DFType[ir.DFBitsWL, Args2[w, l]]] => showBitsHL[w, l] - case '[DFUInt[w]] => s"UInt[${Type.show[w]}]" - case '[DFInt32] => "Int" - case '[DFSInt[w]] => s"SInt[${Type.show[w]}]" + case '[DFUInt[w]] => s"UInt[${Type.show[w]}]" + case '[DFInt32] => "Int" + case '[DFSInt[w]] => s"SInt[${Type.show[w]}]" // fixed-point types (non-zero fraction width); UInt/SInt/Int are the zero-fraction // cases already matched above. The magnitude width `m` sits directly in the type's // second parameter, so it binds cleanly here. diff --git a/internals/src/main/scala/dfhdl/internals/helpers.scala b/internals/src/main/scala/dfhdl/internals/helpers.scala index cb1b12013..81f0365ea 100644 --- a/internals/src/main/scala/dfhdl/internals/helpers.scala +++ b/internals/src/main/scala/dfhdl/internals/helpers.scala @@ -394,7 +394,8 @@ lazy val getShellCommand: Option[String] = end getShellCommand lazy val sbtnIsRunning: Boolean = - sbtIsRunning && getShellCommand.exists(cmd => cmd.endsWith("--server") || cmd.endsWith("--detach-stdio")) + sbtIsRunning && + getShellCommand.exists(cmd => cmd.endsWith("--server") || cmd.endsWith("--detach-stdio")) 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/DesignArgs.scala b/lib/src/main/scala/dfhdl/app/DesignArgs.scala index 268fc30b5..d86e3cb21 100644 --- a/lib/src/main/scala/dfhdl/app/DesignArgs.scala +++ b/lib/src/main/scala/dfhdl/app/DesignArgs.scala @@ -34,15 +34,15 @@ case class DesignArg(name: String, value: Any, desc: String)(using dfc: DFC): case _: BigInt => "Int" case dfConst: DFValAny => dfConst.asIR.dfType.runtimeChecked match - case ir.DFBool => "Boolean" - case ir.DFBit => "Bit" - case ir.DFInt32 => "Int" - case ir.DFDouble => "Double" - case ir.DFString => "String" + case ir.DFBool => "Boolean" + case ir.DFBit => "Bit" + case ir.DFInt32 => "Int" + case ir.DFDouble => "Double" + case ir.DFString => "String" case _: ir.DFBitsWL => "Bits" - case ir.DFUInt(_) => "UInt" - case ir.DFSInt(_) => "SInt" - case _ => "" + case ir.DFUInt(_) => "UInt" + case ir.DFSInt(_) => "SInt" + case _ => "" case _ => "" // Raw scalar value used for CLI round-trips: scallop's ValueConverter parses diff --git a/lib/src/test/scala/issues/i131.scala b/lib/src/test/scala/issues/i131.scala index 95b8f8100..88507396d 100644 --- a/lib/src/test/scala/issues/i131.scala +++ b/lib/src/test/scala/issues/i131.scala @@ -25,3 +25,4 @@ import hw.flag.scalaRanges for (i <- 0 until fetch_count.toScalaInt) if ((dict_in(dict_entry_size * (i+1) - 1, dict_entry_size * i) == (idx_r, sym_r)) && (addr_r + d"$i" < entry_count - d"1")) matching(i) := 1 +end DictControl diff --git a/plugin/src/main/scala/plugin/DFHDLTypePrinter.scala b/plugin/src/main/scala/plugin/DFHDLTypePrinter.scala index 2d84bcec7..05ac834ae 100644 --- a/plugin/src/main/scala/plugin/DFHDLTypePrinter.scala +++ b/plugin/src/main/scala/plugin/DFHDLTypePrinter.scala @@ -170,14 +170,14 @@ class DFHDLTypePrinter(_ctx: Context, syms: DFHDLSymbols) extends RefinedPrinter Context ): Option[Text] = (kind, args) match - case (DFBool, _) => Some("Boolean") - case (DFBit, _) => Some("Bit") + case (DFBool, _) => Some("Boolean") + case (DFBit, _) => Some("Bit") case (DFBits, widthTpe :: lowTpe :: Nil) => (constInt(widthTpe), constInt(lowTpe)) match case (_, Some(0)) => Some("Bits[" ~ intPText(widthTpe) ~ "]") case (Some(w), Some(l)) => Some("BitsHL[" ~ (w + l - 1).toString ~ ", " ~ l.toString ~ "]") - case _ => + case _ => val low = intPText(lowTpe) Some("BitsHL[" ~ intPText(widthTpe) ~ " + " ~ low ~ " - 1, " ~ low ~ "]") case (DFDecimal, sign :: IntP(magnitude) :: fraction :: native :: Nil) => From 0a00ea0bc0ff6b8b87d200a7bb6b6eec3fa241a3 Mon Sep 17 00:00:00 2001 From: Oron Date: Fri, 14 Aug 2026 23:32:15 +0300 Subject: [PATCH 32/57] core: a constant-bound BitsHL value conforms to the matching BitsHL[H, L] spelling A `BitsHL[H, L]` spelling with non-literal bounds collapses its width to `Int` at the spelling site (the guarded fold disproves const-ness of a constant singleton), erasing `H` from the type. Two consequences, each fixed at the one place the information still exists: - The type-only spelling in term position (`BitsHL[HI.type, LO.type] <> IN`) had no constructor at all and resolved to an unusable widened value. A no-arg `DFBitsHL.apply` now takes the bounds from the EXPLICIT type arguments via `ValueOf`, the apply site being the one place the high bound survives. - A constructor-form value (`BitsHL(HI, LO) <> IN`) could not convert to the collapsed parameter type `DFBitsWL[Int, LO.type]`: the candidate conversion targeted low-0 `DFBits[Int]` only, and the `fromTC` fallback needs a target type instance no given can produce once `H` is erased. The conversion target is generalized to `DFBitsWL[Int, L]`, the width staying pinned at `Int` so literal-width targets keep the width-checked TC route. The relabel is type-level only; the IR carries the true bounds and drives all checks. Fixes #490 Co-Authored-By: Claude Fable 5 --- .../StagesSpec/PrintCodeStringSpec.scala | 33 +++++++++++++++++++ core/src/main/scala/dfhdl/core/DFBits.scala | 22 +++++++++++-- core/src/test/scala/CoreSpec/DFBitsSpec.scala | 27 +++++++++++++-- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index fcf490b97..ca717655b 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -3811,6 +3811,39 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + test("BitsHL constant bounds: value passed to a matching method parameter") { + val HI: Int <> CONST = 5 + val LO: Int <> CONST = 4 + @inline def fLit(x: BitsHL[5, 4] <> VAL): Bits[2] <> DFRET = x(5, 4) + class CallLit extends RTDesign: + val a = BitsHL(5, 4) <> IN + val o = Bits(2) <> OUT + o <> fLit(a) + assertCodeString( + CallLit(), + """|class CallLit extends RTDesign: + | val a = BitsHL(5, 4) <> IN + | val o = Bits(2) <> OUT + | o <> a(5, 4) + |end CallLit""".stripMargin + ) + @inline def fConst(x: BitsHL[HI.type, LO.type] <> VAL): Bits[2] <> DFRET = x(HI, LO) + class CallConst extends RTDesign: + val b = BitsHL(HI, LO) <> IN + val o = Bits(2) <> OUT + o <> fConst(b) + assertCodeString( + CallConst(), + """|val HI: Int <> CONST = 5 + |val LO: Int <> CONST = 4 + | + |class CallConst extends RTDesign: + | val b = BitsHL(HI, LO) <> IN + | val o = Bits(2) <> OUT + | o <> b(HI, LO) + |end CallConst""".stripMargin + ) + } test("BitsHL constant bounds print as written") { class HLBounds(val HI: Int <> CONST = 5, val LO: Int <> CONST = 4) extends RTDesign: val b = BitsHL(HI, LO) <> OUT diff --git a/core/src/main/scala/dfhdl/core/DFBits.scala b/core/src/main/scala/dfhdl/core/DFBits.scala index 72dad08d8..06a9ec59f 100644 --- a/core/src/main/scala/dfhdl/core/DFBits.scala +++ b/core/src/main/scala/dfhdl/core/DFBits.scala @@ -55,6 +55,17 @@ object DFBitsHL: idxLow.toScalaIntOpt.foreach(lowCheck(_)) ir.DFBitsWL((idxHigh - idxLow + 1).ref, idxLow.ref).asFE[DFBitsHL[H, L]] }(using dfc, CTName("BitsHL constructor")) + // the type-only spelling in term position (e.g. `BitsHL[HI.type, LO.type] <> IN`). The + // bounds are taken from the EXPLICIT type arguments: for non-literal bounds the spelled + // TYPE immediately collapses its width to `Int` (the guarded-fold collapse), erasing `H`, + // so this apply site is the one place the high bound is still recoverable + def apply[H <: IntP & Singleton, L <: IntP & Singleton](using + dfc: DFCG, + h: ValueOf[H], + l: ValueOf[L], + hiloCheck: DFBits.BitsHiLo.CheckNUB[H, L], + lowCheck: Arg.Natural.CheckNUB[L] + ): DFBitsHL[H, L] = apply(IntParam.forced[H](h.value), IntParam.forced[L](l.value)) end DFBitsHL object DFBits: @@ -571,13 +582,18 @@ object DFBits: object TCConv: import DFVal.TCConv - given DFBitsFromCandidateConv[V, RP, IC <: Candidate[V]](using + // the target width is fixed at `Int` (statically unknown), so this relabel-only + // conversion claims exactly the targets no width check can serve; a literal-width + // target falls to the lower-priority `TCConv.fromTC` derivation, which runs the + // width-checked TC. The low index is free: a nonzero-low target arises from a + // `BitsHL` parameter spelling whose non-literal bounds collapsed the width to `Int` + given DFBitsFromCandidateConv[L <: IntP, V, RP, IC <: Candidate[V]](using ic: IC { type OutP = RP } - ): TCConv[DFBits[Int], V] with + ): TCConv[DFBitsWL[Int, L], V] with type OutP = RP def apply(value: V)(using DFC): Out = val dfVal = ic(value) - dfVal.nameInDFCPosition.asValTP[DFBits[Int], RP] + dfVal.nameInDFCPosition.asValTP[DFBitsWL[Int, L], RP] object Compare: import DFVal.Compare diff --git a/core/src/test/scala/CoreSpec/DFBitsSpec.scala b/core/src/test/scala/CoreSpec/DFBitsSpec.scala index 907b90123..52953dafc 100644 --- a/core/src/test/scala/CoreSpec/DFBitsSpec.scala +++ b/core/src/test/scala/CoreSpec/DFBitsSpec.scala @@ -546,9 +546,9 @@ class DFBitsSpec extends DFSpec: |""".stripMargin ) { hl match - case h"12" => - case h"a${bind: B[4]}" => - case _ => + case h"12" => + case h"a${bind: B[4]}" => + case _ => } } test("BitsHL declaration, assignment, and comparison") { @@ -569,4 +569,25 @@ class DFBitsSpec extends DFSpec: val eq = x == y } } + test("BitsHL constant bounds: type-form declaration and conformance") { + val HI: Int <> CONST = 5 + val LO: Int <> CONST = 4 + assertCodeString { + """|val a = BitsHL(HI, LO) <> VAR + |val b = BitsHL(HI, LO) <> VAR + |val o = Bits(2) <> VAR + |o := a(HI, LO) + |b := a + |""".stripMargin + } { + val a = BitsHL(HI, LO) <> VAR + // the type-form declaration takes its bounds from the explicit type arguments + val b = BitsHL[HI.type, LO.type] <> VAR + // a constructor-form value converts to the (width-collapsed) type-form spelling + val x: BitsHL[HI.type, LO.type] <> VAL = a + val o = Bits(2) <> VAR + o := x(HI, LO) + b := a + } + } end DFBitsSpec From 020abeee2ecaf682c8683942793b803e7acb96cc Mon Sep 17 00:00:00 2001 From: Oron Date: Sat, 15 Aug 2026 01:06:43 +0300 Subject: [PATCH 33/57] core: Sig-preserving type arithmetic restores constant-bound BitsHL summoning The collapse of non-literal width types to `Int` (987e0b078) left no type-position spelling from which a constant-bound width operation could be summoned back: a `Struct` field typed `BitsHL[H, L] <> VAL` with constant `Int <> CONST` bounds erased `H` at the spelling site, so the field's `DFType` had no given to serve it. The old Sig-based operations return, now under `IntP.Sig.Ops` (exported by the frontend), keeping TYPE-position arithmetic (`Bits[P1.type - P2.type]`) symbolic as `Sig1`/`Sig2` nodes instead of collapsing. The `Sig` given instances, until now placeholders, reconstruct the operation's `DFConstInt32` from the spelled operand types via `ValueOf`, reusing the value-level `IntParam` operators so the recovered constant matches the constructor-form width tree exactly. `DFBitsHL` is redefined over `Sig.Ops.RangeWidth`, and the `DFBitsWL` type-only given drops its `Singleton` width bound so a Sig-carried width resolves through the same path. Value-level operations stay on the collapsing operators (issue #431 remains fixed); only the type-position spellings are precise. Fixes #491 Co-Authored-By: Claude Fable 5 --- core/src/main/scala/dfhdl/core/DFBits.scala | 22 +++--- core/src/main/scala/dfhdl/core/IntParam.scala | 79 +++++++++++++++++-- core/src/main/scala/dfhdl/hdl.scala | 3 + core/src/test/scala/CoreSpec/DFBitsSpec.scala | 27 +++++++ 4 files changed, 116 insertions(+), 15 deletions(-) diff --git a/core/src/main/scala/dfhdl/core/DFBits.scala b/core/src/main/scala/dfhdl/core/DFBits.scala index 06a9ec59f..ad8dd235f 100644 --- a/core/src/main/scala/dfhdl/core/DFBits.scala +++ b/core/src/main/scala/dfhdl/core/DFBits.scala @@ -26,23 +26,27 @@ object DFBitsWL: summon[Arg.Width.Check[Int]](width) summon[Arg.Natural.Check[Int]](lowIdx) ir.DFBitsWL(ir.IntParamRef(width), ir.IntParamRef(lowIdx)).asFE[DFBitsWL[W, L]] - // the type-only spelling (e.g. a `BitsHL[9, 2] <> VAL` struct field) - given [W <: IntP & Singleton, L <: IntP & Singleton](using - dfc: DFCG, + // the type-only spelling (e.g. a `BitsHL[9, 2] <> VAL` struct field). The width is not + // bounded by `Singleton`: a constant-bound `BitsHL[H, L]` spelling carries its width as an + // `IntP.Sig` node (see `IntP.Sig.Ops`), whose `ValueOf` instance (needing the DFC, hence the + // separate first clause) reconstructs the width operation as a `DFConstInt32` right here + given [W <: IntP, L <: IntP](using + dfc: DFCG + )(using w: ValueOf[W], l: ValueOf[L], widthCheck: Arg.Width.CheckNUB[W], lowCheck: Arg.Natural.CheckNUB[L] ): DFBitsWL[W, L] = trydf { - val width = IntParam.forced(w) - val lowIdx = IntParam.forced(l) + val width = IntParam(w.value) + val lowIdx = IntParam(l.value) width.toScalaIntOpt.foreach(widthCheck(_)) lowIdx.toScalaIntOpt.foreach(lowCheck(_)) ir.DFBitsWL(width.ref, lowIdx.ref).asFE[DFBitsWL[W, L]] }(using dfc, CTName("BitsWL constructor")) end DFBitsWL -type DFBitsHL[H <: IntP, L <: IntP] = DFBitsWL[IntP.RangeWidth[H, L], L] +type DFBitsHL[H <: IntP, L <: IntP] = DFBitsWL[IntP.Sig.Ops.RangeWidth[H, L], L] object DFBitsHL: def apply[H <: IntP, L <: IntP](idxHigh: IntParam[H], idxLow: IntParam[L])(using dfc: DFCG, @@ -55,10 +59,8 @@ object DFBitsHL: idxLow.toScalaIntOpt.foreach(lowCheck(_)) ir.DFBitsWL((idxHigh - idxLow + 1).ref, idxLow.ref).asFE[DFBitsHL[H, L]] }(using dfc, CTName("BitsHL constructor")) - // the type-only spelling in term position (e.g. `BitsHL[HI.type, LO.type] <> IN`). The - // bounds are taken from the EXPLICIT type arguments: for non-literal bounds the spelled - // TYPE immediately collapses its width to `Int` (the guarded-fold collapse), erasing `H`, - // so this apply site is the one place the high bound is still recoverable + // the type-only spelling in term position (e.g. `BitsHL[HI.type, LO.type] <> IN`), + // constructing the type from the EXPLICIT type arguments def apply[H <: IntP & Singleton, L <: IntP & Singleton](using dfc: DFCG, h: ValueOf[H], diff --git a/core/src/main/scala/dfhdl/core/IntParam.scala b/core/src/main/scala/dfhdl/core/IntParam.scala index 9a0d5c2bd..16b027123 100644 --- a/core/src/main/scala/dfhdl/core/IntParam.scala +++ b/core/src/main/scala/dfhdl/core/IntParam.scala @@ -16,13 +16,82 @@ object IntP: val value: DFConstInt32 object Sig: given [S <: Sig](using s: S): ValueOf[S] = ValueOf[S](s) + given [F <: FuncOp, A <: IntP](using + vf: ValueOf[F], + va: ValueOf[A], + dfc: DFC + ): Sig1[F, A] with + val value: DFConstInt32 = + val arg = IntParam(va.value) + vf.value match + case FuncOp.clog2 => arg.clog2.toDFConst + case FuncOp.abs => + given DFC = dfc.anonymize + DFVal.Func(DFInt32, FuncOp.abs, List(arg.toDFConst)) + case op => throw new IllegalArgumentException(s"Unexpected operation: $op") given [F <: FuncOp, L <: IntP, R <: IntP](using - ValueOf[F], - ValueOf[L], - ValueOf[R], - DFC + vf: ValueOf[F], + vl: ValueOf[L], + vr: ValueOf[R], + dfc: DFC ): Sig2[F, L, R] with - val value: DFConstInt32 = ??? + val value: DFConstInt32 = + val lhs = IntParam(vl.value) + val rhs = IntParam(vr.value) + vf.value match + case FuncOp.+ => (lhs + rhs).toDFConst + case FuncOp.- => (lhs - rhs).toDFConst + case FuncOp.`*` => (lhs * rhs).toDFConst + case FuncOp./ => (lhs / rhs).toDFConst + case FuncOp.% => (lhs % rhs).toDFConst + case FuncOp.max => (lhs max rhs).toDFConst + case FuncOp.min => (lhs min rhs).toDFConst + case op => throw new IllegalArgumentException(s"Unexpected operation: $op") + end given + + /** The Sig-preserving spellings of the width algebra, for TYPE-position arithmetic over + * non-literal operands (`BitsHL[H, L]`, `Bits[P1.type - P2.type]`). Unlike the guarded + * operators on [[IntP]] below, which collapse any non-literal operation to `Int`, these keep + * the operation symbolically as a [[Sig1]]/[[Sig2]] node, so the spelled type still names its + * operands and a matching given instance (via [[Sig]]'s `ValueOf`) can reconstruct the + * operation's `DFConstInt32` at the summon site. VALUE-level operations deliberately stay on + * the collapsing operators (see the `IsConstInt2` doc below and issue #431); these spellings + * serve only where a type must be summoned back, most notably a struct field's `DFType` (issue + * #491). + */ + object Ops: + type +[L <: IntP, R <: IntP] <: IntP = (L, R) match + case (Int, Int) => int.+[L, R] + case _ => Sig2[FuncOp.+.type, L, R] + type -[L <: IntP, R <: IntP] <: IntP = (L, R) match + case (Int, Int) => int.-[L, R] + case _ => Sig2[FuncOp.-.type, L, R] + type *[L <: IntP, R <: IntP] <: IntP = (L, R) match + case (Int, Int) => int.*[L, R] + case _ => Sig2[FuncOp.*.type, L, R] + type /[L <: IntP, R <: IntP] <: IntP = (L, R) match + case (Int, Int) => int./[L, R] + case _ => Sig2[FuncOp./.type, L, R] + type %[L <: IntP, R <: IntP] <: IntP = (L, R) match + case (Int, Int) => int.%[L, R] + case _ => Sig2[FuncOp.%.type, L, R] + infix type Max[L <: IntP, R <: IntP] <: IntP = (L, R) match + case (Int, Int) => int.Max[L, R] + case _ => Sig2[FuncOp.max.type, L, R] + infix type Min[L <: IntP, R <: IntP] <: IntP = (L, R) match + case (Int, Int) => int.Min[L, R] + case _ => Sig2[FuncOp.min.type, L, R] + type CLog2[T <: IntP] <: IntP = T match + case Int => int.-[32, NumberOfLeadingZeros[int.-[T, 1]]] + case _ => Sig1[FuncOp.clog2.type, T] + type Abs[T <: IntP] <: IntP = T match + case Int => int.Abs[T] + case _ => Sig1[FuncOp.abs.type, T] + + /** `H - L + 1`, the width of an inclusive bit range, kept symbolic for non-literal bounds. */ + type RangeWidth[H <: IntP, L <: IntP] = +[-[H, L], 1] + end Ops + end Sig sealed trait Sig1[F <: FuncOp, A <: IntP] extends Sig sealed trait Sig2[F <: FuncOp, A <: IntP, B <: IntP] extends Sig diff --git a/core/src/main/scala/dfhdl/hdl.scala b/core/src/main/scala/dfhdl/hdl.scala index fc7f17a71..f838d9664 100644 --- a/core/src/main/scala/dfhdl/hdl.scala +++ b/core/src/main/scala/dfhdl/hdl.scala @@ -57,6 +57,9 @@ object __hdl: val Bits = core.DFBits type BitsHL[H <: IntP, L <: IntP] = core.DFBitsHL[H, L] val BitsHL = core.DFBitsHL + // Sig-preserving TYPE-position width arithmetic (`Bits[P1.type - P2.type]`); value-level + // operations keep the collapsing `IntP` operators + export core.IntP.Sig.Ops.* type UInt[W <: IntP] = core.DFUInt[W] val UInt = core.DFUInt type SInt[W <: IntP] = core.DFSInt[W] diff --git a/core/src/test/scala/CoreSpec/DFBitsSpec.scala b/core/src/test/scala/CoreSpec/DFBitsSpec.scala index 52953dafc..e61e97a9c 100644 --- a/core/src/test/scala/CoreSpec/DFBitsSpec.scala +++ b/core/src/test/scala/CoreSpec/DFBitsSpec.scala @@ -590,4 +590,31 @@ class DFBitsSpec extends DFSpec: b := a } } + test("Bits type-arithmetic spelling over constant params") { + val param1: Int <> CONST = 8 + val param2: Int <> CONST = 4 + assertCodeString { + """|val v = Bits((param1 - param2) + 5) <> VAR + |""".stripMargin + } { + import dfhdl.core.widthIntParam + val v = Bits[param1.type - param2.type + 5] <> VAR + scala.Predef.assert(v.widthIntParam.toScalaIntOpt.get == 9) + } + } + test("BitsHL constant bounds: struct field DFType summon (#491)") { + val HI: Int <> CONST = 5 + val LO: Int <> CONST = 4 + case class const_t(x: BitsHL[HI.type, LO.type] <> VAL) extends Struct + assertCodeString { + """|val p = const_t <> VAR + |val o = Bits(2) <> VAR + |o := p.x(HI, LO) + |""".stripMargin + } { + val p = const_t <> VAR + val o = Bits(2) <> VAR + o := p.x(HI, LO) + } + } end DFBitsSpec From 4c5e66581f0c14017ab3301a350e419785464546 Mon Sep 17 00:00:00 2001 From: Oron Date: Sat, 15 Aug 2026 01:10:17 +0300 Subject: [PATCH 34/57] verilog-to-dfhdl: BitsHL works as a struct field, and the #491 caveat retires #491 is fixed, so a constant-bound BitsHL is usable as a Struct field. The note warning it was not now says what it does instead, with the emitted form. Advances benchmarks to b66331f, which converts the last three non-zero-base fields in the VeeR-EH1 type package. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/verilog-to-dfhdl.md | 6 +++--- benchmarks | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index eb78142b5..d00427b6c 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -305,9 +305,9 @@ and they decide how closely the emitted HDL tracks the gold. - a **parameter needs `BitsHL` only if the method indexes it with the baseline's indices**; anything merely combined (XOR, concat, compare) takes `Bits[W]`, and width-based compatibility passes a `BitsHL` argument straight in; - - constant bounds do not currently unify between the `BitsHL(H, L)` constructor and a - `BitsHL[H.type, L.type]` parameter (DFHDL#490), so a helper indexing a config-ranged signal - needs literal bounds or a `Bits` parameter. + - it works as a `Struct` field too, with literal or constant bounds: + `index: BitsHL[RV_BTB_ADDR_HI.type, RV_BTB_ADDR_LO.type] <> VAL` emits + `logic [RV_BTB_ADDR_HI:RV_BTB_ADDR_LO] index;` inside the packed struct. - **A fully-assigned `VAR` read through a *parameter*-bounded slice is misreported as a latch** (DFHDL#484). A local `Int <> CONST` bound is fine; only a design parameter trips it, and only for a `VAR` (a port or parameter sliced the same way is fine). Where the variable is a pure rename, slice diff --git a/benchmarks b/benchmarks index f37d70ebc..b66331f1a 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit f37d70ebceb77cefbb7f916b982ad0fef17ca7f7 +Subproject commit b66331f1a58fd99981c06522a4e1d558843b4b98 From d04e0bdfac5f72f4ec06abdb9f6a5b894c9d0682 Mon Sep 17 00:00:00 2001 From: Oron Date: Sat, 15 Aug 2026 13:27:12 +0300 Subject: [PATCH 35/57] related domains: an explicit Clk <> IN declares a derived (gated) clock A related domain may now declare its own input clock port: a clock fully synchronous with the clock of its related target (typically a gated version of it), while the reset is still shared through the relation. Identity is the dcl's design-relative name (domain `active` with dcl `clk` identifies as `active_clk`, independent of `@flatten`), so same-named derived clocks of the same origin refer to one clock across the hierarchy. - core: the related-domain check now permits `Clk <> IN` only, with dedicated errors for output/var clocks and any reset dcl. - AddClkRst: resolves derived-clock identities globally, mint-when-driven: if any same-identity dcl is explicitly connected (directly or via a parent's `child.active.clk` by-name selection), a distinct `Clk_` opaque is minted and the magnet flow threads the gated clock by type; otherwise the dcls retype to their origin's opaque and collapse onto the origin clock net (the ungated form). Nested gating chains through derived origins. - ToED: clock resolves to the nearest related-chain member with a clk dcl, reset still resolves through the full chain (honoring includeReset); no `@timing.clock` is moved onto derived clock ports (no create_clock). - DropDomains: by-name selection paths of domain-nested ports follow the port's flattened name. - SanityCheck/MagnetMap: ports nested in domain blocks are legal by-name selection targets and magnet points (Flattened collection), and domain-nested magnet points propagate under their design-relative name. - DB: device-top clock-location check exempts internally-driven clk dcls (generalizes the clk-VAR escape); pbnsToPort opened to the compiler. Tests: driven/collapse/nested/cross-design-unification and applied-twice in AddClkRstSpec; async shared reset, related-of-related, collapse, and includeReset=false in ToEDSpec; a four-level gated-clock threading test in ConnectMagnetsSpec; pass-through naming in AddMagnetsSpec; by-name flattened-path rewrite in DropDomainsSpec; error checks in ElaborationChecksSpec. Docs: "Derived Clocks (Gated Clocks)" in the design-domains guide; skill notes in verilog-to-dfhdl and new-stage. Co-Authored-By: Claude Fable 5 --- .claude/commands/new-stage.md | 17 + .claude/commands/verilog-to-dfhdl.md | 22 ++ .../src/main/scala/dfhdl/compiler/ir/DB.scala | 16 +- .../scala/dfhdl/compiler/ir/MagnetMap.scala | 13 +- .../dfhdl/compiler/stages/AddClkRst.scala | 108 ++++++- .../dfhdl/compiler/stages/DropDomains.scala | 51 ++- .../dfhdl/compiler/stages/SanityCheck.scala | 5 +- .../scala/dfhdl/compiler/stages/ToED.scala | 36 ++- .../test/scala/StagesSpec/AddClkRstSpec.scala | 303 ++++++++++++++++++ .../scala/StagesSpec/AddMagnetsSpec.scala | 38 +++ .../scala/StagesSpec/ConnectMagnetsSpec.scala | 109 +++++++ .../scala/StagesSpec/DropDomainsSpec.scala | 78 +++++ .../src/test/scala/StagesSpec/ToEDSpec.scala | 186 +++++++++++ core/src/main/scala/dfhdl/core/Modifier.scala | 18 +- docs/user-guide/design-domains/index.md | 43 +++ .../test/scala/ElaborationChecksSpec.scala | 46 ++- 16 files changed, 1039 insertions(+), 50 deletions(-) diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index fbabf5b55..c13b54fba 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -1476,6 +1476,23 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) removes that member in the same patch still has to. Literal widths carry no type ref at all, so this only ever shows up on parameter-width designs — write the spec test with a `val W: Int <> CONST` design parameter, not a literal. +34. **Ports can be nested in domain blocks — `Folded` on a design block misses them** — a port dcl + may live inside a `DomainBlock` (every AddClkRst-added domain clk/rst, and a related domain's + derived clock), and a `PortByNameSelect.portNamePath` may be multi-part (`active.clk`). Three + port-shaped assumptions broke on this at once: `SanityCheck.instPortsByNameSet` and + `MagnetMap.viaRMPs` collected `members(MemberView.Folded)` (design-level ports only), and + `DropDomains` renamed the port without rewriting PBNS paths that reference it from parent + designs. When collecting "the ports of a design", use `Flattened` (in the hierarchical model + nested designs are `DFDesignInst` placeholders, so there is no cross-design leakage) and name + ports by `getRelativeName(design)` with dots-to-underscores, which is also what + `ConnectPoint.getName` does. +35. **The magnet stages run in two different orders and must work in both** — in the real backend + pipeline `AddMagnets`/`ConnectMagnets` are first demanded by `DropMagnets`, which sits AFTER + `DropDomains` in `BackendPrepStage`, so magnets connect on the flattened design where domain + ports are already design-level. But `Spec` tests invoke `.addMagnets`/`.connectMagnets` + directly, running them BEFORE any flattening. A magnet-layer change must be validated in both + shapes (a spec test plus a full-pipeline compile), and magnet matching semantics must not + depend on domains having been dropped. --- diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index d00427b6c..11171a4f6 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -148,6 +148,28 @@ port; a `VAR.SHARED` mem is for **multi-ported** RAMs (its clocked writes also l inline and binds an explicit `Clk`/`Rst` port to its deasserted value (both added to `DFacsimile.scala` alongside this port). +## Derived (gated) clock ports - the faithful option + +When the baseline threads gated clocks as ordinary ports (VeeR's `active_clk`, `*_c1_*_clk`) and you +want to **keep** those ports rather than reduce them to enables, declare a related domain with its +own input clock (see "Derived Clocks" in [Design Domains][design-domains]): + +```scala +@hw.constraints.timing.related(this) +val active = new RTDomain: + val clk = Clk <> IN // identifies (and flattens) as `active_clk` + // flops clocked by the gated clock, still reset by the module's shared reset +``` + +Same-named domains+ports of the same origin unify across the hierarchy: if any of them is driven +somewhere (a parent connects an ICG output via `child.active.clk <> g.as(child.active.Clk)`), all of +them thread to it through auto-added `active_clk` pass-through ports; if none is driven, they all +collapse onto the root clock net (`.active_clk(clk)`, the `RV_FPGA_OPTIMIZE` form) while the ports +remain. Only `Clk <> IN` is legal in a related domain (no `OUT`/`VAR`, no `Rst`), and the gating +site is always a parent's connection, never the domain's own design scope (a domain's input port is +externally driven by construction). The reduce-to-enables strategy remains the right call when the +target build ties all derived clocks to the root anyway and the ports are noise. + ## Parameters - beyond the guide Follow [from-verilog][from-verilog] for `Int <> CONST`/`String <> CONST` (they emit as SV 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 4e1feeeff..88fe892fb 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -906,7 +906,7 @@ final case class DB private ( // navigating DOWN to the targeted child design's sub-DB and walking its // namedOwnerMemberTable there. Returns the port with the child sub-DB that // owns it, so callers can resolve the port's domain in the right getSet. - private def pbnsToPort( + private[compiler] def pbnsToPort( pbns: DFVal.PortByNameSelect, ctxSub: DB ): Option[(DFVal.Dcl, DB)] = @@ -2025,7 +2025,8 @@ final case class DB private ( val locationCollisions = mutable.ListBuffer.empty[String] designMemberList.foreach { case (design, members) if design.isDeviceTop => - domainOwnerToSubDB(design).atGetSet { + val designSub = domainOwnerToSubDB(design) + designSub.atGetSet { val locationMap = mutable.Map.empty[String, String] // loc -> portName(idx) // the root-aware designMemberList already includes the design block as // the head of its member list, so iterate `members` directly (the flat @@ -2046,10 +2047,15 @@ final case class DB private ( foundLoc = true case _ => } - val clkIsVar = domainOwnerMemberTable(domainOwner).view.collectFirst { - case dcl: DFVal.Dcl if dcl.isClkDcl => dcl.isVar + // a clk VAR is generated internally, and a clk dcl driven by an internal + // connection is likewise not a device pin (e.g. a related domain's derived + // clock driven by a gated version of its origin clock), so neither needs a + // pin location constraint + val clkIsInternal = domainOwnerMemberTable(domainOwner).view.collectFirst { + case dcl: DFVal.Dcl if dcl.isClkDcl => + dcl.isVar || designSub.connectionTable.connectToVals.contains(dcl) }.getOrElse(false) - if (!foundLoc && !clkIsVar) + if (!foundLoc && !clkIsInternal) errors += s"${domainOwner.getFullName} is missing a clock location constraint" case _ => end match diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala index 94ca78b16..d22077a32 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala @@ -33,10 +33,12 @@ enum ConnectPoint(_dfType: DFType, _dir: DFVal.Modifier.Dir) derives CanEqual: case Via(_, _, designInst, portNamePath) => s"${designInst.getFullName}.$portNamePath" case Direct(dcl) => dcl.getFullName - // TODO: do we need to support creating magnets within domain blocks? + // The name is the point's design-relative path with dots replaced by underscores, so a + // magnet dcl nested in a domain block (e.g. `active.clk`) propagates as `active_clk` when + // AddMagnets mints pass-through ports named after it (a design-level dcl keeps its bare name). def getName(using MemberGetSet): String = this match case Via(_, _, _, portNamePath) => portNamePath.replace('.', '_') - case Direct(dcl) => dcl.getName + case Direct(dcl) => dcl.getRelativeName(dcl.getOwnerDesign).replace('.', '_') // override equals and hashCode to ignore the Via dfType that may be different across different // different connection point hierachies due to the ReachableType mechanism override def equals(that: Any): Boolean = @@ -116,7 +118,10 @@ object MagnetMap: val instPos = inst.meta.position rootDB.subDBs.get(childDesign.ownerRef).iterator.flatMap { childSub => childSub.atGetSet { - childDesign.members(MemberView.Folded).iterator.collect { + // Flattened: a magnet dcl may be nested in a domain block (e.g. a related + // domain's derived clock); nested designs are DFDesignInst placeholders in + // the hierarchical model, so no cross-design leakage + childDesign.members(MemberView.Flattened).iterator.collect { case dcl @ MagnetDcl(_) => val cp = ConnectPoint.Via(inst, dcl) RMP( @@ -142,7 +147,7 @@ object MagnetMap: case dcl @ MagnetDcl(_) => val cp = ConnectPoint.Direct(dcl) val ownerDesign = dcl.getOwnerDesign - RMP(cp, ownerDesign, ownerDesign, ownerDesign.isBlackBox, dcl.getName, + RMP(cp, ownerDesign, ownerDesign, ownerDesign.isBlackBox, cp.getName, dcl.getFullName, dcl.meta.position) } } diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddClkRst.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddClkRst.scala index efcc1c3af..738c30d50 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddClkRst.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddClkRst.scala @@ -17,11 +17,32 @@ import dfhdl.core.{asFE, DFCG} * annotations, unless already explicitly declared. * - Memoizes the opaque clk/rst types by the annotation content tuples (so identical * configurations share the same opaque type). + * - Resolves derived clocks: a related domain may declare its own `Clk <> IN` dcl, a clock that + * is fully synchronous with the clock of its related target (typically a gated version of it), + * while the reset is still shared through the relation. The dcl's identity is its + * design-relative name (domain `active` with dcl `clk` identifies as `active_clk`) paired with + * the origin clock it derives from. When any dcl of an identity is explicitly driven somewhere + * (a gated clock is connected to it), a distinct opaque type `Clk_` is minted for the + * whole identity, so the magnet connection flow threads the gated clock across the hierarchy + * by type (and, through `AddMagnets` pass-through ports, by name). When no dcl of the identity + * is driven anywhere, the dcls are retyped to their origin's opaque type instead, so the + * magnet flow collapses them onto the origin clock net (the ungated form: each derived clock + * port is connected wherever its origin clock connects). * - Generates the sim-driver block for top-level simulation designs. */ case object AddClkRst extends GlobalStage: def dependencies: List[Stage] = List(ToRT, ExplicitClkRstCfg) def nullifies: Set[Stage] = Set(ViaConnection) + + // Identity of a derived clock declared as a `Clk <> IN` dcl inside a related domain: the + // dcl's design-relative name paired with the origin clock it derives from. The origin is + // either a root clock configuration (the related chain ends at an owner carrying a resolved + // `@timing.clock`) or another derived clock (nested gating). + private final case class DerivedClkId(origin: DerivedClkOrigin, relName: String) + derives CanEqual + private enum DerivedClkOrigin derives CanEqual: + case Root(clk: constraints.Timing.Clock) + case Derived(id: DerivedClkId) def transformGlobal(designDB: DB)(using co: CompilerOptions, refGen: RefGen @@ -52,6 +73,15 @@ case object AddClkRst extends GlobalStage: // it with "Default"), so `.get` is safe. def grpName(clk: constraints.Timing.Clock): String = clk.grpName.get + // Mints a fresh Clk opaque type with the given printed name. The magnet ID a fresh + // context assigns is constant, so the printed name is the type's sole identity (two + // mints with the same name yield equal types). + def mintClkType(name: String)(using DFCG): coreDFOpaque[coreDFOpaque.Clk] = + class Unique: + case class Clk() extends coreDFOpaque.Clk: + override lazy val typeName: String = name + coreDFOpaque(Unique().Clk()) + extension (domainOwner: DFDomainOwner) // Only IO constraints are moved from the owner onto the generated clk port — // `@timing.clock` / `@timing.reset` stay on the owner (the domain is the canonical @@ -141,13 +171,9 @@ case object AddClkRst extends GlobalStage: val opaqueDFC = DFCG() val clkTypeOpt: Option[coreDFOpaque[coreDFOpaque.Clk]] = clkAnnotOpt.map { clkAnnot => - val name = grpName(clkAnnot) - class Unique: - case class Clk() extends coreDFOpaque.Clk: - override lazy val typeName: String = s"Clk_${name}" clkTypeMap.getOrElseUpdate( clkAnnot, - coreDFOpaque(Unique().Clk())(using opaqueDFC) + mintClkType(s"Clk_${grpName(clkAnnot)}")(using opaqueDFC) ) } val rstTypeOpt: Option[coreDFOpaque[coreDFOpaque.Rst]] = rstAnnotOpt.map { @@ -323,6 +349,78 @@ case object AddClkRst extends GlobalStage: } } + // Pre-pass: derived (related-domain) clock resolution. Collects every related domain's + // clk dcl with its identity, and every explicitly driven clk dcl (directly connected, or + // connected from a parent design via port-by-name selection). Populates opaqueReplaceMap + // up front so that per-owner processing below retypes the dcls and any values of their + // user opaque types (e.g. `.as(dmn.Clk)` casts of gated clocks). See the stage doc for + // the mint-when-driven / collapse-when-undriven semantics. + val relatedClkDcls = mutable.ListBuffer.empty[(DFVal.Dcl, DerivedClkId)] + val drivenClkDcls = mutable.Set.empty[DFVal.Dcl] // membership queries only + designDB.subDBs.foreach { case (_, subDB) => + subDB.atGetSet { + def relatedTargetOf(owner: DFDomainOwner): Option[DFDomainOwner] = + owner.meta.annotations.collectFirst { + case rel: constraints.Timing.Related => rel.ref.get + } + def clkDclOf(owner: DFDomainOwner): Option[DFVal.Dcl] = + subDB.domainOwnerMemberTable.getOrElse(owner, Nil).collectFirst { + case dcl: DFVal.Dcl if dcl.isClkDcl => dcl + } + def identityOf(owner: DFDomainOwner, dcl: DFVal.Dcl): Option[DerivedClkId] = + originOf(owner).map(origin => + DerivedClkId(origin, dcl.getRelativeName(dcl.getOwnerDesign).replace('.', '_')) + ) + def originOf(owner: DFDomainOwner): Option[DerivedClkOrigin] = + relatedTargetOf(owner) match + case Some(target) => + clkDclOf(target) match + case Some(targetDcl) if relatedTargetOf(target).nonEmpty => + // the target's clk dcl is itself a derived clock (nested gating) + identityOf(target, targetDcl).map(DerivedClkOrigin.Derived.apply) + case _ => originOf(target) + case None => + owner.meta.annotations.collectFirst { + case clk: constraints.Timing.Clock => clk + }.map(DerivedClkOrigin.Root.apply) + subDB.domainOwnerMemberList.foreach { case (owner, members) => + owner.domainType match + case DomainType.RT if relatedTargetOf(owner).nonEmpty => + members.collectFirst { case dcl: DFVal.Dcl if dcl.isClkDcl => dcl }.foreach { + dcl => identityOf(owner, dcl).foreach(id => relatedClkDcls += ((dcl, id))) + } + case _ => + } + subDB.connectionTable.connectToVals.foreach { + case dcl: DFVal.Dcl if dcl.isClkDcl => drivenClkDcls += dcl + case pbns: DFVal.PortByNameSelect => + designDB.pbnsToPort(pbns, subDB).foreach { case (dcl, childSub) => + if (childSub.atGetSet(dcl.isClkDcl)) drivenClkDcls += dcl + } + case _ => + } + } + } + locally { + val drivenIds: Set[DerivedClkId] = + relatedClkDcls.view.collect { case (dcl, id) if drivenClkDcls.contains(dcl) => id }.toSet + val mintedDerived = mutable.Map.empty[DerivedClkId, coreDFOpaque[coreDFOpaque.Clk]] + def chosenTypeOf(id: DerivedClkId): coreDFOpaque[coreDFOpaque.Clk] = + if (drivenIds.contains(id)) + mintedDerived.getOrElseUpdate(id, mintClkType(s"Clk_${id.relName}")(using DFCG())) + else + id.origin match + case DerivedClkOrigin.Root(clkAnnot) => + clkTypeMap.getOrElseUpdate( + clkAnnot, + mintClkType(s"Clk_${grpName(clkAnnot)}")(using DFCG()) + ) + case DerivedClkOrigin.Derived(parentId) => chosenTypeOf(parentId) + relatedClkDcls.foreach { case (dcl, id) => + opaqueReplaceMap += dcl.dfType.asInstanceOf[DFOpaque] -> chosenTypeOf(id).asIR + } + } + // Iterate sub-DBs in elaboration order (top first) so the shared cross-design // state accumulates deterministically; build and apply each sub-DB's patches // under its own getSet. diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDomains.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDomains.scala index 33b1730ee..15877190f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDomains.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDomains.scala @@ -13,6 +13,24 @@ case object DropDomains extends HierarchyStage: def dependencies: List[Stage] = List(ToED) def nullifies: Set[Stage] = Set(DFHDLUniqueNames, SimpleOrderMembers) def transformSubDB(rootDB: DB)(using MemberGetSet, CompilerOptions, RefGen): DB = + // the flattened name of a member owned by `domain`, applying the name flattening mode + // of each domain in the composition chain until reaching a non-domain owner + def flattenedName(name: String, domain: DomainBlock)(using MemberGetSet): String = + var currentDomain: DomainBlock = domain + var currentName = name + var inDomain = true + while (inDomain) + currentDomain.flattenMode match + case FlattenMode.Transparent => // no change + case FlattenMode.Prefix(sep) => + currentName = s"${currentDomain.getName}$sep$currentName" + case FlattenMode.Suffix(sep) => + currentName = s"${currentName}$sep${currentDomain.getName}" + currentDomain.getOwner match + case domain: DomainBlock => currentDomain = domain + case _ => inDomain = false + currentName + end flattenedName val patchList = subDB.membersNoGlobals.flatMap { // all domains are removed and their members referencing them need to point to the owner design case domain: DomainBlock => @@ -21,25 +39,30 @@ case object DropDomains extends HierarchyStage: ) // ignore design block members case designBlock: DFDesignBlock => None + // a by-name selection of a domain-nested port (e.g. a related domain's derived clock, + // selected as `active.clk`) must follow the port's flattened name in the child design + case pbns: DFVal.PortByNameSelect if pbns.portNamePath.contains('.') => + rootDB.pbnsToPort(pbns, subDB).flatMap { case (dcl, childSub) => + val flatName = childSub.atGetSet { + dcl.getOwner match + case domain: DomainBlock => flattenedName(dcl.getName, domain) + case _ => dcl.getName + } + if (flatName == pbns.portNamePath) None + else + Some( + pbns -> Patch.Replace( + pbns.copy(portNamePath = flatName), + Patch.Replace.Config.FullReplacement + ) + ) + } // named members owned by domains could need to change their name depending on the flattening mode // of its domain owner chain case member: DFMember.Named if !member.isAnonymous => member.getOwner match case domain: DomainBlock => - var currentDomain: DomainBlock = domain - var currentName = member.getName - var inDomain = true - // looping through domain composition until reaching a non-domain and applying the name flattening - while (inDomain) - currentDomain.flattenMode match - case FlattenMode.Transparent => // no change - case FlattenMode.Prefix(sep) => - currentName = s"${currentDomain.getName}$sep$currentName" - case FlattenMode.Suffix(sep) => - currentName = s"${currentName}$sep${currentDomain.getName}" - currentDomain.getOwner match - case domain: DomainBlock => currentDomain = domain - case _ => inDomain = false + val currentName = flattenedName(member.getName, domain) // when all domains are transparent then there is no name change if (currentName != member.getName) Some( 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 ad7746f9e..f7658a25d 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala @@ -36,7 +36,10 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends HierarchyStage: rootDB.subDBs.get(StaticRef(childBlock.ownerRef)) match case Some(childSub) => childSub.atGetSet { - childBlock.members(MemberView.Folded).view.collect { + // Flattened: a port may be nested in a domain block (e.g. a related domain's + // derived clock, by-name-selected as `active.clk`); in the hierarchical model + // nested designs are DFDesignInst placeholders, so no cross-design leakage. + childBlock.members(MemberView.Flattened).view.collect { case port: DFVal.Dcl if port.isPort => (inst, port.getRelativeName(childBlock)) }.toList } 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 0d2ef9b1c..5afbc4338 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala @@ -73,18 +73,23 @@ case object ToED extends HierarchyStage: } def lookupClkRst(owner: DFDomainOwner): (Option[DFVal.Dcl], Option[DFVal.Dcl]) = ownerClkRstCache.getOrElseUpdate( - owner, - relatedTarget(owner) match - case Some(target) => lookupClkRst(target) - case None => - val members = subDB.domainOwnerMemberTable(owner) - val clkOpt = members.collectFirst { - case clk: DFVal.Dcl if clk.isClkDcl => clk - } - val rstOpt = members.collectFirst { - case rst: DFVal.Dcl if rst.isRstDcl => rst - } - (clkOpt, rstOpt) + owner, { + val members = subDB.domainOwnerMemberTable(owner) + val ownClkOpt = members.collectFirst { + case clk: DFVal.Dcl if clk.isClkDcl => clk + } + relatedTarget(owner) match + case Some(target) => + // a related domain may declare its own derived clock dcl, which overrides the + // clock resolved through the relation; the reset always resolves through it + val (targetClkOpt, targetRstOpt) = lookupClkRst(target) + (ownClkOpt.orElse(targetClkOpt), targetRstOpt) + case None => + val rstOpt = members.collectFirst { + case rst: DFVal.Dcl if rst.isRstDcl => rst + } + (ownClkOpt, rstOpt) + } ) // the last handled design to know when a design is switched to clear @@ -700,7 +705,12 @@ case object ToED extends HierarchyStage: val alreadyHasClk = dcl.meta.annotations.exists { case _: constraints.Timing.Clock => true; case _ => false } - if (alreadyHasClk) None + // a related domain's own clk dcl is a derived clock (typically gated): it must not + // receive the origin's `@timing.clock` (no `create_clock` on a derived clock port) + val isDerivedClk = dcl.getOwnerDomain.meta.annotations.exists { + case _: constraints.Timing.Related => true; case _ => false + } + if (alreadyHasClk || isDerivedClk) None else resolveTimingOwner(dcl.getOwnerDomain).meta.annotations.collectFirst { case c: constraints.Timing.Clock => c diff --git a/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala b/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala index 3bc4c5597..b8a46f70b 100644 --- a/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala @@ -735,4 +735,307 @@ class AddClkRstSpec extends StageSpec: |""".stripMargin ) } + test("Related domain with a driven derived clock mints a distinct clk type") { + class ID extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + y.din := x + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + class Top extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val gclk = Bit <> IN + val id = ID() + id.x <> x + y <> id.y + id.active.clk <> gclk.as(id.active.Clk) + val top = (new Top).addClkRst + assertCodeString( + top, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class ID extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | y.din := x + | @timing.related(ID) + | val active = new RTDomain: + | val clk = Clk_active_clk <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end active + |end ID + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Top extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val gclk = Bit <> IN + | val id = ID() + | id.x <> x + | y <> id.y + | id.active.clk <> gclk.as(Clk_active_clk) + |end Top + |""".stripMargin + ) + } + test("Related domain with a driven derived clock, applied twice") { + class ID extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + y.din := x + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + class Top extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val gclk = Bit <> IN + val id = ID() + id.x <> x + y <> id.y + id.active.clk <> gclk.as(id.active.Clk) + val top = (new Top).addClkRst.addClkRst + assertCodeString( + top, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class ID extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | y.din := x + | @timing.related(ID) + | val active = new RTDomain: + | val clk = Clk_active_clk <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end active + |end ID + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Top extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val gclk = Bit <> IN + | val id = ID() + | id.x <> x + | y <> id.y + | id.active.clk <> gclk.as(Clk_active_clk) + |end Top + |""".stripMargin + ) + } + test("Related domain with an undriven derived clock collapses to the origin clk type") { + class ID extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + y.din := x + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + val id = (new ID).addClkRst + assertCodeString( + id, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class ID extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | y.din := x + | @timing.related(ID) + | val active = new RTDomain: + | val clk = Clk_default <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end active + |end ID + |""".stripMargin + ) + } + test("Nested derived clocks (gated clock of a gated clock)") { + class ID extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + y.din := x + @hw.constraints.timing.related(this) + val l2 = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + @hw.constraints.timing.related(l2) + val l1 = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + class Top extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val gclk1 = Bit <> IN + val gclk2 = Bit <> IN + val id = ID() + id.x <> x + y <> id.y + id.l2.clk <> gclk1.as(id.l2.Clk) + id.l1.clk <> gclk2.as(id.l1.Clk) + val top = (new Top).addClkRst + assertCodeString( + top, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + |case class Clk_l2_clk() extends Clk + |case class Clk_l1_clk() extends Clk + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class ID extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | y.din := x + | @timing.related(ID) + | val l2 = new RTDomain: + | val clk = Clk_l2_clk <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end l2 + | @timing.related(l2) + | val l1 = new RTDomain: + | val clk = Clk_l1_clk <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end l1 + |end ID + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Top extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val gclk1 = Bit <> IN + | val gclk2 = Bit <> IN + | val id = ID() + | id.x <> x + | y <> id.y + | id.l2.clk <> gclk1.as(Clk_l2_clk) + | id.l1.clk <> gclk2.as(Clk_l1_clk) + |end Top + |""".stripMargin + ) + } + test("Same-named derived clocks unify across designs") { + class Leaf extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + y.din := x + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + class Core extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val leaf = Leaf() + leaf.x <> x + y <> leaf.y + class Top extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val gclk = Bit <> IN + val core = Core() + core.x <> x + y <> core.y + core.active.clk <> gclk.as(core.active.Clk) + val top = (new Top).addClkRst + assertCodeString( + top, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Leaf extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | y.din := x + | @timing.related(Leaf) + | val active = new RTDomain: + | val clk = Clk_active_clk <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end active + |end Leaf + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Core extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | @timing.related(Core) + | val active = new RTDomain: + | val clk = Clk_active_clk <> IN + | end active + | val leaf = Leaf() + | leaf.x <> x + | y <> leaf.y + |end Core + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Top extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val gclk = Bit <> IN + | val core = Core() + | core.x <> x + | y <> core.y + | core.active.clk <> gclk.as(Clk_active_clk) + |end Top + |""".stripMargin + ) + } end AddClkRstSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/AddMagnetsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/AddMagnetsSpec.scala index 45c62e2eb..72f06e364 100644 --- a/compiler/stages/src/test/scala/StagesSpec/AddMagnetsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/AddMagnetsSpec.scala @@ -84,6 +84,44 @@ class AddMagnetsSpec extends StageSpec: |""".stripMargin ) } + test("Domain-nested magnet source: pass-through ports use the design-relative name") { + class Leaf extends EDDesign: + val x = M1 <> IN + val y = Bit <> OUT + process(all): + y :== x.actual + class Mid extends EDDesign: + val leaf = Leaf() + class Top extends EDDesign: + val src = new EDDomain: + val m = M1 <> IN + val mid = Mid() + val top = (new Top).addMagnets + assertCodeString( + top, + """|case class M1() extends Magnet(Bit) + | + |class Leaf extends EDDesign: + | val x = M1 <> IN + | val y = Bit <> OUT + | process(all): + | y :== x.actual + |end Leaf + | + |class Mid extends EDDesign: + | val src_m = M1 <> IN + | val leaf = Leaf() + |end Mid + | + |class Top extends EDDesign: + | val src = new EDDomain: + | val m = M1 <> IN + | end src + | val mid = Mid() + |end Top + |""".stripMargin + ) + } test("Basic hierarchy with bottom-up and THEN top-down magnet propagation") { class Deeper1 extends EDDesign: val u8 = UInt(8) <> IN diff --git a/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala index 151f7b6fa..bd92bffe3 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala @@ -229,5 +229,114 @@ class ConnectMagnetsSpec extends StageSpec: |end Top""".stripMargin ) } + test("Derived clock threads through a pass-through design by name") { + class Leaf extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + y.din := x + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + class Mid extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val leaf = Leaf() + leaf.x <> x + y <> leaf.y + class Core extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val mid = Mid() + mid.x <> x + y <> mid.y + class Top extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val gclk = Bit <> IN + val core = Core() + core.x <> x + y <> core.y + core.active.clk <> gclk.as(core.active.Clk) + val top = (new Top).connectMagnets + assertCodeString( + top, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Leaf extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | y.din := x + | @timing.related(Leaf) + | val active = new RTDomain: + | val clk = Clk_active_clk <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end active + |end Leaf + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Mid extends RTDesign: + | val active_clk = Clk_active_clk <> IN + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val leaf = Leaf() + | leaf.x <> x + | y <> leaf.y + | leaf.active.clk <> active_clk + | leaf.clk <> clk + | leaf.rst <> rst + |end Mid + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Core extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | @timing.related(Core) + | val active = new RTDomain: + | val clk = Clk_active_clk <> IN + | end active + | val mid = Mid() + | mid.x <> x + | y <> mid.y + | mid.active_clk <> active.clk + | mid.clk <> clk + | mid.rst <> rst + |end Core + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Top extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val gclk = Bit <> IN + | val core = Core() + | core.x <> x + | y <> core.y + | core.active.clk <> gclk.as(Clk_active_clk) + | core.clk <> clk + | core.rst <> rst + |end Top + |""".stripMargin + ) + } end ConnectMagnetsSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/DropDomainsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropDomainsSpec.scala index 3237986a2..ce3b1c31f 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropDomainsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropDomainsSpec.scala @@ -113,4 +113,82 @@ class DropDomainsSpec extends StageSpec: |""".stripMargin ) } + test("By-name selection of a domain-nested derived clock follows the flattened name") { + class Leaf extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + y.din := x + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + @hw.constraints.timing.related(this) + @flattenMode.suffix("_") + val slow = new RTDomain: + val clk = Clk <> IN + val w = SInt(16) <> OUT.REG init 0 + w.din := x + class Top extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val gclk1 = Bit <> IN + val gclk2 = Bit <> IN + val leaf = Leaf() + leaf.x <> x + y <> leaf.y + leaf.active.clk <> gclk1.as(leaf.active.Clk) + leaf.slow.clk <> gclk2.as(leaf.slow.Clk) + val top = (new Top).dropDomains + assertCodeString( + top, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk + |case class Clk_slow_clk() extends Clk + | + |class Leaf extends EDDesign: + | @timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val active_clk = Clk_active_clk <> IN + | val active_z = SInt(16) <> OUT + | process(active_clk): + | if (active_clk.actual.rising) + | if (rst.actual == 1) active_z :== sd"16'0" + | else active_z :== x + | end if + | val clk_slow = Clk_slow_clk <> IN + | val w_slow = SInt(16) <> OUT + | process(clk_slow): + | if (clk_slow.actual.rising) + | if (rst.actual == 1) w_slow :== sd"16'0" + | else w_slow :== x + | end if + | process(clk): + | if (clk.actual.rising) + | if (rst.actual == 1) y :== sd"16'0" + | else y :== x + | end if + |end Leaf + | + |class Top extends EDDesign: + | @timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val gclk1 = Bit <> IN + | val gclk2 = Bit <> IN + | val leaf = Leaf() + | leaf.x <> x + | y <> leaf.y + | leaf.active_clk <> gclk1.as(Clk_active_clk) + | leaf.clk_slow <> gclk2.as(Clk_slow_clk) + |end Top + |""".stripMargin + ) + } end DropDomainsSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/ToEDSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ToEDSpec.scala index 85fb2bc68..105133c25 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ToEDSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ToEDSpec.scala @@ -1741,4 +1741,190 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + test("Related domain with a driven derived clock and a shared async reset") { + class IDTop extends EDDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + @hw.constraints.timing.reset(mode = _.async) + val dmn1 = new RTDomain: + val o = SInt(16) <> OUT + o := x.reg(1, init = 0) + @hw.constraints.timing.related(dmn1) + val active = new RTDomain: + val clk = Clk <> IN + val o = SInt(16) <> OUT + o := x.reg(1, init = 0) + y <> dmn1.o + active.o + val id = (new IDTop).toED + assertCodeString( + id, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + | + |class IDTop extends EDDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val dmn1 = new EDDomain: + | @timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val o = SInt(16) <> OUT + | process(clk, rst): + | if (rst.actual == 1) o :== sd"16'0" + | else if (clk.actual.rising) o :== x + | end dmn1 + | val active = new EDDomain: + | val clk = Clk_default <> IN + | val o = SInt(16) <> OUT + | process(clk, dmn1.rst): + | if (dmn1.rst.actual == 1) o :== sd"16'0" + | else if (clk.actual.rising) o :== x + | end active + | y <> (dmn1.o + active.o) + |end IDTop + |""".stripMargin + ) + } + test("Related-of-related uses the nearest derived clock and the origin reset") { + class IDTop extends EDDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val dmn1 = new RTDomain: + val o = SInt(16) <> OUT + o := x.reg(1, init = 0) + @hw.constraints.timing.related(dmn1) + val gated = new RTDomain: + val clk = Clk <> IN + @hw.constraints.timing.related(gated) + val user = new RTDomain: + val o = SInt(16) <> OUT + o := x.reg(1, init = 0) + y <> dmn1.o + user.o + val id = (new IDTop).toED + assertCodeString( + id, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + | + |class IDTop extends EDDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val dmn1 = new EDDomain: + | @timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val o = SInt(16) <> OUT + | process(clk): + | if (clk.actual.rising) + | if (rst.actual == 1) o :== sd"16'0" + | else o :== x + | end if + | end dmn1 + | val gated = new EDDomain: + | val clk = Clk_default <> IN + | end gated + | val user = new EDDomain: + | val o = SInt(16) <> OUT + | process(gated.clk): + | if (gated.clk.actual.rising) + | if (dmn1.rst.actual == 1) o :== sd"16'0" + | else o :== x + | end if + | end user + | y <> (dmn1.o + user.o) + |end IDTop + |""".stripMargin + ) + } + test("Related domain with an undriven derived clock collapses onto the origin clock") { + class IDTop extends EDDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val dmn1 = new RTDomain: + val o = SInt(16) <> OUT + o := x.reg(1, init = 0) + @hw.constraints.timing.related(dmn1) + val active = new RTDomain: + val clk = Clk <> IN + val o = SInt(16) <> OUT + o := x.reg(1, init = 0) + y <> dmn1.o + active.o + val id = (new IDTop).toED + assertCodeString( + id, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + | + |class IDTop extends EDDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val dmn1 = new EDDomain: + | @timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val o = SInt(16) <> OUT + | process(clk): + | if (clk.actual.rising) + | if (rst.actual == 1) o :== sd"16'0" + | else o :== x + | end if + | end dmn1 + | val active = new EDDomain: + | val clk = Clk_default <> IN + | val o = SInt(16) <> OUT + | process(clk): + | if (clk.actual.rising) + | if (dmn1.rst.actual == 1) o :== sd"16'0" + | else o :== x + | end if + | end active + | y <> (dmn1.o + active.o) + |end IDTop + |""".stripMargin + ) + } + test("Related domain with a derived clock and no reset keeps register inits") { + class IDTop extends EDDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val dmn1 = new RTDomain: + val o = SInt(16) <> OUT + o := x.reg(1, init = 0) + @hw.constraints.timing.related(dmn1, includeReset = false) + val active = new RTDomain: + val clk = Clk <> IN + val o = SInt(16) <> OUT + o := x.reg(1, init = 0) + y <> dmn1.o + active.o + val id = (new IDTop).toED + assertCodeString( + id, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + | + |class IDTop extends EDDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val dmn1 = new EDDomain: + | @timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val o = SInt(16) <> OUT + | process(clk): + | if (clk.actual.rising) + | if (rst.actual == 1) o :== sd"16'0" + | else o :== x + | end if + | end dmn1 + | val active = new EDDomain: + | val clk = Clk_default <> IN + | val o = SInt(16) <> OUT init sd"16'0" + | process(clk): + | if (clk.actual.rising) o :== x + | end active + | y <> (dmn1.o + active.o) + |end IDTop + |""".stripMargin + ) + } end ToEDSpec diff --git a/core/src/main/scala/dfhdl/core/Modifier.scala b/core/src/main/scala/dfhdl/core/Modifier.scala index 18db2249e..c9dc3ebc5 100644 --- a/core/src/main/scala/dfhdl/core/Modifier.scala +++ b/core/src/main/scala/dfhdl/core/Modifier.scala @@ -103,10 +103,22 @@ object Modifier: case rel: ir.constraints.Timing.Related => rel.ref.get } match case Some(target) => - throw new IllegalArgumentException( - s"Cannot create a clk/rst in a related domain.\nYou can create the clk/rst in the primary domain `${target.getName}` and reference it here instead." - ) + kind match + // an input clock port is allowed: it declares a derived clock that is + // fully synchronous with the related domain's clock (e.g. a gated + // version of it), while the reset is still shared through the relation + case ir.DFOpaque.Kind.Clk + if modifier.value.isPort && modifier.value.dir == IRModifier.IN => + case ir.DFOpaque.Kind.Clk => + throw new IllegalArgumentException( + s"Only an input clock port (`Clk <> IN`) is allowed in a related domain.\nSuch a clock is derived from (fully synchronous with) the clock of the related domain `${target.getName}`, and is typically driven by a gated version of it." + ) + case _ => + throw new IllegalArgumentException( + s"Cannot create a rst in a related domain.\nA related domain always shares the reset of its related domain `${target.getName}`. To opt out of the reset, use `@timing.related(..., includeReset = false)`." + ) case None => + end match case _ => case _ => case _ => diff --git a/docs/user-guide/design-domains/index.md b/docs/user-guide/design-domains/index.md index c4022fe2f..0d9d4d650 100644 --- a/docs/user-guide/design-domains/index.md +++ b/docs/user-guide/design-domains/index.md @@ -134,6 +134,49 @@ class NoResetRelatedDomain extends RTDesign: val related_reg = UInt(8) <> VAR.REG init 0 // relies on its init value, no reset ``` +#### Derived Clocks (Gated Clocks) +A related domain may declare its own clock port, and only an input clock port (`Clk <> IN`): + +```scala +class GatedDomainDesign extends RTDesign: + val x = UInt(8) <> IN + @timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val r = UInt(8) <> VAR.REG init 0 + r.din := x +``` + +This declares a *derived clock*: a clock that is fully synchronous with the clock of the +related target (same source, same edges, phase-aligned), while the reset (subject to +`includeReset`) is still shared through the relation. The typical use is a gated clock: +the port sets the stage for a parent design to connect a gated version of the origin clock, +yet nothing in this design asserts that gating actually happens; that is the parent's +connectivity decision. Because the domains are related, no clock-domain-crossing discipline +applies between them, and sharing an asynchronous reset across the gated clocks is safe (a +flop whose clock is gated off still sees the reset assertion). + +The identity of a derived clock is its design-relative name: domain `active` with port +`clk` identifies as `active_clk`, which is also its flattened port name. Same-named derived +clocks of the same origin refer to the same clock everywhere in the hierarchy. The compiler +resolves them globally: + +- **Driven somewhere**: when any same-identity port is explicitly connected (e.g. a parent + connects an ICG output via `child.active.clk <> gatedClk.as(child.active.Clk)`), a + distinct clock type `Clk_active_clk` is created, and every same-identity port across the + hierarchy is threaded to that connection through automatically added pass-through ports + (also named `active_clk`). +- **Driven nowhere**: the ports take the origin clock's type, and each is automatically + connected wherever its origin clock connects. This is the ungated form: the derived clock + collapses onto the origin clock net, as in an FPGA build of an ASIC design that removes + clock gating. + +Derived clocks nest: a related domain with its own clock port may itself be the target of +another related domain, whose clock port then derives from the outer derived clock (gating +a gated clock). A related domain without its own clock port that targets a clocked related +domain uses that domain's derived clock, while its reset still resolves through the full +relation chain to the origin. + ### Register Types and Initialization #### Register Declarations vs Aliases diff --git a/lib/src/test/scala/ElaborationChecksSpec.scala b/lib/src/test/scala/ElaborationChecksSpec.scala index 5ef7fcd17..e08762eb6 100644 --- a/lib/src/test/scala/ElaborationChecksSpec.scala +++ b/lib/src/test/scala/ElaborationChecksSpec.scala @@ -496,23 +496,23 @@ class ElaborationChecksSpec extends DesignSpec: |Add a location constraint to the ports by connecting them to a located resource or |by using the `@io` constraint.""".stripMargin ) - test("clk/rst in related domain check"): + test("rst in related domain check"): object Test: @top(false) class Top extends RTDesign: self => @hw.constraints.timing.related(self) val dmn = new RTDomain: - val clk = Clk <> IN + val rst = Rst <> IN end Test import Test.* assertElaborationErrors(Top())( s"""|Elaboration errors found! |DFiant HDL elaboration error! |Position: ${currentFilePos}ElaborationChecksSpec.scala:505:21 - 505:30 - |Hierarchy: Top.clk + |Hierarchy: Top.rst |Operation: `Port/Variable constructor` - |Message: Cannot create a clk/rst in a related domain. - |You can create the clk/rst in the primary domain `Top` and reference it here instead.""".stripMargin + |Message: Cannot create a rst in a related domain. + |A related domain always shares the reset of its related domain `Top`. To opt out of the reset, use `@timing.related(..., includeReset = false)`.""".stripMargin ) test("resource direction mismatch check"): object Test: @@ -1878,5 +1878,41 @@ class ElaborationChecksSpec extends DesignSpec: |Hierarchy: PartialAssign |Message: Found a latch variable `v`. Latches are not allowed under RT domains.""".stripMargin ) + test("output clk in related domain check"): + object Test: + @top(false) class Top extends RTDesign: + self => + @hw.constraints.timing.related(self) + val dmn = new RTDomain: + val clk = Clk <> OUT + end Test + import Test.* + assertElaborationErrors(Top())( + s"""|Elaboration errors found! + |DFiant HDL elaboration error! + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1887:21 - 1887:31 + |Hierarchy: Top.clk + |Operation: `Port/Variable constructor` + |Message: Only an input clock port (`Clk <> IN`) is allowed in a related domain. + |Such a clock is derived from (fully synchronous with) the clock of the related domain `Top`, and is typically driven by a gated version of it.""".stripMargin + ) + test("var clk in related domain check"): + object Test: + @top(false) class Top extends RTDesign: + self => + @hw.constraints.timing.related(self) + val dmn = new RTDomain: + val clk = Clk <> VAR + end Test + import Test.* + assertElaborationErrors(Top())( + s"""|Elaboration errors found! + |DFiant HDL elaboration error! + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1905:21 - 1905:31 + |Hierarchy: Top.clk + |Operation: `Port/Variable constructor` + |Message: Only an input clock port (`Clk <> IN`) is allowed in a related domain. + |Such a clock is derived from (fully synchronous with) the clock of the related domain `Top`, and is typically driven by a gated version of it.""".stripMargin + ) end ElaborationChecksSpec From 9ab1268f54e4d47ebf0c08028bcf0931b13c8601 Mon Sep 17 00:00:00 2001 From: Oron Date: Sat, 15 Aug 2026 14:12:48 +0300 Subject: [PATCH 36/57] core: RTRelatedDomain / RTDerivedClkDomain / RTTransparentDomain shorthands Every RT container now provides three shorthand domain classes, each exactly equivalent to (and manifesting as) a plain RTDomain with the corresponding annotations: - RTRelatedDomain: `@timing.related(this)` of the enclosing container, injected at construction so it precedes any subclass body member. - RTDerivedClkDomain: RTRelatedDomain plus a `val clk = Clk <> IN` derived clock port (built via direct DFVal.Dcl, since core compiles pluginless). - RTTransparentDomain: RTRelatedDomain plus `@flattenMode.transparent`, for regrouping internal logic into related domains with zero naming impact. Being container members, the related target is selected by the instantiation path: `new gated.RTTransparentDomain` relates to `gated` rather than to the enclosing design. PrintCodeStringSpec pins the manifestation of all three and the path-prefixed form; the design-domains guide documents the shorthands and when to use each. Co-Authored-By: Claude Fable 5 --- .../StagesSpec/PrintCodeStringSpec.scala | 52 +++++++++++++++++++ .../src/main/scala/dfhdl/core/Container.scala | 25 +++++++++ docs/user-guide/design-domains/index.md | 45 ++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index ca717655b..4e78bcff0 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -901,6 +901,58 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + test("RTRelatedDomain and RTDerivedClkDomain manifest as plain related RTDomains") { + @hw.constraints.timing.reset() + class IDWithDomains extends RTDesign: + val y = SInt(16) <> OUT + val r = SInt(16) <> VAR.REG init 0 + r.din := r + 1 + val related = new RTRelatedDomain: + val x = SInt(16) <> VAR init 0 + val gated = new RTDerivedClkDomain: + val z = SInt(16) <> VAR.REG init 0 + z.din := z + 1 + val trans = new RTTransparentDomain: + val w = SInt(16) <> VAR init 0 + // path-prefixed shorthand: a domain related to `gated` rather than to the design + val sub = new gated.RTTransparentDomain: + val v = SInt(16) <> VAR init 0 + y := r + related.x + gated.z + trans.w + sub.v + end IDWithDomains + val id = (new IDWithDomains) + assertCodeString( + id, + """| + |@timing.reset() + |class IDWithDomains extends RTDesign: + | val y = SInt(16) <> OUT + | val r = SInt(16) <> VAR.REG init sd"16'0" + | r.din := r + sd"16'1" + | @timing.related(IDWithDomains) + | val related = new RTDomain: + | val x = SInt(16) <> VAR init sd"16'0" + | end related + | @timing.related(IDWithDomains) + | val gated = new RTDomain: + | val clk = Clk <> IN + | val z = SInt(16) <> VAR.REG init sd"16'0" + | z.din := z + sd"16'1" + | end gated + | @timing.related(IDWithDomains) + | @hw.annotation.flattenMode.transparent() + | val trans = new RTDomain: + | val w = SInt(16) <> VAR init sd"16'0" + | end trans + | @timing.related(gated) + | @hw.annotation.flattenMode.transparent() + | val sub = new RTDomain: + | val v = SInt(16) <> VAR init sd"16'0" + | end sub + | y := r + related.x + gated.z + trans.w + sub.v + |end IDWithDomains + |""".stripMargin + ) + } test("Domain related with includeReset = false") { @hw.constraints.timing.reset() class IDWithDomains extends DFDesign: diff --git a/core/src/main/scala/dfhdl/core/Container.scala b/core/src/main/scala/dfhdl/core/Container.scala index 894ccbc80..391b5e730 100644 --- a/core/src/main/scala/dfhdl/core/Container.scala +++ b/core/src/main/scala/dfhdl/core/Container.scala @@ -29,4 +29,29 @@ abstract class DomainContainer[D <: DomainType](domainType: D) extends Container abstract class RTDomainContainer extends DomainContainer(DomainType.RT): final case class Clk() extends DFOpaque.Clk final case class Rst() extends DFOpaque.Rst + // A domain related to its enclosing container, sharing its clock and reset: shorthand for + // annotating the domain with `@timing.related(this)` of the enclosing container. The + // annotation is injected at construction (before any subclass body member elaborates), so + // it manifests exactly like a plain annotated `RTDomain`. + abstract class RTRelatedDomain extends RTDomain: + locally { + import dfc.getSet + val relatedAnnot = dfhdl.hw.constraints.timing.related(RTDomainContainer.this)(using dfc) + containedOwner.asIR.setMeta(m => m.copy(annotations = m.annotations :+ relatedAnnot.asIR)) + } + // A related domain with its own derived clock port (typically driven by a gated version of + // the enclosing container's clock): shorthand for an `RTRelatedDomain` with an explicit + // `val clk = Clk <> IN` declaration. + abstract class RTDerivedClkDomain extends RTRelatedDomain: + val clk = DFVal.Dcl(DFOpaque(Clk()), Modifier.IN)(using dfc.setName("clk")) + // A related domain that flattens transparently (its members keep their bare names, without + // the domain-name prefix): shorthand for an `RTRelatedDomain` additionally annotated with + // `@flattenMode.transparent`. + abstract class RTTransparentDomain extends RTRelatedDomain: + locally { + import dfc.getSet + containedOwner.asIR.setMeta(m => + m.copy(annotations = m.annotations :+ ir.annotation.FlattenMode.Transparent) + ) + } end RTDomainContainer diff --git a/docs/user-guide/design-domains/index.md b/docs/user-guide/design-domains/index.md index 0d9d4d650..ca6791dc2 100644 --- a/docs/user-guide/design-domains/index.md +++ b/docs/user-guide/design-domains/index.md @@ -177,6 +177,51 @@ a gated clock). A related domain without its own clock port that targets a clock domain uses that domain's derived clock, while its reset still resolves through the full relation chain to the origin. +#### Related Domain Shorthands +The most common related target is the enclosing design or domain itself, so every RT +container provides three shorthand domain classes. Each is exactly equivalent to a plain +`RTDomain` with the corresponding annotations, and manifests as such (printing, compilation, +and naming see no difference): + +| Shorthand | Equivalent to | +|---|---| +| `RTRelatedDomain` | `@timing.related(this)` `new RTDomain` | +| `RTDerivedClkDomain` | `RTRelatedDomain` with a `val clk = Clk <> IN` declaration | +| `RTTransparentDomain` | `RTRelatedDomain` with `@flattenMode.transparent` | + +```scala +class Shorthands extends RTDesign: + val related = new RTRelatedDomain: // shares this design's clock and reset + val a = UInt(8) <> VAR.REG init 0 + val gated = new RTDerivedClkDomain: // derived clock port `clk`, shared reset + val b = UInt(8) <> VAR.REG init 0 + val trans = new RTTransparentDomain: // shared clock/reset, transparent naming + val c = UInt(8) <> VAR.REG init 0 // flattens as `c`, not `trans_c` + val sub = new gated.RTRelatedDomain: // path-prefixed: related to `gated`, not to the design + val d = UInt(8) <> VAR.REG init 0 // clocked by gated's derived clock +``` + +The shorthands are members of every RT container, so the related target is selected by the +instantiation path: a bare `new RTRelatedDomain` relates to the enclosing container, while +`new gated.RTRelatedDomain` (or `new gated.RTTransparentDomain`, etc.) relates to the +`gated` domain instead, equivalent to `@timing.related(gated)`. + +When to reach for each: + +- **`RTRelatedDomain`** is the general grouping tool: it scopes a piece of logic under the + same clock and reset without minting a new clock group. Use the annotation form + (`@timing.related(this, includeReset = false)`) when the domain must opt out of the reset. +- **`RTDerivedClkDomain`** declares a derived (typically gated) clock as described in the + previous section; its `clk` port identifies by the domain's name (domain `active` yields + the `active_clk` identity and flattened port name). +- **`RTTransparentDomain`** is useful when a design declares its domain configuration once, + around its ports, and internal logic needs to be regrouped into related domains without + affecting the naming of any internal component: the transparent flattening keeps every + member's own name, so the regrouping leaves ports, signals, and the generated HDL + untouched. (The related variant that also opts out of the reset, e.g. to keep a memory + outside the reset scope, still uses the annotation form: + `@timing.related(this, includeReset = false)` together with `@flattenMode.transparent`.) + ### Register Types and Initialization #### Register Declarations vs Aliases From ed149966dd9ab0e56a371c1e1bca8240bd257ce0 Mon Sep 17 00:00:00 2001 From: Oron Date: Sat, 15 Aug 2026 14:18:00 +0300 Subject: [PATCH 37/57] printer: a design-targeted @timing.related prints a qualified self reference `@timing.related(Foo)` did not re-elaborate: inside the design's own body the bare class name resolves to the companion, and a bare `this` would resolve to the annotated domain when the annotation sits inside a nested domain body. A design target now prints as `Foo.this` (from the design's `dclName`, since the annotation is printed within that class's body); domain targets keep their val-name reference. Co-Authored-By: Claude Fable 5 --- .../main/scala/dfhdl/compiler/ir/annotation.scala | 12 ++++++++++-- .../src/test/scala/StagesSpec/AddClkRstSpec.scala | 14 +++++++------- .../test/scala/StagesSpec/ConnectMagnetsSpec.scala | 4 ++-- .../scala/StagesSpec/ExplicitClkRstCfgSpec.scala | 14 +++++++------- .../scala/StagesSpec/PrintCodeStringSpec.scala | 6 +++--- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/annotation.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/annotation.scala index 86a3e44f0..7bef010a7 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/annotation.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/annotation.scala @@ -441,9 +441,17 @@ object constraints: lazy val getRefs: List[DFRef.TwoWayAny] = List(ref) def copyWithNewRefs(using RefGen): this.type = Related(ref.copyAsNewRef, includeReset).asInstanceOf[this.type] - def codeString(using Printer): String = + def codeString(using printer: Printer): String = + import printer.getSet val extraArgs = if (includeReset) "" else ", includeReset = false" - s"""@timing.related(${ref.refCodeString}$extraArgs)""" + // a design target is the domain's enclosing design, referenced from within its own + // body, so it prints as a qualified self reference (`Foo.this`): the bare class name + // would resolve to the companion, and a bare `this` would resolve to the annotated + // domain when the annotation sits inside a nested domain body + val targetCS = ref.get match + case design: DFDesignBlock => s"${design.dclName}.this" + case _ => ref.refCodeString + s"""@timing.related($targetCS$extraArgs)""" end Related object Related: type Ref = DFRef.TwoWay[DomainBlock | DFDesignBlock, DomainBlock] diff --git a/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala b/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala index b8a46f70b..f2aff7aff 100644 --- a/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala @@ -231,7 +231,7 @@ class AddClkRstSpec extends StageSpec: | val rst = Rst_cfg <> IN | val x = SInt(16) <> IN | val y = SInt(16) <> OUT - | @timing.related(ID) + | @timing.related(ID.this) | val internal = new RTDomain: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT @@ -768,7 +768,7 @@ class AddClkRstSpec extends StageSpec: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT.REG init sd"16'0" | y.din := x - | @timing.related(ID) + | @timing.related(ID.this) | val active = new RTDomain: | val clk = Clk_active_clk <> IN | val z = SInt(16) <> OUT.REG init sd"16'0" @@ -825,7 +825,7 @@ class AddClkRstSpec extends StageSpec: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT.REG init sd"16'0" | y.din := x - | @timing.related(ID) + | @timing.related(ID.this) | val active = new RTDomain: | val clk = Clk_active_clk <> IN | val z = SInt(16) <> OUT.REG init sd"16'0" @@ -873,7 +873,7 @@ class AddClkRstSpec extends StageSpec: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT.REG init sd"16'0" | y.din := x - | @timing.related(ID) + | @timing.related(ID.this) | val active = new RTDomain: | val clk = Clk_default <> IN | val z = SInt(16) <> OUT.REG init sd"16'0" @@ -924,7 +924,7 @@ class AddClkRstSpec extends StageSpec: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT.REG init sd"16'0" | y.din := x - | @timing.related(ID) + | @timing.related(ID.this) | val l2 = new RTDomain: | val clk = Clk_l2_clk <> IN | val z = SInt(16) <> OUT.REG init sd"16'0" @@ -998,7 +998,7 @@ class AddClkRstSpec extends StageSpec: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT.REG init sd"16'0" | y.din := x - | @timing.related(Leaf) + | @timing.related(Leaf.this) | val active = new RTDomain: | val clk = Clk_active_clk <> IN | val z = SInt(16) <> OUT.REG init sd"16'0" @@ -1013,7 +1013,7 @@ class AddClkRstSpec extends StageSpec: | val rst = Rst_default <> IN | val x = SInt(16) <> IN | val y = SInt(16) <> OUT - | @timing.related(Core) + | @timing.related(Core.this) | val active = new RTDomain: | val clk = Clk_active_clk <> IN | end active diff --git a/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala index bd92bffe3..471353a87 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala @@ -277,7 +277,7 @@ class ConnectMagnetsSpec extends StageSpec: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT.REG init sd"16'0" | y.din := x - | @timing.related(Leaf) + | @timing.related(Leaf.this) | val active = new RTDomain: | val clk = Clk_active_clk <> IN | val z = SInt(16) <> OUT.REG init sd"16'0" @@ -308,7 +308,7 @@ class ConnectMagnetsSpec extends StageSpec: | val rst = Rst_default <> IN | val x = SInt(16) <> IN | val y = SInt(16) <> OUT - | @timing.related(Core) + | @timing.related(Core.this) | val active = new RTDomain: | val clk = Clk_active_clk <> IN | end active diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitClkRstCfgSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitClkRstCfgSpec.scala index 980ab98c3..6d00ddb49 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitClkRstCfgSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitClkRstCfgSpec.scala @@ -255,12 +255,12 @@ class ExplicitClkRstCfgSpec extends StageSpec(stageCreatesUnrefAnons = true): |class IDTop extends RTDesign: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT - | @timing.related(IDTop) + | @timing.related(IDTop.this) | val dmn1 = new RTDomain: | val id = ID() | id.x <> x | end dmn1 - | @timing.related(IDTop) + | @timing.related(IDTop.this) | val dmn2 = new RTDomain: | val id = ID() | id.x <> dmn1.id.y @@ -310,12 +310,12 @@ class ExplicitClkRstCfgSpec extends StageSpec(stageCreatesUnrefAnons = true): |class IDTop extends RTDesign: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT - | @timing.related(IDTop) + | @timing.related(IDTop.this) | val dmn1 = new RTDomain: | val id = ID() | id.x <> x | end dmn1 - | @timing.related(IDTop) + | @timing.related(IDTop.this) | val dmn2 = new RTDomain: | val id = ID() | id.x <> dmn1.id.y @@ -369,12 +369,12 @@ class ExplicitClkRstCfgSpec extends StageSpec(stageCreatesUnrefAnons = true): |class IDTop extends RTDesign: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT - | @timing.related(IDTop) + | @timing.related(IDTop.this) | val dmn1 = new RTDomain: | val id = ID() | id.x <> x | end dmn1 - | @timing.related(IDTop) + | @timing.related(IDTop.this) | val dmn2 = new RTDomain: | val id = ID() | id.x <> dmn1.id.y @@ -576,7 +576,7 @@ class ExplicitClkRstCfgSpec extends StageSpec(stageCreatesUnrefAnons = true): | val x = SInt(16) <> IN | val y = SInt(16) <> OUT | val clkGen = ClkGen() - | @timing.related(ID) + | @timing.related(ID.this) | val internal = new RTDomain: | val x = SInt(16) <> IN | val y = SInt(16) <> OUT diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index 4e78bcff0..d69aa5439 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -928,17 +928,17 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): | val y = SInt(16) <> OUT | val r = SInt(16) <> VAR.REG init sd"16'0" | r.din := r + sd"16'1" - | @timing.related(IDWithDomains) + | @timing.related(IDWithDomains.this) | val related = new RTDomain: | val x = SInt(16) <> VAR init sd"16'0" | end related - | @timing.related(IDWithDomains) + | @timing.related(IDWithDomains.this) | val gated = new RTDomain: | val clk = Clk <> IN | val z = SInt(16) <> VAR.REG init sd"16'0" | z.din := z + sd"16'1" | end gated - | @timing.related(IDWithDomains) + | @timing.related(IDWithDomains.this) | @hw.annotation.flattenMode.transparent() | val trans = new RTDomain: | val w = SInt(16) <> VAR init sd"16'0" From 5fc8c8bc76fb89ea30ff9611afec6bbddecc70e1 Mon Sep 17 00:00:00 2001 From: Oron Date: Sat, 15 Aug 2026 14:50:00 +0300 Subject: [PATCH 38/57] core: rename RTTransparentDomain to RTRegion A region is a scoping construct rather than a domain in its own right: it places logic under a timing context with no observable footprint, neither a clock identity nor a naming one. The name follows the construct's use site, where its value lives: `new active.RTRegion: ` opens a region of the `active` domain. Dropping the `Domain` suffix is deliberate; the two shorthands that create a grouping with a footprint keep it. The docs section is reframed accordingly (two domain shorthands plus one scoping construct) and gains "The Domain-and-Regions Pattern": declare a timing context once (e.g. `new RTDerivedClkDomain {}`) and open sparse regions of it wherever pieces of logic naturally live, none of them paying a naming cost, so code order follows the dataflow rather than the clock grouping. Co-Authored-By: Claude Fable 5 --- .../StagesSpec/PrintCodeStringSpec.scala | 6 +- .../src/main/scala/dfhdl/core/Container.scala | 10 ++- docs/user-guide/design-domains/index.md | 76 ++++++++++++++----- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index d69aa5439..5da5c9c50 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -901,7 +901,7 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } - test("RTRelatedDomain and RTDerivedClkDomain manifest as plain related RTDomains") { + test("RTRelatedDomain, RTDerivedClkDomain, and RTRegion manifest as plain related RTDomains") { @hw.constraints.timing.reset() class IDWithDomains extends RTDesign: val y = SInt(16) <> OUT @@ -912,10 +912,10 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): val gated = new RTDerivedClkDomain: val z = SInt(16) <> VAR.REG init 0 z.din := z + 1 - val trans = new RTTransparentDomain: + val trans = new RTRegion: val w = SInt(16) <> VAR init 0 // path-prefixed shorthand: a domain related to `gated` rather than to the design - val sub = new gated.RTTransparentDomain: + val sub = new gated.RTRegion: val v = SInt(16) <> VAR init 0 y := r + related.x + gated.z + trans.w + sub.v end IDWithDomains diff --git a/core/src/main/scala/dfhdl/core/Container.scala b/core/src/main/scala/dfhdl/core/Container.scala index 391b5e730..8436ae7a1 100644 --- a/core/src/main/scala/dfhdl/core/Container.scala +++ b/core/src/main/scala/dfhdl/core/Container.scala @@ -44,10 +44,12 @@ abstract class RTDomainContainer extends DomainContainer(DomainType.RT): // `val clk = Clk <> IN` declaration. abstract class RTDerivedClkDomain extends RTRelatedDomain: val clk = DFVal.Dcl(DFOpaque(Clk()), Modifier.IN)(using dfc.setName("clk")) - // A related domain that flattens transparently (its members keep their bare names, without - // the domain-name prefix): shorthand for an `RTRelatedDomain` additionally annotated with - // `@flattenMode.transparent`. - abstract class RTTransparentDomain extends RTRelatedDomain: + // A scoping construct rather than a domain in its own right: a region groups logic under + // this container's timing context with no observable footprint, neither a clock identity + // nor a naming one (its members keep their bare names). Equivalent to an `RTRelatedDomain` + // additionally annotated with `@flattenMode.transparent`. Typically used path-prefixed, + // opening sparse regions of a domain declared once: `new active.RTRegion: `. + abstract class RTRegion extends RTRelatedDomain: locally { import dfc.getSet containedOwner.asIR.setMeta(m => diff --git a/docs/user-guide/design-domains/index.md b/docs/user-guide/design-domains/index.md index ca6791dc2..ec818cd1f 100644 --- a/docs/user-guide/design-domains/index.md +++ b/docs/user-guide/design-domains/index.md @@ -177,17 +177,17 @@ a gated clock). A related domain without its own clock port that targets a clock domain uses that domain's derived clock, while its reset still resolves through the full relation chain to the origin. -#### Related Domain Shorthands +#### Related Domain Shorthands and Regions The most common related target is the enclosing design or domain itself, so every RT -container provides three shorthand domain classes. Each is exactly equivalent to a plain -`RTDomain` with the corresponding annotations, and manifests as such (printing, compilation, -and naming see no difference): +container provides two shorthand domain classes and one scoping construct. Each is exactly +equivalent to a plain `RTDomain` with the corresponding annotations, and manifests as such +(printing, compilation, and naming see no difference): -| Shorthand | Equivalent to | +| Construct | Equivalent to | |---|---| | `RTRelatedDomain` | `@timing.related(this)` `new RTDomain` | | `RTDerivedClkDomain` | `RTRelatedDomain` with a `val clk = Clk <> IN` declaration | -| `RTTransparentDomain` | `RTRelatedDomain` with `@flattenMode.transparent` | +| `RTRegion` | `RTRelatedDomain` with `@flattenMode.transparent` | ```scala class Shorthands extends RTDesign: @@ -195,32 +195,68 @@ class Shorthands extends RTDesign: val a = UInt(8) <> VAR.REG init 0 val gated = new RTDerivedClkDomain: // derived clock port `clk`, shared reset val b = UInt(8) <> VAR.REG init 0 - val trans = new RTTransparentDomain: // shared clock/reset, transparent naming - val c = UInt(8) <> VAR.REG init 0 // flattens as `c`, not `trans_c` + val region = new RTRegion: // shared clock/reset, no naming footprint + val c = UInt(8) <> VAR.REG init 0 // flattens as `c`, not `region_c` val sub = new gated.RTRelatedDomain: // path-prefixed: related to `gated`, not to the design val d = UInt(8) <> VAR.REG init 0 // clocked by gated's derived clock ``` -The shorthands are members of every RT container, so the related target is selected by the +All three are members of every RT container, so the related target is selected by the instantiation path: a bare `new RTRelatedDomain` relates to the enclosing container, while -`new gated.RTRelatedDomain` (or `new gated.RTTransparentDomain`, etc.) relates to the -`gated` domain instead, equivalent to `@timing.related(gated)`. +`new gated.RTRelatedDomain` (or `new gated.RTRegion`, etc.) relates to the `gated` domain +instead, equivalent to `@timing.related(gated)`. -When to reach for each: +The two domain shorthands create a grouping with a footprint of its own: - **`RTRelatedDomain`** is the general grouping tool: it scopes a piece of logic under the - same clock and reset without minting a new clock group. Use the annotation form + same clock and reset without minting a new clock group, and its members flatten with the + domain-name prefix. Use the annotation form (`@timing.related(this, includeReset = false)`) when the domain must opt out of the reset. - **`RTDerivedClkDomain`** declares a derived (typically gated) clock as described in the previous section; its `clk` port identifies by the domain's name (domain `active` yields the `active_clk` identity and flattened port name). -- **`RTTransparentDomain`** is useful when a design declares its domain configuration once, - around its ports, and internal logic needs to be regrouped into related domains without - affecting the naming of any internal component: the transparent flattening keeps every - member's own name, so the regrouping leaves ports, signals, and the generated HDL - untouched. (The related variant that also opts out of the reset, e.g. to keep a memory - outside the reset scope, still uses the annotation form: - `@timing.related(this, includeReset = false)` together with `@flattenMode.transparent`.) + +An **`RTRegion`** is deliberately the opposite: a scoping construct with no observable +footprint of its own, neither a clock identity nor a naming one. It places logic under a +timing context while leaving every member's own name (and therefore the generated HDL) +untouched, which is what makes it useful where a design declares its domain configuration +once, around its ports, and internal logic is later regrouped without renaming anything. +(The variant that also opts out of the reset, e.g. to keep a memory outside the reset +scope, still uses the annotation form: `@timing.related(this, includeReset = false)` +together with `@flattenMode.transparent`.) + +##### The Domain-and-Regions Pattern +Regions unfold their full value path-prefixed. The common pattern declares a timing context +exactly once as a named domain, and then opens sparse regions of it wherever pieces of +logic naturally live in the code, with none of them paying a naming cost: + +```scala +class Core extends RTDesign: + val start = Bit <> IN + // the gated clock context, declared once + val active = new RTDerivedClkDomain {} + + // ... free-running logic ... + val busy = Bit <> OUT.REG init 0 + busy.din := start || busy + + // a piece of logic in the gated context, at its natural code location + val ctrl = new active.RTRegion: + val state = UInt(8) <> VAR.REG init 0 + state.din := state + 1 + + // ... more free-running logic ... + + // another sparse region of the same context + val datapath = new active.RTRegion: + val acc = UInt(8) <> VAR.REG init 0 + acc.din := acc + ctrl.state +``` + +Every region's registers are clocked by `active`'s derived clock and reset by the design's +shared reset, yet `state` and `acc` flatten under their own names, exactly as if the design +had a single domain. The regions can be scattered freely between free-running logic, so the +code order follows the design's dataflow rather than its clock grouping. ### Register Types and Initialization From c44e65ed674f99863345fd26d4cc008ea57aa512 Mon Sep 17 00:00:00 2001 From: Oron Date: Sat, 15 Aug 2026 15:09:11 +0300 Subject: [PATCH 39/57] verilog-to-dfhdl: derived-clock composition, Vec index order, and the yosys equiv blind spot Lessons from porting `dec_gpr_ctl` (bumped in the benchmarks submodule): - The domain-and-regions composition for a derived clock. Registers placed directly in an `RTDerivedClkDomain` take the domain-name prefix, and marking that same domain transparent is worse: it strips the prefix from the `clk` dcl too, which then collides with the design's own `clk` and the pair emits as `clk_0`/`clk_1`, renaming the design's clock port. The clock goes in a named domain and the logic in `RTRegion`s of it, placed where the baseline declares those flops. Reach the members with `import`; `export` is rejected outright because the region's type is anonymous. - `BitsHL` covers a non-zero-base bit *range*, not an array: a `Vec` is 0-based, so a baseline `[31:1]` array is indexed shifted by one, and the shift belongs at the `Vec` subscript alone. - A `Bits` never compares against a Scala `Int`; the compare needs `.uint`. - A parameter that is a pure function of another belongs in the body, where it emits as a `localparam` in the parameter port list. Its derivation must be transcribed rather than simplified, since guards like `(N == 1) ? 1 : $clog2(N)` exist to avoid a zero width. Plus a new section on proving a port against its baseline with yosys: - `read_slang` instead of `read_verilog`, because yosys cannot parse the `'{default: ...}` assignment pattern the emitter uses for a vector reset (yosys#6120, filed upstream). - A `Vec` and a Verilog packed array flatten in opposite order, so `equiv_make` pairs those aggregates by name and mispairs them bit for bit; rename them out of the way and pair the state through canonical-order taps added to both wrappers. - `equiv_simple`/`equiv_induct` ignore the CLK net. Verified rather than assumed: tying a derived clock to `1'b0` in both wrappers still reports "Equivalence successfully proven" against a gate whose flop was moved to the root clock. Moving a flop between clocks is therefore not a valid negative control. Co-Authored-By: Claude Opus 5 --- .claude/commands/verilog-to-dfhdl.md | 107 +++++++++++++++++++++++++++ benchmarks | 2 +- 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index 11171a4f6..c5e59b8eb 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -161,6 +161,29 @@ val active = new RTDomain: // flops clocked by the gated clock, still reset by the module's shared reset ``` +**Declare the clock once, then open regions of it** — the domain-and-regions pattern in +[Design Domains][design-domains]. Putting registers directly in the `RTDerivedClkDomain` prefixes +every one of them with the domain name (`gpr_bank_id` becomes `active_gpr_bank_id`), and adding +`@flattenMode.transparent` to that same domain is worse: it strips the prefix from the `clk` dcl +too, which then collides with the design's own `clk` and the pair emits as `clk_0`/`clk_1`, +renaming the design's clock port. An `RTRegion` has no naming footprint, so it carries the logic: + +```scala +val active = new RTDerivedClkDomain {} // declares the `active_clk` port, nothing else +val bankid = new active.RTRegion: + val gpr_bank_id = Bits(1) <> VAR.REG init all(0) // flattens as `gpr_bank_id` + if (wen_bank_id) gpr_bank_id.din := wr_bank_id +import bankid.gpr_bank_id // scope for the rest of the design body +``` + +Regions are sparse and scattered by design, so **put each one where the baseline declares those +flops** rather than collecting a module's gated logic into one block: the transcription keeps the +gold's statement order, and the emitted HDL is identical either way. + +Use **`import`, not `export`** to reach the members afterwards: `export` is rejected outright +(*"not accessible"*) because the region's type is anonymous, and scope is all that is wanted here — +an `import` adds no member and no net. + Same-named domains+ports of the same origin unify across the hierarchy: if any of them is driven somewhere (a parent connects an ICG output via `child.active.clk <> g.as(child.active.Clk)`), all of them thread to it through auto-added `active_clk` pass-through ports; if none is driven, they all @@ -199,6 +222,13 @@ Follow [from-verilog][from-verilog] for `Int <> CONST`/`String <> CONST` (they e - Note that writing `.toScalaInt` is not what pins a parameter, and dropping it does not unpin one: the *read* pins it, and an elaboration-time loop reads its bound either way. Removing a redundant `.toScalaInt` is a readability fix, not a genericity fix. +- **A parameter that is a pure function of another belongs in the body, not the signature.** If + every use is a width or slice bound and the parent computes it from a sibling parameter, declare + it as a body `Int <> CONST`; it emits as a `localparam int` in the parameter port list with the + expression intact, and the module can no longer be instantiated with an inconsistent pair. Two + consequences: **transcribe the derivation exactly** — `$clog2(1)` is 0, so a baseline's + `(N == 1) ? 1 : $clog2(N)` is a zero-width guard, and "simplifying" it to `clog2(N)` is a real + bug — and a formal harness must stop passing the value, since a `localparam` cannot be overridden. - **`all(0)` for an explicitly-typed constant default** — `val CCM_SADR: Bits[32] <> CONST = all(0)` rather than spelling out `h"32'00000000"`. - **DFacsimile rejects `String <> CONST`** (the minimum tier can't resolve a `DFString` const's @@ -330,6 +360,21 @@ and they decide how closely the emitted HDL tracks the gold. - it works as a `Struct` field too, with literal or constant bounds: `index: BitsHL[RV_BTB_ADDR_HI.type, RV_BTB_ADDR_LO.type] <> VAL` emits `logic [RV_BTB_ADDR_HI:RV_BTB_ADDR_LO] index;` inside the packed struct. +- **`BitsHL` covers a non-zero-base *bit range* only; a non-zero-base *array* has no counterpart.** + `logic [31:1][31:0] gpr_out` is 31 words indexed 1..31, and a DFHDL `Vec` is always 0-based + (`Bits(32) X 31`), so the baseline's index `j` reaches it as `j - 1`. Keep the baseline's own loop + bounds and put the `- 1` at the `Vec` subscript alone, so every other appearance of `j` — the + address compare, the `BitsHL` write-enable bit — still reads like the gold: + ```scala + for (j <- 1 until 32) + w0v(j) <> wen0 & (waddr0.uint == j) // BitsHL: absolute + gpr_in(j - 1) <> (w0v(j).repeat(32) & wd0) | ... // Vec: shifted + ``` + This one is not cosmetic; see the flat-order trap in the verification section. +- **A `Bits` never compares against a Scala `Int`** ("An integer value cannot be a candidate for a + Bits type"). The baseline's `addr[4:0] == 5'(j)` is an unsigned compare, so it transcribes as + `addr.uint == j`. That is the operator, not an intermediate, so it stays inline at each use — the + "convert once at the definition" rule applies to a value the baseline itself names. - **A fully-assigned `VAR` read through a *parameter*-bounded slice is misreported as a latch** (DFHDL#484). A local `Int <> CONST` bound is fine; only a design parameter trips it, and only for a `VAR` (a port or parameter sliced the same way is fine). Where the variable is a pure rename, slice @@ -362,6 +407,68 @@ and they decide how closely the emitted HDL tracks the gold. Replace with **observation output ports** (e.g. an `o_halt` pulse instead of `$finish`) and/or a **synthesizable stand-in** design; note every deviation in the file header. +## Proving a port against its baseline (yosys) + +The ladder itself (combinational miter, `equiv_make`/`equiv_simple`/`equiv_induct` for sequential, +`async2sync`, the mandatory negative control) belongs in the port's own plan. These are the parts +that are about **DFHDL's output specifically** and recur in every port: + +- **Read the DFHDL output with `read_slang`, not `read_verilog`** (`yosys -m slang`, plugin shipped + with OSS CAD Suite). yosys's own frontend rejects the assignment pattern DFHDL emits to reset a + vector — `gpr_out <= '{default: '{default: 32'h0}};` — with *"syntax error, unexpected + TOK_DEFAULT"* (yosys#6120). It is the **`default:` key** that has no grammar rule, not the nesting + and not unpacked arrays: the positional form `'{a, b, c, d}` parses, while every keyed form fails + (packed, unpacked, declaration initializer, nested). The construct is legal SystemVerilog and + Verilator accepts it, so this is a frontend gap, not something to work around in the design. + Since the emitter uses it for *any* vector-wide constant, expect it in every module with a reset + array. slang also elaborates only the parameterizations actually instantiated, which + incidentally fixes a baseline whose `generate` arm `$error`s under its *default* parameters + (`rvdffe`'s "width must be >= 8"); `read_verilog -defer` is the equivalent for the gold. +- **A `Vec` and a Verilog packed array flatten in opposite order.** DFHDL packs `Vec` index 0 at the + MSB; Verilog packs the *highest* index of `[31:1]` at the MSB. So a `Vec` holding the same 31 + registers is bit-reversed against the baseline's aggregate — harmless in hardware, fatal to + `equiv_make`, which pairs public wires **by name** and will pair those two 992-bit wires + bit-for-bit and wrongly. Every register then reports unproven and the failure looks like a design + bug. Fix it in the harness, not the design: + ```tcl + cd # `rename` needs the module selected, or "Object not found" + rename \u.gpr_out \u.gpr_out_vecorder # unpair the reversed aggregates + rename \u.gpr_in \u.gpr_in_vecorder + cd .. + ``` +- **Add canonical-order state taps when the flop names differ.** With the aggregates unpaired, and + with the gold's flops buried under `rvdff` instance paths (`u.gpr_banks[0].gpr[7]...dffs.dout`) + while the gate's are slices of one `Vec` wire, induction has no internal anchor. Give **both** + wrappers the same extra outputs, each side reading its own layout: + ```systemverilog + output logic [31:1][31:0] dbg; + for (genvar j = 1; j < 32; j++) assign dbg[j] = u.gpr_out[0][j]; // gold + for (genvar j = 1; j < 32; j++) assign dbg[j] = u.gpr_out[0][j-1]; // gate, Vec is 0-based + ``` + Extra observation points are extra proof obligations, so they can only make the check stronger — + they cannot manufacture a false pass. +- **A dropped derived-clock port needs a gold wrapper**, not an edit to the gold. Wrap the baseline + with the DFHDL port list and tie the derived clock to the root (`u (.*, .active_clk(clk))`); give + the gate an identically-named wrapper so `equiv_make` still pairs the ports. +- **`equiv_simple`/`equiv_induct` ignore the CLK net.** They model every `$dff` as advancing one + step per cycle no matter which net drives it, so **the proof says nothing about which clock a + flop sits on**. Verified, not assumed: tying a derived clock to constant `1'b0` in both wrappers — + so the gold's flop can never clock — still reports *"Equivalence successfully proven"* against a + gate whose flop was moved to the root clock. Consequences: + - Moving a flop between a derived clock and the root clock is **not a valid negative control**. + Use a data-path or enable mutation instead (dropping the `if (en)` on the derived-clock flop is + a good one: it targets that domain and goes red). + - **Tie the derived clock to the root clock in both wrappers** rather than leaving it a free + input. It cannot make the check weaker (the tool ignores it either way), it matches the build + being verified, and it puts the assumption in the harness instead of leaving it implicit in the + tool's semantics. + - Clock *assignment* is therefore checked by reading the emitted `always_ff` sensitivity lists and + the parent's connection, not by the proof. A build where derived clocks are genuinely gated + would need `clk2fflogic`, which models clocks explicitly. +- **Identical `stat` cell counts across gold and gate** (same `$aldff`/`$and`/`$eq`/`$or` totals) is + a fast structural sanity check before spending minutes in `equiv_induct`, and it is what tells you + an "unproven" result is a *pairing* problem rather than a logic one. + ## Simulating ported designs (DFacsimile) The typed sim API does **not** expose the implicit reset magnet as `dut.rst`. DFacsimile applies all diff --git a/benchmarks b/benchmarks index b66331f1a..f60c52f6d 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit b66331f1a58fd99981c06522a4e1d558843b4b98 +Subproject commit f60c52f6d13e0594b3ed9787c7b7d32472036018 From b39c45a4560f154e2436907fd06cc6eba217782e Mon Sep 17 00:00:00 2001 From: Oron Date: Sat, 15 Aug 2026 17:55:00 +0300 Subject: [PATCH 40/57] DropWholeVecAssign: element-wise lowering of whole-vector drives (#492) The pre-SystemVerilog Verilog dialects have no unpacked-array assignment, so a `Vec` reset emitted a PACKED replication into an UNPACKED array (`mem <= {4{8'h00}}`). iverilog and yosys reject it outright; verilator reads it as an assignment pattern initializing element 0 only, which would be silent wrong hardware rather than a syntax error. The new stage lowers a whole-vector drive into an element-wise one, in three rules: a declaration's `init` becomes an `initial` block, a connection becomes per-cell connections, and an assignment is unrolled where it stands. It runs for verilog.v95/v2001, and for any backend under the new `dropWholeVecAssign` option (`--drop-whole-vec-assign`), and sits before `DropStructsVecs` in `BackendPrepStage` so the vector type is still there to unroll. That ordering is deliberately NOT a dependency: it would drag the whole pre-backend pipeline into every direct `.dropStructsVecs` invocation. Only a declaration whose vector type actually reaches the backend is lowered, which under the old dialects means a block-ram variable; anything else is flattened to `Bits` and its whole-vector drive is already legal. An anonymous composition (`all(x)`, a `Vector(a, b, c)` concatenation) is taken apart into its own operands and needs no constant; any other source is taken apart by selecting cell by cell, which does. A plain vector-to-vector drive is left alone. Only the outermost dimension unrolls: below it the cell type is either flattened to `Bits` or, in SystemVerilog, legal as an array literal. A uniform source loops over the declaration's OWN element-count parameter, so a parametric length stays parametric. Rule 2 emits connections rather than the `process(all)` the issue's shape suggests: the only sources it lowers read nothing, so that process would carry an empty sensitivity list and never trigger ("@* found no sensitivities" under iverilog). Rule 1 is Verilog-only, VHDL having no `initial` construct and no need of the lowering. The Verilog printer's own vector-init workaround is removed rather than kept as a fallback: a whole-vector init that reaches a dialect which cannot inline it is now `unsupported`, since the stage owns the lowering. Three latent backend bugs the stage exposed, each fixed and pinned: - A `for` loop iterator was declared inside the unnamed procedural block ("Variable declaration in unnamed block requires SystemVerilog"). Iterators now join the module's declaration region, like the process-local declarations already there. This broke any user loop in a v95/v2001 process. - A named process printed as `myblock : always_comb`. Verilog names a BLOCK, so the label belongs on the `begin`: `initial begin : mem_init`. - `DropProcessAll` derived its explicit sensitivity list by walking block kinds it enumerates, and loop blocks were missing, so a signal read only inside a `for` body never reached `always @(...)`. Loop bodies, `while` guards and `for` ranges are now walked. And one that the loop fix in turn exposed: an ARRAY cannot be named in a Verilog event control (it takes expressions, and `@*` is undefined over arrays besides being absent from v95), so `always @(mem)` was rejected by iverilog and yosys alike. A constant-index read now contributes just that cell, which is both precise and nameable; only a dynamic-index or whole read falls back to listing every cell, `@(mem[0] or mem[1] or ...)`, the form synthesis has always required here. VHDL names the array signal itself and is left alone. Verified against iverilog, verilator and yosys on v95 and v2001 (and verilator on sv2009 under the option), with the AES cipher simulation re-run across every tool/dialect combination. No reference output changed. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/new-stage.md | 56 +++ .../compiler/stages/BackendPrepStage.scala | 1 + .../compiler/stages/DropProcessAll.scala | 111 ++++-- .../compiler/stages/DropStructsVecs.scala | 6 + .../compiler/stages/DropWholeVecAssign.scala | 298 ++++++++++++++++ .../stages/verilog/VerilogOwnerPrinter.scala | 39 +- .../stages/verilog/VerilogValPrinter.scala | 30 +- .../scala/dfhdl/options/CompilerOptions.scala | 17 +- .../scala/StagesSpec/DropProcessAllSpec.scala | 80 +++++ .../StagesSpec/DropWholeVecAssignSpec.scala | 337 ++++++++++++++++++ .../StagesSpec/PrintVerilogCodeSpec.scala | 168 ++++++++- docs/user-guide/command-line/index.md | 1 + lib/src/main/scala/dfhdl/app/DFApp.scala | 4 +- .../scala/dfhdl/app/ParsedCommandLine.scala | 6 + 14 files changed, 1074 insertions(+), 80 deletions(-) create mode 100644 compiler/stages/src/main/scala/dfhdl/compiler/stages/DropWholeVecAssign.scala create mode 100644 compiler/stages/src/test/scala/StagesSpec/DropWholeVecAssignSpec.scala diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index c13b54fba..d5ca4e44e 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -1493,6 +1493,37 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) directly, running them BEFORE any flattening. A magnet-layer change must be validated in both shapes (a spec test plus a full-pipeline compile), and magnet matching semantics must not depend on domains having been dropped. +36. **Ordering between two stages is expressed by their positions in `BackendPrepStage`, not + always by `dependencies`** — `StageRunner` walks a `BundleStage`'s dependency list in order, so + listing stage A before stage B in `BackendPrepStage` is enough to run A first. Adding A to + `B.dependencies` instead drags A's WHOLE dependency chain into every direct `.b` invocation — + including `Spec`, whose self-contained `DFDesign` inputs suddenly arrive post-`ToED` and + whose every expected code string breaks. Reach for `dependencies` only when B genuinely cannot + run without A, and say in a comment why the ordering lives where it does. +37. **A lowering that reads only constants must not become a `process(all)`** — `always @(*)` / + `process(all)` derives its sensitivity from what the body READS, so a body that reads only + constants gets an EMPTY sensitivity list and never triggers (`iverilog: @* found no + sensitivities so it will never trigger`; verilator and yosys are silent, so it reaches + hardware as a permanently undriven signal). Emit the drive as a CONNECTION instead (still + continuous, still concurrent), or as an `initial` block if the semantics allow. Relatedly, a + connection can never live inside a procedural `for` loop, so a loop-shaped lowering of a + connection has to unroll. +38. **A `lengthIntOpt` / `widthUNSAFE` read of a parametric type silently hardcodes the default** + — a design parameter resolves to its DEFAULT value there, so a loop bound or slice built from + it is correct only for an un-overridden instantiation. Build the bound from the type's own + parameter instead (`vecType.cellDimParamRefs.head.get.cloneAnonValueAndDepsHere.toDFConst`), + which prints as the parameter name. Reserve the resolved Int for what genuinely needs + unrolling, and pin the difference with a spec test on a `val N: Int <> CONST` design. +39. **Moving a read into a nested block can drop it from the sensitivity list** — + `DropProcessAll` (v95 / vhdl.v93) derives an explicit sensitivity list by walking a + `process(all)`'s statements, and its walker enumerates block kinds explicitly. Loop blocks were + missing from that match, so a signal read only inside a `for`/`while` body silently never + reached `always @(...)`. If your stage relocates reads into a block kind, check that walker + covers it — nothing else will tell you, since the output is legal HDL that simply never + re-evaluates. Relatedly, a Verilog event control takes EXPRESSIONS and an array name is not + one, so an array sensitivity item has to be listed cell by cell + (`@(mem[0] or mem[1] or ...)`); `@*` is undefined over arrays in the standard and absent from + v95 entirely. VHDL names the array signal itself, so the expansion is Verilog-only. --- @@ -1718,6 +1749,31 @@ Relatedly, a new `Func.Op` whose result is constant over a NON-constant argument argument TYPE's width params, product-base equivalence so `vec.width` matches `W * N`), or every symbolic width-equivalence check against such an expression fails at elaboration. +### Emitting a `for` loop / `initial` block inside a MetaDesign + +A `DFRange`'s `using DFC` comes FIRST (`DFRange(using dfc)(start, end, op)`), and the iterator +declaration is created in the ENCLOSING owner, before the block: + +```scala +import dfhdl.core.get // `get` on an `IntParamRef`; keep it local so `DFRef.get` stays unambiguous +val iter = dfhdl.core.DFVal.Dcl.iterator(using dfc.setName(s"${dcl.getName}_i")) +val end = vecType.cellDimParamRefs.head.get(using dfc.anonymize) + .cloneAnonValueAndDepsHere(using dfc.anonymize).toDFConst(using dfc.anonymize) +val range = dfhdl.core.DFRange(using dfc.anonymize)( + dfhdl.core.DFConstInt32(0)(using dfc.anonymize), end, ir.DFRange.Op.Until +) +dfc.enterOwner(dfhdl.core.DFFor.Block(iter, range)(using dfc.anonymize)) +// ... body ... +dfc.exitOwner() +``` + +An `initial` block is `dfhdl.core.Process.Block.initial(using dfc.setName("..."))` + +`enterOwner`/`exitOwner` (`Process.Block.all` / `.list` for the sensitivity-bearing forms). Naming +the block prints as `val = initial:` in DFHDL and as a Verilog block label. Do NOT reuse the +name of a value the MetaDesign body also binds with `val`: `MetaDesign` extends `Design` and +`reflect.Selectable`, so a `val length` (or any name a `Design` already carries) collides with the +inherited member and fails to compile with an ambiguity error. + ### Compile-time constant evaluation of values `dfVal.getConstDataThroughParams[Any]` returns `Some(data)` when the (possibly substituted) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala index 0ceb061ff..47066b549 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala @@ -14,6 +14,7 @@ case object BackendPrepStage DropForkJoinsED, DropLocalBlocksED, ApplyInvertConstraint, + DropWholeVecAssign, DropStructsVecs, MatchToIf, SimplifyMatchSel, diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropProcessAll.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropProcessAll.scala index 77dc15d90..97d80f41e 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropProcessAll.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropProcessAll.scala @@ -4,6 +4,7 @@ import dfhdl.compiler.analysis.* import dfhdl.compiler.ir.* import dfhdl.compiler.patching.* import dfhdl.options.CompilerOptions +import dfhdl.core.DFType.asFE import dfhdl.compiler.stages.vhdl.VHDLDialect import dfhdl.compiler.stages.verilog.VerilogDialect import dfhdl.compiler.ir.ProcessBlock.Sensitivity @@ -40,6 +41,23 @@ case object DropProcessAll extends HierarchyStage: case VHDLDialect.v93 => true case _ => false case _ => true // verilog v95 (the only verilog dialect passing runCondition) + // see `sensitivityItems`: only Verilog cannot name an array in an event control + val expandArrayItems = co.backend.isVerilog + // A CONSTANT-index cell selection of an array declaration. It is exactly what the process is + // sensitive to, so it is preferred over the array it selects from: precise (an array read at + // one fixed index does not sensitize the process to the other cells), and directly nameable + // in a Verilog event control, which the array itself is not. VHDL can name the array, and + // does, so this only narrows the Verilog lists. + object ConstArrayCell: + def unapply(dfVal: DFVal)(using MemberGetSet): Option[DFVal.Dcl] = + if (!expandArrayItems) None + else + dfVal match + case applyIdx: DFVal.Alias.ApplyIdx if applyIdx.relIdx.get.isConst => + applyIdx.relValRef.get match + case dcl: DFVal.Dcl if dcl.dfType.isInstanceOf[DFVector] => Some(dcl) + case _ => None + case _ => None def hasPhantomCall(pb: ProcessBlock): Boolean = pb.members(MemberView.Flattened).exists { case DFVal.Func.Call(_, key) => @@ -52,11 +70,16 @@ case object DropProcessAll extends HierarchyStage: .collect { case pb @ ProcessBlock(sensitivity = Sensitivity.All) if dropAllProcesses || hasPhantomCall(pb) => - // recursively through value dependents + // recursively through value dependents. A constant-index array cell selection is + // where the walk STOPS: the cell alone is what the process is sensitive to, and + // continuing would pull in the whole array (see `ConstArrayCell`). def getDFValDependents(dfVal: DFVal): collection.View[DFVal] = - dfVal.getRefs.view.filterNot(_.isInstanceOf[DFRef.TypeRef]).map(_.get).collect { - case dfVal: DFVal => dfVal - }.flatMap(getDFValDependents).++(Some(dfVal)) + dfVal match + case ConstArrayCell(_) => collection.View(dfVal) + case _ => + dfVal.getRefs.view.filterNot(_.isInstanceOf[DFRef.TypeRef]).map(_.get).collect { + case dfVal: DFVal => dfVal + }.flatMap(getDFValDependents).++(Some(dfVal)) // recursively through internal conditional block members def getBlockDependents(block: DFBlock): collection.View[DFVal] = val members = subDB.blockMemberTable(block) @@ -72,36 +95,80 @@ case object DropProcessAll extends HierarchyStage: case textOut: TextOut => textOut.getRefs.view.map(_.get).collect { case dfVal: DFVal => dfVal } case cb: DFConditional.Block => getBlockDependents(cb) ++ cb.getGuardOption - case _ => None + // a loop body's statements are the process's statements too, so what they read + // belongs in the sensitivity list — as does what decides how often the loop + // runs (a `while` guard, a `for` range) + case wb: DFLoop.DFWhileBlock => + getBlockDependents(wb) ++ Some(wb.guardRef.get) + case fb: DFLoop.DFForBlock => + val range = fb.rangeRef.get + getBlockDependents(fb) ++ + List(range.startRef.get, range.endRef.get, range.stepRef.get) + case _ => None }.flatMap(getDFValDependents) end getBlockDependents // memoization of added port-by-name val addedCPs = mutable.Set.empty[ConnectPoint] - // get all dependent declarations (except local variables) + // Each sensitivity item, paired with the declaration it ultimately reads. They differ + // only for an array cell selection, where the item is the cell and the declaration is + // the array: the filters below are about WHERE that declaration lives, never about + // where the selection expression itself sits. val dcls = - ListSet.from(getBlockDependents(pb).flatMap(_.departialPBNS.map(_._1))) + ListSet.from(getBlockDependents(pb).flatMap { + case cell @ ConstArrayCell(arrayDcl) => Some(cell -> (arrayDcl: DFVal)) + case dfVal => dfVal.departialPBNS.map(root => (root._1: DFVal) -> (root._1: DFVal)) + }) // filter out local variables, but keep port-by-name which may be inside the process, // but refer to vias outside of it. we also need to account that different PBNS are // considered to be different values, so we use `addedCPs` to only add one port-by-name per connect point. - .view.filter { - // HDL method call ports are not signals — the call's actual reads are - // collected through the call's input connections instead - case pbns: DFVal.PortByNameSelect - if pbns.getDesignInst.getDesignBlock.isHDLMethod => - false - case pbns: DFVal.PortByNameSelect => - val cp = ConnectPoint.Via(pbns) - if (addedCPs.contains(cp)) false - else - addedCPs += cp - true - case v => !v.isInsideOwner(pb) - }.toList + .view.filter { (_, root) => + root match + // HDL method call ports are not signals — the call's actual reads are + // collected through the call's input connections instead + case pbns: DFVal.PortByNameSelect + if pbns.getDesignInst.getDesignBlock.isHDLMethod => + false + case pbns: DFVal.PortByNameSelect => + val cp = ConnectPoint.Via(pbns) + if (addedCPs.contains(cp)) false + else + addedCPs += cp + true + case v => !v.isInsideOwner(pb) + }.map(_._1).toList val dsn = new MetaDesign( pb, Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement) ): - val updatedDcls = dcls.map(_.cloneAnonValueAndDepsHere.asValAny) + // An array read at a NON-constant index (or read whole) leaves the array itself as + // the item, and a Verilog event control cannot name one: it takes expressions, and + // an array name is not one — with no `@*` in v95 to fall back on. Such an item is + // listed cell by cell, `@(mem[0] or mem[1] or ...)`, the form synthesis has always + // required, and sensitizing to every cell is right whatever index is read. A VHDL + // sensitivity list takes the array signal itself, so it is left whole there. + // + // The cells are never arrays in turn: every dialect needing this stage has had + // `DropStructsVecs` flatten the cell type to `Bits` already. + def sensitivityItems(dcl: DFVal.Dcl): List[dfhdl.core.DFValAny] = + dcl.dfType match + case vecType: DFVector if expandArrayItems => + vecType.lengthIntOpt match + case Some(vecLength) => + List.tabulate(vecLength) { i => + dfhdl.core.DFVal.Alias.ApplyIdx( + vecType.cellType.asFE[dfhdl.core.DFTypeAny], + dcl.asValAny, + dfhdl.core.DFConstInt32(i)(using dfc.anonymize) + )(using dfc.anonymize) + } + case None => List(dcl.asValAny) + case _ => List(dcl.asValAny) + // only a NAMED declaration expands: each cell selection references it again, and + // an anonymous value may be read exactly once + val updatedDcls = dcls.flatMap { + case dcl: DFVal.Dcl => sensitivityItems(dcl) + case other => List(other.cloneAnonValueAndDepsHere.asValAny) + } dfhdl.core.Process.Block.list(updatedDcls)(using dfc.setMeta(pb.meta)) dsn.patch diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala index f37c284e8..e2fd266ad 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropStructsVecs.scala @@ -24,6 +24,12 @@ case object DropStructsVecs extends GlobalStage: case VerilogDialect.v95 | VerilogDialect.v2001 => true case _ => false case _ => false + // NOTE: `DropWholeVecAssign` must run BEFORE this stage (it unrolls whole-vector constant drives + // while the vector type is still there to unroll, and the flattening below would otherwise turn + // its cell selections into variable-bound part-selects). That ordering is expressed by their + // relative positions in `BackendPrepStage`, and NOT as a dependency here: `DropWholeVecAssign` + // depends on `ToED`, which would drag the whole pre-backend pipeline into every direct + // `.dropStructsVecs` invocation (its spec included). override def dependencies: List[Stage] = List(ExplicitRomVar) override def nullifies: Set[Stage] = Set(DropUnreferencedAnons) def transformGlobal(designDB: DB)(using co: CompilerOptions, refGen: RefGen): DB = diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropWholeVecAssign.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropWholeVecAssign.scala new file mode 100644 index 000000000..3edff0dde --- /dev/null +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropWholeVecAssign.scala @@ -0,0 +1,298 @@ +package dfhdl.compiler.stages + +import dfhdl.compiler.analysis.* +import dfhdl.compiler.ir +import dfhdl.compiler.ir.* +import dfhdl.compiler.patching.* +import dfhdl.options.CompilerOptions +import dfhdl.compiler.stages.verilog.VerilogDialect +import dfhdl.core.DFType.asFE +import dfhdl.core.{DFTypeAny, asValAny, cloneAnonValueAndDepsHere} +import DFVal.Func.Op as FuncOp + +//format: off +/** Lowers a WHOLE-vector constant drive into an element-wise one, because the pre-SystemVerilog + * Verilog dialects have no unpacked-array assignment at all: a vector literal there is a PACKED + * replication/concatenation (`{4{8'h00}}`), which every tool either rejects outright or, worse, + * reads as an assignment pattern that initializes element 0 only (issue #492). + * + * Runs for `verilog.v2001` / `verilog.v95`, and for any backend when the `dropWholeVecAssign` + * compiler option is set. Rule 1 is Verilog-only: VHDL has no `initial` construct, and its + * aggregate initialization needs no lowering. + * + * A drive is lowered only into a declaration whose vector type actually reaches the backend (see + * `keepsVectorType`). An anonymous COMPOSITION (`all(x)` / `x.repeat(n)`, or a `Vector(a, b, c)` + * concatenation) is taken apart into its own operands; any other source is taken apart by + * selecting each cell out of it, which requires it to be constant, so a plain vector-to-vector + * drive is left alone. Only the + * declaration's own (outermost) dimension is unrolled: a cell-level whole-vector drive is legal + * in SystemVerilog, and under the older dialects `DropStructsVecs` has already flattened the cell + * type to `Bits`, making it a plain bit-vector drive. + * + * ==Rule 1: A declaration's `init` becomes an `initial` block== + * {{{ + * // Before + * val mem = Bits(8) X 4 <> VAR init all(h"00") + * + * // After + * val mem = Bits(8) X 4 <> VAR + * val mem_init = initial: + * for (mem_i <- 0 until 4) mem(mem_i) := h"00" + * }}} + * + * ==Rule 2: A connection becomes per-cell connections== + * {{{ + * // Before + * val con = Bits(8) X 4 <> VAR + * con <> all(h"00") + * + * // After + * val con = Bits(8) X 4 <> VAR + * con(0) <> h"00" + * con(1) <> h"00" + * con(2) <> h"00" + * con(3) <> h"00" + * }}} + * + * A connection is concurrent, so it cannot be wrapped in a procedural loop: the cells are + * unrolled even for a uniform source. Driving them from a `process(all)` instead would produce a + * process with an EMPTY sensitivity list (the only sources lowered here are constants, so the + * body reads nothing), which never triggers. + * + * ==Rule 3: An assignment is unrolled in place== + * + * The loop takes the assignment's own place, inside whatever process or conditional branch it + * sits in, and keeps the assignment's operator. + * {{{ + * // Before + * process(clk.rising): + * if (rst == 1) mem :== all(h"00") + * + * // After + * process(clk.rising): + * if (rst == 1) + * for (mem_i <- 0 until 4) mem(mem_i) :== h"00" + * }}} + * + * ==Source shapes== + * + * A uniform source (`all(x)`, `x.repeat(n)`) drives every cell from one expression, so it becomes + * a `for` loop. A per-cell source (a `Vector(a, b, c)` concatenation) unrolls into one assignment + * per cell, since its cells differ. Any other constant source (a named constant vector, a + * `Bits`-to-vector cast, a folded vector literal) unrolls into a cell selection per cell. + * + * The operands need not be constant: `vec :== all(x)` becomes `for (i) vec(i) :== x`, which reads + * `x` exactly where the whole-vector form did. Only Rule 1 insists on a constant, since an + * `initial` block runs once, at time zero. An unrolled drive of a non-constant ANONYMOUS + * expression clones that expression per cell (an anonymous value may be read exactly once); + * a named operand is simply referenced by each cell. + */ +//format: on +case object DropWholeVecAssign extends HierarchyStage: + def dependencies: List[Stage] = List(ToED) + def nullifies: Set[Stage] = Set(DropUnreferencedAnons) + override def runCondition(using co: CompilerOptions): Boolean = + co.dropWholeVecAssign || + (co.backend match + case be: dfhdl.backends.verilog => + be.dialect match + case VerilogDialect.v95 | VerilogDialect.v2001 => true + case _ => false + case _ => false) + + // The declaration must still be a vector by the time the backend prints it. `DropStructsVecs` + // flattens every vector but a block-ram variable into `Bits`, and a flattened whole-vector drive + // is a plain (legal) bit-vector drive; unrolling it first would instead leave cell selections + // that the flattening turns into variable-bound part-selects, which the old dialects reject. + private def keepsVectorType(dcl: DFVal.Dcl)(using MemberGetSet, CompilerOptions): Boolean = + !DropStructsVecs.runCondition || BlockRamVar.unapply(dcl) + + // How the source supplies the declaration's cells. + private enum VecSource: + // one expression for every cell (`all(x)` / `x.repeat(n)`), driven by a loop over the + // declaration's own element-count parameter, so a parametric length stays parametric + case Uniform(elem: DFVal) + // one expression per cell (a `Vector(a, b, c)` concatenation) + case PerCell(elems: List[DFVal]) + // any other vector value, selected cell by cell + case CellSelect(src: DFVal, vecLength: Int) + // `constOnly` is set where the drive is applied once, at time zero (a declaration's `init`), + // and a non-constant operand would therefore be read at the wrong time. + private def vecSourceOf(src: DFVal, vecLengthOpt: Option[Int], constOnly: Boolean)(using + MemberGetSet + ): Option[VecSource] = + src match + case _ if constOnly && !src.isConst => None + case DFVal.Func(op = FuncOp.repeat, args = elemRef :: _) if src.isAnonymous => + Some(VecSource.Uniform(elemRef.get)) + case DFVal.Func(op = FuncOp.`++`, args = argRefs) + if src.isAnonymous && vecLengthOpt.forall(argRefs.lengthIs == _) => + Some(VecSource.PerCell(argRefs.map(_.get))) + // any other shape is taken apart by selecting each cell out of the source itself, which + // needs the source to be CONSTANT: a named non-constant vector is a plain vector-to-vector + // drive (left alone), and an anonymous non-constant one would be duplicated per cell + case _ if src.isConst => vecLengthOpt.map(VecSource.CellSelect(src, _)) + case _ => None + + // The whole-vector drive candidate: the target declaration, its vector type and the decomposed + // source. + private def candidate(toVal: DFVal, fromVal: DFVal, constOnly: Boolean)(using + MemberGetSet, + CompilerOptions + ): Option[(DFVal.Dcl, DFVector, VecSource)] = + toVal match + case dcl: DFVal.Dcl => + dcl.dfType match + case vecType: DFVector if keepsVectorType(dcl) => + vecSourceOf(fromVal, vecType.lengthIntOpt, constOnly).map((dcl, vecType, _)) + case _ => None + case _ => None + + // A cell drive `dcl(idx) rhs`, emitted in the current meta-design context. + private def cellDrive( + dcl: DFVal.Dcl, + cellType: DFType, + idx: dfhdl.core.DFValOf[dfhdl.core.DFInt32], + rhs: ir.DFVal, + op: DFNet.Op, + netMeta: Meta + )(using dfc: dfhdl.core.DFC): Unit = + import dfhdl.core.{refTW, addMember} + val lhs = dfhdl.core.DFVal.Alias.ApplyIdx( + cellType.asFE[DFTypeAny], + dcl.asValAny, + idx + )(using dfc.anonymize).asIR + ir.DFNet( + lhs.refTW[ir.DFNet], + op, + rhs.refTW[ir.DFNet], + dfc.ownerOrEmptyRef, + netMeta, + ir.DFTags.empty + ).addMember + end cellDrive + + // A `for` loop over the whole declaration is emitted only for a uniform source, and only where + // a procedural loop is legal: a connection is concurrent, so its cells are unrolled instead. + private def unrollsUniform(op: DFNet.Op): Boolean = op == DFNet.Op.Connection + + // The element-wise drive of the whole declaration, emitted in the current meta-design context: + // a `for` loop for a uniform source, an unrolled sequence of cell drives otherwise. + private def elemDrive( + dcl: DFVal.Dcl, + vecType: DFVector, + source: VecSource, + op: DFNet.Op, + netMeta: Meta + )(using dfc: dfhdl.core.DFC): Unit = + given MemberGetSet = dfc.getSet + def constIdx(i: Int): dfhdl.core.DFValOf[dfhdl.core.DFInt32] = + dfhdl.core.DFConstInt32(i)(using dfc.anonymize) + def unrolledCellDrive(elems: Int => ir.DFVal, vecLength: Int): Unit = + (0 until vecLength).foreach(i => + cellDrive(dcl, vecType.cellType, constIdx(i), elems(i), op, netMeta) + ) + source match + case VecSource.Uniform(elem) if unrollsUniform(op) => + // one clone of the source per cell: an anonymous value may be read exactly once + unrolledCellDrive( + _ => elem.cloneAnonValueAndDepsHere(using dfc.anonymize), + vecType.lengthUNSAFE + ) + case VecSource.Uniform(elem) => + // `get` on an `IntParamRef` is a core extension; imported here so the file's other + // `.get` uses (on `DFRef`, through `MemberGetSet`) stay unambiguous + import dfhdl.core.get + val iter = + dfhdl.core.DFVal.Dcl.iterator(using dfc.setName(s"${dcl.getName}_i")) + // the loop bound is the declaration's OWN element-count parameter, so a parametric + // length stays parametric in the generated loop + val vecLength = vecType.cellDimParamRefs.head.get(using dfc.anonymize) + .cloneAnonValueAndDepsHere(using dfc.anonymize).toDFConst(using dfc.anonymize) + val range = dfhdl.core.DFRange(using dfc.anonymize)( + constIdx(0), + vecLength, + ir.DFRange.Op.Until + ) + val forBlock = dfhdl.core.DFFor.Block(iter, range)(using dfc.anonymize) + dfc.enterOwner(forBlock) + cellDrive( + dcl, + vecType.cellType, + iter, + elem.cloneAnonValueAndDepsHere(using dfc.anonymize), + op, + netMeta + ) + dfc.exitOwner() + case VecSource.PerCell(elems) => + unrolledCellDrive( + i => elems(i).cloneAnonValueAndDepsHere(using dfc.anonymize), + elems.length + ) + case VecSource.CellSelect(src, vecLength) => + unrolledCellDrive( + i => + dfhdl.core.DFVal.Alias.ApplyIdx( + vecType.cellType.asFE[DFTypeAny], + // an ANONYMOUS source is read once per cell, so each cell selects out of its own + // clone; a named one is simply referenced by all of them + src.cloneAnonValueAndDepsHere(using dfc.anonymize).asValAny, + constIdx(i) + )(using dfc.anonymize).asIR, + vecLength + ) + end match + end elemDrive + + def transformSubDB(rootDB: DB)(using MemberGetSet, CompilerOptions, RefGen): DB = + val patchList: List[(DFMember, Patch)] = subDB.members.flatMap { + // Rule 1: a declaration's whole-vector constant `init` becomes an `initial` block. + // Skipped under VHDL, which has no `initial` construct (`DropInitialBlocks`, which lowers + // them, has long since run by now) and whose aggregate initialization is legal anyway. + case dcl @ DclVar() + if dcl.initRefList.sizeIs == 1 && !summon[CompilerOptions].backend.isVHDL => + candidate(dcl, dcl.initRefList.head.get, constOnly = true).map { (dcl, vecType, source) => + val dsn = new MetaDesign(dcl, Patch.Add.Config.After, dfhdl.core.DomainType.ED): + val block = dfhdl.core.Process.Block.initial(using + dfc.setName(s"${dcl.getName}_init") + ) + dfc.enterOwner(block) + elemDrive(dcl, vecType, source, DFNet.Op.Assignment, dcl.meta.anonymize)(using dfc) + dfc.exitOwner() + List( + dcl -> Patch.Replace( + dcl.copy(initRefList = Nil), + Patch.Replace.Config.FullReplacement + ), + dsn.patch + ) + }.getOrElse(Nil) + // Rule 2: a whole-vector constant connection becomes per-cell connections + // (a plain connection only: a via/lazy connection carries a link this rewrite would drop) + case net @ DFNet.Connection(toVal, fromVal, _) if net.op == DFNet.Op.Connection => + candidate(toVal, fromVal, constOnly = false).filter { (_, vecType, _) => + // an unrolled drive needs a statically known length + vecType.lengthIntOpt.isDefined + }.map { (dcl, vecType, source) => + val dsn = new MetaDesign(net, Patch.Add.Config.Before, dfhdl.core.DomainType.ED): + elemDrive(dcl, vecType, source, DFNet.Op.Connection, net.meta)(using dfc) + List(dsn.patch, net -> Patch.Remove()) + }.getOrElse(Nil) + // Rule 3: a whole-vector constant assignment is unrolled where it stands + case net @ DFNet.Assignment(toVal, fromVal) => + candidate(toVal, fromVal, constOnly = false).map { (dcl, vecType, source) => + val dsn = new MetaDesign(net, Patch.Add.Config.Before, dfhdl.core.DomainType.ED): + elemDrive(dcl, vecType, source, net.op, net.meta)(using dfc) + List(dsn.patch, net -> Patch.Remove()) + }.getOrElse(Nil) + case _ => Nil + } + subDB.patch(patchList) + end transformSubDB +end DropWholeVecAssign + +extension [T: HasDB](t: T) + def dropWholeVecAssign(using CompilerOptions): DB = + StageRunner.run(DropWholeVecAssign)(t.db) 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 0fdc78bcf..43e2bbcb3 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 @@ -103,8 +103,7 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: case _ => None } .mkString("\n") - // one constant or signal declaration; a vector signal that cannot be inline-initialized - // additionally emits an `initial` block right after its declaration + // one constant or signal declaration def csDcl(m: DFVal): List[String] = m match case p: DFVal.Dcl if p.isVar || !parameterizedModuleSupport => // a shared variable is multi-driven by design (e.g., one clocked process per RAM port), @@ -117,8 +116,11 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: |/* verilator lint_on MULTIDRIVEN */""".stripMargin else cs p.dfType match + // a whole-vector init this dialect cannot inline is `DropWholeVecAssign`'s to lower + // into an `initial` block; one still here is a shape that stage declined, and there is + // no legal form for it in this dialect case _: DFVector if !printer.supportVectorInlineInit && p.initRefList.nonEmpty => - List(csDclLine, printer.csDFValDclInitialBlock(p)) + printer.unsupported case _ => List(csDclLine) case c @ DclConst() => List(printer.csDFMember(c) + ";") case _ => Nil @@ -141,10 +143,21 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: case LocalDecl.EDMethod(b) => List((true, csMethodLocal(b))) } ) + // v95/v2001 has no `for`-header iterator declaration AND forbids a declaration in an unnamed + // block, which is what a process is, so every loop iterator of this module is declared in the + // module's own declaration region. Iterators of the same name are declared once: a procedural + // block runs atomically between event controls, so two loops can never interleave over one. + val iteratorDcls = + if (forInteratorDclSupport) "" + else + design.members(MemberView.Flattened).view.collect { + case dcl @ IteratorDcl() if dcl.getOwnerDesign == design => s"${dcl.codeString};" + }.toList.distinct.mkString("\n") val declarations = sn"""|$constIntDcls |$localTypeDcls |$portDcls + |$iteratorDcls |$orderedDcls""" val statements = csDFMembers( designMembers.filter { @@ -414,19 +427,16 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: case const: DFVal.Const if !const.isAnonymous => false case _ => true } - // iterator declarations within `for` loops only supported in SystemVerilog, - // so we need to declare them at the process block level for Verilog v95/v2001 - val iteratorDcls = - if (forInteratorDclSupport) "" - else - pb.members(MemberView.Flattened).view.collect { case dcl @ IteratorDcl() => - dcl.codeString - }.toList.distinct.mkString(";\n").emptyOr(x => s"$x;\n") - val body = iteratorDcls + csDFMembers(statements) + // `for` loop iterators are declared in the module's declaration region under v95/v2001 (a + // process is an unnamed block, which may hold no declaration); see `csModuleDcl` + val body = csDFMembers(statements) val dcl = if (dcls.isEmpty) "" else s"${csDFMembers(dcls)}\n" - val named = pb.meta.nameOpt.map(n => s"$n : ").getOrElse("") + // Verilog names a BLOCK, not a procedural construct: the label belongs on the `begin`, never + // in front of the `initial`/`always` keyword. A labelled block keeps the `begin` on the + // keyword's own line, so the name reads as part of the construct (`initial begin : mem_init`). + val namedOpt = pb.meta.nameOpt.map(n => s" begin : $n") // `always_ff` guarantees a single driver for everything it writes, so a process writing a // shared variable (multi-driven by design, e.g. one clocked process per RAM port) degrades // to a plain `always`, which carries no such guarantee (issue #473). Only the writers @@ -456,7 +466,8 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: case Sensitivity.List(refs) => if (refs.isEmpty) "" else s" @${refs.map(_.refCodeString).mkString("(", sensitivityListSep, ")")}" - s"$dcl${named}$alwaysKW$senList\nbegin\n${body.hindent}\nend" + val begin = namedOpt.getOrElse("\nbegin") + s"$dcl$alwaysKW$senList$begin\n${body.hindent}\nend" end csProcessBlock def csForkBlock(fb: ForkBlock): String = // `join_any` / `join_none` exist only in SystemVerilog (sv2005+). Old Verilog (v95/v2001) has 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 32ca61a9d..76a604859 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 @@ -105,35 +105,17 @@ protected trait VerilogValPrinter extends AbstractValPrinter: def csInitSingle(ref: Dcl.InitRef): String = ref.refCodeString def csInitSeq(refs: List[Dcl.InitRef]): String = printer.unsupported def csDFValDclEnd(dfVal: Dcl): String = "" + // The `initial` block an output port's init needs (no Verilog dialect can inline one). A + // WHOLE-VECTOR init that the dialect cannot inline either is not lowered here: + // `DropWholeVecAssign` turns it into a real `initial` block in the IR, so one arriving at this + // printer is a shape that stage declined and that this dialect has no legal form for. def csDFValDclInitialBlock(dfVal: Dcl): String = val contents = dfVal.initRefList match - case DFRef(DFVal.Alias.AsIs(dfType = dfType: DFVector, relValRef = DFRef(initVal))) :: Nil => - initVal match - case _ if !initVal.isAnonymous => - val cellWidth = dfType.cellType.widthUNSAFE - val length = dfType.cellDimParamRefs.head.getIntOpt.get - val ret = for (i <- 0 until length) - 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 - .map((a, i) => s"${dfVal.getName}[$i] = ${a.refCodeString};") - .mkString("\n") - case Func(op = Func.Op.repeat, args = repeatedArgRef :: _) => - val length = dfType.cellDimParamRefs.head.refCodeString - s"""|integer i; - |for (i = 0; i < ${length}; i = i + 1) begin - | ${dfVal.getName}[i] = ${repeatedArgRef.refCodeString}; - |end""".stripMargin - case _ => printer.unsupported - case initRef :: Nil => - s"${dfVal.getName} = ${initRef.refCodeString};" - case _ => printer.unsupported + case initRef :: Nil => s"${dfVal.getName} = ${initRef.refCodeString};" + case _ => printer.unsupported s"""|initial begin : ${dfVal.getName}_init |${contents.hindent} |end""".stripMargin - end csDFValDclInitialBlock val allowDoubleStarPowerSyntax: Boolean = printer.dialect match case VerilogDialect.v95 => false diff --git a/compiler/stages/src/main/scala/dfhdl/options/CompilerOptions.scala b/compiler/stages/src/main/scala/dfhdl/options/CompilerOptions.scala index 6d076bdb3..d1b1f4381 100644 --- a/compiler/stages/src/main/scala/dfhdl/options/CompilerOptions.scala +++ b/compiler/stages/src/main/scala/dfhdl/options/CompilerOptions.scala @@ -14,7 +14,8 @@ final case class CompilerOptions( logLevel: _LogLevel, printDFHDLCode: PrintDFHDLCode, printBackendCode: PrintBackendCode, - dropUserOpaques: DropUserOpaques + dropUserOpaques: DropUserOpaques, + dropWholeVecAssign: DropWholeVecAssign ) object CompilerOptions: opaque type Defaults[-T] <: CompilerOptions = CompilerOptions @@ -26,13 +27,15 @@ object CompilerOptions: logLevel: LogLevel, printDFHDLCode: PrintDFHDLCode, printBackendCode: PrintBackendCode, - dropUserOpaques: DropUserOpaques + dropUserOpaques: DropUserOpaques, + dropWholeVecAssign: DropWholeVecAssign ): Defaults[Any] = CompilerOptions( commitFolder = commitFolder, newFolderForTop = newFolderForTop, backend = backend(dfhdl.backends), logLevel = logLevel(wvlet.log.LogLevel), printDFHDLCode = printDFHDLCode, printBackendCode = printBackendCode, - dropUserOpaques = dropUserOpaques + dropUserOpaques = dropUserOpaques, + dropWholeVecAssign = dropWholeVecAssign ) end Defaults given (using defaults: Defaults[Design]): CompilerOptions = defaults @@ -89,4 +92,12 @@ object CompilerOptions: object DropUserOpaques: given DropUserOpaques = false given Conversion[Boolean, DropUserOpaques] = identity + + // Forces the element-wise lowering of whole-vector constant drives (see `DropWholeVecAssign`). + // The pre-SystemVerilog Verilog dialects always get it, since they have no unpacked array + // assignment at all; this option enables it for every other backend as well. + into opaque type DropWholeVecAssign <: Boolean = Boolean + object DropWholeVecAssign: + given DropWholeVecAssign = false + given Conversion[Boolean, DropWholeVecAssign] = identity end CompilerOptions diff --git a/compiler/stages/src/test/scala/StagesSpec/DropProcessAllSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropProcessAllSpec.scala index 97a6386f3..7b4502677 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropProcessAllSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropProcessAllSpec.scala @@ -111,4 +111,84 @@ class DropProcessAllSpec extends StageSpec: | else oBits := rshifter.oBits |end Foo""".stripMargin ) + // a loop body's statements are the process's statements, so what they read must reach the + // sensitivity list — as must the range that decides how often the loop runs + test("Reads inside a loop"): + class Top extends EDDesign: + val x = Bits(8) <> IN + val v = Bits(8) X 4 <> VAR + val y = Bits(8) <> OUT + process(all): + for (i <- 0 until 4) v(i) := x + y := v(0) + end Top + val top = (new Top).dropProcessAll + assertCodeString( + top, + """|class Top extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | val v = Bits(8) X 4 <> VAR + | process(x, v): + | for (i <- 0 until 4) + | v(i) := x + | end for + | y := v(0) + |end Top + |""".stripMargin + ) + // A constant-index cell selection is exactly what the process is sensitive to, and unlike the + // array it selects from it can be named in a Verilog event control. + test("Constant-indexed array item under verilog.v95"): + given options.CompilerOptions.Backend = _.verilog.v95 + class Top extends EDDesign: + val x = Bits(8) <> IN + val v = Bits(8) X 4 <> VAR + val y = Bits(8) <> OUT + process(all): + v(0) := x + y := v(1) + end Top + val top = (new Top).dropProcessAll + assertCodeString( + top, + """|class Top extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | val v = Bits(8) X 4 <> VAR + | process(x, v(1)): + | v(0) := x + | y := v(1) + |end Top + |""".stripMargin + ) + + // a NON-constant index cannot name the cell that is read, so the whole array is the item — and + // a Verilog event control cannot name an array (v95 has no `@*` either), so it is listed cell + // by cell. VHDL names the array signal itself, which the loop test above pins. + test("Dynamically indexed array item under verilog.v95"): + given options.CompilerOptions.Backend = _.verilog.v95 + class Top extends EDDesign: + val x = Bits(8) <> IN + val idx = UInt(2) <> IN + val v = Bits(8) X 4 <> VAR + val y = Bits(8) <> OUT + process(all): + v(0) := x + y := v(idx) + end Top + val top = (new Top).dropProcessAll + assertCodeString( + top, + """|class Top extends EDDesign: + | val x = Bits(8) <> IN + | val idx = UInt(2) <> IN + | val y = Bits(8) <> OUT + | val v = Bits(8) X 4 <> VAR + | process(x, idx, v(0), v(1), v(2), v(3)): + | v(0) := x + | y := v(idx.toInt) + |end Top + |""".stripMargin + ) end DropProcessAllSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/DropWholeVecAssignSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropWholeVecAssignSpec.scala new file mode 100644 index 000000000..7148aa53b --- /dev/null +++ b/compiler/stages/src/test/scala/StagesSpec/DropWholeVecAssignSpec.scala @@ -0,0 +1,337 @@ +package StagesSpec + +import dfhdl.* +import dfhdl.compiler.stages.dropWholeVecAssign +// scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} + +class DropWholeVecAssignSpec extends StageSpec(stageCreatesUnrefAnons = true): + given options.CompilerOptions.Backend = _.verilog.v2001 + + test("uniform init becomes an initial block loop"): + class Top extends EDDesign: + val x = Bits(8) <> IN + val y = Bits(8) <> OUT + val mem = Bits(8) X 4 <> VAR init all(all(0)) + process(all): + mem(0) :== x + y :== mem(3) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | val mem = Bits(8) X 4 <> VAR + | val mem_init = initial: + | for (mem_i <- 0 until 4) + | mem(mem_i) := h"00" + | end for + | process(all): + | mem(0) :== x + | y :== mem(3) + |end Top + |""".stripMargin + ) + + test("uniform connection becomes per-cell connections"): + class Top extends EDDesign: + val y = Bits(8) <> OUT + val con = Bits(8) X 4 <> VAR + con <> all(all(0)) + y <> con(2) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val y = Bits(8) <> OUT + | val con = Bits(8) X 4 <> VAR + | con(0) <> h"00" + | con(1) <> h"00" + | con(2) <> h"00" + | con(3) <> h"00" + | y <> con(2) + |end Top + |""".stripMargin + ) + + test("uniform assignment becomes a loop in place"): + class Top extends EDDesign: + val x = Bit <> IN + val y = Bits(8) <> OUT + val mem = Bits(8) X 4 <> VAR + process(all): + if (x) mem :== all(all(0)) + else mem(0) :== h"ff" + y :== mem(1) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val x = Bit <> IN + | val y = Bits(8) <> OUT + | val mem = Bits(8) X 4 <> VAR + | process(all): + | if (x) + | for (mem_i <- 0 until 4) + | mem(mem_i) :== h"00" + | end for + | else mem(0) :== h"ff" + | end if + | y :== mem(1) + |end Top + |""".stripMargin + ) + + test("differing cells unroll"): + class Top extends EDDesign: + val y = Bits(8) <> OUT + val lut = Bits(8) X 4 <> VAR init Vector(h"01", h"02", h"03", h"04") + y <> lut(1) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val y = Bits(8) <> OUT + | val lut = Bits(8) X 4 <> VAR + | val lut_init = initial: + | lut(0) := h"01" + | lut(1) := h"02" + | lut(2) := h"03" + | lut(3) := h"04" + | y <> lut(1) + |end Top + |""".stripMargin + ) + + test("a named constant source unrolls into cell selections"): + class Top extends EDDesign: + val TBL: Bits[8] X 4 <> CONST = Vector(h"01", h"02", h"03", h"04") + val y = Bits(8) <> OUT + val rom = Bits(8) X 4 <> VAR init TBL + y <> rom(1) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val TBL: Bits[8] X 4 <> CONST = DFVector(Bits(8) X 4)(h"01", h"02", h"03", h"04") + | val y = Bits(8) <> OUT + | val rom = Bits(8) X 4 <> VAR + | val rom_init = initial: + | rom(0) := TBL(0) + | rom(1) := TBL(1) + | rom(2) := TBL(2) + | rom(3) := TBL(3) + | y <> rom(1) + |end Top + |""".stripMargin + ) + + // any other constant source is taken apart by selecting each cell out of it, which covers the + // `Bits`-to-vector cast the Verilog printer used to special-case + test("a bits-to-vector cast source unrolls into cell selections"): + class Top extends EDDesign: + val SRC: Bits[32] <> CONST = h"01020304" + val y = Bits(8) <> OUT + val mem = Bits(8) X 4 <> VAR init SRC.as(Bits(8) X 4) + y <> mem(1) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val SRC: Bits[32] <> CONST = h"01020304" + | val y = Bits(8) <> OUT + | val mem = Bits(8) X 4 <> VAR + | val mem_init = initial: + | mem(0) := SRC.as(Bits(8) X 4)(0) + | mem(1) := SRC.as(Bits(8) X 4)(1) + | mem(2) := SRC.as(Bits(8) X 4)(2) + | mem(3) := SRC.as(Bits(8) X 4)(3) + | y <> mem(1) + |end Top + |""".stripMargin + ) + + test("a parametric length keeps the parameter as the loop bound"): + class Top(val N: Int <> CONST = 4) extends EDDesign: + val y = Bits(8) <> OUT + val mem = Bits(8) X N <> VAR init all(all(0)) + y <> mem(1) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top(val N: Int <> CONST = 4) extends EDDesign: + | val y = Bits(8) <> OUT + | val mem = Bits(8) X N <> VAR + | val mem_init = initial: + | for (mem_i <- 0 until N) + | mem(mem_i) := h"00" + | end for + | y <> mem(1) + |end Top + |""".stripMargin + ) + + // the operands need not be constant: the loop body reads them exactly where the whole-vector + // form did + test("a non-constant uniform source still becomes a loop"): + class Top extends EDDesign: + val x = Bits(8) <> IN + val y = Bits(8) <> OUT + val mem = Bits(8) X 4 <> VAR + process(all): + mem :== all(x) + y :== mem(1) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | val mem = Bits(8) X 4 <> VAR + | process(all): + | for (mem_i <- 0 until 4) + | mem(mem_i) :== x + | end for + | y :== mem(1) + |end Top + |""".stripMargin + ) + + test("a non-constant per-cell source unrolls"): + class Top extends EDDesign: + val x = Bits(8) <> IN + val y = Bits(8) <> OUT + val cat = Bits(8) X 2 <> VAR + process(all): + cat :== Vector(x, x | h"01") + y :== cat(1) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | val cat = Bits(8) X 2 <> VAR + | process(all): + | cat(0) :== x + | cat(1) :== x | h"01" + | y :== cat(1) + |end Top + |""".stripMargin + ) + + // a NAMED non-constant source is a plain vector-to-vector drive, which is left alone + test("a named non-constant source is left alone"): + class Top extends EDDesign: + val y = Bits(8) <> OUT + val src = Bits(8) X 4 <> VAR + val mem = Bits(8) X 4 <> VAR + process(all): + mem :== src + y :== mem(1) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val y = Bits(8) <> OUT + | val src = Bits(8) X 4 <> VAR + | val mem = Bits(8) X 4 <> VAR + | process(all): + | mem :== src + | y :== mem(1) + |end Top + |""".stripMargin + ) + + test("a vector that the backend flattens is left alone"): + // `v` is read whole, so `DropStructsVecs` will flatten it into Bits and the whole-vector + // drive becomes a plain (legal) bit-vector drive; unrolling it would only leave cell + // selections for the flattening to turn into variable-bound part-selects + class Top extends EDDesign: + val y = Bits(32) <> OUT + val v = Bits(8) X 4 <> VAR + process(all): + v :== all(all(0)) + y :== v.bits + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val y = Bits(32) <> OUT + | val v = Bits(8) X 4 <> VAR + | process(all): + | v :== all(h"00") + | y :== v.bits + |end Top + |""".stripMargin + ) + + // Only the declaration's OWN (outermost) dimension is unrolled. Under the older dialects + // `DropStructsVecs` then flattens the cell type to `Bits`, making the cell drive a plain + // bit-vector drive, and in SystemVerilog a cell-level array literal is legal as it stands. + test("a multi-dimensional vector unrolls its outermost dimension only"): + class Top extends EDDesign: + val x = Bits(8) <> IN + val y = Bits(8) <> OUT + val mem = Bits(8) X 4 X 2 <> VAR init all(all(all(0))) + val con = Bits(8) X 4 X 2 <> VAR + con <> all(all(all(0))) + process(all): + mem :== all(all(x)) + y :== mem(1)(2) | con(0)(3) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | val mem = Bits(8) X 4 X 2 <> VAR + | val mem_init = initial: + | for (mem_i <- 0 until 2) + | mem(mem_i) := all(h"00") + | end for + | val con = Bits(8) X 4 X 2 <> VAR + | con(0) <> all(h"00") + | con(1) <> all(h"00") + | process(all): + | for (mem_i <- 0 until 2) + | mem(mem_i) :== all(x) + | end for + | y :== mem(1)(2) | con(0)(3) + |end Top + |""".stripMargin + ) + + test("a multi-dimensional per-cell source unrolls its outermost dimension only"): + class Top extends EDDesign: + val y = Bits(8) <> OUT + val lut = Bits(8) X 2 X 2 <> VAR init Vector(Vector(h"01", h"02"), Vector(h"03", h"04")) + y <> lut(1)(0) + end Top + val top = (new Top).dropWholeVecAssign + assertCodeString( + top, + """|class Top extends EDDesign: + | val y = Bits(8) <> OUT + | val lut = Bits(8) X 2 X 2 <> VAR + | val lut_init = initial: + | lut(0) := DFVector(Bits(8) X 2)(h"01", h"02") + | lut(1) := DFVector(Bits(8) X 2)(h"03", h"04") + | y <> lut(1)(0) + |end Top + |""".stripMargin + ) + +end DropWholeVecAssignSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 1ffafd88b..ac9043e58 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -330,8 +330,7 @@ class PrintVerilogCodeSpec extends StageSpec: | if (rst) y <= c; | else y <= x; | end - | myblock : always_comb - | begin + | always_comb begin : myblock | my_var = x; | y <= my_var; | end @@ -993,12 +992,12 @@ class PrintVerilogCodeSpec extends StageSpec: | output reg [639:0] matrix |); | `include "dfhdl_defs.vh" + | integer i; + | integer j; + | integer k; | | always | begin - | integer i; - | integer j; - | integer k; | for (i = 0; i < 8; i = i + 1) begin | if ((i % 2) == 0) begin | for (j = 0; j < 8; j = j + 1) begin @@ -1141,7 +1140,7 @@ class PrintVerilogCodeSpec extends StageSpec: | $display("These are the values: %d, %d, %h, %h, %d, %b, %s, %s", param3, param4, param5, param6, param7, param8, param9 ? "true" : "false", param10.name()); | $info( | "Debug at Foo\n", - | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1093:9\n", + | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1092:9\n", | "param3 = %d\n", param3, | "param4 = %d\n", param4, | "param5 = %h\n", param5, @@ -1212,7 +1211,7 @@ class PrintVerilogCodeSpec extends StageSpec: | $display("These are the values: %d, %d, %h, %h, %d, %b, %s, %s", param3, param4, param5, param6, param7, param8, param9 ? "true" : "false", MyEnum_to_string(param10)); | $display( | "INFO: Debug at Foo\n", - | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1093:9\n", + | "compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala:1092:9\n", | "param3 = %d\n", param3, | "param4 = %d\n", param4, | "param5 = %h\n", param5, @@ -1265,6 +1264,9 @@ class PrintVerilogCodeSpec extends StageSpec: |endmodule""".stripMargin ) } + // the whole-vector inits are lowered into `initial` blocks by `DropWholeVecAssign`; a uniform + // source loops over the declaration's own element-count parameter, while the differing-cell + // sources unroll test("vector init printing under verilog.v95") { given options.CompilerOptions.Backend = _.verilog.v95 class Foo extends EDDesign: @@ -1286,27 +1288,28 @@ class PrintVerilogCodeSpec extends StageSpec: | `include "dfhdl_defs.vh" | parameter integer PORT_WIDTH = 8; | parameter integer PORT_DEPTH = 4; + | integer v2_i; | parameter [(PORT_WIDTH * PORT_DEPTH) - 1:0] initArg = {8'h01, 8'h02, 8'h03, 8'h04}; | reg [PORT_WIDTH - 1:0] v1 [0:PORT_DEPTH - 1]; + | reg [PORT_WIDTH - 1:0] v2 [0:PORT_DEPTH - 1]; + | reg [PORT_WIDTH - 1:0] v3 [0:PORT_DEPTH - 1]; | initial begin : v1_init | v1[0] = 8'h01; | v1[1] = 8'h02; | v1[2] = 8'h03; | v1[3] = 8'h04; | end - | reg [PORT_WIDTH - 1:0] v2 [0:PORT_DEPTH - 1]; + | | initial begin : v2_init - | integer i; - | for (i = 0; i < PORT_DEPTH; i = i + 1) begin - | v2[i] = {PORT_WIDTH{1'b0}}; + | for (v2_i = 0; v2_i < PORT_DEPTH; v2_i = v2_i + 1) begin + | v2[v2_i] = {PORT_WIDTH{1'b0}}; | end | end - | reg [PORT_WIDTH - 1:0] v3 [0:PORT_DEPTH - 1]; | initial begin : v3_init - | v3[0] = initArg[31:24]; - | v3[1] = initArg[23:16]; - | v3[2] = initArg[15:8]; - | v3[3] = initArg[7:0]; + | v3[0] = initArg[(PORT_WIDTH + (((PORT_WIDTH * PORT_DEPTH) - (PORT_WIDTH * 1)) + 0)) - 1:((PORT_WIDTH * PORT_DEPTH) - (PORT_WIDTH * 1)) + 0]; + | v3[1] = initArg[(PORT_WIDTH + (((PORT_WIDTH * PORT_DEPTH) - (PORT_WIDTH * 2)) + 0)) - 1:((PORT_WIDTH * PORT_DEPTH) - (PORT_WIDTH * 2)) + 0]; + | v3[2] = initArg[(PORT_WIDTH + (((PORT_WIDTH * PORT_DEPTH) - (PORT_WIDTH * 3)) + 0)) - 1:((PORT_WIDTH * PORT_DEPTH) - (PORT_WIDTH * 3)) + 0]; + | v3[3] = initArg[(PORT_WIDTH + (((PORT_WIDTH * PORT_DEPTH) - (PORT_WIDTH * 4)) + 0)) - 1:((PORT_WIDTH * PORT_DEPTH) - (PORT_WIDTH * 4)) + 0]; | end |endmodule""".stripMargin ) @@ -3803,4 +3806,137 @@ class PrintVerilogCodeSpec extends StageSpec: |""".stripMargin ) } + // issue #492: `mem <= {4{8'h00}}` is a PACKED replication assigned to an UNPACKED array, which + // v95/v2001 cannot express (and which a lenient tool reads as initializing element 0 only). + // `DropWholeVecAssign` lowers it into the element-wise loop every 1364-2001 tool accepts. + test("whole vector reset under verilog.v2001") { + given options.CompilerOptions.Backend = _.verilog.v2001 + class VecReset extends RTDesign: + val din = Bits(8) <> IN + val dout = Bits(8) <> OUT + val mem = Bits(8) X 4 <> VAR.REG init all(all(0)) + mem(0).din := din + for (i <- 1 until 4) mem(i).din := mem(i - 1) + dout <> mem(3) + end VecReset + val top = VecReset().getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module VecReset( + | input wire clk, + | input wire rst, + | input wire [7:0] din, + | output wire [7:0] dout + |); + | `include "dfhdl_defs.vh" + | integer mem_i; + | reg [7:0] mem [0:3]; + | assign dout = mem[3]; + | always @(posedge clk) + | begin + | if (rst == 1'b1) begin + | for (mem_i = 0; mem_i < 4; mem_i = mem_i + 1) begin + | mem[mem_i] <= 8'h00; + | end + | end + | else begin + | mem[0] <= din; + | mem[1] <= mem[0]; + | mem[2] <= mem[1]; + | mem[3] <= mem[2]; + | end + | end + |endmodule + |""".stripMargin + ) + } + // the same lowering, opted into for a dialect that CAN express the whole-vector forms + test("whole vector drives under the dropWholeVecAssign option") { + given options.CompilerOptions.Backend = _.verilog.sv2009 + given options.CompilerOptions.DropWholeVecAssign = true + class VecOpt extends EDDesign: + val y = Bits(8) <> OUT + val mem = Bits(8) X 4 <> VAR init all(all(0)) + val con = Bits(8) X 4 <> VAR + con <> all(all(1)) + y <> mem(1) | con(2) + end VecOpt + val top = VecOpt().getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module VecOpt( + | output logic [7:0] y + |); + | `include "dfhdl_defs.svh" + | logic [7:0] mem [0:3]; + | logic [7:0] con [0:3]; + | + | initial begin : mem_init + | for (int mem_i = 0; mem_i < 4; mem_i = mem_i + 1) begin + | mem[mem_i] = 8'h00; + | end + | end + | assign con[0] = 8'hff; + | assign con[1] = 8'hff; + | assign con[2] = 8'hff; + | assign con[3] = 8'hff; + | assign y = mem[1] | con[2]; + |endmodule + |""".stripMargin + ) + } + // only the declaration's OWN dimension is unrolled: `DropStructsVecs` flattens the cell type to + // `Bits`, so the cell drive lands as a legal packed replication rather than an array literal + test("multi-dimensional whole vector drives under verilog.v2001") { + given options.CompilerOptions.Backend = _.verilog.v2001 + class VecDims extends EDDesign: + val x = Bits(8) <> IN + val y = Bits(8) <> OUT + val mem = Bits(8) X 4 X 2 <> VAR init all(all(all(0))) + val con = Bits(8) X 4 X 2 <> VAR + con <> all(all(all(0))) + process(all): + mem :== all(all(x)) + y :== mem(1)(2) | con(0)(3) + end VecDims + val top = VecDims().getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module VecDims( + | input wire [7:0] x, + | output reg [7:0] y + |); + | `include "dfhdl_defs.vh" + | integer mem_i; + | reg [31:0] mem [0:1]; + | wire [31:0] con [0:1]; + | + | initial begin : mem_init + | for (mem_i = 0; mem_i < 2; mem_i = mem_i + 1) begin + | mem[mem_i] = {4{8'h00}}; + | end + | end + | assign con[0] = {4{8'h00}}; + | assign con[1] = {4{8'h00}}; + | + | always @(*) + | begin + | for (mem_i = 0; mem_i < 2; mem_i = mem_i + 1) begin + | mem[mem_i] <= {4{x}}; + | end + | y <= mem[1][15:8] | con[0][7:0]; + | end + |endmodule + |""".stripMargin + ) + } end PrintVerilogCodeSpec diff --git a/docs/user-guide/command-line/index.md b/docs/user-guide/command-line/index.md index 6c224ee6c..6d325fe8a 100644 --- a/docs/user-guide/command-line/index.md +++ b/docs/user-guide/command-line/index.md @@ -231,6 +231,7 @@ Each mode adds the options of the modes it depends on, so `commit` accepts every | `-b`, `--backend ` | `verilog.sv2009` | Target language and dialect, see `help backend` | | `--print-compile` | off | Print the DFHDL design after compilation | | `--print-backend` | off, on in the browser | Print the generated backend code | +| `--drop-whole-vec-assign` | off | Lower whole-vector constant drives into element-wise loops. Always applied for `verilog.v95` / `verilog.v2001`, which cannot express an unpacked-array assignment | | `--global-defs-name ` | none | Override the name of the global definitions file, without its suffix | The backend takes a language and an optional dialect. `-b vhdl` selects VHDL with its default dialect, `-b verilog.v2001` pins Verilog 2001: diff --git a/lib/src/main/scala/dfhdl/app/DFApp.scala b/lib/src/main/scala/dfhdl/app/DFApp.scala index e15183bde..efb4957d7 100644 --- a/lib/src/main/scala/dfhdl/app/DFApp.scala +++ b/lib/src/main/scala/dfhdl/app/DFApp.scala @@ -178,6 +178,7 @@ class DFApp: object compile extends diskCache.Step[StagedDesign, CompiledDesign](elaborate)( elaborationOptions.defaultRTDomainCfgTag, compilerOptions.dropUserOpaques, + compilerOptions.dropWholeVecAssign, printerOptions.align, compilerOptions.backend.toString() ): @@ -500,7 +501,8 @@ class DFApp: compilerOptions = compilerOptions.copy( backend = mode.backend.toOption.get, printDFHDLCode = mode.`print-compile`.toOption.get, - printBackendCode = mode.`print-backend`.toOption.get + printBackendCode = mode.`print-backend`.toOption.get, + dropWholeVecAssign = mode.`drop-whole-vec-assign`.toOption.get ) printerOptions = printerOptions.copy( globalDefsFileName = mode.`global-defs-name`.toOption.get diff --git a/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala b/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala index 311126887..8d952e75b 100644 --- a/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala +++ b/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala @@ -93,6 +93,12 @@ class ParsedCommandLine( hidden = hidden, noshort = true ) + val `drop-whole-vec-assign` = opt[Boolean]( + descr = "lower whole-vector constant drives into element-wise loops", + default = Some(co.dropWholeVecAssign), + hidden = hidden, + noshort = true + ) val `global-defs-name` = opt[String]( descr = "override the global definitions file name (without suffix)", default = Some(pto.globalDefsFileName), From 898e35e79b98a72c8c10209fa42098b822f70b4a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 10:12:49 +0000 Subject: [PATCH 41/57] verilog-to-dfhdl: note that cav applies the read_slang guidance itself The manual-proving section already prescribes read_slang for DFHDL output; the training repo's cav now does this automatically for both sides when the plugin is present (CAV_FRONTEND=auto), so say so where the prescription lives. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_017Sr34sJ2wXWXZ9ENf817aT --- .claude/commands/verilog-to-dfhdl.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index c5e59b8eb..7168120e2 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -414,7 +414,8 @@ The ladder itself (combinational miter, `equiv_make`/`equiv_simple`/`equiv_induc that are about **DFHDL's output specifically** and recur in every port: - **Read the DFHDL output with `read_slang`, not `read_verilog`** (`yosys -m slang`, plugin shipped - with OSS CAD Suite). yosys's own frontend rejects the assignment pattern DFHDL emits to reset a + with OSS CAD Suite; the training repo's `cav` does this automatically for both sides when the + plugin is present, via `CAV_FRONTEND=auto`). yosys's own frontend rejects the assignment pattern DFHDL emits to reset a vector — `gpr_out <= '{default: '{default: 32'h0}};` — with *"syntax error, unexpected TOK_DEFAULT"* (yosys#6120). It is the **`default:` key** that has no grammar rule, not the nesting and not unpacked arrays: the positional form `'{a, b, c, d}` parses, while every keyed form fails From 3c44fbab6d56ba0c66f9ead89cd2282d065be8e4 Mon Sep 17 00:00:00 2001 From: Oron Date: Sun, 16 Aug 2026 20:58:48 +0300 Subject: [PATCH 42/57] change XYDesign into trait --- .../src/main/scala/dfhdl/core/Container.scala | 20 ++++++++++++++----- core/src/main/scala/dfhdl/core/Design.scala | 6 +++--- core/src/main/scala/dfhdl/core/Domain.scala | 4 ++-- .../src/main/scala/dfhdl/core/Interface.scala | 2 +- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/dfhdl/core/Container.scala b/core/src/main/scala/dfhdl/core/Container.scala index 8436ae7a1..81463e992 100644 --- a/core/src/main/scala/dfhdl/core/Container.scala +++ b/core/src/main/scala/dfhdl/core/Container.scala @@ -21,12 +21,22 @@ private trait Container extends OnCreateEvents, HasDFC, Wait.ContainerOps: dfc.enterOwner(__initOwner) end Container -abstract class DomainContainer[D <: DomainType](domainType: D) extends Container: - private[core] type TDomain = D - final protected given TDomain = domainType - final private[core] lazy val __domainType: ir.DomainType = domainType.asIR +sealed trait DomainContainer extends Container -abstract class RTDomainContainer extends DomainContainer(DomainType.RT): +trait DFDomainContainer extends DomainContainer: + private[core] type TDomain = DomainType.DF + final protected given TDomain = DomainType.DF + final private[core] lazy val __domainType: ir.DomainType = ir.DomainType.DF + +trait EDDomainContainer extends DomainContainer: + private[core] type TDomain = DomainType.ED + final protected given TDomain = DomainType.ED + final private[core] lazy val __domainType: ir.DomainType = ir.DomainType.ED + +trait RTDomainContainer extends DomainContainer: + private[core] type TDomain = DomainType.RT + final protected given TDomain = DomainType.RT + final private[core] lazy val __domainType: ir.DomainType = ir.DomainType.RT final case class Clk() extends DFOpaque.Clk final case class Rst() extends DFOpaque.Rst // A domain related to its enclosing container, sharing its clock and reset: shorthand for diff --git a/core/src/main/scala/dfhdl/core/Design.scala b/core/src/main/scala/dfhdl/core/Design.scala index 22d41ebfd..2a4adefff 100644 --- a/core/src/main/scala/dfhdl/core/Design.scala +++ b/core/src/main/scala/dfhdl/core/Design.scala @@ -376,11 +376,11 @@ object Design: end extension end Design -abstract class DFDesign extends DomainContainer(DomainType.DF), Design +trait DFDesign extends DFDomainContainer, Design -abstract class RTDesign extends RTDomainContainer, Design +trait RTDesign extends RTDomainContainer, Design -abstract class EDDesign extends DomainContainer(DomainType.ED), Design +trait EDDesign extends EDDomainContainer, Design abstract class EDBlackBox extends EDDesign: // `source` is a `def` (not a constructor-param field) so `mkInstMode` is safe diff --git a/core/src/main/scala/dfhdl/core/Domain.scala b/core/src/main/scala/dfhdl/core/Domain.scala index 26503e07c..c13316a13 100644 --- a/core/src/main/scala/dfhdl/core/Domain.scala +++ b/core/src/main/scala/dfhdl/core/Domain.scala @@ -52,8 +52,8 @@ trait NoClkRstDomain extends Domain: protected inline def Rst: DFOpaque[DFOpaque.Rst] = compiletime.error("Clk/Rst declarations are not allowed in this domain.") -abstract class DFDomain extends DomainContainer(DomainType.DF), NoClkRstDomain +abstract class DFDomain extends DFDomainContainer, NoClkRstDomain abstract class RTDomain extends RTDomainContainer, Domain -abstract class EDDomain extends DomainContainer(DomainType.ED), NoClkRstDomain +abstract class EDDomain extends EDDomainContainer, NoClkRstDomain diff --git a/core/src/main/scala/dfhdl/core/Interface.scala b/core/src/main/scala/dfhdl/core/Interface.scala index 3394eb014..283c0cdb6 100644 --- a/core/src/main/scala/dfhdl/core/Interface.scala +++ b/core/src/main/scala/dfhdl/core/Interface.scala @@ -21,7 +21,7 @@ import scala.quoted.* // never has clk/rst injected, and the same `Interface` is reusable inside a // design of any domain. abstract class Interface - extends DomainContainer(DomainType.ED), HasClsMeta, HasClsArgs: + extends EDDomainContainer, HasClsMeta, HasClsArgs: self => private[core] type TScope = DFC.Scope.Interface private[core] type TOwner = Design.Block From 47198369b21ecfb37c57ec4a5e2878e89d9e5d8e Mon Sep 17 00:00:00 2001 From: Oron Date: Sun, 16 Aug 2026 21:29:44 +0300 Subject: [PATCH 43/57] derived clocks: same-named-only auto-connection, Clk <> OUT sources Revised connection semantics for related-domain derived clocks, so that clocks connect automatically only in a predictable way: - A related domain may declare `Clk <> OUT`: the internal gating site, driven from its own design scope (`active.clk <> icgOut.as(active.Clk)`) and sourcing every same-named derived clock in scope through the magnet flow (the veer.sv structure, where the core creates its gated clocks). - Same-named derived clocks within a clock group always form one clock: AddClkRst mints the distinct `Clk_` type unconditionally, and the mint-when-driven detection and collapse-to-origin fallback are gone. A derived clock is never implicitly merged onto its origin clock; the ungated form is an explicit connection at a wrapper. - A derived clock group with no source anywhere surfaces as a top-level input port: sourceless Clk-kind magnet targets (magnetUnmatchedTargets, now exposed by MagnetMap in deterministic groupByOrdered order) climb the whole hierarchy in AddMagnets, so a forgotten gated-clock connection becomes a visible port instead of a silently dead or wrongly merged clock. ConnectMagnetsSpec pins the new behaviors (an output derived clock sourcing a sibling consumer; the sourceless bubble-to-top chain), AddClkRstSpec covers OUT minting, and the former collapse tests are re-harvested under always-mint semantics. Docs updated accordingly. Co-Authored-By: Claude Fable 5 --- .claude/commands/verilog-to-dfhdl.md | 19 +- .../src/main/scala/dfhdl/compiler/ir/DB.scala | 9 +- .../scala/dfhdl/compiler/ir/MagnetMap.scala | 31 ++-- .../dfhdl/compiler/stages/AddClkRst.scala | 73 +++----- .../dfhdl/compiler/stages/AddMagnets.scala | 73 +++++--- .../test/scala/StagesSpec/AddClkRstSpec.scala | 44 ++++- .../scala/StagesSpec/ConnectMagnetsSpec.scala | 173 ++++++++++++++++++ .../src/test/scala/StagesSpec/ToEDSpec.scala | 16 +- core/src/main/scala/dfhdl/core/Modifier.scala | 14 +- docs/user-guide/design-domains/index.md | 44 +++-- .../test/scala/ElaborationChecksSpec.scala | 24 +-- 11 files changed, 373 insertions(+), 147 deletions(-) diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index 7168120e2..662cf258d 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -184,14 +184,17 @@ Use **`import`, not `export`** to reach the members afterwards: `export` is reje (*"not accessible"*) because the region's type is anonymous, and scope is all that is wanted here — an `import` adds no member and no net. -Same-named domains+ports of the same origin unify across the hierarchy: if any of them is driven -somewhere (a parent connects an ICG output via `child.active.clk <> g.as(child.active.Clk)`), all of -them thread to it through auto-added `active_clk` pass-through ports; if none is driven, they all -collapse onto the root clock net (`.active_clk(clk)`, the `RV_FPGA_OPTIMIZE` form) while the ports -remain. Only `Clk <> IN` is legal in a related domain (no `OUT`/`VAR`, no `Rst`), and the gating -site is always a parent's connection, never the domain's own design scope (a domain's input port is -externally driven by construction). The reduce-to-enables strategy remains the right call when the -target build ties all derived clocks to the root anyway and the ports are noise. +Same-named domains+ports of the same clock group unify into one clock across the hierarchy, +threaded through auto-added `active_clk` pass-through ports. The source is either a `Clk <> OUT` +related-domain port (the internal gating site, driven from its design scope: +`active.clk <> icgOut.as(active.Clk)`, the veer.sv structure) or a parent's explicit connection to +a child's input port (`child.active.clk <> g.as(child.active.Clk)`). A derived clock is NEVER +implicitly merged onto the root clock: with no source anywhere it surfaces as a top-level input +port (a forgotten connection is a visible port, not a silently dead clock), and the ungated +`RV_FPGA_OPTIMIZE` form (`.active_clk(clk)`) is an explicit wrapper connection from a declared root +clock port. Only `Clk <> IN`/`Clk <> OUT` are legal in a related domain (no `VAR`, no `Rst`). The +reduce-to-enables strategy remains the right call when the target build ties all derived clocks to +the root anyway and the ports are noise. ## Parameters - beyond the guide 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 88fe892fb..350f68a8a 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -741,12 +741,17 @@ final case class DB private ( // the root-aware design tree (no flattening). The point info lets consumers // avoid re-resolving a cross-design ConnectPoint (which would need a flat // member index). - private lazy val magnetData - : (Map[ConnectPoint, ConnectPoint], Map[ConnectPoint, (DFDesignBlock, String)]) = + private lazy val magnetData: ( + Map[ConnectPoint, ConnectPoint], + Map[ConnectPoint, (DFDesignBlock, String)], + List[ConnectPoint] + ) = if (!isRoot) rootDB.magnetData else MagnetMap.get(this) lazy val magnetConnectionMap: Map[ConnectPoint, ConnectPoint] = magnetData._1 lazy val magnetPointInfo: Map[ConnectPoint, (DFDesignBlock, String)] = magnetData._2 + // magnet targets with no source anywhere in the hierarchy (deterministic order) + lazy val magnetUnmatchedTargets: List[ConnectPoint] = magnetData._3 // Dangling-port check, run on the root DB. The assignment coverage and the // connected-point set are aggregated across all sub-DBs (each design's diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala index d22077a32..dfbc5005a 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala @@ -83,7 +83,8 @@ object MagnetMap: // tree (designBlockOwnershipMap) — no ref resolution during matching, so the // throwing root getSet is fine. Also returns each magnet point's (owner design, // name) so consumers don't re-resolve a cross-design ConnectPoint. - def get(rootDB: DB): (MagnetMap, Map[ConnectPoint, (DFDesignBlock, String)]) = + def get(rootDB: DB) + : (MagnetMap, Map[ConnectPoint, (DFDesignBlock, String)], List[ConnectPoint]) = // a magnet ConnectPoint with its design context precomputed under the // owning sub-DB getSet (so the matching never resolves refs) final case class RMP( @@ -200,18 +201,18 @@ object MagnetMap: // and never resolve refs on a DFDesignBlock, so this never throws. given MemberGetSet = rootDB.getSet - val groups: List[List[RMP]] = allRMPs.groupBy(_.dfType).values.map(_.toList).toList + def isCandidateTarget(rmp: RMP): Boolean = + rmp.cp match + case ConnectPoint.Direct(dcl) + if rmp.isPortIn || rmp.isPortOut && rmp.ownerIsBlackBox || + alreadyConnectedOrAssignedDcls.contains(dcl) => + false + case via: ConnectPoint.Via if rmp.isPortOut || alreadyConnectedMPVias.contains(via) => + false + case _ => true + val groups: List[List[RMP]] = allRMPs.groupByOrdered(_.dfType).map(_._2) val ret = groups.flatMap { grp => - grp.view.filter { rmp => - rmp.cp match - case ConnectPoint.Direct(dcl) - if rmp.isPortIn || rmp.isPortOut && rmp.ownerIsBlackBox || - alreadyConnectedOrAssignedDcls.contains(dcl) => - false - case via: ConnectPoint.Via if rmp.isPortOut || alreadyConnectedMPVias.contains(via) => - false - case _ => true - }.flatMap { targetRMP => + grp.view.filter(isCandidateTarget).flatMap { targetRMP => val targetDsn = targetRMP.ownerDesign val sourceRMP: Option[RMP] = if (targetRMP.isPortIn) @@ -295,6 +296,10 @@ object MagnetMap: throw new IllegalArgumentException(errors.view.reverse.mkString("\n\n")) val pointInfo: Map[ConnectPoint, (DFDesignBlock, String)] = allRMPs.iterator.map(rmp => rmp.cp -> (rmp.ownerDesign, rmp.name)).toMap - (ret, pointInfo) + // candidate targets for which no source was found anywhere, in allRMPs (deterministic) + // order; consumers may e.g. surface a sourceless derived clock at the top design + val unmatchedTargets = + allRMPs.filter(rmp => isCandidateTarget(rmp) && !ret.contains(rmp.cp)).map(_.cp) + (ret, pointInfo, unmatchedTargets) end get end MagnetMap diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddClkRst.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddClkRst.scala index 738c30d50..200dbc23f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddClkRst.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddClkRst.scala @@ -17,31 +17,32 @@ import dfhdl.core.{asFE, DFCG} * annotations, unless already explicitly declared. * - Memoizes the opaque clk/rst types by the annotation content tuples (so identical * configurations share the same opaque type). - * - Resolves derived clocks: a related domain may declare its own `Clk <> IN` dcl, a clock that - * is fully synchronous with the clock of its related target (typically a gated version of it), - * while the reset is still shared through the relation. The dcl's identity is its - * design-relative name (domain `active` with dcl `clk` identifies as `active_clk`) paired with - * the origin clock it derives from. When any dcl of an identity is explicitly driven somewhere - * (a gated clock is connected to it), a distinct opaque type `Clk_` is minted for the - * whole identity, so the magnet connection flow threads the gated clock across the hierarchy - * by type (and, through `AddMagnets` pass-through ports, by name). When no dcl of the identity - * is driven anywhere, the dcls are retyped to their origin's opaque type instead, so the - * magnet flow collapses them onto the origin clock net (the ungated form: each derived clock - * port is connected wherever its origin clock connects). + * - Resolves derived clocks: a related domain may declare its own `Clk <> IN` / `Clk <> OUT` + * dcl, a clock that is fully synchronous with the clock of its related target (typically a + * gated version of it), while the reset is still shared through the relation. An input port + * consumes the derived clock and an output port sources it (the gating site). The dcl's + * identity is its design-relative name (domain `active` with dcl `clk` identifies as + * `active_clk`) paired with the origin clock group it derives from, and a distinct opaque type + * `Clk_` is minted per identity, so the magnet connection flow threads same-named + * derived clocks of the same group into one clock across the hierarchy (and, through + * `AddMagnets` pass-through ports, by name). A derived clock is never implicitly merged onto + * its origin clock: connecting the two (the ungated form) is always an explicit connection, + * and a derived clock group with no source anywhere surfaces as a top-level input port (see + * the sourceless-magnet handling in `AddMagnets`). * - Generates the sim-driver block for top-level simulation designs. */ case object AddClkRst extends GlobalStage: def dependencies: List[Stage] = List(ToRT, ExplicitClkRstCfg) def nullifies: Set[Stage] = Set(ViaConnection) - // Identity of a derived clock declared as a `Clk <> IN` dcl inside a related domain: the - // dcl's design-relative name paired with the origin clock it derives from. The origin is - // either a root clock configuration (the related chain ends at an owner carrying a resolved - // `@timing.clock`) or another derived clock (nested gating). + // Identity of a derived clock declared as a clk dcl inside a related domain: the dcl's + // design-relative name paired with the origin clock it derives from. The origin is either + // a root clock group (the related chain ends at an owner carrying a resolved + // `@timing.clock`, identified by its grpName) or another derived clock (nested gating). private final case class DerivedClkId(origin: DerivedClkOrigin, relName: String) derives CanEqual private enum DerivedClkOrigin derives CanEqual: - case Root(clk: constraints.Timing.Clock) + case Root(grpName: String) case Derived(id: DerivedClkId) def transformGlobal(designDB: DB)(using co: CompilerOptions, @@ -350,13 +351,14 @@ case object AddClkRst extends GlobalStage: } // Pre-pass: derived (related-domain) clock resolution. Collects every related domain's - // clk dcl with its identity, and every explicitly driven clk dcl (directly connected, or - // connected from a parent design via port-by-name selection). Populates opaqueReplaceMap - // up front so that per-owner processing below retypes the dcls and any values of their - // user opaque types (e.g. `.as(dmn.Clk)` casts of gated clocks). See the stage doc for - // the mint-when-driven / collapse-when-undriven semantics. + // clk dcl (IN or OUT) with its identity and mints a distinct `Clk_` opaque type + // per identity, unconditionally: same-named derived clocks within the same clock group + // form one clock, threaded by the magnet connection flow, and are never implicitly + // merged onto their origin clock (connecting a derived clock to the origin is always an + // explicit choice). Populates opaqueReplaceMap up front so that per-owner processing + // below retypes the dcls and any values of their user opaque types (e.g. `.as(dmn.Clk)` + // casts of gated clocks). val relatedClkDcls = mutable.ListBuffer.empty[(DFVal.Dcl, DerivedClkId)] - val drivenClkDcls = mutable.Set.empty[DFVal.Dcl] // membership queries only designDB.subDBs.foreach { case (_, subDB) => subDB.atGetSet { def relatedTargetOf(owner: DFDomainOwner): Option[DFDomainOwner] = @@ -382,7 +384,7 @@ case object AddClkRst extends GlobalStage: case None => owner.meta.annotations.collectFirst { case clk: constraints.Timing.Clock => clk - }.map(DerivedClkOrigin.Root.apply) + }.map(clk => DerivedClkOrigin.Root(grpName(clk))) subDB.domainOwnerMemberList.foreach { case (owner, members) => owner.domainType match case DomainType.RT if relatedTargetOf(owner).nonEmpty => @@ -391,33 +393,14 @@ case object AddClkRst extends GlobalStage: } case _ => } - subDB.connectionTable.connectToVals.foreach { - case dcl: DFVal.Dcl if dcl.isClkDcl => drivenClkDcls += dcl - case pbns: DFVal.PortByNameSelect => - designDB.pbnsToPort(pbns, subDB).foreach { case (dcl, childSub) => - if (childSub.atGetSet(dcl.isClkDcl)) drivenClkDcls += dcl - } - case _ => - } } } locally { - val drivenIds: Set[DerivedClkId] = - relatedClkDcls.view.collect { case (dcl, id) if drivenClkDcls.contains(dcl) => id }.toSet val mintedDerived = mutable.Map.empty[DerivedClkId, coreDFOpaque[coreDFOpaque.Clk]] - def chosenTypeOf(id: DerivedClkId): coreDFOpaque[coreDFOpaque.Clk] = - if (drivenIds.contains(id)) - mintedDerived.getOrElseUpdate(id, mintClkType(s"Clk_${id.relName}")(using DFCG())) - else - id.origin match - case DerivedClkOrigin.Root(clkAnnot) => - clkTypeMap.getOrElseUpdate( - clkAnnot, - mintClkType(s"Clk_${grpName(clkAnnot)}")(using DFCG()) - ) - case DerivedClkOrigin.Derived(parentId) => chosenTypeOf(parentId) relatedClkDcls.foreach { case (dcl, id) => - opaqueReplaceMap += dcl.dfType.asInstanceOf[DFOpaque] -> chosenTypeOf(id).asIR + val mintedType = + mintedDerived.getOrElseUpdate(id, mintClkType(s"Clk_${id.relName}")(using DFCG())) + opaqueReplaceMap += dcl.dfType.asInstanceOf[DFOpaque] -> mintedType.asIR } } diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddMagnets.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddMagnets.scala index da4e68deb..3487dd2df 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddMagnets.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/AddMagnets.scala @@ -27,38 +27,46 @@ case object AddMagnets extends GlobalStage: def nameOf(cp: ConnectPoint): String = designDB.magnetPointInfo(cp)._2 // Populating a missing magnets map with the suggested port names and direction val missingMagnets = mutable.Map.empty[DFDesignBlock, Map[DFType, (String, DFVal.Modifier.Dir)]] + def registerMissingMagnet( + dsn: DFDesignBlock, + dfType: DFType, + name: String, + dir: DFVal.Modifier.Dir + ): Unit = + missingMagnets.get(dsn) match + case None => missingMagnets += dsn -> Map(dfType -> (name, dir)) + case Some(dfTypeNameMap) if !dfTypeNameMap.contains(dfType) => + missingMagnets += dsn -> (dfTypeNameMap + (dfType -> (name, dir))) + case _ => // do nothing + // climb from a bottom design upward while memoizing missing magnets. With + // DFDesignBlock.ownerRef == Empty the lexical parent is no longer reachable via + // getOwnerDesign — walk up through `designBlockOwnershipMap` (parents-via-instances, + // root-aware) instead. Multiple parents at any level are all visited; iteration stops + // once we reach `topDsnOpt` (exclusive; None climbs through the entire hierarchy, + // top design included). The bottom endpoint is excluded from the registration. + def climbUpDsn( + bottomDsn: DFDesignBlock, + topDsnOpt: Option[DFDesignBlock], + dfType: DFType, + name: String, + dir: DFVal.Modifier.Dir + ): Unit = + val visited = mutable.Set.empty[DFDesignBlock] + val queue = mutable.Queue.empty[DFDesignBlock] + queue ++= designDB.designBlockOwnershipMap.getOrElse(bottomDsn, Set.empty) + while (queue.nonEmpty) + val dsn = queue.dequeue() + if (!topDsnOpt.contains(dsn) && visited.add(dsn)) + registerMissingMagnet(dsn, dfType, name, dir) + queue ++= designDB.designBlockOwnershipMap.getOrElse(dsn, Set.empty) + end climbUpDsn designDB.magnetConnectionMap.foreach { (toMP, fromMP) => val toDsn = ownerOf(toMP) val fromDsn = ownerOf(fromMP) val fromName = nameOf(fromMP) val dfType = toMP.dfType - def anotherMissingMagnet(dsn: DFDesignBlock, dir: DFVal.Modifier.Dir): Unit = - missingMagnets.get(dsn) match - case None => missingMagnets += dsn -> Map(dfType -> (fromName, dir)) - case Some(dfTypeNameMap) if !dfTypeNameMap.contains(dfType) => - missingMagnets += dsn -> (dfTypeNameMap + (dfType -> (fromName, dir))) - case _ => // do nothing - // climb from a bottom design to a top design, while memoizing missing - // magnets between the designs. With DFDesignBlock.ownerRef == Empty the - // lexical parent is no longer reachable via getOwnerDesign — walk up - // through `designBlockOwnershipMap` (parents-via-instances, root-aware) - // instead. Multiple parents at any level are all visited; iteration stops - // once we reach `topDsn`. Both endpoints are excluded from the registration. - def climbUpDsn( - bottomDsn: DFDesignBlock, - topDsn: DFDesignBlock, - dir: DFVal.Modifier.Dir - ): Unit = - val visited = mutable.Set.empty[DFDesignBlock] - val queue = mutable.Queue.empty[DFDesignBlock] - queue ++= designDB.designBlockOwnershipMap.getOrElse(bottomDsn, Set.empty) - while (queue.nonEmpty) - val dsn = queue.dequeue() - if (dsn != topDsn && visited.add(dsn)) - anotherMissingMagnet(dsn, dir) - queue ++= designDB.designBlockOwnershipMap.getOrElse(dsn, Set.empty) def climbUp(bottomPort: ConnectPoint, topDsn: DFDesignBlock): Unit = - climbUpDsn(ownerOf(bottomPort), topDsn, bottomPort.dir) + climbUpDsn(ownerOf(bottomPort), Some(topDsn), dfType, fromName, bottomPort.dir) (toMP.dir, fromMP.dir) match // climbing up to the source input port case (IN, IN) => climbUp(toMP, fromDsn) @@ -74,6 +82,19 @@ case object AddMagnets extends GlobalStage: case _ => // do nothing end match } + // Sourceless derived clocks: a Clk-kind magnet target with no source anywhere in the + // hierarchy climbs all the way up, top design included, so the derived clock surfaces + // as a top-level input port instead of dangling internally (a forgotten gated-clock + // connection becomes a visible port rather than a silently dead clock). ConnectMagnets + // recomputes the magnet map on the patched DB, where the top-added port is the source + // that wires the whole chain. Root clock groups never reach here: AddClkRst gives every + // design that uses them its own dcl, which is the subtree's source. + designDB.magnetUnmatchedTargets.foreach { toMP => + toMP.dfType match + case DFOpaque(kind = DFOpaque.Kind.Clk) if toMP.isPortIn => + climbUpDsn(ownerOf(toMP), None, toMP.dfType, nameOf(toMP), DFVal.Modifier.Dir.IN) + case _ => + } // Add each design's missing magnet ports under that design's own sub-DB getSet. val newSubDBs = ListMap.from( designDB.subDBs.iterator.map { case (key, subDB) => diff --git a/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala b/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala index f2aff7aff..339fe5bb8 100644 --- a/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/AddClkRstSpec.scala @@ -849,7 +849,7 @@ class AddClkRstSpec extends StageSpec: |""".stripMargin ) } - test("Related domain with an undriven derived clock collapses to the origin clk type") { + test("An unconnected derived clock keeps its distinct clk type") { class ID extends RTDesign: val x = SInt(16) <> IN val y = SInt(16) <> OUT.REG init 0 @@ -864,6 +864,7 @@ class AddClkRstSpec extends StageSpec: id, """|case class Clk_default() extends Clk |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk | |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) @@ -875,10 +876,49 @@ class AddClkRstSpec extends StageSpec: | y.din := x | @timing.related(ID.this) | val active = new RTDomain: - | val clk = Clk_default <> IN + | val clk = Clk_active_clk <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end active + |end ID + |""".stripMargin + ) + } + test("Related domain with an output derived clock (internal gating site)") { + class ID extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + val gclk = Bit <> IN + y.din := x + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> OUT + val z = SInt(16) <> OUT.REG init 0 + z.din := x + active.clk <> gclk.as(active.Clk) + val id = (new ID).addClkRst + assertCodeString( + id, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class ID extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | val gclk = Bit <> IN + | y.din := x + | @timing.related(ID.this) + | val active = new RTDomain: + | val clk = Clk_active_clk <> OUT | val z = SInt(16) <> OUT.REG init sd"16'0" | z.din := x | end active + | active.clk <> gclk.as(Clk_active_clk) |end ID |""".stripMargin ) diff --git a/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala index 471353a87..2ac3fc31a 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala @@ -339,4 +339,177 @@ class ConnectMagnetsSpec extends StageSpec: ) } + test("An output derived clock sources same-named derived clocks") { + class Gater extends RTDesign: + val gclk = Bit <> IN + val en = Bit <> IN + val enq = Bit <> VAR.REG init 0 + enq.din := en + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> OUT + active.clk <> (gclk && enq).as(active.Clk) + class User extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + y.din := x + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + class Top extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val gclk = Bit <> IN + val en = Bit <> IN + val gater = Gater() + val user = User() + gater.gclk <> gclk + gater.en <> en + user.x <> x + y <> user.y + val top = (new Top).connectMagnets + assertCodeString( + top, + """|case class Clk_default() extends Clk + |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Gater extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val gclk = Bit <> IN + | val en = Bit <> IN + | val enq = Bit <> VAR.REG init 0 + | enq.din := en + | @timing.related(Gater.this) + | val active = new RTDomain: + | val clk = Clk_active_clk <> OUT + | end active + | active.clk <> (gclk && enq).as(Clk_active_clk) + |end Gater + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class User extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | y.din := x + | @timing.related(User.this) + | val active = new RTDomain: + | val clk = Clk_active_clk <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end active + |end User + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Top extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val gclk = Bit <> IN + | val en = Bit <> IN + | val gater = Gater() + | val user = User() + | gater.gclk <> gclk + | gater.en <> en + | user.x <> x + | y <> user.y + | user.active.clk <> gater.active.clk + | user.clk <> clk + | gater.clk <> clk + | user.rst <> rst + | gater.rst <> rst + |end Top + |""".stripMargin + ) + } + test("A sourceless derived clock surfaces as a top-level port") { + class Leaf extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + y.din := x + @hw.constraints.timing.related(this) + val active = new RTDomain: + val clk = Clk <> IN + val z = SInt(16) <> OUT.REG init 0 + z.din := x + class Mid extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val leaf = Leaf() + leaf.x <> x + y <> leaf.y + class Top extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val mid = Mid() + mid.x <> x + y <> mid.y + val top = (new Top).connectMagnets + assertCodeString( + top, + """|case class Clk_active_clk() extends Clk + |case class Clk_default() extends Clk + |case class Rst_default() extends Rst + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Leaf extends RTDesign: + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | y.din := x + | @timing.related(Leaf.this) + | val active = new RTDomain: + | val clk = Clk_active_clk <> IN + | val z = SInt(16) <> OUT.REG init sd"16'0" + | z.din := x + | end active + |end Leaf + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Mid extends RTDesign: + | val active_clk = Clk_active_clk <> IN + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val leaf = Leaf() + | leaf.x <> x + | y <> leaf.y + | leaf.active.clk <> active_clk + | leaf.clk <> clk + | leaf.rst <> rst + |end Mid + | + |@timing.clock(rate = 50.MHz, edge = _.rising, portName = "clk", inclusionPolicy = _.asneeded, grpName = "default") + |@timing.reset(mode = _.sync, active = _.high, portName = "rst", inclusionPolicy = _.asneeded) + |class Top extends RTDesign: + | val active_clk = Clk_active_clk <> IN + | val clk = Clk_default <> IN + | val rst = Rst_default <> IN + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val mid = Mid() + | mid.x <> x + | y <> mid.y + | mid.active_clk <> active_clk + | mid.clk <> clk + | mid.rst <> rst + |end Top + |""".stripMargin + ) + } + end ConnectMagnetsSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/ToEDSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ToEDSpec.scala index 105133c25..eaa96230d 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ToEDSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ToEDSpec.scala @@ -1741,7 +1741,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } - test("Related domain with a driven derived clock and a shared async reset") { + test("Related domain with a derived clock and a shared async reset") { class IDTop extends EDDesign: val x = SInt(16) <> IN val y = SInt(16) <> OUT @@ -1760,6 +1760,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): id, """|case class Clk_default() extends Clk |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk | |class IDTop extends EDDesign: | val x = SInt(16) <> IN @@ -1774,7 +1775,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): | else if (clk.actual.rising) o :== x | end dmn1 | val active = new EDDomain: - | val clk = Clk_default <> IN + | val clk = Clk_active_clk <> IN | val o = SInt(16) <> OUT | process(clk, dmn1.rst): | if (dmn1.rst.actual == 1) o :== sd"16'0" @@ -1805,6 +1806,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): id, """|case class Clk_default() extends Clk |case class Rst_default() extends Rst + |case class Clk_gated_clk() extends Clk | |class IDTop extends EDDesign: | val x = SInt(16) <> IN @@ -1821,7 +1823,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): | end if | end dmn1 | val gated = new EDDomain: - | val clk = Clk_default <> IN + | val clk = Clk_gated_clk <> IN | end gated | val user = new EDDomain: | val o = SInt(16) <> OUT @@ -1836,7 +1838,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } - test("Related domain with an undriven derived clock collapses onto the origin clock") { + test("Related domain with an unconnected derived clock keeps its own clock") { class IDTop extends EDDesign: val x = SInt(16) <> IN val y = SInt(16) <> OUT @@ -1854,6 +1856,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): id, """|case class Clk_default() extends Clk |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk | |class IDTop extends EDDesign: | val x = SInt(16) <> IN @@ -1870,7 +1873,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): | end if | end dmn1 | val active = new EDDomain: - | val clk = Clk_default <> IN + | val clk = Clk_active_clk <> IN | val o = SInt(16) <> OUT | process(clk): | if (clk.actual.rising) @@ -1901,6 +1904,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): id, """|case class Clk_default() extends Clk |case class Rst_default() extends Rst + |case class Clk_active_clk() extends Clk | |class IDTop extends EDDesign: | val x = SInt(16) <> IN @@ -1917,7 +1921,7 @@ class ToEDSpec extends StageSpec(stageCreatesUnrefAnons = true): | end if | end dmn1 | val active = new EDDomain: - | val clk = Clk_default <> IN + | val clk = Clk_active_clk <> IN | val o = SInt(16) <> OUT init sd"16'0" | process(clk): | if (clk.actual.rising) o :== x diff --git a/core/src/main/scala/dfhdl/core/Modifier.scala b/core/src/main/scala/dfhdl/core/Modifier.scala index c9dc3ebc5..64eaeeca1 100644 --- a/core/src/main/scala/dfhdl/core/Modifier.scala +++ b/core/src/main/scala/dfhdl/core/Modifier.scala @@ -104,14 +104,18 @@ object Modifier: } match case Some(target) => kind match - // an input clock port is allowed: it declares a derived clock that is - // fully synchronous with the related domain's clock (e.g. a gated - // version of it), while the reset is still shared through the relation + // input/output clock ports are allowed: they declare a derived clock + // that is fully synchronous with the related domain's clock (e.g. a + // gated version of it), while the reset is still shared through the + // relation. An input port consumes the derived clock; an output port + // sources it (the gating site drives it from the design scope) case ir.DFOpaque.Kind.Clk - if modifier.value.isPort && modifier.value.dir == IRModifier.IN => + if modifier.value.isPort && + (modifier.value.dir == IRModifier.IN || + modifier.value.dir == IRModifier.OUT) => case ir.DFOpaque.Kind.Clk => throw new IllegalArgumentException( - s"Only an input clock port (`Clk <> IN`) is allowed in a related domain.\nSuch a clock is derived from (fully synchronous with) the clock of the related domain `${target.getName}`, and is typically driven by a gated version of it." + s"Only clock ports (`Clk <> IN` / `Clk <> OUT`) are allowed in a related domain.\nSuch a clock is derived from (fully synchronous with) the clock of the related domain `${target.getName}`, and is typically a gated version of it." ) case _ => throw new IllegalArgumentException( diff --git a/docs/user-guide/design-domains/index.md b/docs/user-guide/design-domains/index.md index ec818cd1f..2fcd67962 100644 --- a/docs/user-guide/design-domains/index.md +++ b/docs/user-guide/design-domains/index.md @@ -135,7 +135,8 @@ class NoResetRelatedDomain extends RTDesign: ``` #### Derived Clocks (Gated Clocks) -A related domain may declare its own clock port, and only an input clock port (`Clk <> IN`): +A related domain may declare its own clock port, either an input (`Clk <> IN`, consuming +the derived clock) or an output (`Clk <> OUT`, sourcing it): ```scala class GatedDomainDesign extends RTDesign: @@ -150,26 +151,31 @@ class GatedDomainDesign extends RTDesign: This declares a *derived clock*: a clock that is fully synchronous with the clock of the related target (same source, same edges, phase-aligned), while the reset (subject to `includeReset`) is still shared through the relation. The typical use is a gated clock: -the port sets the stage for a parent design to connect a gated version of the origin clock, -yet nothing in this design asserts that gating actually happens; that is the parent's -connectivity decision. Because the domains are related, no clock-domain-crossing discipline -applies between them, and sharing an asynchronous reset across the gated clocks is safe (a -flop whose clock is gated off still sees the reset assertion). +an input port receives a gated version of the origin clock from outside, and an output +port exports one that the design gates internally (the design scope drives it, e.g. +`active.clk <> gatedClk.as(active.Clk)`). Because the domains are related, no +clock-domain-crossing discipline applies between them, and sharing an asynchronous reset +across the gated clocks is safe (a flop whose clock is gated off still sees the reset +assertion). The identity of a derived clock is its design-relative name: domain `active` with port -`clk` identifies as `active_clk`, which is also its flattened port name. Same-named derived -clocks of the same origin refer to the same clock everywhere in the hierarchy. The compiler -resolves them globally: - -- **Driven somewhere**: when any same-identity port is explicitly connected (e.g. a parent - connects an ICG output via `child.active.clk <> gatedClk.as(child.active.Clk)`), a - distinct clock type `Clk_active_clk` is created, and every same-identity port across the - hierarchy is threaded to that connection through automatically added pass-through ports - (also named `active_clk`). -- **Driven nowhere**: the ports take the origin clock's type, and each is automatically - connected wherever its origin clock connects. This is the ungated form: the derived clock - collapses onto the origin clock net, as in an FPGA build of an ASIC design that removes - clock gating. +`clk` identifies as `active_clk`, which is also its flattened port name. The connection +rule is deliberately narrow and predictable: **same-named derived clocks within the same +clock group form one clock**, automatically threaded across the hierarchy (through +automatically added pass-through ports, also named `active_clk`), with an output port or +an explicitly connected port as the source. A derived clock is *never* implicitly merged +onto its origin clock: + +- **Sourced somewhere**: a `Clk <> OUT` port (the internal gating site), or any port a + parent explicitly connects (e.g. `child.active.clk <> gatedClk.as(child.active.Clk)`), + sources every same-named port in scope. +- **Sourced nowhere**: the derived clock surfaces as a top-level input port instead of + silently taking the origin clock, so a forgotten gated-clock connection is visible in + the port list rather than a silently dead or wrongly merged clock. +- **The ungated form is an explicit choice**: to run a derived clock from the origin clock + (as in an FPGA build of an ASIC design that removes clock gating), connect the two + explicitly, e.g. at a wrapper that declares its own root clock port: + `core.active.clk <> clk.as(core.active.Clk)`. Derived clocks nest: a related domain with its own clock port may itself be the target of another related domain, whose clock port then derives from the outer derived clock (gating diff --git a/lib/src/test/scala/ElaborationChecksSpec.scala b/lib/src/test/scala/ElaborationChecksSpec.scala index e08762eb6..8fc967130 100644 --- a/lib/src/test/scala/ElaborationChecksSpec.scala +++ b/lib/src/test/scala/ElaborationChecksSpec.scala @@ -1878,24 +1878,6 @@ class ElaborationChecksSpec extends DesignSpec: |Hierarchy: PartialAssign |Message: Found a latch variable `v`. Latches are not allowed under RT domains.""".stripMargin ) - test("output clk in related domain check"): - object Test: - @top(false) class Top extends RTDesign: - self => - @hw.constraints.timing.related(self) - val dmn = new RTDomain: - val clk = Clk <> OUT - end Test - import Test.* - assertElaborationErrors(Top())( - s"""|Elaboration errors found! - |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1887:21 - 1887:31 - |Hierarchy: Top.clk - |Operation: `Port/Variable constructor` - |Message: Only an input clock port (`Clk <> IN`) is allowed in a related domain. - |Such a clock is derived from (fully synchronous with) the clock of the related domain `Top`, and is typically driven by a gated version of it.""".stripMargin - ) test("var clk in related domain check"): object Test: @top(false) class Top extends RTDesign: @@ -1908,11 +1890,11 @@ class ElaborationChecksSpec extends DesignSpec: assertElaborationErrors(Top())( s"""|Elaboration errors found! |DFiant HDL elaboration error! - |Position: ${currentFilePos}ElaborationChecksSpec.scala:1905:21 - 1905:31 + |Position: ${currentFilePos}ElaborationChecksSpec.scala:1887:21 - 1887:31 |Hierarchy: Top.clk |Operation: `Port/Variable constructor` - |Message: Only an input clock port (`Clk <> IN`) is allowed in a related domain. - |Such a clock is derived from (fully synchronous with) the clock of the related domain `Top`, and is typically driven by a gated version of it.""".stripMargin + |Message: Only clock ports (`Clk <> IN` / `Clk <> OUT`) are allowed in a related domain. + |Such a clock is derived from (fully synchronous with) the clock of the related domain `Top`, and is typically a gated version of it.""".stripMargin ) end ElaborationChecksSpec From f6ec4b38bf2c5dce41ccab2c76aed8010e2e4919 Mon Sep 17 00:00:00 2001 From: Oron Date: Sun, 16 Aug 2026 21:30:57 +0300 Subject: [PATCH 44/57] new-stage: name groupByOrdered as the deterministic groupBy replacement Co-Authored-By: Claude Fable 5 --- .claude/commands/new-stage.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index d5ca4e44e..5e68fe9a5 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -31,6 +31,7 @@ Given the same input `DB`, a stage must always produce bit-for-bit the same outp **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). +- `xs.groupBy(f).values` — the grouping itself is fine, but a standard `Map`'s value iteration order is not. Use `xs.groupByOrdered(f)` from `dfhdl.internals` instead: it returns `List[(P, List[T])]` with groups in first-appearance order and members in input order, a drop-in replacement whenever grouping drives output order (`MagnetMap.get` is the working example). - Using `hashCode`-based identity anywhere in the transformation logic. - Relying on mutable external state (counters, caches, `var`s outside the `transform` call). From a8dbc4dbb22346a9697000923cfcca1f97cea254 Mon Sep 17 00:00:00 2001 From: Oron Date: Sun, 16 Aug 2026 21:44:12 +0300 Subject: [PATCH 45/57] core: RTDerivedClkDomainSrc, the sourcing derived-clock shorthand The Clk <> OUT counterpart of RTDerivedClkDomain: the internal gating site, whose design scope drives the derived clock (e.g. from an ICG output) and exports it to every same-named derived clock in scope. Co-Authored-By: Claude Fable 5 --- .../test/scala/StagesSpec/PrintCodeStringSpec.scala | 11 ++++++++++- core/src/main/scala/dfhdl/core/Container.scala | 5 +++++ docs/user-guide/design-domains/index.md | 6 +++++- 3 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 5da5c9c50..53137c083 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -917,7 +917,10 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): // path-prefixed shorthand: a domain related to `gated` rather than to the design val sub = new gated.RTRegion: val v = SInt(16) <> VAR init 0 - y := r + related.x + gated.z + trans.w + sub.v + val gclk = Bit <> IN + val src = new RTDerivedClkDomainSrc {} + src.clk <> gclk.as(src.Clk) + y := r + related.x + gated.z + trans.w + sub.v end IDWithDomains val id = (new IDWithDomains) assertCodeString( @@ -948,6 +951,12 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): | val sub = new RTDomain: | val v = SInt(16) <> VAR init sd"16'0" | end sub + | val gclk = Bit <> IN + | @timing.related(IDWithDomains.this) + | val src = new RTDomain: + | val clk = Clk <> OUT + | end src + | src.clk <> gclk.as(Clk) | y := r + related.x + gated.z + trans.w + sub.v |end IDWithDomains |""".stripMargin diff --git a/core/src/main/scala/dfhdl/core/Container.scala b/core/src/main/scala/dfhdl/core/Container.scala index 81463e992..80adba19d 100644 --- a/core/src/main/scala/dfhdl/core/Container.scala +++ b/core/src/main/scala/dfhdl/core/Container.scala @@ -54,6 +54,11 @@ trait RTDomainContainer extends DomainContainer: // `val clk = Clk <> IN` declaration. abstract class RTDerivedClkDomain extends RTRelatedDomain: val clk = DFVal.Dcl(DFOpaque(Clk()), Modifier.IN)(using dfc.setName("clk")) + // The sourcing variant of `RTDerivedClkDomain` (`val clk = Clk <> OUT`): the internal + // gating site, whose design scope drives the derived clock (e.g. from an ICG output) and + // exports it to every same-named derived clock in scope. + abstract class RTDerivedClkDomainSrc extends RTRelatedDomain: + val clk = DFVal.Dcl(DFOpaque(Clk()), Modifier.OUT)(using dfc.setName("clk")) // A scoping construct rather than a domain in its own right: a region groups logic under // this container's timing context with no observable footprint, neither a clock identity // nor a naming one (its members keep their bare names). Equivalent to an `RTRelatedDomain` diff --git a/docs/user-guide/design-domains/index.md b/docs/user-guide/design-domains/index.md index 2fcd67962..6106bce0a 100644 --- a/docs/user-guide/design-domains/index.md +++ b/docs/user-guide/design-domains/index.md @@ -193,6 +193,7 @@ equivalent to a plain `RTDomain` with the corresponding annotations, and manifes |---|---| | `RTRelatedDomain` | `@timing.related(this)` `new RTDomain` | | `RTDerivedClkDomain` | `RTRelatedDomain` with a `val clk = Clk <> IN` declaration | +| `RTDerivedClkDomainSrc` | `RTRelatedDomain` with a `val clk = Clk <> OUT` declaration | | `RTRegion` | `RTRelatedDomain` with `@flattenMode.transparent` | ```scala @@ -220,7 +221,10 @@ The two domain shorthands create a grouping with a footprint of its own: (`@timing.related(this, includeReset = false)`) when the domain must opt out of the reset. - **`RTDerivedClkDomain`** declares a derived (typically gated) clock as described in the previous section; its `clk` port identifies by the domain's name (domain `active` yields - the `active_clk` identity and flattened port name). + the `active_clk` identity and flattened port name). **`RTDerivedClkDomainSrc`** is its + sourcing variant (`Clk <> OUT`): the internal gating site, whose design scope drives the + derived clock (e.g. `active.clk <> icgOut.as(active.Clk)`) and exports it to every + same-named derived clock in scope. An **`RTRegion`** is deliberately the opposite: a scoping construct with no observable footprint of its own, neither a clock identity nor a naming one. It places logic under a From 699593e1ec2c097fd5f48907574c2c0ed7becd0b Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 00:49:30 +0300 Subject: [PATCH 46/57] ir: Meta identity excludes position/doc; named comparisons replace CanEqual Cache entries stay valid across formatting/doc edits (the code digest hashes typed trees), so stored members carry positions the live run no longer has, and value unification at adoption (globals, refTable re-uniting) broke on exactly the edits the digest is designed to survive. Meta now defines two named comparisons and derives no CanEqual, so a direct `meta == meta` does not compile and every site names which notion it means: - `sameIdentityAs` (name + annotations, precisely the digest-visible fields): implemented by equals/hashCode, so member case-class equality composes it implicitly and cached members unify with their live counterparts. - `sameDclAs` (all fields): "same declaration", anchored on position. Used by DesignLoadKey's intra-run gate equality (without it, same-named designs from different declarations unify into one) and UniqueDesigns' grouping (reachable via adopted cache children, which skip elaboration's dclName enumeration). The hashCode change flushed out a latent nondeterminism: magnetConnectionMap was a hash Map iterated by ConnectMagnets to emit connections, with only a by-name sort on top (which ties for same-named points). It is now an insertion-ordered ListMap, so connection order follows instantiation order; one ConnectMagnetsSpec expectation updates from the old hash-derived order. Tests: MetaSpec pins both notions; SubDesignCacheSpec adds the position-drift adoption regression (fails with "Failed reference check!" without the fix); UniqueDesignsSpec pins that same-named distinct declarations stay separate. Co-Authored-By: Claude Fable 5 --- .claude/commands/new-stage.md | 9 +++ .../scala/dfhdl/compiler/ir/MagnetMap.scala | 5 +- .../main/scala/dfhdl/compiler/ir/Meta.scala | 23 +++++++- .../dfhdl/compiler/stages/UniqueDesigns.scala | 9 ++- .../scala/StagesSpec/ConnectMagnetsSpec.scala | 4 +- .../src/test/scala/StagesSpec/MetaSpec.scala | 38 ++++++++++++ .../scala/StagesSpec/SubDesignCacheSpec.scala | 55 +++++++++++++++++ .../scala/StagesSpec/UniqueDesignsSpec.scala | 59 +++++++++++++++++++ .../main/scala/dfhdl/core/DesignLoadKey.scala | 14 +++++ 9 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index 5e68fe9a5..cf161634d 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -1525,6 +1525,15 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) one, so an array sensitivity item has to be listed cell by cell (`@(mem[0] or mem[1] or ...)`); `@*` is undefined over arrays in the standard and absent from v95 entirely. VHDL names the array signal itself, so the expansion is Verilog-only. +40. **`Meta` has no `CanEqual` — name the comparison you mean** — a direct `meta == meta` does + not compile; choose `sameIdentityAs` (excludes `position`/`docOpt`, which can drift while an + elaboration-cache entry stays valid — cached members must still unify by value with live + ones) or `sameDclAs` (all fields; "same declaration" is anchored on position — see + `UniqueDesigns`' grouping and `DesignLoadKey`'s intra-run equality). `Meta.equals`/`hashCode` + implement `sameIdentityAs`, so member case-class equality composes the identity notion + implicitly. Watch for token-free case classes holding a `Meta` (e.g. `DesignLoadKey`): their + derived equality composes that loosened notion silently, unlike IR members, whose unique ref + tokens keep distinct members unequal regardless. --- diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala index dfbc5005a..e15002a01 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/MagnetMap.scala @@ -291,7 +291,10 @@ object MagnetMap: end match sourceRMP.map(s => targetRMP.cp -> s.cp) } - }.toMap + // insertion-ordered: consumers iterate this map to EMIT connections (with only a + // stable by-name sort on top, which ties for same-named points), so a hash map + // would order same-named connections by ConnectPoint hash codes + }.to(scala.collection.immutable.ListMap) if (errors.nonEmpty) throw new IllegalArgumentException(errors.view.reverse.mkString("\n\n")) val pointInfo: Map[ConnectPoint, (DFDesignBlock, String)] = diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/Meta.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/Meta.scala index 6fdc60819..c9fa36552 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/Meta.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/Meta.scala @@ -8,7 +8,28 @@ final case class Meta( position: Position, docOpt: Option[String], annotations: List[HWAnnotation] -) extends HasRefCompare[Meta] derives CanEqual, ReadWriter: +) extends HasRefCompare[Meta] derives ReadWriter: + // Two distinct comparisons, and deliberately NO `CanEqual` (a direct `meta == meta` + // does not compile under strictEquality), so every call site names which one it means: + // + // `sameIdentityAs`: excludes `position` and `docOpt`. The elaboration cache digest + // hashes typed trees, so positions and doc comments can drift while a cached entry + // stays valid, and cached members must still unify by value with their live + // counterparts (globals in `SubDesignEntry.cloneForAdoption` and sub-DB assembly + // unify by member equality). This is also what `equals`/`hashCode` implement, since + // member case-class equality composes through them implicitly. + // + // `sameDclAs`: all fields, position and doc included. Answers "same declaration", + // which position is what anchors — same-named designs from different declarations + // must not unify (`DesignLoadKey`'s intra-run gate tier, `UniqueDesigns`' grouping). + def sameIdentityAs(that: Meta): Boolean = + this.nameOpt == that.nameOpt && this.annotations == that.annotations + def sameDclAs(that: Meta): Boolean = + this.sameIdentityAs(that) && this.position == that.position && this.docOpt == that.docOpt + override def equals(that: Any): Boolean = that match + case that: Meta => this.sameIdentityAs(that) + case _ => false + override def hashCode: Int = (nameOpt, annotations).## val isAnonymous: Boolean = nameOpt.isEmpty val name: String = nameOpt.getOrElse(s"anon${this.hashString}") diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueDesigns.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueDesigns.scala index d42794a41..0599b0f13 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueDesigns.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueDesigns.scala @@ -34,7 +34,13 @@ case object UniqueDesigns extends GlobalStage: ): List[List[DFDesignBlock]] = val eqDesign: ((DFDesignBlock, List[DFMember]), (DFDesignBlock, List[DFMember])) => Boolean = case ((thisBlock, theseMembers), (thatBlock, thoseMembers)) - if thisBlock.dclMeta == thatBlock.dclMeta => + // `sameDclAs`, not `sameIdentityAs`: same-named, structurally-identical + // designs from DIFFERENT declarations must stay separate (there is no + // cross-declaration structural dedup). In-run inputs arrive with same-named + // distinct declarations already dclName-enumerated by elaboration, so this + // matters for inputs that skip that enumeration (adopted cache children, see + // the adopted-child dclName-clash gap in devdocs/elaboration-caching.md). + if thisBlock.dclMeta.sameDclAs(thatBlock.dclMeta) => (theseMembers lazyZip thoseMembers).forall { case (l, r) => l =~ r } case _ => false // we're grouping always according to case-insensitive design names because these affect @@ -43,6 +49,7 @@ case object UniqueDesigns extends GlobalStage: db.designMemberList.view .groupByCompare(eqDesign, d => scopedDclNameKey(d._1, ownerByDesign).hashCode()) .map(_.unzip._1).toList + end groupDesigns def transformGlobal(designDB: DB)(using co: CompilerOptions, refGen: RefGen): DB = // Cross-design structural comparison resolves refs from BOTH designs, so it diff --git a/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala index 2ac3fc31a..47f03ec6b 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ConnectMagnetsSpec.scala @@ -424,10 +424,10 @@ class ConnectMagnetsSpec extends StageSpec: | user.x <> x | y <> user.y | user.active.clk <> gater.active.clk - | user.clk <> clk | gater.clk <> clk - | user.rst <> rst + | user.clk <> clk | gater.rst <> rst + | user.rst <> rst |end Top |""".stripMargin ) diff --git a/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala b/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala new file mode 100644 index 000000000..f84da8dcb --- /dev/null +++ b/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala @@ -0,0 +1,38 @@ +package StagesSpec + +import munit.FunSuite +import dfhdl.compiler.ir.Meta +import dfhdl.internals.Position + +/** Pins `Meta`'s two comparison notions (`Meta` has no `CanEqual`, so call sites must name one): + * - `sameIdentityAs` excludes position and doc, which can drift under a valid elaboration-cache + * entry (the code digest hashes typed trees and never sees formatting or comments). + * `equals`/`hashCode` implement it, so member value-equality (cache adoption, global + * unification) composes through it. + * - `sameDclAs` compares all fields; "same declaration" is anchored on position. + */ +class MetaSpec extends FunSuite: + val posA = Position("FileA.scala", 1, 1, 1, 10) + val posB = Position("FileB.scala", 5, 3, 7, 2) + + test("identity excludes position and doc") { + val a = Meta(Some("x"), posA, None, Nil) + val b = Meta(Some("x"), posB, Some("a doc comment"), Nil) + assert(a.sameIdentityAs(b)) + assert(a.equals(b)) + assertEquals(a.hashCode, b.hashCode) + } + + test("identity includes the name") { + val a = Meta(Some("x"), posA, None, Nil) + assert(!a.sameIdentityAs(Meta(Some("y"), posA, None, Nil))) + assert(!a.sameIdentityAs(Meta(None, posA, None, Nil))) + } + + test("declaration sameness is anchored on position and doc") { + val a = Meta(Some("x"), posA, None, Nil) + assert(a.sameDclAs(Meta(Some("x"), posA, None, Nil))) + assert(!a.sameDclAs(Meta(Some("x"), posB, None, Nil))) + assert(!a.sameDclAs(Meta(Some("x"), posA, Some("a doc comment"), Nil))) + } +end MetaSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/SubDesignCacheSpec.scala b/compiler/stages/src/test/scala/StagesSpec/SubDesignCacheSpec.scala index 59e3ab8d9..f0fba5ba0 100644 --- a/compiler/stages/src/test/scala/StagesSpec/SubDesignCacheSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/SubDesignCacheSpec.scala @@ -16,6 +16,12 @@ def topCalc(arg: UInt[8] <> VAL): UInt[8] <> DFRET = def topCalcA(arg: UInt[8] <> VAL): UInt[8] <> DFRET = (arg + 2) * 3 def topCalcB(arg: UInt[8] <> VAL): UInt[8] <> DFRET = (arg - 4) * 5 +// a global constant read by both a cached def and the adopting host: the position-drift +// test shifts its STORED position, emulating a formatting edit above this declaration, +// which the code digest (a typed-tree hash) deliberately does not see +val globalW: UInt[8] <> CONST = 5 +def topCalcG(arg: UInt[8] <> VAL): UInt[8] <> DFRET = arg + globalW + /** Tests for the sub-design cache tier of the elaboration design load gate * (`ElaborationOptions.CacheEnable`): a pure method whose cached DB is found by the * `SubDesignDiskCache` service skips its body elaboration entirely; the harness still creates the @@ -461,6 +467,55 @@ class SubDesignCacheSpec extends StageSpec(stageCreatesUnrefAnons = true): assertEquals(localRefs(sub).intersect(localRefs(adopted)), Set.empty[ir.DFRefAny]) } + // The code digest hashes typed trees, so a formatting/doc edit above a declaration keeps + // every cache entry valid while shifting the positions the next live run captures. A + // stored global must still unify with its live counterpart under such drift, which is why + // `Meta` equality excludes position and doc. + test("a stored global with a drifted position still unifies with the live global") { + def genGHost(using DFC): dfhdl.core.Design = + class GHost extends DFDesign: + val data = UInt(8) <> IN + val o = UInt(8) <> OUT + o := topCalcG(data) + globalW + new GHost + val expectedG = + """|val globalW: UInt[8] <> CONST = d"8'5" + | + |def topCalcG(arg: UInt[8] <> VAL): UInt[8] <> DFRET = + | arg + globalW + |end topCalcG + | + |class GHost extends DFDesign: + | val data = UInt(8) <> IN + | val o = UInt(8) <> OUT + | o := topCalcG(data) + globalW + |end GHost + |""".stripMargin + val cache = new MapSubDesignCache + assertCodeString(genHostOf(genGHost, cache), expectedG) + assertEquals(cache.hits, 0) + // doctor the stored entries: shift every global member's position by one line + cache.entries.mapValuesInPlace { (_, json) => + val entry = ir.SubDesignEntry.fromJsonString(json) + given ir.MemberGetSet = entry.db.getSet + val shifted = entry.db.members.map { + case c: ir.DFVal.Const if c.isGlobal => + val pos = c.meta.position + c.copy(meta = + c.meta.copy(position = + pos.copy(lineStart = pos.lineStart + 1, lineEnd = pos.lineEnd + 1) + ) + ) + case m => m + } + entry.copy(db = entry.db.update(members = shifted), children = entry.children).toJsonString + } + // the adopted entry carries the drifted global; it must still re-unite with the live + // run's global (the same JVM object, created with the un-drifted position) + assertCodeString(genHostOf(genGHost, cache), expectedG) + assertEquals(cache.hits, 1) + } + test("without cacheEnable the elaboration is unaffected") { assertCodeString(genHost(using liveDFC), expectedCodeString) // the live dropped view matches the cached one asserted above diff --git a/compiler/stages/src/test/scala/StagesSpec/UniqueDesignsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/UniqueDesignsSpec.scala index 6bb31dda5..b980079a0 100644 --- a/compiler/stages/src/test/scala/StagesSpec/UniqueDesignsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/UniqueDesignsSpec.scala @@ -143,6 +143,65 @@ class UniqueDesignsSpec extends StageSpec: |""".stripMargin ) } + test("Same-named designs from distinct declarations stay separate") { + // two structurally-identical designs that share a name but NOT a declaration: + // there is no cross-declaration structural dedup, so they must remain two designs, + // dclName-enumerated. `Meta` equality excludes position, so "same declaration" is + // anchored on position explicitly by `DesignLoadKey` (the gate tier this test pins: + // without it the two declarations unify at elaboration into a single `Dup`) and by + // this stage's grouping guard (defensive: reachable only through adopted cache + // children, which skip elaboration's dclName enumeration). + object scope_a: + class Dup extends DFDesign: + val x = UInt(8) <> IN + val y = UInt(8) <> OUT + y := x + object scope_b: + class Dup extends DFDesign: + val x = UInt(8) <> IN + val y = UInt(8) <> OUT + y := x + class Top extends DFDesign: + val x1 = UInt(8) <> IN + val y1 = UInt(8) <> OUT + val x2 = UInt(8) <> IN + val y2 = UInt(8) <> OUT + val a = new scope_a.Dup + val b = new scope_b.Dup + a.x <> x1 + a.y <> y1 + b.x <> x2 + b.y <> y2 + val id = (new Top).uniqueDesigns + assertCodeString( + id, + """|class Dup_0 extends DFDesign: + | val x = UInt(8) <> IN + | val y = UInt(8) <> OUT + | y := x + |end Dup_0 + | + |class Dup_1 extends DFDesign: + | val x = UInt(8) <> IN + | val y = UInt(8) <> OUT + | y := x + |end Dup_1 + | + |class Top extends DFDesign: + | val x1 = UInt(8) <> IN + | val y1 = UInt(8) <> OUT + | val x2 = UInt(8) <> IN + | val y2 = UInt(8) <> OUT + | val a = Dup_0() + | val b = Dup_1() + | a.x <> x1 + | y1 <> a.y + | b.x <> x2 + | y2 <> b.y + |end Top + |""".stripMargin + ) + } test("Identical instances should share a single design") { class ID extends DFDesign: val x = SInt(16) <> IN diff --git a/core/src/main/scala/dfhdl/core/DesignLoadKey.scala b/core/src/main/scala/dfhdl/core/DesignLoadKey.scala index 383785ffc..60a56cf23 100644 --- a/core/src/main/scala/dfhdl/core/DesignLoadKey.scala +++ b/core/src/main/scala/dfhdl/core/DesignLoadKey.scala @@ -17,6 +17,20 @@ final case class DesignLoadKey( scalaArgs: List[Any], impureParamsKey: List[String] ): + // `sameDclAs`, not `sameIdentityAs`: this key answers "same declaration", so + // same-named designs from different declarations must not unify through the intra-run + // gate tier. This keeps the in-memory equality aligned with `localKey`, which + // serializes the full `dclMeta`. + override def equals(that: Any): Boolean = that match + case that: DesignLoadKey => + this.dclMeta.sameDclAs(that.dclMeta) && + this.inputTypes == that.inputTypes && + this.scalaArgs.equals(that.scalaArgs) && + this.impureParamsKey == that.impureParamsKey + case _ => false + override def hashCode: Int = + (dclMeta, dclMeta.position, inputTypes, scalaArgs, impureParamsKey).## + /** The cross-run content key: a stable digest of the key parts, used by the sub-design cache * service. `dclMeta` serializes through its IR writer; the DFType and impure-data parts are * already codeStrings; plain Scala args fold through their string forms. Best effort: unstable From acbdb3782d30c6ab4e1e19049aa0671e08c4b4e1 Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 01:23:14 +0300 Subject: [PATCH 47/57] ir: Meta.namespace and full Meta on named DFTypes Meta gains `namespace: String` (default ""), the Scala package path of the declaration it describes. It is digest-visible (a package clause is in the typed tree), so it joins `sameIdentityAs`/`hashCode`/`prot_=~`; regular values keep "" since their namespace is their design scope. The eventual packages feature places a named type by relating its namespace to the top design's. Capture, three paths: - plugin: `metaGen` takes a namespace argument; design classes and method declarations (`genDclMeta`) record their enclosing package via `mkNamespace`. Namespaces stop at the package level deliberately: enclosing objects are scoping, not namespacing. - macros: `TypeMetaGen` builds a full declaration Meta (name, namespace, position, doc) from the class symbol inside the struct and enum derivation macros, mirroring the plugin's `Position.fromAbsPath` convention. - runtime fallbacks: the product/reflection struct path, the enum-companion path, and the opaque `ClassEv` path record name + `getPackageName`, which agrees with the macro's package-level namespace (`SameFields.check` compares the two constructions by type equality). `NamedDFType` (DFStruct, DFEnum, DFOpaque, DFView) carries `meta: Meta` instead of a bare `name: String`: `name` reads `meta.name`, `updateName` goes through `meta.setName` (preserving namespace/position/doc under UniqueNames renames), and type identity becomes name + namespace + annotations + structure, composed through Meta's equality. Tuple structs are structural and get `Meta.named` (root namespace); DropRTProcess's synthesized state enum likewise. Five positional extractors that would have silently bound a Meta where a String stood (four `DFStruct(name, _)`, one `DFEnum(name, _, _)`) had dead binders, renamed to `_`. Zero output diff: the full suite passes unchanged from a cleared-cache state. MetaSpec extends to namespace identity and pins the captured type meta (struct/enum position + doc, opaque name + namespace, tuple root namespace). Co-Authored-By: Claude Fable 5 --- .../analysis/DFConditionalAnalysis.scala | 2 +- .../main/scala/dfhdl/compiler/ir/DFType.scala | 23 +++--- .../main/scala/dfhdl/compiler/ir/Meta.scala | 25 +++++-- .../compiler/printing/DFTypePrinter.scala | 12 ++-- .../compiler/printing/DFValPrinter.scala | 4 +- .../dfhdl/compiler/stages/DropRTProcess.scala | 3 +- .../stages/verilog/VerilogValPrinter.scala | 2 +- .../compiler/stages/vhdl/VHDLValPrinter.scala | 2 +- .../src/test/scala/StagesSpec/MetaSpec.scala | 71 +++++++++++++++++-- core/src/main/scala/dfhdl/core/DFEnum.scala | 20 ++++-- core/src/main/scala/dfhdl/core/DFOpaque.scala | 8 ++- core/src/main/scala/dfhdl/core/DFStruct.scala | 29 +++++--- core/src/main/scala/dfhdl/core/DFVal.scala | 2 +- .../main/scala/dfhdl/core/TypeMetaGen.scala | 37 ++++++++++ .../main/scala/dfhdl/core/r__For_Plugin.scala | 5 +- .../src/main/scala/plugin/CommonPhase.scala | 18 ++++- .../scala/plugin/MetaContextPlacerPhase.scala | 3 +- .../src/main/scala/plugin/MethodsPhase.scala | 4 +- 18 files changed, 215 insertions(+), 55 deletions(-) create mode 100644 core/src/main/scala/dfhdl/core/TypeMetaGen.scala diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala index b94781720..adfde7b4c 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFConditionalAnalysis.scala @@ -82,7 +82,7 @@ extension [CB <: DFConditional.Block](cb: CB)(using MemberGetSet) // A decimal is considered covered when all its values are covered. // All the possible values are determined by the width of the decimal. Some((1 << dec.widthIntOpt.get) == constSet.size) - case DFEnum(name, width, entries) => + case DFEnum(_, _, entries) => // An enum is considered covered when all its entries are covered. // Since both constant set and entries set are unique and type checking // already confirmed, then we can safely assume that everything is diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala index fb963b235..86dd2a14c 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala @@ -81,7 +81,10 @@ end DFType sealed trait ComposedDFType extends DFType sealed trait NamedDFType extends DFType: - val name: String + // full declaration meta (name, namespace, position, doc, annotations); type identity + // composes `Meta`'s equality (name + namespace + annotations), never position/doc + val meta: Meta + final def name: String = meta.name def updateName(newName: String)(using MemberGetSet): this.type object NamedDFTypes: def unapply(dfVal: DFVal)(using MemberGetSet): Option[ListSet[NamedDFType]] = @@ -333,13 +336,13 @@ final val DFInt32 = ir.DFDecimal(true, ir.IntParamRef(32), 0, Int32) // DFEnum ///////////////////////////////////////////////////////////////////////////// final case class DFEnum( - name: String, + meta: Meta, widthParam: Int, entries: ListMap[String, BigInt] ) extends NamedDFType derives ReadWriter: type Data = Option[BigInt] def updateName(newName: String)(using MemberGetSet): this.type = - copy(name = newName).asInstanceOf[this.type] + copy(meta = meta.setName(newName)).asInstanceOf[this.type] def widthIntOpt(using MemberGetSet): Option[Int] = Some(widthParam) def createBubbleData(using MemberGetSet): Data = None def isDataBubble(data: Data): Boolean = data.isEmpty @@ -423,14 +426,14 @@ object DFVector extends DFType.Companion[DFVector, Vector[Any]] // DFOpaque ///////////////////////////////////////////////////////////////////////////// final case class DFOpaque( - name: String, + meta: Meta, kind: DFOpaque.Kind, id: Int, actualType: DFType ) extends NamedDFType, ComposedDFType derives ReadWriter: type Data = Any def updateName(newName: String)(using MemberGetSet): this.type = - copy(name = newName).asInstanceOf[this.type] + copy(meta = meta.setName(newName)).asInstanceOf[this.type] def widthIntOpt(using MemberGetSet): Option[Int] = actualType.widthIntOpt def isMagnet: Boolean = kind match case _: DFOpaque.Kind.Magnet => true @@ -485,12 +488,12 @@ end DFOpaque // DFStruct ///////////////////////////////////////////////////////////////////////////// final case class DFStruct( - name: String, + meta: Meta, fieldMap: ListMap[String, DFType] ) extends NamedDFType, ComposedDFType derives ReadWriter: type Data = List[Any] def updateName(newName: String)(using MemberGetSet): this.type = - copy(name = newName).asInstanceOf[this.type] + copy(meta = meta.setName(newName)).asInstanceOf[this.type] def getNameForced: String = name def widthIntOpt(using MemberGetSet): Option[Int] = val fieldWidthsOpt = fieldMap.values.map(_.widthIntOpt) @@ -557,7 +560,7 @@ object DFTuple: def fieldName(idx: Int): String = s"_${idx + 1}" def apply(fieldList: List[DFType]): DFStruct = DFStruct( - structName(fieldList.length), + Meta.named(structName(fieldList.length)), ListMap.from(fieldList.view.zipWithIndex.map((f, i) => (fieldName(i), f))) ) ///////////////////////////////////////////////////////////////////////////// @@ -638,7 +641,7 @@ end DFInterface ///////////////////////////////////////////////////////////////////////////// final case class DFView( interfaceType: DFInterface, - name: String, + meta: Meta, // direction overlay over `interfaceType`, for LEAF ports only. The field // DFTypes are NOT repeated here — they live in `interfaceType`. dirMap: Map[String, DFVal.Modifier.Dir], @@ -656,7 +659,7 @@ final case class DFView( def bitsDataToData(data: (BitVector, BitVector))(using MemberGetSet): Data = noTypeErr def defaultData(using MemberGetSet): Data = noTypeErr def updateName(newName: String)(using MemberGetSet): this.type = - copy(name = newName).asInstanceOf[this.type] + copy(meta = meta.setName(newName)).asInstanceOf[this.type] // The full, directed field map of this view: `interfaceType`'s structure with the // resolved directions merged in (leaf dirs from `dirMap`; nested fields replaced by // their chosen sub-view). Derived on demand, so nothing is stored redundantly. diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/Meta.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/Meta.scala index c9fa36552..4e4ef1c4c 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/Meta.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/Meta.scala @@ -7,7 +7,13 @@ final case class Meta( nameOpt: Option[String], position: Position, docOpt: Option[String], - annotations: List[HWAnnotation] + annotations: List[HWAnnotation], + // The Scala package path of the DECLARATION this meta describes ("" for the root + // package and for members whose namespace is their design scope, i.e. regular + // values). Captured by the plugin for design classes and methods and by the type + // derivation macros for named DFTypes; the eventual packages feature places a + // named type by relating its namespace to the top design's. + namespace: String = "" ) extends HasRefCompare[Meta] derives ReadWriter: // Two distinct comparisons, and deliberately NO `CanEqual` (a direct `meta == meta` // does not compile under strictEquality), so every call site names which one it means: @@ -16,20 +22,22 @@ final case class Meta( // hashes typed trees, so positions and doc comments can drift while a cached entry // stays valid, and cached members must still unify by value with their live // counterparts (globals in `SubDesignEntry.cloneForAdoption` and sub-DB assembly - // unify by member equality). This is also what `equals`/`hashCode` implement, since - // member case-class equality composes through them implicitly. + // unify by member equality). `namespace` is digest-visible (a package clause is in + // the typed tree), so it participates. This is also what `equals`/`hashCode` + // implement, since member case-class equality composes through them implicitly. // // `sameDclAs`: all fields, position and doc included. Answers "same declaration", // which position is what anchors — same-named designs from different declarations // must not unify (`DesignLoadKey`'s intra-run gate tier, `UniqueDesigns`' grouping). def sameIdentityAs(that: Meta): Boolean = - this.nameOpt == that.nameOpt && this.annotations == that.annotations + this.nameOpt == that.nameOpt && this.namespace == that.namespace && + this.annotations == that.annotations def sameDclAs(that: Meta): Boolean = this.sameIdentityAs(that) && this.position == that.position && this.docOpt == that.docOpt override def equals(that: Any): Boolean = that match case that: Meta => this.sameIdentityAs(that) case _ => false - override def hashCode: Int = (nameOpt, annotations).## + override def hashCode: Int = (nameOpt, namespace, annotations).## val isAnonymous: Boolean = nameOpt.isEmpty val name: String = nameOpt.getOrElse(s"anon${this.hashString}") @@ -43,7 +51,8 @@ final case class Meta( annotations.filterNot(_ == annotation) ) protected def `prot_=~`(that: Meta)(using MemberGetSet): Boolean = - this.nameOpt == that.nameOpt && this.docOpt == that.docOpt && + this.nameOpt == that.nameOpt && this.namespace == that.namespace && + this.docOpt == that.docOpt && this.annotations.lazyZip(that.annotations).forall(_ =~ _) lazy val getRefs: List[DFRef.TwoWayAny] = annotations.flatMap(_.getRefs) @@ -55,3 +64,7 @@ end Meta object Meta: given ReadWriter[Position] = macroRW def empty: Meta = Meta(None, Position.unknown, None, Nil) + // meta of a SYNTHESIZED named declaration (a compiler-made type/design with no + // Scala declaration behind it): name only, unknown position, root namespace + def named(name: String, namespace: String = ""): Meta = + Meta(Some(name), Position.unknown, None, Nil, namespace) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala index 2b00fe05f..4ace1aefb 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala @@ -62,9 +62,9 @@ trait AbstractTypePrinter extends AbstractPrinter: case dfType: DFStruct if dfType.isTuple && tupleSupportEnable => false // skipping unknown clock and reset definitions (they are unknown because // they lack additional name suffix that belongs to their configuration) - case DFOpaque(name = "Clk", kind = DFOpaque.Kind.Clk) => false - case DFOpaque(name = "Rst", kind = DFOpaque.Kind.Rst) => false - case _ => true + case t: DFOpaque if t.name == "Clk" && t.kind == DFOpaque.Kind.Clk => false + case t: DFOpaque if t.name == "Rst" && t.kind == DFOpaque.Kind.Rst => false + case _ => true } .map(x => p.csNamedDFTypeDcl(x, global = true)) }.mkString("\n") @@ -82,9 +82,9 @@ trait AbstractTypePrinter extends AbstractPrinter: case dfType: DFStruct if dfType.isTuple && tupleSupportEnable => false // skipping unknown clock and reset definitions (they are unknown because // they lack additional name suffix that belongs to their configuration) - case DFOpaque(name = "Clk", kind = DFOpaque.Kind.Clk) => false - case DFOpaque(name = "Rst", kind = DFOpaque.Kind.Rst) => false - case _ => true + case t: DFOpaque if t.name == "Clk" && t.kind == DFOpaque.Kind.Clk => false + case t: DFOpaque if t.name == "Rst" && t.kind == DFOpaque.Kind.Rst => false + case _ => true } .map(x => printer.csNamedDFTypeDcl(x, global = false)) .mkString("\n") 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 487a84182..ed7a8fdd4 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala @@ -403,7 +403,7 @@ protected trait DFValPrinter extends AbstractValPrinter: if (csArgs.length == 2) s"${csArgs.head.applyBrackets()} + ${csArgs.last.applyBrackets()}" else ??? // TODO: handle more than 2 args - case structType @ DFStruct(structName, fieldMap) => + case structType @ DFStruct(_, fieldMap) => if (structType.isTuple) argsInBrackets else structType.name + @@ -524,7 +524,7 @@ protected trait DFValPrinter extends AbstractValPrinter: // field selections changes from `dv._${idx+1}` to `dv($idx)` val TUPLE_MIN_INDEXING = 3 def csDFValAliasSelectField(dfVal: Alias.SelectField): String = - val dfType @ DFStruct(structName, fieldMap) = dfVal.relValRef.get.dfType.runtimeChecked + val dfType @ DFStruct(_, fieldMap) = dfVal.relValRef.get.dfType.runtimeChecked val fieldSel = if (dfType.isTuple) if (fieldMap.size > TUPLE_MIN_INDEXING) 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 1441b8739..97aad7219 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala @@ -306,7 +306,8 @@ case object DropRTProcess extends HierarchyStage: val entries = ListMap.from(stateBlocks.view.zipWithIndex.map { case (sb, idx) => sb.getName -> BigInt(idx) }) - val stateEnumIR = DFEnum(enumName, (stateBlocks.length - 1).bitsWidth(false), entries) + val stateEnumIR = + DFEnum(Meta.named(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/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala index 76a604859..9a8057c98 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 @@ -495,7 +495,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: // field selections changes from `dv._${idx+1}` to `dv($idx)` val TUPLE_MIN_INDEXING = 3 def csDFValAliasSelectField(dfVal: Alias.SelectField): String = - val dfType @ DFStruct(structName, fieldMap) = dfVal.relValRef.get.dfType.runtimeChecked + val dfType @ DFStruct(_, fieldMap) = dfVal.relValRef.get.dfType.runtimeChecked val fieldSel = if (dfType.isTuple) if (fieldMap.size > TUPLE_MIN_INDEXING) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala index e07e780bd..e3dd2abaf 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala @@ -322,7 +322,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: // field selections changes from `dv._${idx+1}` to `dv($idx)` val TUPLE_MIN_INDEXING = 3 def csDFValAliasSelectField(dfVal: Alias.SelectField): String = - val dfType @ DFStruct(structName, fieldMap) = dfVal.relValRef.get.dfType.runtimeChecked + val dfType @ DFStruct(_, fieldMap) = dfVal.relValRef.get.dfType.runtimeChecked val fieldSel = if (dfType.isTuple) if (fieldMap.size > TUPLE_MIN_INDEXING) diff --git a/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala b/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala index f84da8dcb..7ce22d076 100644 --- a/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala @@ -1,17 +1,30 @@ package StagesSpec import munit.FunSuite +import dfhdl.* +import dfhdl.compiler.ir import dfhdl.compiler.ir.Meta -import dfhdl.internals.Position +import dfhdl.internals.{NoTopAnnotIsRequired, Position} + +/** the struct doc */ +case class TMCStruct(x: UInt[8] <> VAL, y: Bit <> VAL) extends Struct +enum TMCEnum extends Encoded: + case Alpha, Beta, Gamma +case class TMCOpaque() extends Opaque(UInt(8)) /** Pins `Meta`'s two comparison notions (`Meta` has no `CanEqual`, so call sites must name one): * - `sameIdentityAs` excludes position and doc, which can drift under a valid elaboration-cache - * entry (the code digest hashes typed trees and never sees formatting or comments). - * `equals`/`hashCode` implement it, so member value-equality (cache adoption, global - * unification) composes through it. + * entry (the code digest hashes typed trees and never sees formatting or comments), while the + * name, namespace, and annotations participate. `equals`/`hashCode` implement it, so member + * value-equality (cache adoption, global unification) composes through it. * - `sameDclAs` compares all fields; "same declaration" is anchored on position. + * + * Also pins the declaration meta captured for named DFTypes: the derivation macros record the + * name, the enclosing Scala package (namespace), the declaration position, and the doc comment for + * structs and enums; the opaque path is runtime-instance based, so it records name and namespace + * only. Tuples are structural: name only, root namespace. */ -class MetaSpec extends FunSuite: +class MetaSpec extends FunSuite, NoTopAnnotIsRequired: val posA = Position("FileA.scala", 1, 1, 1, 10) val posB = Position("FileB.scala", 5, 3, 7, 2) @@ -29,10 +42,58 @@ class MetaSpec extends FunSuite: assert(!a.sameIdentityAs(Meta(None, posA, None, Nil))) } + test("identity includes the namespace") { + val a = Meta(Some("x"), posA, None, Nil, "pkg.a") + assert(!a.sameIdentityAs(Meta(Some("x"), posA, None, Nil, "pkg.b"))) + assert(!a.sameDclAs(Meta(Some("x"), posA, None, Nil, "pkg.b"))) + assert(a.sameIdentityAs(Meta(Some("x"), posB, Some("doc"), Nil, "pkg.a"))) + } + test("declaration sameness is anchored on position and doc") { val a = Meta(Some("x"), posA, None, Nil) assert(a.sameDclAs(Meta(Some("x"), posA, None, Nil))) assert(!a.sameDclAs(Meta(Some("x"), posB, None, Nil))) assert(!a.sameDclAs(Meta(Some("x"), posA, Some("a doc comment"), Nil))) } + + class Top extends DFDesign: + val s = TMCStruct <> VAR + val e = TMCEnum <> VAR + val o = TMCOpaque <> VAR + val t = (UInt(8), Bit) <> VAR + + lazy val dclTypes: Map[String, ir.DFType] = + val db = (new Top).getDB + db.subDBs.values.toList.flatMap { sub => + sub.members.collect { case dcl: ir.DFVal.Dcl => + dcl.getName(using sub.getSet) -> dcl.dfType + } + }.toMap + + test("struct meta: name, namespace, position, doc") { + val meta = dclTypes("s").asInstanceOf[ir.DFStruct].meta + assertEquals(meta.name, "TMCStruct") + assertEquals(meta.namespace, "StagesSpec") + assert(meta.position.file.endsWith("MetaSpec.scala"), meta.position.toString) + assert(meta.docOpt.nonEmpty && meta.comment.contains("the struct doc"), meta.docOpt.toString) + } + + test("enum meta: name, namespace, position") { + val meta = dclTypes("e").asInstanceOf[ir.DFEnum].meta + assertEquals(meta.name, "TMCEnum") + assertEquals(meta.namespace, "StagesSpec") + assert(meta.position.file.endsWith("MetaSpec.scala"), meta.position.toString) + } + + test("opaque meta: name, namespace") { + val meta = dclTypes("o").asInstanceOf[ir.DFOpaque].meta + assertEquals(meta.name, "TMCOpaque") + assertEquals(meta.namespace, "StagesSpec") + } + + test("tuple struct meta: structural, root namespace") { + val meta = dclTypes("t").asInstanceOf[ir.DFStruct].meta + assertEquals(meta.name, "DFTuple2") + assertEquals(meta.namespace, "") + } end MetaSpec diff --git a/core/src/main/scala/dfhdl/core/DFEnum.scala b/core/src/main/scala/dfhdl/core/DFEnum.scala index 66c2acad5..5274a553a 100644 --- a/core/src/main/scala/dfhdl/core/DFEnum.scala +++ b/core/src/main/scala/dfhdl/core/DFEnum.scala @@ -80,6 +80,17 @@ object DFEnum: end match end unapply def apply[E <: DFEncoding](enumCompanion: Object): DFEnum[E] = + // reflection fallback of the derivation macro's capture: name and package only + val enumCompanionCls = enumCompanion.getClass + val meta = ir.Meta( + Some(enumCompanionCls.getSimpleName.replace("$", "")), + Position.unknown, + None, + Nil, + enumCompanionCls.getPackageName + ) + apply[E](enumCompanion, meta) + def apply[E <: DFEncoding](enumCompanion: Object, meta: ir.Meta): DFEnum[E] = val enumClass = classOf[scala.reflect.Enum] val enumCompanionCls = enumCompanion.getClass val fieldsAsPairs = @@ -89,20 +100,21 @@ object DFEnum: ) yield field.setAccessible(true) (field.getName, field.get(enumCompanion).asInstanceOf[DFEncoding]) - val name = enumCompanionCls.getSimpleName.replace("$", "") val width = fieldsAsPairs.head._2.calcWidth(fieldsAsPairs.size) val entryPairs = fieldsAsPairs.zipWithIndex.map { case ((name, entry), idx) => (name, entry.bigIntValue) } - ir.DFEnum(name, width, ListMap(entryPairs*)).asFE[DFEnum[E]] + ir.DFEnum(meta, width, ListMap(entryPairs*)).asFE[DFEnum[E]] end apply inline given [E <: DFEncoding]: DFEnum[E] = ${ dfTypeMacro[E] } def dfTypeMacro[E <: DFEncoding](using Quotes, Type[E]): Expr[DFEnum[E]] = import quotes.reflect.* - val companionSym = TypeRepr.of[E].typeSymbol.companionModule + val enumSym = TypeRepr.of[E].typeSymbol + val companionSym = enumSym.companionModule val companionIdent = Ref(companionSym).asExprOf[Object] - '{ DFEnum[E]($companionIdent) } + val metaExpr = TypeMetaGen(using quotes)(enumSym) + '{ DFEnum[E]($companionIdent, $metaExpr) } object Val: object TC: diff --git a/core/src/main/scala/dfhdl/core/DFOpaque.scala b/core/src/main/scala/dfhdl/core/DFOpaque.scala index 722c7d762..6ed39efed 100644 --- a/core/src/main/scala/dfhdl/core/DFOpaque.scala +++ b/core/src/main/scala/dfhdl/core/DFOpaque.scala @@ -57,8 +57,14 @@ object DFOpaque: // but are in different packages, and remains stable between runs val fullyQualifiedClassName = t.getClass.getName fullyQualifiedClassName.hashCode + // runtime capture: name and package only (the opaque instance arrives through + // `ClassEv`, so there is no declaration symbol at hand for position/doc) + val meta = ir.Meta( + Some(t.typeName), Position.unknown, None, Nil, + t.getClass.getPackageName + ) ir.DFOpaque( - t.typeName, + meta, kind, id, t.actualType.asIR.dropUnreachableRefs(allowDesignParamRefs = false) diff --git a/core/src/main/scala/dfhdl/core/DFStruct.scala b/core/src/main/scala/dfhdl/core/DFStruct.scala index e862e534d..8a2d5d5d0 100644 --- a/core/src/main/scala/dfhdl/core/DFStruct.scala +++ b/core/src/main/scala/dfhdl/core/DFStruct.scala @@ -12,19 +12,19 @@ type DFStruct[+F <: FieldsOrTuple] = object DFStruct: abstract class Fields extends Product with Serializable private[core] def apply[F <: FieldsOrTuple]( - name: String, + meta: ir.Meta, fieldMap: ListMap[String, DFTypeAny] )(using DFC): DFStruct[F] = ir.DFStruct( - name, + meta, fieldMap.map((n, t) => (n, t.asIR.dropUnreachableRefs(allowDesignParamRefs = false))) ).asFE[DFStruct[F]] private[core] def apply[F <: FieldsOrTuple]( - name: String, + meta: ir.Meta, fieldNames: List[String], fieldTypes: List[DFTypeAny] )(using DFC): DFStruct[F] = - apply[F](name, ListMap(fieldNames.lazyZip(fieldTypes).toSeq*)) + apply[F](meta, ListMap(fieldNames.lazyZip(fieldTypes).toSeq*)) private[core] def apply[F <: FieldsOrTuple](product: F)(using DFC): DFStruct[F] = unapply(product.asInstanceOf[Product]).get.asInstanceOf[DFStruct[F]] private[core] def unapply( @@ -36,8 +36,16 @@ object DFStruct: }.toList if (fieldTypes.length == product.productIterator.size) val fieldNames = product.productElementNames.toList - Some(DFStruct(product.productPrefix, fieldNames, fieldTypes)) + // reflection fallback of the derivation macro's capture: name and package only + // (no declaration position/doc). The namespace MUST agree with the macro's + // (package-level in both), or `SameFields.check`'s type equality would split. + val meta = ir.Meta( + Some(product.productPrefix), Position.unknown, None, Nil, + product.getClass.getPackageName + ) + Some(DFStruct(meta, fieldNames, fieldTypes)) else None + end unapply inline given apply[F <: FieldsOrTuple](using dfc: DFCG): DFStruct[F] = ${ dfTypeMacro[F]('dfc) } def dfTypeMacro[F <: FieldsOrTuple](using @@ -46,17 +54,21 @@ object DFStruct: )(dfc: Expr[DFC]): Expr[DFStruct[F]] = import quotes.reflect.* val fTpe = TypeRepr.of[F] - val (structName, fields) = fTpe.asTypeOf[Any] match + val (structName, metaExpr, fields) = fTpe.asTypeOf[Any] match case '[NonEmptyTuple] => val args = fTpe.getTupleArgs + val name = ir.DFTuple.structName(args.length) ( - ir.DFTuple.structName(args.length), + name, + // tuples are structural, declared nowhere: name only, root namespace + '{ ir.Meta.named(${ Expr(name) }) }, args.zipWithIndex.map((t, i) => (ir.DFTuple.fieldName(i), t.asTypeOf[Any])) ) case _ => val clsSym = fTpe.classSymbol.get ( clsSym.name.toString, + TypeMetaGen(using quotes)(clsSym), clsSym.caseFields.view .map(m => (m.name.toString, fTpe.memberType(m).asTypeOf[Any])) ) @@ -71,9 +83,8 @@ object DFStruct: }.toList val fieldNamesExpr = Varargs(fieldNames) val fieldTypesExpr = Varargs(fieldTypes) - val nameExpr = Expr(structName) '{ - DFStruct.apply[F]($nameExpr, List($fieldNamesExpr*), List($fieldTypesExpr*))(using $dfc) + DFStruct.apply[F]($metaExpr, List($fieldNamesExpr*), List($fieldTypesExpr*))(using $dfc) } else val fieldTypesStr = fieldErrors diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index be6b4c1b1..411c505ea 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -23,7 +23,7 @@ into final class DFVal[+T <: DFTypeAny, +M <: ModifierAny](val irValue: ir.DFVal def wait(using DFC): Unit = trydf { Wait(this.asValOf[DFBoolOrBit]) } def selectDynamic(name: String)(using DFC): Any = trydf { - val ir.DFStruct(structName, fieldMap) = this.asIR.dfType.runtimeChecked + val ir.DFStruct(_, fieldMap) = this.asIR.dfType.runtimeChecked val dfType = fieldMap(name) DFVal.Alias .SelectField(this, name) diff --git a/core/src/main/scala/dfhdl/core/TypeMetaGen.scala b/core/src/main/scala/dfhdl/core/TypeMetaGen.scala new file mode 100644 index 000000000..09a4f2b1e --- /dev/null +++ b/core/src/main/scala/dfhdl/core/TypeMetaGen.scala @@ -0,0 +1,37 @@ +package dfhdl.core +import dfhdl.compiler.ir +import scala.quoted.* + +/** Builds a named DFType's declaration `ir.Meta` inside a derivation macro: name, enclosing Scala + * package (namespace), declaration position, and doc comment, mirroring what the compiler plugin + * captures for design classes and methods. Namespaces stop at the package level deliberately: + * enclosing objects and classes are scoping, not namespacing, for the packages feature. + */ +private[core] object TypeMetaGen: + def namespaceOf(using q: Quotes)(sym: q.reflect.Symbol): String = + import quotes.reflect.* + var owner = sym.owner + while (!owner.isPackageDef) do owner = owner.owner + val fullName = owner.fullName + if (fullName.startsWith("<")) "" else fullName + + def apply(using q: Quotes)(sym: q.reflect.Symbol): Expr[ir.Meta] = + import quotes.reflect.* + val nameExpr = Expr(sym.name.toString) + val namespaceExpr = Expr(namespaceOf(sym)) + val posExpr = sym.pos match + case Some(pos) if scala.util.Try(pos.sourceFile.path).isSuccess => + '{ + dfhdl.internals.Position.fromAbsPath( + ${ Expr(pos.sourceFile.path) }, + ${ Expr(pos.startLine + 1) }, + ${ Expr(pos.startColumn + 1) }, + ${ Expr(pos.endLine + 1) }, + ${ Expr(pos.endColumn + 1) } + ) + } + case _ => '{ dfhdl.internals.Position.unknown } + val docExpr = Expr(sym.docstring) + '{ ir.Meta(Some($nameExpr), $posExpr, $docExpr, Nil, $namespaceExpr) } + end apply +end TypeMetaGen 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 f8fa7daf8..58c5f7f4c 100644 --- a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala +++ b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala @@ -16,8 +16,9 @@ object r__For_Plugin: nameOpt: Option[String], position: Position, docOpt: Option[String], - annotations: List[Annotation] - ): ir.Meta = ir.Meta(nameOpt, position, docOpt, annotations.getActiveHWAnnotations) + annotations: List[Annotation], + namespace: String + ): ir.Meta = ir.Meta(nameOpt, position, docOpt, annotations.getActiveHWAnnotations, namespace) def toFunc1[R](block: => R): () => R = () => block def toTuple2[T1, T2](t1: T1, t2: T2): (T1, T2) = (t1, t2) def toTuple3[T1, T2, T3](t1: T1, t2: T2, t3: T3): (T1, T2, T3) = (t1, t2, t3) diff --git a/plugin/src/main/scala/plugin/CommonPhase.scala b/plugin/src/main/scala/plugin/CommonPhase.scala index 2e94509c2..e0b38a944 100755 --- a/plugin/src/main/scala/plugin/CommonPhase.scala +++ b/plugin/src/main/scala/plugin/CommonPhase.scala @@ -266,15 +266,29 @@ abstract class CommonPhase extends PluginPhase: def dfValTpeOpt: Option[Type] = tree.tpt.tpe.dfValTpeOpt + // The enclosing Scala package path of a declaration's symbol, "" for the root/empty + // package. Namespaces stop at the package level deliberately: enclosing objects and + // classes are scoping, not namespacing, for the packages feature. + protected def mkNamespace(sym: Symbol)(using Context): Tree = + val pkg = sym.enclosingPackageClass + val ns = + if (pkg.isEffectiveRoot || pkg.name.toString.startsWith("<")) "" + else pkg.fullName.toString + Literal(Constant(ns)) + extension (tree: ValOrDefDef)(using Context) - def genMeta: Tree = + private def genMetaWith(namespaceTree: Tree): Tree = val nameOptTree = mkOptionString(Some(tree.name.toString.nameCheck(tree))) val positionTree = tree.srcPos.positionTree val docOptTree = mkOptionString(tree.symbol.docString) val annotTree = mkList(tree.symbol.annotations.map(_.tree)) ref(metaGenSym).appliedToArgs( - nameOptTree :: positionTree :: docOptTree :: annotTree :: Nil + nameOptTree :: positionTree :: docOptTree :: annotTree :: namespaceTree :: Nil ) + // meta of a regular VALUE: its namespace is its design scope, not a Scala package + def genMeta: Tree = genMetaWith(Literal(Constant(""))) + // meta of a DECLARATION (a method that becomes a design): carries its package + def genDclMeta: Tree = genMetaWith(mkNamespace(tree.symbol)) end extension extension (v: ValDef)(using Context) diff --git a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala index 0b66988ed..90cba95ae 100644 --- a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala @@ -238,7 +238,8 @@ class MetaContextPlacerPhase(setting: Setting) extends CapturePhase, IdentityDen mkOptionString(Some(clsSym.getFinalName())), tree.positionTree, mkOptionString(clsSym.docString), - mkList(clsSym.staticAnnotations.map(a => reownLocalDefs(dropProxies(a.tree), sym))) + mkList(clsSym.staticAnnotations.map(a => reownLocalDefs(dropProxies(a.tree), sym))), + mkNamespace(clsSym) ) ) // metaGen(...) :: super.__clsMeta (i.e. super.__clsMeta.::(metaGen(...))) diff --git a/plugin/src/main/scala/plugin/MethodsPhase.scala b/plugin/src/main/scala/plugin/MethodsPhase.scala index 28fa84224..5059cc149 100644 --- a/plugin/src/main/scala/plugin/MethodsPhase.scala +++ b/plugin/src/main/scala/plugin/MethodsPhase.scala @@ -267,7 +267,7 @@ class MethodsPhase(setting: Setting) extends CapturePhase: def genCapturedMeta(path: List[Symbol], t: Tree): Tree = ref(metaGenSym).appliedToArgs( mkOptionString(Some(captureName(path))) :: t.symbol.srcPos.positionTree :: - mkOptionString(None) :: mkList(Nil) :: Nil + mkOptionString(None) :: mkList(Nil) :: Literal(Constant("")) :: Nil ) // list of (value, meta, isOutput, isNonBlocking) tuples of the value arguments. The // `isOutput` flag lets the harness build a direction-correct formal port (an `<> OUT` @@ -366,7 +366,7 @@ class MethodsPhase(setting: Setting) extends CapturePhase: .appliedToArgs(List( args, constArgs, - tree.genMeta, // meta represents the transformed tree + tree.genDclMeta, // meta represents the transformed tree scalaArgs, phantomArgs, phantomConstArgs, From deb75a0bb3b63361f72f72d0cbadd2bf034b016b Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 02:31:27 +0300 Subject: [PATCH 48/57] meta: global-value namespaces, full opaque capture, doc emission on named types Closes the phase-2 loose ends ahead of the packages-feature emission phase: - Global values: the plugin now passes the declaring package on every named value's `setMeta` (MetaContext/DFC carry it), and `DFC.getMeta` keeps it only at GLOBAL scope (no owner in context): a global constant records its package (gold veer packages hold parameters too), while a design-scoped value's namespace stays its design (""). - Opaques: `ClassEv` captures the declaration's position, doc, and package at materialization, so the `DFOpaque` given and every `ce`-holding `as`-op build full meta; direct-instance callers (Clk()/Rst(), stage-minted magnets) keep the runtime name+package fallback. The general-opaque FQN-hash id retires to 0 (identity is meta: name + namespace, like structs and enums); magnets keep their per-instance id, and `prot_=~`/`isSimilarTo` compare meta identity. The dead `opaqueType` extension (cast the Int id to TFE, zero callers) is removed. - Doc comments on named types now emit at the type declaration through the shared `csNamedDFTypeDcl` choke point: `/** */` in DFHDL code, `/* */` above SystemVerilog typedefs, `--` above VHDL type declarations. Macro-captured docstrings include the raw comment markers, unlike the plugin's cooked form, so `sanitizedDocstring` (internals) normalizes them; without it the printers double-frame (`/**/** doc */*/`). Tests: the SAME "Docstrings on named types" design (documented struct, enum, and opaque) is pinned with exact expected output in PrintCodeStringSpec, PrintVerilogCodeSpec, and PrintVHDLCodeSpec (appended at file end; earlier tests there embed their own source positions). MetaSpec pins global-vs-local value namespace, opaque position, and the inert general-opaque id. The backend doc tests fail with the emission line reverted. Co-Authored-By: Claude Fable 5 --- .../main/scala/dfhdl/compiler/ir/DFType.scala | 4 +- .../compiler/printing/DFTypePrinter.scala | 4 +- .../src/test/scala/StagesSpec/MetaSpec.scala | 24 ++++++++++- .../StagesSpec/PrintCodeStringSpec.scala | 35 +++++++++++++++ .../scala/StagesSpec/PrintVHDLCodeSpec.scala | 43 +++++++++++++++++++ .../StagesSpec/PrintVerilogCodeSpec.scala | 39 +++++++++++++++++ core/src/main/scala/dfhdl/core/DFC.scala | 18 ++++++-- core/src/main/scala/dfhdl/core/DFOpaque.scala | 41 ++++++++++-------- .../main/scala/dfhdl/core/TypeMetaGen.scala | 2 +- .../scala/dfhdl/internals/MetaContext.scala | 15 ++++++- .../main/scala/dfhdl/internals/helpers.scala | 28 ++++++++++++ .../scala/plugin/MetaContextGenPhase.scala | 5 ++- 12 files changed, 227 insertions(+), 31 deletions(-) diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala index 86dd2a14c..46ff62be3 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala @@ -447,12 +447,12 @@ final case class DFOpaque( actualType.bitsDataToData(data) protected def `prot_=~`(that: DFType)(using MemberGetSet): Boolean = that match case that: DFOpaque => - this.name == that.name && this.id == that.id && + this.meta.sameIdentityAs(that.meta) && this.id == that.id && this.actualType =~ that.actualType case _ => false def isSimilarTo(that: DFType)(using MemberGetSet): Boolean = that match case that: DFOpaque => - this.name == that.name && this.id == that.id && + this.meta.sameIdentityAs(that.meta) && this.id == that.id && this.actualType.isSimilarTo(that.actualType) case _ => false lazy val getRefs: List[DFRef.TypeRef] = actualType.getRefs diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala index 4ace1aefb..10a629557 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala @@ -9,10 +9,12 @@ trait AbstractTypePrinter extends AbstractPrinter: def csDFBits(dfType: DFBitsWL, typeCS: Boolean): String def csDFDecimal(dfType: DFDecimal, typeCS: Boolean): String final def csNamedDFTypeDcl(dfType: NamedDFType, global: Boolean): String = - dfType match + val dcl = dfType match case dt: DFEnum => csDFEnumDcl(dt, global) case dt: DFOpaque => csDFOpaqueDcl(dt) case dt: DFStruct => csDFStructDcl(dt) + val doc = printer.csDocString(dfType.meta) + if (doc.isEmpty) dcl else s"$doc\n$dcl" private def isInt32Val(member: DFMember): Boolean = member match case dfVal: DFVal => diff --git a/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala b/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala index 7ce22d076..5f5af55e6 100644 --- a/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala @@ -8,6 +8,7 @@ import dfhdl.internals.{NoTopAnnotIsRequired, Position} /** the struct doc */ case class TMCStruct(x: UInt[8] <> VAL, y: Bit <> VAL) extends Struct +val TMCGlobal: UInt[8] <> CONST = 7 enum TMCEnum extends Encoded: case Alpha, Beta, Gamma case class TMCOpaque() extends Opaque(UInt(8)) @@ -61,6 +62,7 @@ class MetaSpec extends FunSuite, NoTopAnnotIsRequired: val e = TMCEnum <> VAR val o = TMCOpaque <> VAR val t = (UInt(8), Bit) <> VAR + val loc = UInt(8) <> VAR init TMCGlobal lazy val dclTypes: Map[String, ir.DFType] = val db = (new Top).getDB @@ -85,10 +87,15 @@ class MetaSpec extends FunSuite, NoTopAnnotIsRequired: assert(meta.position.file.endsWith("MetaSpec.scala"), meta.position.toString) } - test("opaque meta: name, namespace") { - val meta = dclTypes("o").asInstanceOf[ir.DFOpaque].meta + test("opaque meta: name, namespace, position") { + val opaque = dclTypes("o").asInstanceOf[ir.DFOpaque] + val meta = opaque.meta assertEquals(meta.name, "TMCOpaque") assertEquals(meta.namespace, "StagesSpec") + assert(meta.position.file.endsWith("MetaSpec.scala"), meta.position.toString) + // a GENERAL opaque is identified by its meta (name + namespace) like structs and + // enums; only magnets carry a per-instance id + assertEquals(opaque.id, 0) } test("tuple struct meta: structural, root namespace") { @@ -96,4 +103,17 @@ class MetaSpec extends FunSuite, NoTopAnnotIsRequired: assertEquals(meta.name, "DFTuple2") assertEquals(meta.namespace, "") } + + test("a global constant carries its package; a design-scoped value does not") { + val db = (new Top).getDB + val globalMeta = db.subDBs.values.toList.flatMap(_.membersGlobals).collectFirst { + case g: ir.DFVal if g.meta.name == "TMCGlobal" => g.meta + }.get + assertEquals(globalMeta.namespace, "StagesSpec") + val locMeta = db.subDBs.values.toList.flatMap(_.members).collectFirst { + case dcl: ir.DFVal.Dcl if dcl.meta.name == "loc" => dcl.meta + }.get + assertEquals(locMeta.namespace, "") + } + end MetaSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index 53137c083..efe548252 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -3926,4 +3926,39 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |""".stripMargin ) } + test("Docstrings on named types"): + /** struct doc */ + case class DocS(a: Bit <> VAL) extends Struct + + /** enum doc */ + enum DocE extends Encoded: + case E0, E1 + + /** opaque doc */ + case class DocO() extends Opaque(Bit) + class DocTop extends DFDesign: + val s = DocS <> VAR + val e = DocE <> VAR + val o = DocO <> VAR + val top = (new DocTop) + assertCodeString( + top, + """|class DocTop extends DFDesign: + | /** struct doc */ + | final case class DocS( + | a: Bit <> VAL + | ) extends Struct + | /** enum doc */ + | enum DocE(val value: UInt[1] <> CONST) extends Encoded.Manual(1): + | case E0 extends DocE(d"1'0") + | case E1 extends DocE(d"1'1") + | /** opaque doc */ + | case class DocO() extends Opaque(Bit) + | + | val s = DocS <> VAR + | val e = DocE <> VAR + | val o = DocO <> VAR + |end DocTop + |""".stripMargin + ) end PrintCodeStringSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index 8c8c19e10..9a53190c7 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -3873,4 +3873,47 @@ class PrintVHDLCodeSpec extends StageSpec: |""".stripMargin ) } + test("Docstrings on named types"): + /** struct doc */ + case class DocS(a: Bit <> VAL) extends Struct + + /** enum doc */ + enum DocE extends Encoded: + case E0, E1 + + /** opaque doc */ + case class DocO() extends Opaque(Bit) + class DocTop extends DFDesign: + val s = DocS <> VAR + val e = DocE <> VAR + val o = DocO <> VAR + val top = (new DocTop).getCompiledCodeString + assertNoDiff( + top, + """|library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + | + |entity DocTop is + |end DocTop; + | + |architecture DocTop_arch of DocTop is + | -- struct doc + | type t_struct_DocS is record + | a : std_logic; + | end record; + | -- enum doc + | type t_enum_DocE is ( + | DocE_E0, DocE_E1 + | ); + | -- opaque doc + | subtype t_opaque_DocO is std_logic; + | signal s : t_struct_DocS; + | signal e : t_enum_DocE; + | signal o : t_opaque_DocO; + |begin + |end DocTop_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 ac9043e58..213ef5e1d 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -3939,4 +3939,43 @@ class PrintVerilogCodeSpec extends StageSpec: |""".stripMargin ) } + test("Docstrings on named types"): + /** struct doc */ + case class DocS(a: Bit <> VAL) extends Struct + + /** enum doc */ + enum DocE extends Encoded: + case E0, E1 + + /** opaque doc */ + case class DocO() extends Opaque(Bit) + class DocTop extends DFDesign: + val s = DocS <> VAR + val e = DocE <> VAR + val o = DocO <> VAR + val top = (new DocTop).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module DocTop; + | `include "dfhdl_defs.svh" + | /* struct doc */ + | typedef struct packed { + | logic a; + | } t_struct_DocS; + | /* enum doc */ + | typedef enum logic [0:0] { + | DocE_E0 = 0, + | DocE_E1 = 1 + | } t_enum_DocE; + | /* opaque doc */ + | typedef logic t_opaque_DocO; + | t_struct_DocS s; + | t_enum_DocE e; + | t_opaque_DocO o; + |endmodule + |""".stripMargin + ) end PrintVerilogCodeSpec diff --git a/core/src/main/scala/dfhdl/core/DFC.scala b/core/src/main/scala/dfhdl/core/DFC.scala index 0947bf956..2c4d4b72e 100644 --- a/core/src/main/scala/dfhdl/core/DFC.scala +++ b/core/src/main/scala/dfhdl/core/DFC.scala @@ -17,6 +17,9 @@ final case class DFC( position: Position, docOpt: Option[String], annotations: List[HWAnnotation] = Nil, // TODO: removing default causes stale symbol crash + // the declaring Scala package of the value this context names; consumed by `getMeta` + // only at GLOBAL scope (a design-scoped value's namespace is its design) + namespace: String = "", mutableDB: MutableDB = new MutableDB(), refGen: ir.RefGen = ir.RefGen.initial, tags: ir.DFTags = ir.DFTags.empty, @@ -28,7 +31,8 @@ final case class DFC( nameOpt: Option[String] = nameOpt, position: Position = position, docOpt: Option[String] = docOpt, - annotations: List[Annotation] = Nil + annotations: List[Annotation] = Nil, + namespace: String = namespace ) = if (refGen.getGrpId == (0, 0)) refGen.setGrpId(DFC.getGrpId(position)) @@ -36,8 +40,10 @@ final case class DFC( nameOpt = nameOpt, position = position, docOpt = docOpt, - annotations = annotations.getActiveHWAnnotations + annotations = annotations.getActiveHWAnnotations, + namespace = namespace ).asInstanceOf[this.type] + end setMeta def setMeta( meta: ir.Meta ) = @@ -47,13 +53,17 @@ final case class DFC( nameOpt = meta.nameOpt, position = meta.position, docOpt = meta.docOpt, - annotations = meta.annotations + annotations = meta.annotations, + namespace = meta.namespace ).asInstanceOf[this.type] def setTags(tags: ir.DFTags) = copy(tags = tags) def tag[CT <: ir.DFTag: ClassTag](customTag: CT) = setTags(tags.tag(customTag)) def emptyTags = setTags(ir.DFTags.empty) given getSet: ir.MemberGetSet = mutableDB.getSet - def getMeta: ir.Meta = ir.Meta(nameOpt, position, docOpt, annotations) + // the namespace reaches the meta only for GLOBAL values (no owner in context): a + // design-scoped value's namespace is its design, not its declaring Scala package + def getMeta: ir.Meta = + ir.Meta(nameOpt, position, docOpt, annotations, if (ownerOption.isEmpty) namespace else "") def enterOwner(owner: DFOwnerAny): Unit = mutableDB.OwnershipContext.enter(owner.asIR) def exitOwner(): Unit = mutableDB.OwnershipContext.exit() diff --git a/core/src/main/scala/dfhdl/core/DFOpaque.scala b/core/src/main/scala/dfhdl/core/DFOpaque.scala index 6ed39efed..46ab9e5e7 100644 --- a/core/src/main/scala/dfhdl/core/DFOpaque.scala +++ b/core/src/main/scala/dfhdl/core/DFOpaque.scala @@ -39,30 +39,36 @@ object DFOpaque: abstract class Clk extends Magnet[DFBit](DFBit) abstract class Rst extends Magnet[DFBit](DFBit) - given [TFE <: Abstract](using ce: ClassEv[TFE], dfc: DFCG): DFOpaque[TFE] = DFOpaque(ce.value) + given [TFE <: Abstract](using ce: ClassEv[TFE], dfc: DFCG): DFOpaque[TFE] = DFOpaque(ce) + // full capture through `ClassEv`'s declaration fields (position, doc, package) + def apply[TFE <: Abstract]( + ce: ClassEv[TFE] + )(using DFCG): DFOpaque[TFE] = + apply( + ce.value, + ir.Meta(Some(ce.value.typeName), ce.dclPosition, ce.dclDocOpt, Nil, ce.dclNamespace) + ) + // runtime fallback for direct-instance callers: name and package only def apply[TFE <: Abstract]( t: TFE + )(using DFCG): DFOpaque[TFE] = + apply(t, ir.Meta(Some(t.typeName), Position.unknown, None, Nil, t.getClass.getPackageName)) + def apply[TFE <: Abstract]( + t: TFE, + meta: ir.Meta )(using dfc: DFCG): DFOpaque[TFE] = trydf { val kind = t match case _: Clk => ir.DFOpaque.Kind.Clk case _: Rst => ir.DFOpaque.Kind.Rst case _: Magnet[?] => ir.DFOpaque.Kind.Magnet case _ => ir.DFOpaque.Kind.General + // Magnets are identified per INSTANCE (each `Unique().Clk()` and friends must stay a + // distinct type). General opaques are identified by their meta (name + namespace), + // like structs and enums, so their id is inert. val id = t match case _: Magnet[?] => dfc.refGen.getMagnetID(t) - case _ => - // Generate a stable ID based on the fully qualified class name - // This ensures different case classes have different IDs even if they have the same simple name - // but are in different packages, and remains stable between runs - val fullyQualifiedClassName = t.getClass.getName - fullyQualifiedClassName.hashCode - // runtime capture: name and package only (the opaque instance arrives through - // `ClassEv`, so there is no declaration symbol at hand for position/doc) - val meta = ir.Meta( - Some(t.typeName), Position.unknown, None, Nil, - t.getClass.getPackageName - ) + case _ => 0 ir.DFOpaque( meta, kind, @@ -72,7 +78,6 @@ object DFOpaque: }(using dfc, CTName("Opaque constructor")) extension [A <: DFTypeAny, TFE <: Frontend[A]](dfType: DFOpaque[TFE]) def actualType: A = dfType.asIR.actualType.asFE[A] - def opaqueType: TFE = dfType.asIR.id.asInstanceOf[TFE] object Val: object TC: @@ -102,7 +107,7 @@ object DFOpaque: new ExactOp2["as", DFC, DFValAny, L, Comp]: type Out = DFValTP[DFOpaque[TFE], tc.OutP] def apply(lhs: L, tfeComp: Comp)(using DFC): Out = trydf { - DFVal.Alias.AsIs(DFOpaque[TFE](ce.value), tc(ce.value.actualType, lhs)) + DFVal.Alias.AsIs(DFOpaque[TFE](ce), tc(ce.value.actualType, lhs)) }(using dfc, CTName("cast as opaque")) end evOpAsDFOpaqueComp @@ -149,7 +154,7 @@ object DFOpaque: new ExactOp2["as", DFC, DFValAny, L, Comp]: type Out = DFValOf[DFOpaque[TFE]] def apply(lhs: L, tfeComp: Comp)(using DFC): Out = trydf { - DFVal.Alias.AsIs(DFOpaque[TFE](ce.value), lhs) + DFVal.Alias.AsIs(DFOpaque[TFE](ce), lhs) }(using dfc, CTName("cast clk as a different clk")) end evOpClkAsClkComp @@ -166,7 +171,7 @@ object DFOpaque: new ExactOp2["as", DFC, DFValAny, L, Comp]: type Out = DFValOf[DFOpaque[TFE]] def apply(lhs: L, tfeComp: Comp)(using DFC): Out = trydf { - DFVal.Alias.AsIs(DFOpaque[TFE](ce.value), lhs) + DFVal.Alias.AsIs(DFOpaque[TFE](ce), lhs) }(using dfc, CTName("cast rst as a different rst")) end evOpRstAsRstComp @@ -187,7 +192,7 @@ object DFOpaque: f: DFValOf[AT] => DFValOf[AT] )(using dfc: DFC, ce: ClassEv[TFE]): DFValOf[DFOpaque[TFE]] = DFVal.Alias.AsIs( - DFOpaque[TFE](ce.value), + DFOpaque[TFE](ce), f(lhs.actual) ) end extension diff --git a/core/src/main/scala/dfhdl/core/TypeMetaGen.scala b/core/src/main/scala/dfhdl/core/TypeMetaGen.scala index 09a4f2b1e..658deed5f 100644 --- a/core/src/main/scala/dfhdl/core/TypeMetaGen.scala +++ b/core/src/main/scala/dfhdl/core/TypeMetaGen.scala @@ -31,7 +31,7 @@ private[core] object TypeMetaGen: ) } case _ => '{ dfhdl.internals.Position.unknown } - val docExpr = Expr(sym.docstring) + val docExpr = Expr(sym.docstring.map(dfhdl.internals.sanitizedDocstring)) '{ ir.Meta(Some($nameExpr), $posExpr, $docExpr, Nil, $namespaceExpr) } end apply end TypeMetaGen diff --git a/internals/src/main/scala/dfhdl/internals/MetaContext.scala b/internals/src/main/scala/dfhdl/internals/MetaContext.scala index ad984d17a..65ba1cff3 100644 --- a/internals/src/main/scala/dfhdl/internals/MetaContext.scala +++ b/internals/src/main/scala/dfhdl/internals/MetaContext.scala @@ -24,17 +24,28 @@ object Position: columnEnd: Int ): Position = Position(getRelativePath(fileAbsPath), lineStart, columnStart, lineEnd, columnEnd) +/** Normalizes a RAW scaladoc comment (as `Symbol.docstring` returns it in macros, markers included) + * to the cooked body the compiler plugin's `docString` yields: the `/**`/`*/` markers removed, and + * each continuation line's leading whitespace-and-`*` margin stripped. Keeps the first line's + * spacing verbatim (a single-line `/** My in */` cooks to " My in "). + */ +def sanitizedDocstring(raw: String): String = + val body = raw.stripPrefix("/**").stripSuffix("*/") + val lines = body.split("\n", -1) + (lines.head +: lines.tail.map(_.replaceFirst("^\\s*(\\*|$)", ""))).mkString("\n") + trait MetaContext: def setMeta( nameOpt: Option[String], position: Position, doc: Option[String], - annotations: List[Annotation] + annotations: List[Annotation], + namespace: String ): this.type def setMetaAnon( position: Position - ): this.type = setMeta(None, position, None, Nil) + ): this.type = setMeta(None, position, None, Nil, "") def setName(name: String): this.type diff --git a/internals/src/main/scala/dfhdl/internals/helpers.scala b/internals/src/main/scala/dfhdl/internals/helpers.scala index 81f0365ea..b81bf42f4 100644 --- a/internals/src/main/scala/dfhdl/internals/helpers.scala +++ b/internals/src/main/scala/dfhdl/internals/helpers.scala @@ -208,6 +208,12 @@ end ValueOfTuple //evidence of class T which has no arguments and no type arguments trait ClassEv[T]: val value: T + // best-effort declaration capture of T (for DFHDL type meta): position, doc comment, + // and enclosing Scala package. Package-level only: enclosing objects are scoping, + // not namespacing. + val dclPosition: Position + val dclDocOpt: Option[String] + val dclNamespace: String object ClassEv: inline given [T]: ClassEv[T] = ${ macroImpl[T] } def macroImpl[T](using Quotes, Type[T]): Expr[ClassEv[T]] = @@ -219,10 +225,32 @@ object ClassEv: .select(sym.primaryConstructor) .appliedToNone .asExprOf[T] + val posExpr = sym.pos match + case Some(pos) if scala.util.Try(pos.sourceFile.path).isSuccess => + '{ + dfhdl.internals.Position.fromAbsPath( + ${ Expr(pos.sourceFile.path) }, + ${ Expr(pos.startLine + 1) }, + ${ Expr(pos.startColumn + 1) }, + ${ Expr(pos.endLine + 1) }, + ${ Expr(pos.endColumn + 1) } + ) + } + case _ => '{ dfhdl.internals.Position.unknown } + val docExpr = Expr(sym.docstring.map(sanitizedDocstring)) + var pkgOwner = sym.owner + while (!pkgOwner.isPackageDef) do pkgOwner = pkgOwner.owner + val nsExpr = Expr( + if (pkgOwner.fullName.startsWith("<")) "" else pkgOwner.fullName + ) '{ new ClassEv[T]: val value: T = $valueExpr + val dclPosition: dfhdl.internals.Position = $posExpr + val dclDocOpt: Option[String] = $docExpr + val dclNamespace: String = $nsExpr } + end macroImpl end ClassEv // gets the case class from a companion object reference diff --git a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala index da7e78e84..885fd03d2 100755 --- a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala @@ -88,10 +88,13 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: // `Erasure`) in the shape the compiler would have handed us for an ordinary term. // Also, we revesrse the annotations since for some reason the compiler reverses the order of the annotations. val annotTree = mkList(annotations.map(a => transformAllDeep(inlineCalls(a.tree))).reverse) + // the declaring package travels on every named value; DFC's `getMeta` keeps it + // only at global scope (design-scoped values get "") tree .select(setMetaSym) .appliedToArgs( - nameOptTree :: positionTree :: docOptTree :: annotTree :: Nil + nameOptTree :: positionTree :: docOptTree :: annotTree :: + mkNamespace(summon[Context].owner) :: Nil ) .withType(TermRef(tree.tpe, setMetaSym)) else From 0111302471d4939f4795288dd248c2499fbc87dd Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 04:15:15 +0300 Subject: [PATCH 49/57] packages: namespace-derived emission for SystemVerilog and the DFHDL printer Named types, global constants, and global static functions are placed by the namespace rules (printing.Namespacing): a namespace equal to or an ancestor of the TOP design's stays in the general global defs file; anything else lands in a dedicated package named by the namespace RELATIVE to the top's (dots to underscores). Two namespaces mapping to one package name, or a package name colliding with a design name, are hard errors. Clk/Rst/Magnet opaques are language-level and never packaged. Emission: shared partition in DFTypePrinter/Printer (packagedTypeDcls + packagedGlobalDecls + packagedContents) with cross-package topological ordering, sub-DB dedup, placement overriding design-locality, and hoisting of global-placed types referenced by packaged content into the global file (a package file cannot reference a type declared inside a design). Package files join printedDB ahead of designs and csDB after the globals section. References qualify instead of importing, so same-named declarations from different packages can never collide: SystemVerilog prints `pkg::name` (type names, enum entries, global constants, static-function calls), the DFHDL printer prints fully qualified `.name` and renders each package as a real Scala `package :` section. v95/v2001 keep merging everything into the single global header. Fixed along the way: DFC gains `getDclMeta` (design-block dclMeta keeps its namespace; the value-oriented `getMeta` owner gate was nulling def and child class design namespaces); global-scope method copies (the pre-existing non-unification mints one block per global call nest) dedup by `sameDclAs` at printing; DFSpec's mock top now carries the concrete spec's package namespace via `@metaContextIgnore` (the plugin's static injection would stamp `dfhdl`). The object-hierarchy question was settled back to packages-only: a Scala object is a value (aliasable, importable), so objects remain scoping. Pinned end-to-end in PrintCodeStringSpec and PrintVerilogCodeSpec over PkgFixtures.scala: sibling packages typespkg1/typespkg2 in one file, with typespkg2 referencing typespkg1 (struct field + qualified static call) and typespkg1 referencing uniquely-named general globals. Co-Authored-By: Claude Fable 5 --- .../compiler/printing/DFDataPrinter.scala | 5 +- .../compiler/printing/DFTypePrinter.scala | 143 +++++++++++++--- .../compiler/printing/DFValPrinter.scala | 9 +- .../dfhdl/compiler/printing/Namespacing.scala | 37 ++++ .../dfhdl/compiler/printing/Printer.scala | 158 +++++++++++++++++- .../stages/verilog/VerilogDataPrinter.scala | 7 +- .../stages/verilog/VerilogPrinter.scala | 15 ++ .../stages/verilog/VerilogTypePrinter.scala | 13 +- .../stages/verilog/VerilogValPrinter.scala | 3 +- .../src/test/scala/StagesSpec/MetaSpec.scala | 16 ++ .../test/scala/StagesSpec/PkgFixtures.scala | 27 +++ .../StagesSpec/PrintCodeStringSpec.scala | 50 ++++++ .../StagesSpec/PrintVerilogCodeSpec.scala | 65 +++++++ core/src/main/scala/dfhdl/core/DFC.scala | 4 + core/src/main/scala/dfhdl/core/Design.scala | 2 +- .../main/scala/dfhdl/core/TypeMetaGen.scala | 2 +- core/src/test/scala/DFSpec.scala | 9 +- .../src/main/scala/plugin/CommonPhase.scala | 3 +- 18 files changed, 530 insertions(+), 38 deletions(-) create mode 100644 compiler/ir/src/main/scala/dfhdl/compiler/printing/Namespacing.scala create mode 100644 compiler/stages/src/test/scala/StagesSpec/PkgFixtures.scala diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFDataPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFDataPrinter.scala index a23fc9f8e..154761c78 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFDataPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFDataPrinter.scala @@ -226,7 +226,10 @@ protected trait DFDataPrinter extends AbstractDataPrinter: data match case Some(value) => val entryName = dfType.entries.find(_._2 == value).get._1 - s"${dfType.name}.${entryName}" + val nsQualifier = printer.typePlacementOf(dfType) match + case Some(pkg) if !printer.currentPackage.contains(pkg) => s"${dfType.meta.namespace}." + case _ => "" + s"$nsQualifier${dfType.name}.${entryName}" case None => "?" val maxVectorDisplay: Int = 64 def csDFVectorData(dfType: DFVector, data: Vector[Any]): String = diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala index 10a629557..74645acc8 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala @@ -47,29 +47,119 @@ trait AbstractTypePrinter extends AbstractPrinter: globalConstGroups.iterator .map((p, gs) => p.csDFMembers(gs.filterNot(isInt32Val))) .filter(_.nonEmpty).mkString("\n") - final def csGlobalTypeDcls: String = + // Every global named type across sub-DBs (first-occurrence dedup in sub-DB + // iteration order), each paired with the printer that renders it. + private def globalTypeGroups: List[(TPrinter, List[NamedDFType])] = + val designDB = getSet.designDB + if (designDB.isRoot) + val seen = mutable.HashSet.empty[NamedDFType] + designDB.subDBs.view.values.flatMap { sub => + val fresh = sub.getGlobalNamedDFTypes.iterator.filter(seen.add).toList + Option.when(fresh.nonEmpty)(withGetSet(sub.getSet) -> fresh) + }.toList + else List(printer -> designDB.getGlobalNamedDFTypes.toList) + private def typeDclFilter(dfType: NamedDFType): Boolean = dfType match + // show tuple structures only if tuple support is disabled + case dfType: DFStruct if dfType.isTuple && tupleSupportEnable => false + // skipping unknown clock and reset definitions (they are unknown because + // they lack additional name suffix that belongs to their configuration) + case t: DFOpaque if t.name == "Clk" && t.kind == DFOpaque.Kind.Clk => false + case t: DFOpaque if t.name == "Rst" && t.kind == DFOpaque.Kind.Rst => false + case _ => true + // The dedicated-package type declarations: (package name, namespace, content) in + // cross-package dependency order (a struct field may be a type of another package), + // first-appearance order among independent packages. Each declaration renders under + // its package context so its own (and same-package) type names print unqualified. + // Two distinct namespaces mapping to one package name (the documented `top.x` vs + // root-level `x` clash) are rejected. + final def packagedTypeEntries: List[(String, String, List[(TPrinter, NamedDFType)])] = val designDB = getSet.designDB - val typeGroups: List[(TPrinter, List[NamedDFType])] = + // design-local named types with a foreign namespace are packaged too (placement + // overrides design-locality); a type used locally by several designs dedups + val localGroups: List[(TPrinter, List[NamedDFType])] = if (designDB.isRoot) - val seen = mutable.HashSet.empty[NamedDFType] designDB.subDBs.view.values.flatMap { sub => - val fresh = sub.getGlobalNamedDFTypes.iterator.filter(seen.add).toList - Option.when(fresh.nonEmpty)(withGetSet(sub.getSet) -> fresh) + val locals = sub.getLocalNamedDFTypes(sub.top).toList + Option.when(locals.nonEmpty)(withGetSet(sub.getSet) -> locals) }.toList - else List(printer -> designDB.getGlobalNamedDFTypes.toList) - typeGroups.iterator.flatMap { (p, types) => - types.view - .filter { - // show tuple structures only if tuple support is disabled - case dfType: DFStruct if dfType.isTuple && tupleSupportEnable => false - // skipping unknown clock and reset definitions (they are unknown because - // they lack additional name suffix that belongs to their configuration) - case t: DFOpaque if t.name == "Clk" && t.kind == DFOpaque.Kind.Clk => false - case t: DFOpaque if t.name == "Rst" && t.kind == DFOpaque.Kind.Rst => false - case _ => true + else + designDB.designMemberList.view.map(_._1).flatMap { design => + val locals = designDB.getLocalNamedDFTypes(design).toList + Option.when(locals.nonEmpty)(printer -> locals) + }.toList + val perPkg = + mutable.LinkedHashMap.empty[String, (String, mutable.ListBuffer[(TPrinter, NamedDFType)])] + val seen = mutable.HashSet.empty[NamedDFType] + (globalTypeGroups ++ localGroups).foreach { (p, types) => + types.view.filter(typeDclFilter).filter(seen.add).foreach { t => + p.typePlacementOf(t).foreach { pkg => + val (ns, buf) = perPkg.getOrElseUpdate(pkg, (t.meta.namespace, mutable.ListBuffer.empty)) + if (ns != t.meta.namespace) + throw new IllegalArgumentException( + s"Namespaces `$ns` and `${t.meta.namespace}` both map to the emitted package `$pkg`." + ) + buf += ((p, t)) } + } + } + // topological order across packages by named-type references + def depsOf(pkg: String): List[String] = + perPkg(pkg)._2.view.flatMap { (p, t) => + given MemberGetSet = p.getSet + t.decompose { case n: NamedDFType => n }.view + .filterNot(_ == t) + .flatMap(p.typePlacementOf) + .filter(_ != pkg) + }.toList.distinct + val ordered = mutable.ListBuffer.empty[String] + val done = mutable.Set.empty[String] + def place(pkg: String): Unit = + if (done.add(pkg)) + depsOf(pkg).foreach(place) + ordered += pkg + perPkg.keys.foreach(place) + ordered.view.map { pkg => + val (ns, entries) = perPkg(pkg) + (pkg, ns, entries.toList) + }.toList + end packagedTypeEntries + final def packagedTypeDcls: List[(String, String, String)] = + packagedTypeEntries.map { (pkg, ns, entries) => + val dcls = entries.map { (p, t) => + p.currentPackage = Some(pkg) + try p.csNamedDFTypeDcl(t, global = true) + finally p.currentPackage = None + } + (pkg, ns, dcls.mkString("\n")) + } + // Design-local named types that packaged content references (e.g. a global-placed + // struct that is a FIELD of a packaged struct): a package file cannot reference a + // type declared inside a design, so these are hoisted into the general global file. + final def packagedHoistedTypes: List[(TPrinter, NamedDFType)] = + val packagedTypes = packagedTypeEntries.flatMap(_._3) + val globalTypes = globalTypeGroups.flatMap(_._2).toSet + val seen = mutable.HashSet.empty[NamedDFType] + packagedTypes.flatMap { (p, t) => + given MemberGetSet = p.getSet + t.decompose { case n: NamedDFType => n }.view + .filterNot(_ == t) + .filter(n => p.typePlacementOf(n).isEmpty) + .filterNot(globalTypes) + .filter(seen.add) + .map(p -> _) + .toList + } + final def csGlobalTypeDcls: String = + val globalPlaced = globalTypeGroups.iterator.flatMap { (p, types) => + types.view + .filter(typeDclFilter) + .filter(t => p.typePlacementOf(t).isEmpty) .map(x => p.csNamedDFTypeDcl(x, global = true)) - }.mkString("\n") + } + val hoisted = packagedHoistedTypes.view + .filter((_, t) => typeDclFilter(t)) + .map((p, t) => p.csNamedDFTypeDcl(t, global = true)) + (globalPlaced ++ hoisted).mkString("\n") end csGlobalTypeDcls final def csLocalTypeDcls(design: DFDesignBlock): String = val designDB = getSet.designDB @@ -77,8 +167,13 @@ trait AbstractTypePrinter extends AbstractPrinter: // its one design and may mis-classify a cross-design global type as local // (empty for a flat DB, which classifies named types directly). val hierGlobal = designDB.rootDB.hierGlobalNamedDFTypes + val hoisted = packagedHoistedTypes.view.map(_._2).toSet designDB.getLocalNamedDFTypes(design).view .filterNot(hierGlobal) + // a foreign-namespace type is never design-local: it is emitted into its package + .filter(t => printer.typePlacementOf(t).isEmpty) + // a type referenced by packaged content is hoisted to the global file + .filterNot(hoisted) .filter { // show tuple structures only if tuple support is disabled case dfType: DFStruct if dfType.isTuple && tupleSupportEnable => false @@ -164,7 +259,14 @@ protected trait DFTypePrinter extends AbstractTypePrinter: .mkString("\n") .hindent s"enum ${enumName}(val value: ${csDFDecimal(DFUInt(IntParamRef(width)), true)} <> CONST) extends Encoded.Manual($width):\n$entries" - def csDFEnum(dfType: DFEnum, typeCS: Boolean): String = dfType.name + // full-namespace qualification of a packaged type's reference (`.`), + // dropped inside its own package section; qualification, not imports, so same-named + // types from different packages can never collide + protected def nsQualifier(dfType: NamedDFType): String = + printer.typePlacementOf(dfType) match + case Some(pkg) if !printer.currentPackage.contains(pkg) => s"${dfType.meta.namespace}." + case _ => "" + def csDFEnum(dfType: DFEnum, typeCS: Boolean): String = s"${nsQualifier(dfType)}${dfType.name}" def csDFVector(dfType: DFVector, typeCS: Boolean): String = import dfType.* val dimStr = @@ -179,7 +281,8 @@ protected trait DFTypePrinter extends AbstractTypePrinter: case DFOpaque.Kind.Magnet => s"Magnet($csActualType)" case _ => s"Opaque($csActualType)" s"case class ${dfType.name}() extends $extendee" - def csDFOpaque(dfType: DFOpaque, typeCS: Boolean): String = dfType.name + def csDFOpaque(dfType: DFOpaque, typeCS: Boolean): String = + s"${nsQualifier(dfType)}${dfType.name}" def csDFStructDcl(dfType: DFStruct): String = val fields = dfType.fieldMap.view .map((n, t) => s"${n}${csDFValType(t)}") @@ -187,7 +290,7 @@ protected trait DFTypePrinter extends AbstractTypePrinter: .hindent(2) s"final case class ${dfType.name}(\n$fields\n) extends Struct" def csDFStruct(dfType: DFStruct, typeCS: Boolean): String = - dfType.name + s"${nsQualifier(dfType)}${dfType.name}" def csDFUnit(dfType: DFUnit, typeCS: Boolean): String = "Unit" def csDFDouble(): String = "Double" def csDFTime(dfType: DFTime, typeCS: Boolean): String = "Time" 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 ed7a8fdd4..e4c4047a1 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala @@ -208,7 +208,7 @@ trait AbstractValPrinter extends AbstractPrinter: case dfVal: DFVal.DesignParam => dfVal.nameCS case dfVal: DFVal.CanBeGlobal if dfVal.isGlobal => if (dfVal.isAnonymous) printer.csDFValExpr(dfVal) - else dfVal.nameCS + else s"${printer.globalValQualifier(dfVal)}${dfVal.nameCS}" case dfVal: DFVal => val callOwner = ref.originMember.getOwner val cs = printer.csDFValRef(dfVal, callOwner) @@ -320,14 +320,17 @@ trait AbstractValPrinter extends AbstractPrinter: val designInst = pbns.designInstRef.get s"${designInst.getRelativeName(fromOwner)}.${pbns.portNamePath}" case expr: CanBeExpr if expr.isAnonymous => csDFValExpr(expr) - case _ => dfVal.getRelativeName(fromOwner) + case g: DFVal.CanBeGlobal if g.isGlobal => + s"${printer.globalValQualifier(g)}${g.getRelativeName(fromOwner)}" + case _ => dfVal.getRelativeName(fromOwner) end AbstractValPrinter protected trait DFValPrinter extends AbstractValPrinter: type TPrinter <: DFPrinter def csMethodCall(call: Func, designKey: StaticRef): String = val design = designKey.getDesignBlock - s"${design.dclName}(${csMethodCallArgs(call, design).mkString(", ")})" + val qualifier = printer.globalMethodQualifier(design) + s"$qualifier${design.dclName}(${csMethodCallArgs(call, design).mkString(", ")})" def csConditionalExprRel(csExp: String, ch: DFConditional.Header): String = s"(${csExp.applyBrackets()}: ${printer.csDFType(ch.dfType, typeCS = true)} <> VAL)" def csDFValDclConst(dfVal: DFVal.CanBeExpr): String = diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Namespacing.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Namespacing.scala new file mode 100644 index 000000000..a5af337c4 --- /dev/null +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Namespacing.scala @@ -0,0 +1,37 @@ +package dfhdl.compiler.printing +import dfhdl.compiler.ir.* + +/** The namespace placement rules of the packages feature: where a named type / global constant / + * global static function lands, relative to the TOP design's namespace. + * + * A declaration whose namespace equals the top's, or is an ancestor package of it (the root + * namespace "" included), stays in the general global defs file, so designs under a dedicated + * package do not reference a separate package for types declared alongside or above them. Anything + * else is emitted into a package of its own. + * + * The emitted package name is the declaration's namespace RELATIVE to the top design's namespace: + * the longest common package prefix is dropped and the remaining segments are joined with `_` + * (e.g. top `veer` with types in `veer.veer_types` -> `veer_types`; types in + * `dfhdl.lib.crypto.aes` under an unrelated top -> `dfhdl_lib_crypto_aes`). Distinct namespaces + * map to distinct names, except a `top.x` vs root-level `x` clash, which the emission must detect + * and reject. + */ +object Namespacing: + /** Does `ns` belong in the general global defs file under a top design of `topNs`? */ + def isGlobalPlaced(ns: String, topNs: String): Boolean = + ns.isEmpty || ns == topNs || topNs.startsWith(s"$ns.") + + /** The emitted package name for a non-global-placed `ns` under a top of `topNs`. */ + def packageNameOf(ns: String, topNs: String): String = + val nsParts = ns.split('.') + val topParts = topNs.split('.') + val common = nsParts.lazyZip(topParts).takeWhile(_ == _).size + nsParts.drop(common).mkString("_") + + /** Placement of one namespace: `None` for the global defs file, `Some(packageName)` for a + * dedicated package. + */ + def placementOf(ns: String, topNs: String): Option[String] = + if (isGlobalPlaced(ns, topNs)) None + else Some(packageNameOf(ns, topNs)) +end Namespacing 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 8e2d50080..1a0393cc4 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala @@ -324,14 +324,37 @@ trait Printer seeds.foreach(visit) result.toSet + // ---- namespace-based type packaging ---- + // Whether this backend emits namespace-derived package files. Backends without + // packages (or not yet migrated to them) keep every global declaration in the + // general global defs file. + def supportPackages: Boolean = false + // the TOP design's namespace: the reference point of the placement rules + final def topNamespace: String = getSet.designDB.rootDB.top.dclMeta.namespace + // Some(packageName) when `dfType` is emitted into a dedicated package file + final def typePlacementOf(dfType: NamedDFType): Option[String] = + dfType match + // Clk/Rst/Magnet opaques are language-level (the DFHDL printer shows them as + // builtins and the backends drop them), so they are never packaged even though + // their declaring namespace is a DFHDL-internal one + case t: DFOpaque if t.isMagnet => None + case _ => + if (printer.supportPackages) Namespacing.placementOf(dfType.meta.namespace, topNamespace) + else None + // the package whose file is currently being rendered: its own declarations (and + // same-package references) print unqualified + var currentPackage: Option[String] = None + protected def hasGlobalContentCheck: Boolean = val designDB = getSet.designDB + def globalPlaced(g: DFVal) = + !g.isAnonymous && printer.memberPlacementOf(g.meta.namespace).isEmpty val anyNamedGlobal = if (designDB.isRoot) - designDB.subDBs.view.values.exists(_.membersGlobals.exists(!_.isAnonymous)) - else designDB.membersGlobals.exists(!_.isAnonymous) + designDB.subDBs.view.values.exists(_.membersGlobals.exists(globalPlaced)) + else designDB.membersGlobals.exists(globalPlaced) anyNamedGlobal || csGlobalTypeDcls.nonEmpty || - globalHDLMethods.nonEmpty + globalHDLMethods.exists(b => printer.memberPlacementOf(b.dclMeta.namespace).isEmpty) lazy val hasGlobalContent: Boolean = hasGlobalContentCheck // Global constants and global HDL methods in DEPENDENCY order. Both HDLs require a name to be // declared before it is used, and the dependency between the two runs BOTH ways: a constant's @@ -524,13 +547,101 @@ trait Printer globalMethodPrinters.toMap private lazy val constPrinterOf: Map[DFMember, TPrinter] = globalConstsWithPrinters.view.map((p, c) => c -> p).toMap + // placement of a global constant / global method under the namespace packaging rules + final def memberPlacementOf(ns: String): Option[String] = + if (printer.supportPackages) Namespacing.placementOf(ns, topNamespace) else None + // the backend-specific spelling of a packaged GLOBAL reference's qualifier + // (`.` in DFHDL code, `::` in SystemVerilog) + def csGlobalMemberQualifier(ns: String, pkgName: String): String = "" + // the reference qualifier of a packaged global VALUE, "" inside its own package + final def globalValQualifier(dfVal: DFVal): String = + memberPlacementOf(dfVal.meta.namespace) match + case Some(pkg) if !printer.currentPackage.contains(pkg) => + csGlobalMemberQualifier(dfVal.meta.namespace, pkg) + case _ => "" + // the call qualifier of a packaged global METHOD, "" inside its own package and for + // design-local methods (whose namespace is incidental) + final def globalMethodQualifier(design: DFDesignBlock): String = + memberPlacementOf(design.dclMeta.namespace) match + case Some(pkg) + if !printer.currentPackage.contains(pkg) && globalMethodPrinterOf.contains(design) => + csGlobalMemberQualifier(design.dclMeta.namespace, pkg) + case _ => "" + // Global-scope methods do not unify at elaboration (each global call nest carries its + // own def-design copy), so printing dedups same-DECLARATION method copies + protected final def globalDeclsDeduped: List[GlobalDecl] = + val seenMethods = collection.mutable.ListBuffer.empty[DFDesignBlock] + globalDeclsOrdered.filter { + case GlobalDecl.Method(b) => + if (seenMethods.exists(_.dclMeta.sameDclAs(b.dclMeta))) false + else + seenMethods += b + true + case _ => true + } + private def globalDeclPlacementOf(decl: GlobalDecl): Option[String] = decl match + case GlobalDecl.Const(c) => memberPlacementOf(c.meta.namespace) + case GlobalDecl.Method(b) => memberPlacementOf(b.dclMeta.namespace) + private def globalDeclNamespaceOf(decl: GlobalDecl): String = decl match + case GlobalDecl.Const(c) => c.meta.namespace + case GlobalDecl.Method(b) => b.dclMeta.namespace + private def globalDeclPrinterOf(decl: GlobalDecl): TPrinter = decl match + case GlobalDecl.Const(c) => constPrinterOf.getOrElse(c, printer) + case GlobalDecl.Method(b) => globalMethodPrinterOf(b) // one global declaration rendered as a DEFINITION (a constant declaration, or a method with // its body). VHDL renders the method half as a prototype in its package spec instead. protected final def csGlobalDecl(decl: GlobalDecl): String = decl match case GlobalDecl.Const(c) => constPrinterOf.getOrElse(c, printer).csDFMembers(List(c)) case GlobalDecl.Method(b) => globalMethodPrinterOf(b).csMethodDcl(b).stripTrailing protected final def csGlobalDecls: String = - globalDeclsOrdered.map(csGlobalDecl).filter(_.nonEmpty).mkString("\n") + globalDeclsDeduped.filter(globalDeclPlacementOf(_).isEmpty) + .map(csGlobalDecl).filter(_.nonEmpty).mkString("\n") + // packaged global constants/methods: (package, namespace, content) with the + // dependency order preserved within each package + protected final def packagedGlobalDecls: List[(String, String, String)] = + val perPkg = + collection.mutable.LinkedHashMap + .empty[String, (String, collection.mutable.ListBuffer[GlobalDecl])] + globalDeclsDeduped.foreach { decl => + globalDeclPlacementOf(decl).foreach { pkg => + val ns = globalDeclNamespaceOf(decl) + val (pkgNs, buf) = perPkg.getOrElseUpdate(pkg, (ns, collection.mutable.ListBuffer.empty)) + if (pkgNs != ns) + throw new IllegalArgumentException( + s"Namespaces `$pkgNs` and `$ns` both map to the emitted package `$pkg`." + ) + buf += decl + } + } + perPkg.view.map { case (pkg, (ns, decls)) => + val content = decls.map { decl => + val p = globalDeclPrinterOf(decl) + p.currentPackage = Some(pkg) + try csGlobalDecl(decl) + finally p.currentPackage = None + }.filter(_.nonEmpty).mkString("\n") + (pkg, ns, content) + }.toList + end packagedGlobalDecls + // every packaged content group (types first, then constants/methods), merged per + // package: type-dependency order first, decl-only packages appended + final def packagedContents: List[(String, String, String)] = + val types = packagedTypeDcls + val decls = packagedGlobalDecls + val declMap = decls.map((pkg, ns, cs) => pkg -> (ns, cs)).toMap + val merged = types.map { (pkg, ns, typeCS) => + declMap.get(pkg) match + case Some((declNs, declCS)) => + if (declNs != ns) + throw new IllegalArgumentException( + s"Namespaces `$ns` and `$declNs` both map to the emitted package `$pkg`." + ) + (pkg, ns, s"$typeCS\n$declCS") + case None => (pkg, ns, typeCS) + } + val typePkgs = types.map(_._1).toSet + merged ++ decls.filterNot((pkg, _, _) => typePkgs.contains(pkg)) + end packagedContents def csGlobalFileContent: String = sn"""|$csGlobalTypeDcls @@ -569,6 +680,9 @@ trait Printer |$designDcl""" def dfhdlDefsFileName: String def dfhdlSourceContents: String + // namespace-derived package emission hooks (meaningful when `supportPackages`) + def packageFileName(pkgName: String): String = "" + def csPackageFileContent(pkgName: String, namespace: String, typeDcls: String): String = "" val hdlFolderName: String = "hdl" final def printedDB: DB = val designDB = getSet.designDB @@ -594,8 +708,31 @@ trait Printer ) ) else None + // namespace-derived package files, in cross-package dependency order, compiled + // ahead of the designs that reference their types (`pkg::name` qualification + // resolves through compilation order, not through includes) + val packageSourceFiles = + if (supportPackages) + val pkgs = packagedContents + val designNames = designPrinters.view.map(_._1.dclName).toSet + pkgs.foreach { (pkgName, _, _) => + if (designNames.contains(pkgName)) + throw new IllegalArgumentException( + s"Emitted package name `$pkgName` collides with a design name." + ) + } + pkgs.map((pkgName, ns, dcls) => + SourceFile( + SourceOrigin.Compiled, + SourceType.GlobalDef, + hdlFolderName + separatorChar + packageFileName(pkgName), + formatCode(csPackageFileContent(pkgName, ns, dcls), withColor = false) + ) + ) + else Nil val compiledFiles = Iterable( dfhdlSourceFile, + packageSourceFiles, globalSourceFile, designPrinters.view // A foreign IP supplies its own HDL wrapper as a bundled resource (copied into the project @@ -737,11 +874,15 @@ trait Printer block.foreignIPSource.forall(src => seenForeignImports.add(src.clsName)) => formatCode(p.csFile(block)) } + val packages = + if (supportPackages) + packagedContents.map((pkg, ns, dcls) => formatCode(csPackageFileContent(pkg, ns, dcls))) + else Nil val globals = formatCode( sn"""|$csGlobalTypeDcls |$csGlobalDecls""" ) - sn"""|$globals + sn"""|${(globals :: packages).filter(_.nonEmpty).mkString("\n")} | |${csFileList.mkString("\n")} |""".stripMargin @@ -807,6 +948,13 @@ class DFPrinter(using val getSet: MemberGetSet, val printerOptions: PrinterOptio new DFPrinter(using subGetSet, printerOptions) override val printVendorIPBlackbox: Boolean = true val tupleSupportEnable: Boolean = true + override def supportPackages: Boolean = true + override def csGlobalMemberQualifier(ns: String, pkgName: String): String = s"$ns." + override def packageFileName(pkgName: String): String = s"$pkgName.scala" + override def csPackageFileContent(pkgName: String, namespace: String, typeDcls: String): String = + sn"""|package $namespace: + |${typeDcls.hindent} + |""" def csViaConnectionSep: String = "" def csAssignment(lhsStr: String, rhsStr: String, lhsDcl: DFVal.Dcl): String = s"$lhsStr := $rhsStr" 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 0a4a81e36..884012b31 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 @@ -51,7 +51,12 @@ protected trait VerilogDataPrinter extends AbstractDataPrinter: case Some(value) => val entryName = dfType.entries.find(_._2 == value).get._1 val verilogDefine = if (printer.allowTypeDef) "" else "`" - s"$verilogDefine${dfType.name}_${entryName}" + // a packaged enum's entries are package-scoped identifiers in SV + val pkgQualifier = printer.typePlacementOf(dfType) match + case Some(pkg) if printer.allowTypeDef && !printer.currentPackage.contains(pkg) => + s"$pkg::" + case _ => "" + s"$verilogDefine$pkgQualifier${dfType.name}_${entryName}" case None => "?" val maxElementsPerLine = 64 def csDFVectorElemCS(elemCS: List[String]): 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 e157e9174..5f29076f3 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 @@ -219,6 +219,21 @@ class VerilogPrinter(val dialect: VerilogDialect)(using printer.dialect match case VerilogDialect.v2001 | VerilogDialect.v95 => "vh" case _ => "svh" + override def csGlobalMemberQualifier(ns: String, pkgName: String): String = s"$pkgName::" + override def supportPackages: Boolean = + printer.dialect match + case VerilogDialect.v95 | VerilogDialect.v2001 => false + case _ => true + override def packageFileName(pkgName: String): String = s"$pkgName.sv" + override def csPackageFileContent(pkgName: String, namespace: String, typeDcls: String): String = + // the global defs header may be referenced by packaged type declarations + // (e.g. a struct field of a global-placed named type); its include guard makes + // the include harmless otherwise + sn"""|package $pkgName; + |${if (hasGlobalContent) s"""`include "$globalFileName"""" else ""} + |$typeDcls + |endpackage + |""" def globalFileName: String = val name = printerOptions.globalDefsFileName if (name.nonEmpty && name.contains('.')) name 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 adc8753c8..b0536f20b 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 @@ -55,8 +55,13 @@ protected trait VerilogTypePrinter extends AbstractTypePrinter: getSet.designDB.getGlobalNamedDFTypes.view.collect { case dfType: DFEnum => csDFEnumToStringFuncDcl(dfType) }.mkString("\n") + // `pkg::` qualification of a packaged type's name, dropped inside its own package file + protected def pkgQualifier(dfType: NamedDFType): String = + printer.typePlacementOf(dfType) match + case Some(pkg) if !printer.currentPackage.contains(pkg) => s"$pkg::" + case _ => "" def csDFEnumTypeName(dfType: DFEnum): String = - if (allowTypeDef) s"t_enum_${dfType.name}" + if (allowTypeDef) s"${pkgQualifier(dfType)}t_enum_${dfType.name}" else csDFBits(DFBits(dfType.widthIntOpt.get), false) def csDFEnumToStringFuncDcl(dfType: DFEnum): String = val enumName = dfType.name @@ -103,11 +108,13 @@ protected trait VerilogTypePrinter extends AbstractTypePrinter: def csDFVector(dfType: DFVector, typeCS: Boolean): String = import dfType.* s"${csDFType(cellType, typeCS)}" - def csDFOpaqueTypeName(dfType: DFOpaque): String = s"t_opaque_${dfType.name}" + def csDFOpaqueTypeName(dfType: DFOpaque): String = + s"${pkgQualifier(dfType)}t_opaque_${dfType.name}" def csDFOpaqueDcl(dfType: DFOpaque): String = s"typedef ${csDFType(dfType.actualType, typeCS = true)} ${csDFOpaqueTypeName(dfType)}${csDFVectorRanges(dfType.actualType)};" def csDFOpaque(dfType: DFOpaque, typeCS: Boolean): String = csDFOpaqueTypeName(dfType) - def csDFStructTypeName(dfType: DFStruct): String = s"t_struct_${dfType.name}" + def csDFStructTypeName(dfType: DFStruct): String = + s"${pkgQualifier(dfType)}t_struct_${dfType.name}" def csDFStructDcl(dfType: DFStruct): String = val fields = dfType.fieldMap.view .map((n, t) => s"${csDFType(t, typeCS = true)} $n${csDFVectorRanges(t)};") 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 9a8057c98..e0d3eb044 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 @@ -13,6 +13,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: case _ => true def csMethodCall(call: Func, designKey: StaticRef): String = val design = designKey.getDesignBlock + val qualifier = printer.globalMethodQualifier(design) val args = csMethodCallArgs(call, design).mkString(", ") // a procedural (Unit-return) call is a task call statement if (call.dfType == DFUnit) @@ -23,7 +24,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: // the v95/v2001 minimum-one-input rule: an argument-less function call passes a // literal `0` to the declared dummy input val argList = if (args.isEmpty && !printer.dummyLessFunctionSupport) "0" else args - s"${printer.moduleName(design)}($argList)" + s"$qualifier${printer.moduleName(design)}($argList)" end csMethodCall val supportGlobalParameters: Boolean = printer.dialect match diff --git a/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala b/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala index 5f5af55e6..4c6c67bbf 100644 --- a/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/MetaSpec.scala @@ -116,4 +116,20 @@ class MetaSpec extends FunSuite, NoTopAnnotIsRequired: assertEquals(locMeta.namespace, "") } + test("namespace placement rules") { + import dfhdl.compiler.printing.Namespacing.* + // equal or ancestor (root included) -> global defs file + assert(isGlobalPlaced("", "veer.core")) + assert(isGlobalPlaced("veer.core", "veer.core")) + assert(isGlobalPlaced("veer", "veer.core")) + assert(!isGlobalPlaced("veer.types", "veer.core")) + assert(!isGlobalPlaced("veercore", "veer.core")) // prefix of a SEGMENT is not an ancestor + // package name: namespace relative to the top, joined with `_` + assertEquals(packageNameOf("veer.veer_types", "veer"), "veer_types") + assertEquals(packageNameOf("veer.types", "veer.core"), "types") + assertEquals(packageNameOf("dfhdl.lib.crypto.aes", "myproj"), "dfhdl_lib_crypto_aes") + assertEquals(packageNameOf("b.util", "a"), "b_util") + // the documented residual clash: `top.x` and root-level `x` both map to "x" + assertEquals(packageNameOf("a.x", "a"), packageNameOf("x", "a")) + } end MetaSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PkgFixtures.scala b/compiler/stages/src/test/scala/StagesSpec/PkgFixtures.scala new file mode 100644 index 000000000..7546d9c3b --- /dev/null +++ b/compiler/stages/src/test/scala/StagesSpec/PkgFixtures.scala @@ -0,0 +1,27 @@ +package StagesSpec + +import dfhdl.* + +// General-global declarations (namespace `StagesSpec`, the specs' top-design namespace), +// referenced from `typespkg1` to pin the package -> general-globals dependency direction. +// Uniquely named to avoid collisions with anything else under StagesSpec. +case class GlbNsStruct(g: Bits[2] <> VAL) extends Struct +val GlbNsConst: UInt[8] <> CONST = 3 + +// Sibling packages in one file: both land in dedicated packages (`typespkg1`, +// `typespkg2`) under the placement rules, with typespkg2 referencing typespkg1, and +// typespkg1 referencing the general globals above. +package typespkg1 { + case class PkgStruct(a: Bits[8] <> VAL, b: Bit <> VAL, g: GlbNsStruct <> VAL) extends Struct + enum PkgEnum extends Encoded: + case P0, P1, P2 + case class PkgOpaque() extends Opaque(Bits(4)) + val PkgConst: UInt[8] <> CONST = GlbNsConst + 39 + def pkgCalc(arg: UInt[8] <> CONST): UInt[8] <> CONSTRET = arg + 1 + val PkgDerived: UInt[8] <> CONST = pkgCalc(PkgConst) +} + +package typespkg2 { + case class PkgWrap(s: typespkg1.PkgStruct <> VAL, n: UInt[8] <> VAL) extends Struct + val PkgWide: UInt[8] <> CONST = typespkg1.pkgCalc(typespkg1.PkgDerived) +} diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index efe548252..a9cb5fd28 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -3961,4 +3961,54 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |end DocTop |""".stripMargin ) + test("Namespace-derived type packages"): + class PkgTop extends DFDesign: + val s = typespkg1.PkgStruct <> VAR + val e = typespkg1.PkgEnum <> VAR + val o = typespkg1.PkgOpaque <> VAR + val w = typespkg2.PkgWrap <> VAR + val u = UInt(8) <> VAR init typespkg2.PkgWide + e := typespkg1.PkgEnum.P0 + val top = (new PkgTop).getCodeString + assertNoDiff( + top, + """|final case class GlbNsStruct( + | g: Bits[2] <> VAL + |) extends Struct + |val GlbNsConst: UInt[8] <> CONST = d"8'3" + |package StagesSpec.typespkg1: + | final case class PkgStruct( + | a: Bits[8] <> VAL + | b: Bit <> VAL + | g: GlbNsStruct <> VAL + | ) extends Struct + | enum PkgEnum(val value: UInt[2] <> CONST) extends Encoded.Manual(2): + | case P0 extends PkgEnum(d"2'0") + | case P1 extends PkgEnum(d"2'1") + | case P2 extends PkgEnum(d"2'2") + | case class PkgOpaque() extends Opaque(Bits(4)) + | val PkgConst: UInt[8] <> CONST = GlbNsConst + d"8'39" + | def pkgCalc(arg: UInt[8] <> CONST): UInt[8] <> CONSTRET = + | arg + d"8'1" + | end pkgCalc + | val PkgDerived: UInt[8] <> CONST = pkgCalc(PkgConst) + | + |package StagesSpec.typespkg2: + | final case class PkgWrap( + | s: StagesSpec.typespkg1.PkgStruct <> VAL + | n: UInt[8] <> VAL + | ) extends Struct + | val PkgWide: UInt[8] <> CONST = StagesSpec.typespkg1.pkgCalc(StagesSpec.typespkg1.PkgDerived) + | + | + |class PkgTop extends DFDesign: + | val s = StagesSpec.typespkg1.PkgStruct <> VAR + | val e = StagesSpec.typespkg1.PkgEnum <> VAR + | val o = StagesSpec.typespkg1.PkgOpaque <> VAR + | val w = StagesSpec.typespkg2.PkgWrap <> VAR + | val u = UInt(8) <> VAR init StagesSpec.typespkg2.PkgWide + | e := StagesSpec.typespkg1.PkgEnum.P0 + |end PkgTop + |""".stripMargin + ) end PrintCodeStringSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 213ef5e1d..37f7e4ce1 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -3978,4 +3978,69 @@ class PrintVerilogCodeSpec extends StageSpec: |endmodule |""".stripMargin ) + test("Namespace-derived type packages"): + class PkgTop extends EDDesign: + val sp = typespkg1.PkgStruct <> IN + val so = typespkg1.PkgStruct <> OUT + val e = typespkg1.PkgEnum <> VAR + val o = typespkg1.PkgOpaque <> VAR + val w = typespkg2.PkgWrap <> VAR + val u = UInt(8) <> VAR init typespkg2.PkgWide + so <> sp + val top = (new PkgTop).getCompiledCodeString + assertNoDiff( + top, + """|typedef struct packed { + | logic [1:0] g; + |} t_struct_GlbNsStruct; + |parameter logic [7:0] GlbNsConst = 8'd3; + |package typespkg1; + |`include "PkgTop_defs.svh" + |typedef struct packed { + | logic [7:0] a; + | logic b; + | t_struct_GlbNsStruct g; + |} t_struct_PkgStruct; + |typedef enum logic [1:0] { + | PkgEnum_P0 = 0, + | PkgEnum_P1 = 1, + | PkgEnum_P2 = 2 + |} t_enum_PkgEnum; + |typedef logic [3:0] t_opaque_PkgOpaque; + |parameter logic [7:0] PkgConst = GlbNsConst + 8'd39; + |function automatic logic [7:0] pkgCalc(input logic [7:0] arg); + |begin + | pkgCalc = arg + 8'd1; + |end + |endfunction + |parameter logic [7:0] PkgDerived = pkgCalc(PkgConst); + |endpackage + | + |package typespkg2; + |`include "PkgTop_defs.svh" + |typedef struct packed { + | typespkg1::t_struct_PkgStruct s; + | logic [7:0] n; + |} t_struct_PkgWrap; + |parameter logic [7:0] PkgWide = typespkg1::pkgCalc(typespkg1::PkgDerived); + |endpackage + | + | + |`default_nettype none + |`timescale 1ns/1ps + |`include "PkgTop_defs.svh" + | + |module PkgTop( + | input wire typespkg1::t_struct_PkgStruct sp, + | output typespkg1::t_struct_PkgStruct so + |); + | `include "dfhdl_defs.svh" + | typespkg1::t_enum_PkgEnum e; + | typespkg1::t_opaque_PkgOpaque o; + | typespkg2::t_struct_PkgWrap w; + | logic [7:0] u = typespkg2::PkgWide; + | assign so = sp; + |endmodule + |""".stripMargin + ) end PrintVerilogCodeSpec diff --git a/core/src/main/scala/dfhdl/core/DFC.scala b/core/src/main/scala/dfhdl/core/DFC.scala index 2c4d4b72e..027358a3b 100644 --- a/core/src/main/scala/dfhdl/core/DFC.scala +++ b/core/src/main/scala/dfhdl/core/DFC.scala @@ -64,6 +64,10 @@ final case class DFC( // design-scoped value's namespace is its design, not its declaring Scala package def getMeta: ir.Meta = ir.Meta(nameOpt, position, docOpt, annotations, if (ownerOption.isEmpty) namespace else "") + // a DECLARATION meta (a design block's dclMeta): the namespace is kept regardless of + // the owner context (a def design or a child class design is created inside its + // instantiating owner, but its declaration still lives in its Scala package) + def getDclMeta: ir.Meta = ir.Meta(nameOpt, position, docOpt, annotations, namespace) def enterOwner(owner: DFOwnerAny): Unit = mutableDB.OwnershipContext.enter(owner.asIR) def exitOwner(): Unit = mutableDB.OwnershipContext.exit() diff --git a/core/src/main/scala/dfhdl/core/Design.scala b/core/src/main/scala/dfhdl/core/Design.scala index 2a4adefff..8dd4c47ab 100644 --- a/core/src/main/scala/dfhdl/core/Design.scala +++ b/core/src/main/scala/dfhdl/core/Design.scala @@ -261,7 +261,7 @@ object Design: object Block: def apply(domain: ir.DomainType, instMode: InstMode)(using DFC): Block = ir.DFDesignBlock( - domain, instMode, dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags + domain, instMode, dfc.ownerOrEmptyRef, dfc.getDclMeta, dfc.tags ).addMember.asFE end apply end Block diff --git a/core/src/main/scala/dfhdl/core/TypeMetaGen.scala b/core/src/main/scala/dfhdl/core/TypeMetaGen.scala index 658deed5f..f95cd8f1f 100644 --- a/core/src/main/scala/dfhdl/core/TypeMetaGen.scala +++ b/core/src/main/scala/dfhdl/core/TypeMetaGen.scala @@ -9,7 +9,7 @@ import scala.quoted.* */ private[core] object TypeMetaGen: def namespaceOf(using q: Quotes)(sym: q.reflect.Symbol): String = - import quotes.reflect.* + import q.reflect.* var owner = sym.owner while (!owner.isPackageDef) do owner = owner.owner val fullName = owner.fullName diff --git a/core/src/test/scala/DFSpec.scala b/core/src/test/scala/DFSpec.scala index d79be6b48..5325692f2 100644 --- a/core/src/test/scala/DFSpec.scala +++ b/core/src/test/scala/DFSpec.scala @@ -35,8 +35,15 @@ abstract class DFSpec extends NoDFCSpec, HasTypeName, HasDFC: type TDomain = core.DomainType.DF given TDomain = core.DomainType.DF given dfPrinter: Printer = DefaultPrinter(using dfc.getSet) - private final val owner: core.Design.Block = + // The mock top design lives where the CONCRETE spec lives: its namespace is the spec + // class's package, so types declared alongside the spec stay global-placed + // (unqualified) under the namespace placement rules. The plugin's meta injection + // would statically stamp THIS file's package (`dfhdl`), so it is bypassed and the + // meta is set explicitly. + @metaContextIgnore private def mkTopOwner(using DFC): core.Design.Block = core.Design.Block(ir.DomainType.DF, InstMode.Normal) + private final val owner: core.Design.Block = + mkTopOwner(using dfc.setMeta(nameOpt = Some("top"), namespace = getClass.getPackageName)) dfc.enterOwner(owner) private val noErrMsg = "No error found" diff --git a/plugin/src/main/scala/plugin/CommonPhase.scala b/plugin/src/main/scala/plugin/CommonPhase.scala index e0b38a944..863a4c12f 100755 --- a/plugin/src/main/scala/plugin/CommonPhase.scala +++ b/plugin/src/main/scala/plugin/CommonPhase.scala @@ -268,7 +268,8 @@ abstract class CommonPhase extends PluginPhase: // The enclosing Scala package path of a declaration's symbol, "" for the root/empty // package. Namespaces stop at the package level deliberately: enclosing objects and - // classes are scoping, not namespacing, for the packages feature. + // classes are scoping (a Scala object is a value, aliasable and importable), not + // namespacing, for the packages feature. protected def mkNamespace(sym: Symbol)(using Context): Tree = val pkg = sym.enclosingPackageClass val ns = From 7c4432e05639f706774e70c0ed11544482ae5210 Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 04:24:32 +0300 Subject: [PATCH 50/57] printers: drop the t_struct_/t_enum_/t_opaque_ type-name prefixes (WYSIWYG) A named type now emits under its own name in both backends, which is what the translation flows compare against (cav's interface_precheck string-compares port types: gold `veer_types::lsu_pkt_t` vs the prefixed form failed every struct-port module). The prefixes were quietly separating types from values, so UniqueNames now does it explicitly: type and value identifiers share one HDL namespace (SV in scope, VHDL case-insensitively), so the FINAL (post-rename) global type names are reserved against every value renamer, and each design's local type names against that design's values. Keyword avoidance for type names already came from the type renamers' reservedNames. Two collision renames this correctly produced are pinned in PrintVHDLCodeSpec (signal `state_0` vs enum `State_0` -> `state_0_0`; port `p` vs record `P` -> `p_0`). All 111 HDL reference files regenerate mechanically: prefix removal plus the column realignment that follows from shorter type names. Co-Authored-By: Claude Fable 5 --- .../dfhdl/compiler/stages/UniqueNames.scala | 28 +- .../stages/verilog/VerilogTypePrinter.scala | 6 +- .../stages/vhdl/VHDLTypePrinter.scala | 6 +- .../scala/StagesSpec/PrintVHDLCodeSpec.scala | 116 ++--- .../StagesSpec/PrintVerilogCodeSpec.scala | 84 ++-- .../verilog.sv2009/hdl/CipherNoOpaques.sv | 12 +- .../hdl/CipherNoOpaques_defs.svh | 16 +- .../verilog.sv2009/hdl/addRoundKey.sv | 6 +- .../verilog.sv2009/hdl/cipher.sv | 192 ++++----- .../verilog.sv2009/hdl/keyExpansion.sv | 402 +++++++++--------- .../verilog.sv2009/hdl/mixColumns.sv | 260 +++++------ .../verilog.sv2009/hdl/mulByte_0.sv | 8 +- .../verilog.sv2009/hdl/mulByte_1.sv | 8 +- .../verilog.sv2009/hdl/mulByte_2.sv | 4 +- .../verilog.sv2009/hdl/rotWord.sv | 4 +- .../verilog.sv2009/hdl/sbox.sv | 4 +- .../verilog.sv2009/hdl/shiftRows.sv | 4 +- .../verilog.sv2009/hdl/subBytes.sv | 68 +-- .../verilog.sv2009/hdl/subWord.sv | 20 +- .../verilog.sv2009/hdl/xtime.sv | 4 +- .../vhdl.v2008/hdl/CipherNoOpaques.vhd | 12 +- .../vhdl.v2008/hdl/CipherNoOpaques_pkg.vhd | 100 ++--- .../vhdl.v2008/hdl/addRoundKey.vhd | 6 +- .../vhdl.v2008/hdl/cipher.vhd | 192 ++++----- .../vhdl.v2008/hdl/keyExpansion.vhd | 402 +++++++++--------- .../vhdl.v2008/hdl/mixColumns.vhd | 260 +++++------ .../vhdl.v2008/hdl/mulByte_0.vhd | 8 +- .../vhdl.v2008/hdl/mulByte_1.vhd | 8 +- .../vhdl.v2008/hdl/mulByte_2.vhd | 4 +- .../vhdl.v2008/hdl/rotWord.vhd | 4 +- .../vhdl.v2008/hdl/sbox.vhd | 4 +- .../vhdl.v2008/hdl/shiftRows.vhd | 4 +- .../vhdl.v2008/hdl/subBytes.vhd | 68 +-- .../vhdl.v2008/hdl/subWord.vhd | 20 +- .../vhdl.v2008/hdl/xtime.vhd | 4 +- .../vhdl.v93/hdl/CipherNoOpaques.vhd | 12 +- .../vhdl.v93/hdl/CipherNoOpaques_pkg.vhd | 144 +++---- .../vhdl.v93/hdl/addRoundKey.vhd | 6 +- .../vhdl.v93/hdl/cipher.vhd | 192 ++++----- .../vhdl.v93/hdl/keyExpansion.vhd | 402 +++++++++--------- .../vhdl.v93/hdl/mixColumns.vhd | 260 +++++------ .../vhdl.v93/hdl/mulByte_0.vhd | 8 +- .../vhdl.v93/hdl/mulByte_1.vhd | 8 +- .../vhdl.v93/hdl/mulByte_2.vhd | 4 +- .../vhdl.v93/hdl/rotWord.vhd | 4 +- .../vhdl.v93/hdl/sbox.vhd | 4 +- .../vhdl.v93/hdl/shiftRows.vhd | 4 +- .../vhdl.v93/hdl/subBytes.vhd | 68 +-- .../vhdl.v93/hdl/subWord.vhd | 20 +- .../vhdl.v93/hdl/xtime.vhd | 4 +- .../verilog.sv2009/hdl/Cipher.sv | 12 +- .../verilog.sv2009/hdl/Cipher_defs.svh | 16 +- .../verilog.sv2009/hdl/addRoundKey.sv | 6 +- .../verilog.sv2009/hdl/cipher_0.sv | 192 ++++----- .../verilog.sv2009/hdl/keyExpansion.sv | 402 +++++++++--------- .../verilog.sv2009/hdl/mixColumns.sv | 260 +++++------ .../verilog.sv2009/hdl/mulByte_0.sv | 8 +- .../verilog.sv2009/hdl/mulByte_1.sv | 8 +- .../verilog.sv2009/hdl/mulByte_2.sv | 4 +- .../verilog.sv2009/hdl/rotWord.sv | 4 +- .../verilog.sv2009/hdl/sbox.sv | 4 +- .../verilog.sv2009/hdl/shiftRows.sv | 4 +- .../verilog.sv2009/hdl/subBytes.sv | 68 +-- .../verilog.sv2009/hdl/subWord.sv | 20 +- .../verilog.sv2009/hdl/xtime.sv | 4 +- .../vhdl.v2008/hdl/Cipher.vhd | 12 +- .../vhdl.v2008/hdl/Cipher_pkg.vhd | 100 ++--- .../vhdl.v2008/hdl/addRoundKey.vhd | 6 +- .../vhdl.v2008/hdl/cipher_0.vhd | 192 ++++----- .../vhdl.v2008/hdl/keyExpansion.vhd | 402 +++++++++--------- .../vhdl.v2008/hdl/mixColumns.vhd | 260 +++++------ .../vhdl.v2008/hdl/mulByte_0.vhd | 8 +- .../vhdl.v2008/hdl/mulByte_1.vhd | 8 +- .../vhdl.v2008/hdl/mulByte_2.vhd | 4 +- .../vhdl.v2008/hdl/rotWord.vhd | 4 +- .../vhdl.v2008/hdl/sbox.vhd | 4 +- .../vhdl.v2008/hdl/shiftRows.vhd | 4 +- .../vhdl.v2008/hdl/subBytes.vhd | 68 +-- .../vhdl.v2008/hdl/subWord.vhd | 20 +- .../vhdl.v2008/hdl/xtime.vhd | 4 +- .../vhdl.v93/hdl/Cipher.vhd | 12 +- .../vhdl.v93/hdl/Cipher_pkg.vhd | 144 +++---- .../vhdl.v93/hdl/addRoundKey.vhd | 6 +- .../vhdl.v93/hdl/cipher_0.vhd | 192 ++++----- .../vhdl.v93/hdl/keyExpansion.vhd | 402 +++++++++--------- .../vhdl.v93/hdl/mixColumns.vhd | 260 +++++------ .../vhdl.v93/hdl/mulByte_0.vhd | 8 +- .../vhdl.v93/hdl/mulByte_1.vhd | 8 +- .../vhdl.v93/hdl/mulByte_2.vhd | 4 +- .../vhdl.v93/hdl/rotWord.vhd | 4 +- .../vhdl.v93/hdl/sbox.vhd | 4 +- .../vhdl.v93/hdl/shiftRows.vhd | 4 +- .../vhdl.v93/hdl/subBytes.vhd | 68 +-- .../vhdl.v93/hdl/subWord.vhd | 20 +- .../vhdl.v93/hdl/xtime.vhd | 4 +- .../verilog.sv2009/hdl/ALU.sv | 8 +- .../verilog.sv2009/hdl/ALU_defs.svh | 2 +- .../vhdl.v2008/hdl/ALU.vhd | 10 +- .../vhdl.v2008/hdl/ALU_pkg.vhd | 18 +- .../docExamples.ALUSpec/vhdl.v93/hdl/ALU.vhd | 12 +- .../vhdl.v93/hdl/ALU_pkg.vhd | 18 +- .../verilog.sv2009/hdl/UART_Tx.sv | 8 +- .../vhdl.v2008/hdl/UART_Tx.vhd | 18 +- .../vhdl.v93/hdl/UART_Tx.vhd | 18 +- .../verilog.sv2009/hdl/LRShiftFlat.sv | 2 +- .../verilog.sv2009/hdl/LRShiftFlat_defs.svh | 2 +- .../vhdl.v2008/hdl/LRShiftFlat.vhd | 2 +- .../vhdl.v2008/hdl/LRShiftFlat_pkg.vhd | 38 +- .../vhdl.v93/hdl/LRShiftFlat.vhd | 2 +- .../vhdl.v93/hdl/LRShiftFlat_pkg.vhd | 38 +- .../verilog.sv2009/hdl/LRShiftDirect.sv | 2 +- .../verilog.sv2009/hdl/LRShiftDirect_defs.svh | 2 +- .../vhdl.v2008/hdl/LRShiftDirect.vhd | 2 +- .../vhdl.v2008/hdl/LRShiftDirect_pkg.vhd | 38 +- .../vhdl.v93/hdl/LRShiftDirect.vhd | 2 +- .../vhdl.v93/hdl/LRShiftDirect_pkg.vhd | 38 +- 116 files changed, 3514 insertions(+), 3502 deletions(-) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueNames.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueNames.scala index 653072523..e4211c12c 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueNames.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueNames.scala @@ -47,6 +47,10 @@ private abstract class UniqueNames(reservedNames: Set[String], caseSensitive: Bo val typeUpdateMap = mutable.LinkedHashMap.empty[NamedDFType, String] val localReservedNamesLCMutable = mutable.Set.from[String](reservedNamesLC) + // the FINAL (post-rename) global type names: without the dropped `t_struct_`-style + // prefixes, type and value identifiers share one HDL namespace, so every value + // renamer must reserve them + var globalTypeNamesFinalLC: Set[String] = Set.empty // ---- global named types + members (cross-design, computed once) ---- // names resolve from member meta only, so any sub-DB getSet works; use the top's. val globalReservedTypeNamesLC: Set[String] = designDB.topDB.atGetSet { @@ -63,6 +67,9 @@ private abstract class UniqueNames(reservedNames: Set[String], caseSensitive: Bo val globalTypeUpdateMap = renamer(globalNamedTypes, reservedNamesLC)(_.name, (e, n) => e -> n).toMap typeUpdateMap ++= globalTypeUpdateMap + globalTypeNamesFinalLC = lowerCases( + globalNamedTypes.map(t => globalTypeUpdateMap.getOrElse(t, t.name)).toSet + ) // the global reserved type names, after unique global type renaming val globalReservedTypeNames: Set[String] = (globalNamedTypes.map(e => e.name) ++ globalTypeUpdateMap.values ++ designNames ++ @@ -77,8 +84,10 @@ private abstract class UniqueNames(reservedNames: Set[String], caseSensitive: Bo ).foreach(entry => memberRenamePatches(entry._1) = entry) resultLC } - // the reserved names for local (design) values will be the given reservedNames - // and the now additional global member names after renaming + // the reserved names for local (design) values: the given reservedNames, the + // renamed global member names, and the (post-rename) global TYPE names (types and + // values share one HDL identifier namespace) + localReservedNamesLCMutable ++= globalTypeNamesFinalLC val localReservedNamesLC = localReservedNamesLCMutable.toSet // ---- per-design local members + local named types ---- @@ -86,16 +95,19 @@ private abstract class UniqueNames(reservedNames: Set[String], caseSensitive: Bo designDB.subDBs.values.foreach { sub => sub.atGetSet { sub.blockMemberList.foreach { (block, members) => + // this design's local type names (post-rename): reserved for its value names + var designLocalTypeNamesLC: Set[String] = Set.empty block match case design: DFDesignBlock => // exclude types promoted to global across the hierarchy (handled above); // a single sub-DB may otherwise mis-classify a cross-design type as local - renamer( - sub.getLocalNamedDFTypes(design) - .filterNot(designDB.hierGlobalNamedDFTypes.contains), - globalReservedTypeNamesLC - )(_.name, (e, n) => e -> n) + val localTypes = sub.getLocalNamedDFTypes(design) + .filterNot(designDB.hierGlobalNamedDFTypes.contains) + renamer(localTypes, globalReservedTypeNamesLC)(_.name, (e, n) => e -> n) .foreach(entry => typeUpdateMap(entry._1) = entry._2) + designLocalTypeNamesLC = lowerCases( + localTypes.map(t => typeUpdateMap.getOrElse(t, t.name)).toSet + ) case _ => renamer( members.view.flatMap { @@ -112,7 +124,7 @@ private abstract class UniqueNames(reservedNames: Set[String], caseSensitive: Bo case m: DFMember.Named if !m.isAnonymous => Some(m) case _ => None }, - localReservedNamesLC + localReservedNamesLC ++ designLocalTypeNamesLC )( _.getName, (m, n) => m -> Patch.Replace(m.setName(n), Patch.Replace.Config.FullReplacement) 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 b0536f20b..78cc97b0c 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 @@ -61,7 +61,7 @@ protected trait VerilogTypePrinter extends AbstractTypePrinter: case Some(pkg) if !printer.currentPackage.contains(pkg) => s"$pkg::" case _ => "" def csDFEnumTypeName(dfType: DFEnum): String = - if (allowTypeDef) s"${pkgQualifier(dfType)}t_enum_${dfType.name}" + if (allowTypeDef) s"${pkgQualifier(dfType)}${dfType.name}" else csDFBits(DFBits(dfType.widthIntOpt.get), false) def csDFEnumToStringFuncDcl(dfType: DFEnum): String = val enumName = dfType.name @@ -109,12 +109,12 @@ protected trait VerilogTypePrinter extends AbstractTypePrinter: import dfType.* s"${csDFType(cellType, typeCS)}" def csDFOpaqueTypeName(dfType: DFOpaque): String = - s"${pkgQualifier(dfType)}t_opaque_${dfType.name}" + s"${pkgQualifier(dfType)}${dfType.name}" def csDFOpaqueDcl(dfType: DFOpaque): String = s"typedef ${csDFType(dfType.actualType, typeCS = true)} ${csDFOpaqueTypeName(dfType)}${csDFVectorRanges(dfType.actualType)};" def csDFOpaque(dfType: DFOpaque, typeCS: Boolean): String = csDFOpaqueTypeName(dfType) def csDFStructTypeName(dfType: DFStruct): String = - s"${pkgQualifier(dfType)}t_struct_${dfType.name}" + s"${pkgQualifier(dfType)}${dfType.name}" def csDFStructDcl(dfType: DFStruct): String = val fields = dfType.fieldMap.view .map((n, t) => s"${csDFType(t, typeCS = true)} $n${csDFVectorRanges(t)};") diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala index f0bf619a4..768f359d8 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala @@ -69,7 +69,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: case dt: DFEnum => csDFEnumConvFuncsBody(dt) case dt: DFStruct => csDFStructConvFuncsBody(dt) case dt: DFOpaque => csDFOpaqueConvFuncsBody(dt) - def csDFEnumTypeName(dfType: DFEnum): String = s"t_enum_${dfType.name}" + def csDFEnumTypeName(dfType: DFEnum): String = dfType.name def csDFEnumDcl(dfType: DFEnum, global: Boolean): String = val enumName = dfType.name val entries = @@ -398,7 +398,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: inVector = false desc end csDFVector - def csDFOpaqueTypeName(dfType: DFOpaque): String = s"t_opaque_${dfType.name}" + def csDFOpaqueTypeName(dfType: DFOpaque): String = dfType.name def csDFOpaqueDcl(dfType: DFOpaque): String = s"subtype ${csDFOpaqueTypeName(dfType)} is ${csDFType(dfType.actualType)};" def csDFOpaque(dfType: DFOpaque, typeCS: Boolean): String = csDFOpaqueTypeName(dfType) @@ -410,7 +410,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: | A0 := A; | return ${printer.csBitsToType(dfType.actualType, "A0")}; |end;""".stripMargin - def csDFStructTypeName(dfType: DFStruct): String = s"t_struct_${dfType.name}" + def csDFStructTypeName(dfType: DFStruct): String = dfType.name def csDFStructDcl(dfType: DFStruct): String = val fields = dfType.fieldMap.view .map((n, t) => s"${n} : ${csDFType(t)};") diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index 9a53190c7..962b07365 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -289,7 +289,7 @@ class PrintVHDLCodeSpec extends StageSpec: |end Top; | |architecture Top_arch of Top is - | type t_struct_DFTuple2 is record + | type DFTuple2 is record | _1 : std_logic_vector(2 downto 0); | _2 : std_logic; | end record; @@ -309,7 +309,7 @@ class PrintVHDLCodeSpec extends StageSpec: | constant c12 : signed(48 downto 0) := -49d"239794508230343"; | constant c13 : unsigned(7 downto 0) := unsigned'(x"--"); | constant c14 : signed(7 downto 0) := signed'(x"--"); - | constant c15 : t_struct_DFTuple2 := t_struct_DFTuple2(_1 = "000", _2 = '1'); + | constant c15 : DFTuple2 := DFTuple2(_1 = "000", _2 = '1'); | constant c16 : t_arrX2_std_logic_vector(0 to 6)(0 to 4)(7 downto 0) := ( | 0 => (0 => x"00", 1 => x"11", 2 => x"22", 3 => x"33", 4 => x"44"), | 1 => (0 => x"00", 1 => x"11", 2 => x"22", 3 => x"33", 4 => x"44"), @@ -501,14 +501,14 @@ class PrintVHDLCodeSpec extends StageSpec: | return F; | end if; | end; - | subtype t_opaque_Foo is t_arrX2_std_logic_vector(0 to 9)(0 to 15)(11 downto 0); - | function to_t_opaque_Foo(A : std_logic_vector) return t_opaque_Foo is + | subtype Foo is t_arrX2_std_logic_vector(0 to 9)(0 to 15)(11 downto 0); + | function to_Foo(A : std_logic_vector) return Foo is | variable A0 : std_logic_vector(A'length - 1 downto 0); | begin | A0 := A; | return to_t_arrX2_std_logic_vector(A0, 10, 16, 12); | end; - | signal v : t_opaque_Foo; + | signal v : Foo; |begin | process (clk) | begin @@ -557,10 +557,10 @@ class PrintVHDLCodeSpec extends StageSpec: y.din := x.as(Foo) val top = (Example()).getCompiledCodeString - // TODO: consider if we want to leave the t_opaque_Foo under `getCompiledCodeString` + // TODO: consider if we want to leave the Foo under `getCompiledCodeString` assertNoDiff( top, - """|subtype t_opaque_Foo is t_arrX2_std_logic_vector(0 to 9)(0 to 15)(11 downto 0); + """|subtype Foo is t_arrX2_std_logic_vector(0 to 9)(0 to 15)(11 downto 0); | |library ieee; |use ieee.std_logic_1164.all; @@ -573,7 +573,7 @@ class PrintVHDLCodeSpec extends StageSpec: | clk : in std_logic; | rst : in std_logic; | x : in std_logic_vector(1919 downto 0); - | y : out t_opaque_Foo + | y : out Foo |); |end Example; | @@ -1181,7 +1181,7 @@ class PrintVHDLCodeSpec extends StageSpec: |end Foo; | |architecture Foo_arch of Foo is - | type t_enum_MyEnum is ( + | type MyEnum is ( | MyEnum_A, MyEnum_B, MyEnum_C | ); | constant bar : string := param & "!"; @@ -1193,7 +1193,7 @@ class PrintVHDLCodeSpec extends StageSpec: | constant param7 : signed(4 downto 0) := -5d"11"; | constant param8 : std_logic := '1'; | constant param9 : boolean := false; - | constant param10 : t_enum_MyEnum := MyEnum_A; + | constant param10 : MyEnum := MyEnum_A; |begin | process (all) | begin @@ -1212,7 +1212,7 @@ class PrintVHDLCodeSpec extends StageSpec: | println(""); | print("I am the one " & param2 & " who knocks"); | print("hello"); - | 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) & ""); + | 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) & ", " & MyEnum'image(param10) & ""); | report | "Debug at Foo" & LF & | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1162:9" & LF & @@ -1223,7 +1223,7 @@ class PrintVHDLCodeSpec extends StageSpec: | "param7 = " & to_string(param7) & LF & | "param8 = " & to_string(param8) & LF & | "param9 = " & to_string(param9) & LF & - | "param10 = " & t_enum_MyEnum'image(param10) + | "param10 = " & MyEnum'image(param10) | severity NOTE; | end process; |end Foo_arch;""".stripMargin @@ -1242,7 +1242,7 @@ class PrintVHDLCodeSpec extends StageSpec: |end Foo; | |architecture Foo_arch of Foo is - | type t_enum_MyEnum is ( + | type MyEnum is ( | MyEnum_A, MyEnum_B, MyEnum_C | ); | constant bar : string := param & "!"; @@ -1254,7 +1254,7 @@ class PrintVHDLCodeSpec extends StageSpec: | constant param7 : signed(4 downto 0) := to_signed(-11, 5); | constant param8 : std_logic := '1'; | constant param9 : boolean := false; - | constant param10 : t_enum_MyEnum := MyEnum_A; + | constant param10 : MyEnum := MyEnum_A; |begin | process | begin @@ -1273,7 +1273,7 @@ class PrintVHDLCodeSpec extends StageSpec: | println(""); | print("I am the one " & param2 & " who knocks"); | print("hello"); - | 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) & ""); + | 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) & ", " & MyEnum'image(param10) & ""); | report | "Debug at Foo" & LF & | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1162:9" & LF & @@ -1284,7 +1284,7 @@ class PrintVHDLCodeSpec extends StageSpec: | "param7 = " & to_string(param7) & LF & | "param8 = " & to_string(param8) & LF & | "param9 = " & to_string(param9) & LF & - | "param10 = " & t_enum_MyEnum'image(param10) + | "param10 = " & MyEnum'image(param10) | severity NOTE; | end process; |end Foo_arch;""".stripMargin @@ -1401,7 +1401,7 @@ class PrintVHDLCodeSpec extends StageSpec: val top = (new Bar).getCompiledCodeString assertNoDiff( top, - """|type t_enum_MyEnum is ( + """|type MyEnum is ( | MyEnum_Zero, MyEnum_One |); | @@ -1413,13 +1413,13 @@ class PrintVHDLCodeSpec extends StageSpec: | |entity Bar is |port ( - | x : in t_enum_MyEnum; + | x : in MyEnum; | y : out std_logic; | z : out boolean; | x1 : in std_logic; | x2 : in boolean; - | y1 : out t_enum_MyEnum; - | y2 : out t_enum_MyEnum + | y1 : out MyEnum; + | y2 : out MyEnum |); |end Bar; | @@ -1427,8 +1427,8 @@ class PrintVHDLCodeSpec extends StageSpec: |begin | y <= to_sl(toggle(x)); | z <= to_bool(toggle(x)); - | y1 <= to_t_enum_MyEnum(x1); - | y2 <= to_t_enum_MyEnum(x2); + | y1 <= to_MyEnum(x1); + | y2 <= to_MyEnum(x2); |end Bar_arch;""".stripMargin ) } @@ -1523,7 +1523,7 @@ class PrintVHDLCodeSpec extends StageSpec: val top = (new Foo).getCompiledCodeString assertNoDiff( top, - """|type t_enum_MyEnum is ( + """|type MyEnum is ( | MyEnum_A, MyEnum_B |); | @@ -1535,8 +1535,8 @@ class PrintVHDLCodeSpec extends StageSpec: | |entity Foo is |port ( - | x : in t_enum_MyEnum; - | y : out t_enum_MyEnum + | x : in MyEnum; + | y : out MyEnum |); |end Foo; | @@ -1558,7 +1558,7 @@ class PrintVHDLCodeSpec extends StageSpec: val top = (new Foo).getCompiledCodeString assertNoDiff( top, - """|type t_enum_MyEnum is ( + """|type MyEnum is ( | MyEnum_A, MyEnum_B |); | @@ -1570,8 +1570,8 @@ class PrintVHDLCodeSpec extends StageSpec: | |entity Foo is |port ( - | x : in t_enum_MyEnum; - | y : out t_enum_MyEnum + | x : in MyEnum; + | y : out MyEnum |); |end Foo; | @@ -1622,7 +1622,7 @@ class PrintVHDLCodeSpec extends StageSpec: val top = (new Foo).getCompiledCodeString assertNoDiff( top, - """|type t_struct_AB is record + """|type AB is record | a : std_logic_vector(3 downto 0); | b : std_logic_vector(3 downto 0); |end record; @@ -1635,8 +1635,8 @@ class PrintVHDLCodeSpec extends StageSpec: | |entity Foo is |port ( - | i : in t_struct_AB; - | y : out t_struct_AB + | i : in AB; + | y : out AB |); |end Foo; | @@ -1773,19 +1773,19 @@ class PrintVHDLCodeSpec extends StageSpec: |end ForkJoinFSM; | |architecture ForkJoinFSM_arch of ForkJoinFSM is - | type t_enum_State_0 is ( + | type State_0 is ( | State_0_S_boot, State_0_S_0 | ); - | type t_enum_State_1 is ( + | type State_1 is ( | State_1_S_0, State_1_S_1 | ); | signal fk_start_0 : std_logic; | signal fk_start_1 : std_logic; | signal fk_done_0 : std_logic; | signal fk_done_1 : std_logic; - | signal state_0 : t_enum_State_0; - | signal state_1 : t_enum_State_1; - | signal state_2 : t_enum_State_1; + | signal state_0_0 : State_0; + | signal state_1_0 : State_1; + | signal state_2 : State_1; |begin | process (clk) | begin @@ -1793,36 +1793,36 @@ class PrintVHDLCodeSpec extends StageSpec: | if rst = '1' then | a <= '0'; | b <= '0'; - | state_0 <= State_0_S_boot; - | state_1 <= State_1_S_0; + | state_0_0 <= State_0_S_boot; + | state_1_0 <= State_1_S_0; | state_2 <= State_1_S_0; | else - | case state_0 is + | case state_0_0 is | when State_0_S_boot => | fk_start_0 <= '1'; | fk_start_1 <= '1'; - | state_0 <= State_0_S_0; + | state_0_0 <= State_0_S_0; | when State_0_S_0 => - | if not (fk_done_0 and fk_done_1) then state_0 <= State_0_S_0; + | if not (fk_done_0 and fk_done_1) then state_0_0 <= State_0_S_0; | else | fk_start_0 <= '0'; | fk_start_1 <= '0'; - | state_0 <= State_0_S_boot; + | state_0_0 <= State_0_S_boot; | end if; | end case; - | case state_1 is + | case state_1_0 is | when State_1_S_0 => - | if not fk_start_0 then state_1 <= State_1_S_0; + | if not fk_start_0 then state_1_0 <= State_1_S_0; | else | a <= '1'; | fk_done_0 <= '1'; - | state_1 <= State_1_S_1; + | state_1_0 <= State_1_S_1; | end if; | when State_1_S_1 => - | if fk_start_0 then state_1 <= State_1_S_1; + | if fk_start_0 then state_1_0 <= State_1_S_1; | else | fk_done_0 <= '0'; - | state_1 <= State_1_S_0; + | state_1_0 <= State_1_S_0; | end if; | end case; | case state_2 is @@ -3766,7 +3766,7 @@ class PrintVHDLCodeSpec extends StageSpec: val top = BitsHLComposite().getCompiledCodeString assertNoDiff( top, - """|type t_struct_P is record + """|type P is record | f : std_logic_vector(9 downto 2); | g : std_logic; |end record; @@ -3779,7 +3779,7 @@ class PrintVHDLCodeSpec extends StageSpec: | |entity BitsHLComposite is |port ( - | p : in t_struct_P; + | p_0 : in P; | v : in t_arrX1_std_logic_vector(0 to 1)(9 downto 2); | f8 : out std_logic_vector(7 downto 0); | f4 : out std_logic_vector(3 downto 0); @@ -3790,9 +3790,9 @@ class PrintVHDLCodeSpec extends StageSpec: | |architecture BitsHLComposite_arch of BitsHLComposite is |begin - | f8 <= p.f; - | f4 <= p.f(5 downto 2); - | fb <= p.f(5); + | f8 <= p_0.f; + | f4 <= p_0.f(5 downto 2); + | fb <= p_0.f(5); | c8 <= v(0); |end BitsHLComposite_arch; |""".stripMargin @@ -3900,18 +3900,18 @@ class PrintVHDLCodeSpec extends StageSpec: | |architecture DocTop_arch of DocTop is | -- struct doc - | type t_struct_DocS is record + | type DocS is record | a : std_logic; | end record; | -- enum doc - | type t_enum_DocE is ( + | type DocE is ( | DocE_E0, DocE_E1 | ); | -- opaque doc - | subtype t_opaque_DocO is std_logic; - | signal s : t_struct_DocS; - | signal e : t_enum_DocE; - | signal o : t_opaque_DocO; + | subtype DocO is std_logic; + | signal s : DocS; + | signal e : DocE; + | signal o : DocO; |begin |end DocTop_arch; |""".stripMargin diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 37f7e4ce1..64eba2a35 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -376,7 +376,7 @@ class PrintVerilogCodeSpec extends StageSpec: | typedef struct packed { | logic [2:0] _1; | logic _2; - | } t_struct_DFTuple2; + | } DFTuple2; | localparam logic c01 = 1'b0; | localparam logic c02 = 1'b1; | localparam logic c03 = 1'bx; @@ -391,7 +391,7 @@ class PrintVerilogCodeSpec extends StageSpec: | localparam logic signed [48:0] c12 = -49'sd239794508230343; | localparam logic [7:0] c13 = 8'hxx; | localparam logic signed [7:0] c14 = $signed(8'hxx); - | localparam t_struct_DFTuple2 c15 = '{3'h0, 1'b1}; + | localparam DFTuple2 c15 = '{3'h0, 1'b1}; | localparam logic [7:0] c16 [0:6] [0:4] = '{ | 0: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, 1: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, | 2: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, 3: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, @@ -1108,7 +1108,7 @@ class PrintVerilogCodeSpec extends StageSpec: | MyEnum_A = 0, | MyEnum_B = 1, | MyEnum_C = 2 - | } t_enum_MyEnum; + | } MyEnum; | localparam string bar = {param, "!"}; | localparam string param2 = {2{param}}; | localparam int param3 = 42; @@ -1118,7 +1118,7 @@ class PrintVerilogCodeSpec extends StageSpec: | localparam logic signed [4:0] param7 = -5'sd11; | localparam logic param8 = 1'b1; | localparam logic param9 = 0; - | localparam t_enum_MyEnum param10 = MyEnum_A; + | localparam MyEnum param10 = MyEnum_A; | always_comb | begin | assert (param == "hello2"); @@ -1338,26 +1338,26 @@ class PrintVerilogCodeSpec extends StageSpec: """|typedef enum logic [0:0] { | MyEnum_Zero = 0, | MyEnum_One = 1 - |} t_enum_MyEnum; + |} MyEnum; | |`default_nettype none |`timescale 1ns/1ps |`include "Bar_defs.svh" | |module Bar( - | input wire t_enum_MyEnum x, + | input wire MyEnum x, | output logic y, | output logic z, | input wire logic x1, | input wire logic x2, - | output t_enum_MyEnum y1, - | output t_enum_MyEnum y2 + | output MyEnum y1, + | output MyEnum y2 |); | `include "dfhdl_defs.svh" | assign y = ~x; | assign z = ~x; - | assign y1 = t_enum_MyEnum'(x1); - | assign y2 = t_enum_MyEnum'(x2); + | assign y1 = MyEnum'(x1); + | assign y2 = MyEnum'(x2); |endmodule""".stripMargin ) } @@ -1537,15 +1537,15 @@ class PrintVerilogCodeSpec extends StageSpec: """|typedef enum logic [0:0] { | MyEnum_A = 0, | MyEnum_B = 1 - |} t_enum_MyEnum; + |} MyEnum; | |`default_nettype none |`timescale 1ns/1ps |`include "otherGlobal.svh" | |module Foo( - | input wire t_enum_MyEnum x, - | output t_enum_MyEnum y + | input wire MyEnum x, + | output MyEnum y |); | `include "dfhdl_defs.svh" | assign y = x; @@ -1567,15 +1567,15 @@ class PrintVerilogCodeSpec extends StageSpec: """|typedef enum logic [0:0] { | MyEnum_A = 0, | MyEnum_B = 1 - |} t_enum_MyEnum; + |} MyEnum; | |`default_nettype none |`timescale 1ns/1ps |`include "otherGlobal.svh" | |module Foo( - | input wire t_enum_MyEnum x, - | output t_enum_MyEnum y + | input wire MyEnum x, + | output MyEnum y |); | `include "dfhdl_defs.svh" | assign y = x; @@ -1665,15 +1665,15 @@ class PrintVerilogCodeSpec extends StageSpec: """|typedef struct packed { | logic [3:0] a; | logic [3:0] b; - |} t_struct_AB; + |} AB; | |`default_nettype none |`timescale 1ns/1ps |`include "Foo_defs.svh" | |module Foo( - | input wire t_struct_AB i, - | output t_struct_AB y + | input wire AB i, + | output AB y |); | `include "dfhdl_defs.svh" | assign y.a = i.b; @@ -1866,18 +1866,18 @@ class PrintVerilogCodeSpec extends StageSpec: | typedef enum logic [0:0] { | State_0_S_boot = 0, | State_0_S_0 = 1 - | } t_enum_State_0; + | } State_0; | typedef enum logic [0:0] { | State_1_S_0 = 0, | State_1_S_1 = 1 - | } t_enum_State_1; + | } State_1; | logic fk_start_0; | logic fk_start_1; | logic fk_done_0; | logic fk_done_1; - | t_enum_State_0 state_0; - | t_enum_State_1 state_1; - | t_enum_State_1 state_2; + | State_0 state_0; + | State_1 state_1; + | State_1 state_2; | always_ff @(posedge clk) | begin | if (rst == 1'b1) begin @@ -3964,17 +3964,17 @@ class PrintVerilogCodeSpec extends StageSpec: | /* struct doc */ | typedef struct packed { | logic a; - | } t_struct_DocS; + | } DocS; | /* enum doc */ | typedef enum logic [0:0] { | DocE_E0 = 0, | DocE_E1 = 1 - | } t_enum_DocE; + | } DocE; | /* opaque doc */ - | typedef logic t_opaque_DocO; - | t_struct_DocS s; - | t_enum_DocE e; - | t_opaque_DocO o; + | typedef logic DocO; + | DocS s; + | DocE e; + | DocO o; |endmodule |""".stripMargin ) @@ -3992,21 +3992,21 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|typedef struct packed { | logic [1:0] g; - |} t_struct_GlbNsStruct; + |} GlbNsStruct; |parameter logic [7:0] GlbNsConst = 8'd3; |package typespkg1; |`include "PkgTop_defs.svh" |typedef struct packed { | logic [7:0] a; | logic b; - | t_struct_GlbNsStruct g; - |} t_struct_PkgStruct; + | GlbNsStruct g; + |} PkgStruct; |typedef enum logic [1:0] { | PkgEnum_P0 = 0, | PkgEnum_P1 = 1, | PkgEnum_P2 = 2 - |} t_enum_PkgEnum; - |typedef logic [3:0] t_opaque_PkgOpaque; + |} PkgEnum; + |typedef logic [3:0] PkgOpaque; |parameter logic [7:0] PkgConst = GlbNsConst + 8'd39; |function automatic logic [7:0] pkgCalc(input logic [7:0] arg); |begin @@ -4019,9 +4019,9 @@ class PrintVerilogCodeSpec extends StageSpec: |package typespkg2; |`include "PkgTop_defs.svh" |typedef struct packed { - | typespkg1::t_struct_PkgStruct s; + | typespkg1::PkgStruct s; | logic [7:0] n; - |} t_struct_PkgWrap; + |} PkgWrap; |parameter logic [7:0] PkgWide = typespkg1::pkgCalc(typespkg1::PkgDerived); |endpackage | @@ -4031,13 +4031,13 @@ class PrintVerilogCodeSpec extends StageSpec: |`include "PkgTop_defs.svh" | |module PkgTop( - | input wire typespkg1::t_struct_PkgStruct sp, - | output typespkg1::t_struct_PkgStruct so + | input wire typespkg1::PkgStruct sp, + | output typespkg1::PkgStruct so |); | `include "dfhdl_defs.svh" - | typespkg1::t_enum_PkgEnum e; - | typespkg1::t_opaque_PkgOpaque o; - | typespkg2::t_struct_PkgWrap w; + | typespkg1::PkgEnum e; + | typespkg1::PkgOpaque o; + | typespkg2::PkgWrap w; | logic [7:0] u = typespkg2::PkgWide; | assign so = sp; |endmodule diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/CipherNoOpaques.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/CipherNoOpaques.sv index 6e170b148..1a61f8ead 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/CipherNoOpaques.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/CipherNoOpaques.sv @@ -3,14 +3,14 @@ `include "CipherNoOpaques_defs.svh" module CipherNoOpaques( - input wire t_opaque_AESKey key, - input wire t_opaque_AESData data, - output t_opaque_AESData o + input wire AESKey key, + input wire AESData data, + output AESData o ); `include "dfhdl_defs.svh" - t_opaque_AESData o_part_cipher_inst_data; - t_opaque_AESKey o_part_cipher_inst_key; - t_opaque_AESData o_part_cipher_inst_o; + AESData o_part_cipher_inst_data; + AESKey o_part_cipher_inst_key; + AESData o_part_cipher_inst_o; cipher o_part_cipher_inst( .data /*<--*/ (o_part_cipher_inst_data), .key /*<--*/ (o_part_cipher_inst_key), diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/CipherNoOpaques_defs.svh b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/CipherNoOpaques_defs.svh index 071826bba..9c9891fe2 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/CipherNoOpaques_defs.svh +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/CipherNoOpaques_defs.svh @@ -1,13 +1,13 @@ `ifndef CIPHERNOOPAQUES_DEFS `define CIPHERNOOPAQUES_DEFS -typedef logic [7:0] t_opaque_AESByte; -typedef t_opaque_AESByte t_opaque_AESWord [0:3]; -typedef t_opaque_AESWord t_opaque_AESKey [0:3]; -typedef t_opaque_AESWord t_opaque_AESData [0:3]; -typedef t_opaque_AESWord t_opaque_AESKeySchedule [0:43]; -typedef t_opaque_AESWord t_opaque_AESState [0:3]; -typedef t_opaque_AESWord t_opaque_AESRoundKey [0:3]; -parameter t_opaque_AESWord Rcon [0:10] = '{ +typedef logic [7:0] AESByte; +typedef AESByte AESWord [0:3]; +typedef AESWord AESKey [0:3]; +typedef AESWord AESData [0:3]; +typedef AESWord AESKeySchedule [0:43]; +typedef AESWord AESState [0:3]; +typedef AESWord AESRoundKey [0:3]; +parameter AESWord Rcon [0:10] = '{ 0: '{0: 8'h00, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 1: '{0: 8'h01, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 2: '{0: 8'h02, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 3: '{0: 8'h04, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 4: '{0: 8'h08, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 5: '{0: 8'h10, 1: 8'h00, 2: 8'h00, 3: 8'h00}, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/addRoundKey.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/addRoundKey.sv index 6ba1b7a43..0a48d5024 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/addRoundKey.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/addRoundKey.sv @@ -3,9 +3,9 @@ `include "CipherNoOpaques_defs.svh" module addRoundKey( - input wire t_opaque_AESState state, - input wire t_opaque_AESRoundKey key, - output t_opaque_AESState o + input wire AESState state, + input wire AESRoundKey key, + output AESState o ); `include "dfhdl_defs.svh" assign o = '{ diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/cipher.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/cipher.sv index 0bb078c7d..ced7d7a96 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/cipher.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/cipher.sv @@ -3,104 +3,104 @@ `include "CipherNoOpaques_defs.svh" module cipher( - input wire t_opaque_AESData data, - input wire t_opaque_AESKey key, - output t_opaque_AESData o + input wire AESData data, + input wire AESKey key, + output AESData o ); `include "dfhdl_defs.svh" - t_opaque_AESKey keySchedule_key; - t_opaque_AESKeySchedule keySchedule_o; - t_opaque_AESState state_00_state; - t_opaque_AESRoundKey state_00_key; - t_opaque_AESState state_00_o; - t_opaque_AESState o_part_subBytes_inst_00_state; - t_opaque_AESState o_part_subBytes_inst_00_o; - t_opaque_AESState o_part_shiftRows_inst_00_state; - t_opaque_AESState o_part_shiftRows_inst_00_o; - t_opaque_AESState o_part_mixColumns_inst_0_state; - t_opaque_AESState o_part_mixColumns_inst_0_o; - t_opaque_AESState state_01_state; - t_opaque_AESRoundKey state_01_key; - t_opaque_AESState state_01_o; - t_opaque_AESState o_part_subBytes_inst_01_state; - t_opaque_AESState o_part_subBytes_inst_01_o; - t_opaque_AESState o_part_shiftRows_inst_01_state; - t_opaque_AESState o_part_shiftRows_inst_01_o; - t_opaque_AESState o_part_mixColumns_inst_1_state; - t_opaque_AESState o_part_mixColumns_inst_1_o; - t_opaque_AESState state_02_state; - t_opaque_AESRoundKey state_02_key; - t_opaque_AESState state_02_o; - t_opaque_AESState o_part_subBytes_inst_02_state; - t_opaque_AESState o_part_subBytes_inst_02_o; - t_opaque_AESState o_part_shiftRows_inst_02_state; - t_opaque_AESState o_part_shiftRows_inst_02_o; - t_opaque_AESState o_part_mixColumns_inst_2_state; - t_opaque_AESState o_part_mixColumns_inst_2_o; - t_opaque_AESState state_03_state; - t_opaque_AESRoundKey state_03_key; - t_opaque_AESState state_03_o; - t_opaque_AESState o_part_subBytes_inst_03_state; - t_opaque_AESState o_part_subBytes_inst_03_o; - t_opaque_AESState o_part_shiftRows_inst_03_state; - t_opaque_AESState o_part_shiftRows_inst_03_o; - t_opaque_AESState o_part_mixColumns_inst_3_state; - t_opaque_AESState o_part_mixColumns_inst_3_o; - t_opaque_AESState state_04_state; - t_opaque_AESRoundKey state_04_key; - t_opaque_AESState state_04_o; - t_opaque_AESState o_part_subBytes_inst_04_state; - t_opaque_AESState o_part_subBytes_inst_04_o; - t_opaque_AESState o_part_shiftRows_inst_04_state; - t_opaque_AESState o_part_shiftRows_inst_04_o; - t_opaque_AESState o_part_mixColumns_inst_4_state; - t_opaque_AESState o_part_mixColumns_inst_4_o; - t_opaque_AESState state_05_state; - t_opaque_AESRoundKey state_05_key; - t_opaque_AESState state_05_o; - t_opaque_AESState o_part_subBytes_inst_05_state; - t_opaque_AESState o_part_subBytes_inst_05_o; - t_opaque_AESState o_part_shiftRows_inst_05_state; - t_opaque_AESState o_part_shiftRows_inst_05_o; - t_opaque_AESState o_part_mixColumns_inst_5_state; - t_opaque_AESState o_part_mixColumns_inst_5_o; - t_opaque_AESState state_06_state; - t_opaque_AESRoundKey state_06_key; - t_opaque_AESState state_06_o; - t_opaque_AESState o_part_subBytes_inst_06_state; - t_opaque_AESState o_part_subBytes_inst_06_o; - t_opaque_AESState o_part_shiftRows_inst_06_state; - t_opaque_AESState o_part_shiftRows_inst_06_o; - t_opaque_AESState o_part_mixColumns_inst_6_state; - t_opaque_AESState o_part_mixColumns_inst_6_o; - t_opaque_AESState state_07_state; - t_opaque_AESRoundKey state_07_key; - t_opaque_AESState state_07_o; - t_opaque_AESState o_part_subBytes_inst_07_state; - t_opaque_AESState o_part_subBytes_inst_07_o; - t_opaque_AESState o_part_shiftRows_inst_07_state; - t_opaque_AESState o_part_shiftRows_inst_07_o; - t_opaque_AESState o_part_mixColumns_inst_7_state; - t_opaque_AESState o_part_mixColumns_inst_7_o; - t_opaque_AESState state_08_state; - t_opaque_AESRoundKey state_08_key; - t_opaque_AESState state_08_o; - t_opaque_AESState o_part_subBytes_inst_08_state; - t_opaque_AESState o_part_subBytes_inst_08_o; - t_opaque_AESState o_part_shiftRows_inst_08_state; - t_opaque_AESState o_part_shiftRows_inst_08_o; - t_opaque_AESState o_part_mixColumns_inst_8_state; - t_opaque_AESState o_part_mixColumns_inst_8_o; - t_opaque_AESState state_09_state; - t_opaque_AESRoundKey state_09_key; - t_opaque_AESState state_09_o; - t_opaque_AESState o_part_subBytes_inst_09_state; - t_opaque_AESState o_part_subBytes_inst_09_o; - t_opaque_AESState o_part_shiftRows_inst_09_state; - t_opaque_AESState o_part_shiftRows_inst_09_o; - t_opaque_AESState state_10_state; - t_opaque_AESRoundKey state_10_key; - t_opaque_AESState state_10_o; + AESKey keySchedule_key; + AESKeySchedule keySchedule_o; + AESState state_00_state; + AESRoundKey state_00_key; + AESState state_00_o; + AESState o_part_subBytes_inst_00_state; + AESState o_part_subBytes_inst_00_o; + AESState o_part_shiftRows_inst_00_state; + AESState o_part_shiftRows_inst_00_o; + AESState o_part_mixColumns_inst_0_state; + AESState o_part_mixColumns_inst_0_o; + AESState state_01_state; + AESRoundKey state_01_key; + AESState state_01_o; + AESState o_part_subBytes_inst_01_state; + AESState o_part_subBytes_inst_01_o; + AESState o_part_shiftRows_inst_01_state; + AESState o_part_shiftRows_inst_01_o; + AESState o_part_mixColumns_inst_1_state; + AESState o_part_mixColumns_inst_1_o; + AESState state_02_state; + AESRoundKey state_02_key; + AESState state_02_o; + AESState o_part_subBytes_inst_02_state; + AESState o_part_subBytes_inst_02_o; + AESState o_part_shiftRows_inst_02_state; + AESState o_part_shiftRows_inst_02_o; + AESState o_part_mixColumns_inst_2_state; + AESState o_part_mixColumns_inst_2_o; + AESState state_03_state; + AESRoundKey state_03_key; + AESState state_03_o; + AESState o_part_subBytes_inst_03_state; + AESState o_part_subBytes_inst_03_o; + AESState o_part_shiftRows_inst_03_state; + AESState o_part_shiftRows_inst_03_o; + AESState o_part_mixColumns_inst_3_state; + AESState o_part_mixColumns_inst_3_o; + AESState state_04_state; + AESRoundKey state_04_key; + AESState state_04_o; + AESState o_part_subBytes_inst_04_state; + AESState o_part_subBytes_inst_04_o; + AESState o_part_shiftRows_inst_04_state; + AESState o_part_shiftRows_inst_04_o; + AESState o_part_mixColumns_inst_4_state; + AESState o_part_mixColumns_inst_4_o; + AESState state_05_state; + AESRoundKey state_05_key; + AESState state_05_o; + AESState o_part_subBytes_inst_05_state; + AESState o_part_subBytes_inst_05_o; + AESState o_part_shiftRows_inst_05_state; + AESState o_part_shiftRows_inst_05_o; + AESState o_part_mixColumns_inst_5_state; + AESState o_part_mixColumns_inst_5_o; + AESState state_06_state; + AESRoundKey state_06_key; + AESState state_06_o; + AESState o_part_subBytes_inst_06_state; + AESState o_part_subBytes_inst_06_o; + AESState o_part_shiftRows_inst_06_state; + AESState o_part_shiftRows_inst_06_o; + AESState o_part_mixColumns_inst_6_state; + AESState o_part_mixColumns_inst_6_o; + AESState state_07_state; + AESRoundKey state_07_key; + AESState state_07_o; + AESState o_part_subBytes_inst_07_state; + AESState o_part_subBytes_inst_07_o; + AESState o_part_shiftRows_inst_07_state; + AESState o_part_shiftRows_inst_07_o; + AESState o_part_mixColumns_inst_7_state; + AESState o_part_mixColumns_inst_7_o; + AESState state_08_state; + AESRoundKey state_08_key; + AESState state_08_o; + AESState o_part_subBytes_inst_08_state; + AESState o_part_subBytes_inst_08_o; + AESState o_part_shiftRows_inst_08_state; + AESState o_part_shiftRows_inst_08_o; + AESState o_part_mixColumns_inst_8_state; + AESState o_part_mixColumns_inst_8_o; + AESState state_09_state; + AESRoundKey state_09_key; + AESState state_09_o; + AESState o_part_subBytes_inst_09_state; + AESState o_part_subBytes_inst_09_o; + AESState o_part_shiftRows_inst_09_state; + AESState o_part_shiftRows_inst_09_o; + AESState state_10_state; + AESRoundKey state_10_key; + AESState state_10_o; keyExpansion keySchedule( .key /*<--*/ (keySchedule_key), .o /*-->*/ (keySchedule_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/keyExpansion.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/keyExpansion.sv index 898b8ee62..2cb946350 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/keyExpansion.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/keyExpansion.sv @@ -3,209 +3,209 @@ `include "CipherNoOpaques_defs.svh" module keyExpansion( - input wire t_opaque_AESKey key, - output t_opaque_AESKeySchedule o + input wire AESKey key, + output AESKeySchedule o ); `include "dfhdl_defs.svh" - t_opaque_AESWord w_0; - t_opaque_AESWord w_1; - t_opaque_AESWord w_2; - t_opaque_AESWord w_3; - t_opaque_AESByte o_part_000; - t_opaque_AESByte o_part_001; - t_opaque_AESByte o_part_002; - t_opaque_AESByte o_part_003; - t_opaque_AESByte o_part_004; - t_opaque_AESByte o_part_005; - t_opaque_AESByte o_part_006; - t_opaque_AESByte o_part_007; - t_opaque_AESByte o_part_008; - t_opaque_AESByte o_part_009; - t_opaque_AESByte o_part_010; - t_opaque_AESByte o_part_011; - t_opaque_AESByte lhs_part_00; - t_opaque_AESByte lhs_part_01; - t_opaque_AESByte lhs_part_02; - t_opaque_AESByte lhs_part_03; - t_opaque_AESWord lhs_part_04; - t_opaque_AESByte o_part_012; - t_opaque_AESByte o_part_013; - t_opaque_AESByte o_part_014; - t_opaque_AESByte o_part_015; - t_opaque_AESByte o_part_016; - t_opaque_AESByte o_part_017; - t_opaque_AESByte o_part_018; - t_opaque_AESByte o_part_019; - t_opaque_AESByte o_part_020; - t_opaque_AESByte o_part_021; - t_opaque_AESByte o_part_022; - t_opaque_AESByte o_part_023; - t_opaque_AESByte lhs_part_05; - t_opaque_AESByte lhs_part_06; - t_opaque_AESByte lhs_part_07; - t_opaque_AESByte lhs_part_08; - t_opaque_AESWord lhs_part_09; - t_opaque_AESByte o_part_024; - t_opaque_AESByte o_part_025; - t_opaque_AESByte o_part_026; - t_opaque_AESByte o_part_027; - t_opaque_AESByte o_part_028; - t_opaque_AESByte o_part_029; - t_opaque_AESByte o_part_030; - t_opaque_AESByte o_part_031; - t_opaque_AESByte o_part_032; - t_opaque_AESByte o_part_033; - t_opaque_AESByte o_part_034; - t_opaque_AESByte o_part_035; - t_opaque_AESByte lhs_part_10; - t_opaque_AESByte lhs_part_11; - t_opaque_AESByte lhs_part_12; - t_opaque_AESByte lhs_part_13; - t_opaque_AESWord lhs_part_14; - t_opaque_AESByte o_part_036; - t_opaque_AESByte o_part_037; - t_opaque_AESByte o_part_038; - t_opaque_AESByte o_part_039; - t_opaque_AESByte o_part_040; - t_opaque_AESByte o_part_041; - t_opaque_AESByte o_part_042; - t_opaque_AESByte o_part_043; - t_opaque_AESByte o_part_044; - t_opaque_AESByte o_part_045; - t_opaque_AESByte o_part_046; - t_opaque_AESByte o_part_047; - t_opaque_AESByte lhs_part_15; - t_opaque_AESByte lhs_part_16; - t_opaque_AESByte lhs_part_17; - t_opaque_AESByte lhs_part_18; - t_opaque_AESWord lhs_part_19; - t_opaque_AESByte o_part_048; - t_opaque_AESByte o_part_049; - t_opaque_AESByte o_part_050; - t_opaque_AESByte o_part_051; - t_opaque_AESByte o_part_052; - t_opaque_AESByte o_part_053; - t_opaque_AESByte o_part_054; - t_opaque_AESByte o_part_055; - t_opaque_AESByte o_part_056; - t_opaque_AESByte o_part_057; - t_opaque_AESByte o_part_058; - t_opaque_AESByte o_part_059; - t_opaque_AESByte lhs_part_20; - t_opaque_AESByte lhs_part_21; - t_opaque_AESByte lhs_part_22; - t_opaque_AESByte lhs_part_23; - t_opaque_AESWord lhs_part_24; - t_opaque_AESByte o_part_060; - t_opaque_AESByte o_part_061; - t_opaque_AESByte o_part_062; - t_opaque_AESByte o_part_063; - t_opaque_AESByte o_part_064; - t_opaque_AESByte o_part_065; - t_opaque_AESByte o_part_066; - t_opaque_AESByte o_part_067; - t_opaque_AESByte o_part_068; - t_opaque_AESByte o_part_069; - t_opaque_AESByte o_part_070; - t_opaque_AESByte o_part_071; - t_opaque_AESByte lhs_part_25; - t_opaque_AESByte lhs_part_26; - t_opaque_AESByte lhs_part_27; - t_opaque_AESByte lhs_part_28; - t_opaque_AESWord lhs_part_29; - t_opaque_AESByte o_part_072; - t_opaque_AESByte o_part_073; - t_opaque_AESByte o_part_074; - t_opaque_AESByte o_part_075; - t_opaque_AESByte o_part_076; - t_opaque_AESByte o_part_077; - t_opaque_AESByte o_part_078; - t_opaque_AESByte o_part_079; - t_opaque_AESByte o_part_080; - t_opaque_AESByte o_part_081; - t_opaque_AESByte o_part_082; - t_opaque_AESByte o_part_083; - t_opaque_AESByte lhs_part_30; - t_opaque_AESByte lhs_part_31; - t_opaque_AESByte lhs_part_32; - t_opaque_AESByte lhs_part_33; - t_opaque_AESWord lhs_part_34; - t_opaque_AESByte o_part_084; - t_opaque_AESByte o_part_085; - t_opaque_AESByte o_part_086; - t_opaque_AESByte o_part_087; - t_opaque_AESByte o_part_088; - t_opaque_AESByte o_part_089; - t_opaque_AESByte o_part_090; - t_opaque_AESByte o_part_091; - t_opaque_AESByte o_part_092; - t_opaque_AESByte o_part_093; - t_opaque_AESByte o_part_094; - t_opaque_AESByte o_part_095; - t_opaque_AESByte lhs_part_35; - t_opaque_AESByte lhs_part_36; - t_opaque_AESByte lhs_part_37; - t_opaque_AESByte lhs_part_38; - t_opaque_AESWord lhs_part_39; - t_opaque_AESByte o_part_096; - t_opaque_AESByte o_part_097; - t_opaque_AESByte o_part_098; - t_opaque_AESByte o_part_099; - t_opaque_AESByte o_part_100; - t_opaque_AESByte o_part_101; - t_opaque_AESByte o_part_102; - t_opaque_AESByte o_part_103; - t_opaque_AESByte o_part_104; - t_opaque_AESByte o_part_105; - t_opaque_AESByte o_part_106; - t_opaque_AESByte o_part_107; - t_opaque_AESByte lhs_part_40; - t_opaque_AESByte lhs_part_41; - t_opaque_AESByte lhs_part_42; - t_opaque_AESByte lhs_part_43; - t_opaque_AESWord lhs_part_44; - t_opaque_AESByte o_part_108; - t_opaque_AESByte o_part_109; - t_opaque_AESByte o_part_110; - t_opaque_AESByte o_part_111; - t_opaque_AESByte o_part_112; - t_opaque_AESByte o_part_113; - t_opaque_AESByte o_part_114; - t_opaque_AESByte o_part_115; - t_opaque_AESByte o_part_116; - t_opaque_AESByte o_part_117; - t_opaque_AESByte o_part_118; - t_opaque_AESByte o_part_119; - t_opaque_AESWord o_part_rotWord_inst_00_o; - t_opaque_AESWord o_part_subWord_inst_00_lhs; - t_opaque_AESWord o_part_subWord_inst_00_o; - t_opaque_AESWord o_part_rotWord_inst_01_o; - t_opaque_AESWord o_part_subWord_inst_01_lhs; - t_opaque_AESWord o_part_subWord_inst_01_o; - t_opaque_AESWord o_part_rotWord_inst_02_o; - t_opaque_AESWord o_part_subWord_inst_02_lhs; - t_opaque_AESWord o_part_subWord_inst_02_o; - t_opaque_AESWord o_part_rotWord_inst_03_o; - t_opaque_AESWord o_part_subWord_inst_03_lhs; - t_opaque_AESWord o_part_subWord_inst_03_o; - t_opaque_AESWord o_part_rotWord_inst_04_o; - t_opaque_AESWord o_part_subWord_inst_04_lhs; - t_opaque_AESWord o_part_subWord_inst_04_o; - t_opaque_AESWord o_part_rotWord_inst_05_o; - t_opaque_AESWord o_part_subWord_inst_05_lhs; - t_opaque_AESWord o_part_subWord_inst_05_o; - t_opaque_AESWord o_part_rotWord_inst_06_o; - t_opaque_AESWord o_part_subWord_inst_06_lhs; - t_opaque_AESWord o_part_subWord_inst_06_o; - t_opaque_AESWord o_part_rotWord_inst_07_o; - t_opaque_AESWord o_part_subWord_inst_07_lhs; - t_opaque_AESWord o_part_subWord_inst_07_o; - t_opaque_AESWord o_part_rotWord_inst_08_o; - t_opaque_AESWord o_part_subWord_inst_08_lhs; - t_opaque_AESWord o_part_subWord_inst_08_o; - t_opaque_AESWord o_part_rotWord_inst_09_o; - t_opaque_AESWord o_part_subWord_inst_09_lhs; - t_opaque_AESWord o_part_subWord_inst_09_o; + AESWord w_0; + AESWord w_1; + AESWord w_2; + AESWord w_3; + AESByte o_part_000; + AESByte o_part_001; + AESByte o_part_002; + AESByte o_part_003; + AESByte o_part_004; + AESByte o_part_005; + AESByte o_part_006; + AESByte o_part_007; + AESByte o_part_008; + AESByte o_part_009; + AESByte o_part_010; + AESByte o_part_011; + AESByte lhs_part_00; + AESByte lhs_part_01; + AESByte lhs_part_02; + AESByte lhs_part_03; + AESWord lhs_part_04; + AESByte o_part_012; + AESByte o_part_013; + AESByte o_part_014; + AESByte o_part_015; + AESByte o_part_016; + AESByte o_part_017; + AESByte o_part_018; + AESByte o_part_019; + AESByte o_part_020; + AESByte o_part_021; + AESByte o_part_022; + AESByte o_part_023; + AESByte lhs_part_05; + AESByte lhs_part_06; + AESByte lhs_part_07; + AESByte lhs_part_08; + AESWord lhs_part_09; + AESByte o_part_024; + AESByte o_part_025; + AESByte o_part_026; + AESByte o_part_027; + AESByte o_part_028; + AESByte o_part_029; + AESByte o_part_030; + AESByte o_part_031; + AESByte o_part_032; + AESByte o_part_033; + AESByte o_part_034; + AESByte o_part_035; + AESByte lhs_part_10; + AESByte lhs_part_11; + AESByte lhs_part_12; + AESByte lhs_part_13; + AESWord lhs_part_14; + AESByte o_part_036; + AESByte o_part_037; + AESByte o_part_038; + AESByte o_part_039; + AESByte o_part_040; + AESByte o_part_041; + AESByte o_part_042; + AESByte o_part_043; + AESByte o_part_044; + AESByte o_part_045; + AESByte o_part_046; + AESByte o_part_047; + AESByte lhs_part_15; + AESByte lhs_part_16; + AESByte lhs_part_17; + AESByte lhs_part_18; + AESWord lhs_part_19; + AESByte o_part_048; + AESByte o_part_049; + AESByte o_part_050; + AESByte o_part_051; + AESByte o_part_052; + AESByte o_part_053; + AESByte o_part_054; + AESByte o_part_055; + AESByte o_part_056; + AESByte o_part_057; + AESByte o_part_058; + AESByte o_part_059; + AESByte lhs_part_20; + AESByte lhs_part_21; + AESByte lhs_part_22; + AESByte lhs_part_23; + AESWord lhs_part_24; + AESByte o_part_060; + AESByte o_part_061; + AESByte o_part_062; + AESByte o_part_063; + AESByte o_part_064; + AESByte o_part_065; + AESByte o_part_066; + AESByte o_part_067; + AESByte o_part_068; + AESByte o_part_069; + AESByte o_part_070; + AESByte o_part_071; + AESByte lhs_part_25; + AESByte lhs_part_26; + AESByte lhs_part_27; + AESByte lhs_part_28; + AESWord lhs_part_29; + AESByte o_part_072; + AESByte o_part_073; + AESByte o_part_074; + AESByte o_part_075; + AESByte o_part_076; + AESByte o_part_077; + AESByte o_part_078; + AESByte o_part_079; + AESByte o_part_080; + AESByte o_part_081; + AESByte o_part_082; + AESByte o_part_083; + AESByte lhs_part_30; + AESByte lhs_part_31; + AESByte lhs_part_32; + AESByte lhs_part_33; + AESWord lhs_part_34; + AESByte o_part_084; + AESByte o_part_085; + AESByte o_part_086; + AESByte o_part_087; + AESByte o_part_088; + AESByte o_part_089; + AESByte o_part_090; + AESByte o_part_091; + AESByte o_part_092; + AESByte o_part_093; + AESByte o_part_094; + AESByte o_part_095; + AESByte lhs_part_35; + AESByte lhs_part_36; + AESByte lhs_part_37; + AESByte lhs_part_38; + AESWord lhs_part_39; + AESByte o_part_096; + AESByte o_part_097; + AESByte o_part_098; + AESByte o_part_099; + AESByte o_part_100; + AESByte o_part_101; + AESByte o_part_102; + AESByte o_part_103; + AESByte o_part_104; + AESByte o_part_105; + AESByte o_part_106; + AESByte o_part_107; + AESByte lhs_part_40; + AESByte lhs_part_41; + AESByte lhs_part_42; + AESByte lhs_part_43; + AESWord lhs_part_44; + AESByte o_part_108; + AESByte o_part_109; + AESByte o_part_110; + AESByte o_part_111; + AESByte o_part_112; + AESByte o_part_113; + AESByte o_part_114; + AESByte o_part_115; + AESByte o_part_116; + AESByte o_part_117; + AESByte o_part_118; + AESByte o_part_119; + AESWord o_part_rotWord_inst_00_o; + AESWord o_part_subWord_inst_00_lhs; + AESWord o_part_subWord_inst_00_o; + AESWord o_part_rotWord_inst_01_o; + AESWord o_part_subWord_inst_01_lhs; + AESWord o_part_subWord_inst_01_o; + AESWord o_part_rotWord_inst_02_o; + AESWord o_part_subWord_inst_02_lhs; + AESWord o_part_subWord_inst_02_o; + AESWord o_part_rotWord_inst_03_o; + AESWord o_part_subWord_inst_03_lhs; + AESWord o_part_subWord_inst_03_o; + AESWord o_part_rotWord_inst_04_o; + AESWord o_part_subWord_inst_04_lhs; + AESWord o_part_subWord_inst_04_o; + AESWord o_part_rotWord_inst_05_o; + AESWord o_part_subWord_inst_05_lhs; + AESWord o_part_subWord_inst_05_o; + AESWord o_part_rotWord_inst_06_o; + AESWord o_part_subWord_inst_06_lhs; + AESWord o_part_subWord_inst_06_o; + AESWord o_part_rotWord_inst_07_o; + AESWord o_part_subWord_inst_07_lhs; + AESWord o_part_subWord_inst_07_o; + AESWord o_part_rotWord_inst_08_o; + AESWord o_part_subWord_inst_08_lhs; + AESWord o_part_subWord_inst_08_o; + AESWord o_part_rotWord_inst_09_o; + AESWord o_part_subWord_inst_09_lhs; + AESWord o_part_subWord_inst_09_o; rotWord o_part_rotWord_inst_00( .o /*-->*/ (o_part_rotWord_inst_00_o), .lhs /*<--*/ (w_3) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mixColumns.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mixColumns.sv index 009784ac8..8ae889017 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mixColumns.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mixColumns.sv @@ -3,138 +3,138 @@ `include "CipherNoOpaques_defs.svh" module mixColumns( - input wire t_opaque_AESState state, - output t_opaque_AESState o + input wire AESState state, + output AESState o ); `include "dfhdl_defs.svh" - t_opaque_AESByte o_part_mulByte_0_inst_00_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_00_o; - t_opaque_AESByte o_part_mulByte_1_inst_00_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_00_o; - t_opaque_AESByte o_part_mulByte_2_inst_00_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_00_o; - t_opaque_AESByte o_part_mulByte_2_inst_01_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_01_o; - t_opaque_AESByte o_part_mulByte_2_inst_02_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_02_o; - t_opaque_AESByte o_part_mulByte_0_inst_01_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_01_o; - t_opaque_AESByte o_part_mulByte_1_inst_01_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_01_o; - t_opaque_AESByte o_part_mulByte_2_inst_03_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_03_o; - t_opaque_AESByte o_part_mulByte_2_inst_04_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_04_o; - t_opaque_AESByte o_part_mulByte_2_inst_05_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_05_o; - t_opaque_AESByte o_part_mulByte_0_inst_02_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_02_o; - t_opaque_AESByte o_part_mulByte_1_inst_02_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_02_o; - t_opaque_AESByte o_part_mulByte_1_inst_03_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_03_o; - t_opaque_AESByte o_part_mulByte_2_inst_06_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_06_o; - t_opaque_AESByte o_part_mulByte_2_inst_07_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_07_o; - t_opaque_AESByte o_part_mulByte_0_inst_03_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_03_o; - t_opaque_AESByte o_part_mulByte_0_inst_04_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_04_o; - t_opaque_AESByte o_part_mulByte_1_inst_04_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_04_o; - t_opaque_AESByte o_part_mulByte_2_inst_08_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_08_o; - t_opaque_AESByte o_part_mulByte_2_inst_09_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_09_o; - t_opaque_AESByte o_part_mulByte_2_inst_10_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_10_o; - t_opaque_AESByte o_part_mulByte_0_inst_05_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_05_o; - t_opaque_AESByte o_part_mulByte_1_inst_05_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_05_o; - t_opaque_AESByte o_part_mulByte_2_inst_11_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_11_o; - t_opaque_AESByte o_part_mulByte_2_inst_12_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_12_o; - t_opaque_AESByte o_part_mulByte_2_inst_13_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_13_o; - t_opaque_AESByte o_part_mulByte_0_inst_06_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_06_o; - t_opaque_AESByte o_part_mulByte_1_inst_06_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_06_o; - t_opaque_AESByte o_part_mulByte_1_inst_07_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_07_o; - t_opaque_AESByte o_part_mulByte_2_inst_14_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_14_o; - t_opaque_AESByte o_part_mulByte_2_inst_15_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_15_o; - t_opaque_AESByte o_part_mulByte_0_inst_07_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_07_o; - t_opaque_AESByte o_part_mulByte_0_inst_08_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_08_o; - t_opaque_AESByte o_part_mulByte_1_inst_08_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_08_o; - t_opaque_AESByte o_part_mulByte_2_inst_16_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_16_o; - t_opaque_AESByte o_part_mulByte_2_inst_17_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_17_o; - t_opaque_AESByte o_part_mulByte_2_inst_18_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_18_o; - t_opaque_AESByte o_part_mulByte_0_inst_09_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_09_o; - t_opaque_AESByte o_part_mulByte_1_inst_09_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_09_o; - t_opaque_AESByte o_part_mulByte_2_inst_19_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_19_o; - t_opaque_AESByte o_part_mulByte_2_inst_20_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_20_o; - t_opaque_AESByte o_part_mulByte_2_inst_21_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_21_o; - t_opaque_AESByte o_part_mulByte_0_inst_10_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_10_o; - t_opaque_AESByte o_part_mulByte_1_inst_10_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_10_o; - t_opaque_AESByte o_part_mulByte_1_inst_11_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_11_o; - t_opaque_AESByte o_part_mulByte_2_inst_22_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_22_o; - t_opaque_AESByte o_part_mulByte_2_inst_23_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_23_o; - t_opaque_AESByte o_part_mulByte_0_inst_11_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_11_o; - t_opaque_AESByte o_part_mulByte_0_inst_12_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_12_o; - t_opaque_AESByte o_part_mulByte_1_inst_12_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_12_o; - t_opaque_AESByte o_part_mulByte_2_inst_24_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_24_o; - t_opaque_AESByte o_part_mulByte_2_inst_25_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_25_o; - t_opaque_AESByte o_part_mulByte_2_inst_26_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_26_o; - t_opaque_AESByte o_part_mulByte_0_inst_13_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_13_o; - t_opaque_AESByte o_part_mulByte_1_inst_13_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_13_o; - t_opaque_AESByte o_part_mulByte_2_inst_27_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_27_o; - t_opaque_AESByte o_part_mulByte_2_inst_28_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_28_o; - t_opaque_AESByte o_part_mulByte_2_inst_29_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_29_o; - t_opaque_AESByte o_part_mulByte_0_inst_14_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_14_o; - t_opaque_AESByte o_part_mulByte_1_inst_14_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_14_o; - t_opaque_AESByte o_part_mulByte_1_inst_15_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_15_o; - t_opaque_AESByte o_part_mulByte_2_inst_30_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_30_o; - t_opaque_AESByte o_part_mulByte_2_inst_31_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_31_o; - t_opaque_AESByte o_part_mulByte_0_inst_15_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_15_o; + AESByte o_part_mulByte_0_inst_00_rhs; + AESByte o_part_mulByte_0_inst_00_o; + AESByte o_part_mulByte_1_inst_00_rhs; + AESByte o_part_mulByte_1_inst_00_o; + AESByte o_part_mulByte_2_inst_00_rhs; + AESByte o_part_mulByte_2_inst_00_o; + AESByte o_part_mulByte_2_inst_01_rhs; + AESByte o_part_mulByte_2_inst_01_o; + AESByte o_part_mulByte_2_inst_02_rhs; + AESByte o_part_mulByte_2_inst_02_o; + AESByte o_part_mulByte_0_inst_01_rhs; + AESByte o_part_mulByte_0_inst_01_o; + AESByte o_part_mulByte_1_inst_01_rhs; + AESByte o_part_mulByte_1_inst_01_o; + AESByte o_part_mulByte_2_inst_03_rhs; + AESByte o_part_mulByte_2_inst_03_o; + AESByte o_part_mulByte_2_inst_04_rhs; + AESByte o_part_mulByte_2_inst_04_o; + AESByte o_part_mulByte_2_inst_05_rhs; + AESByte o_part_mulByte_2_inst_05_o; + AESByte o_part_mulByte_0_inst_02_rhs; + AESByte o_part_mulByte_0_inst_02_o; + AESByte o_part_mulByte_1_inst_02_rhs; + AESByte o_part_mulByte_1_inst_02_o; + AESByte o_part_mulByte_1_inst_03_rhs; + AESByte o_part_mulByte_1_inst_03_o; + AESByte o_part_mulByte_2_inst_06_rhs; + AESByte o_part_mulByte_2_inst_06_o; + AESByte o_part_mulByte_2_inst_07_rhs; + AESByte o_part_mulByte_2_inst_07_o; + AESByte o_part_mulByte_0_inst_03_rhs; + AESByte o_part_mulByte_0_inst_03_o; + AESByte o_part_mulByte_0_inst_04_rhs; + AESByte o_part_mulByte_0_inst_04_o; + AESByte o_part_mulByte_1_inst_04_rhs; + AESByte o_part_mulByte_1_inst_04_o; + AESByte o_part_mulByte_2_inst_08_rhs; + AESByte o_part_mulByte_2_inst_08_o; + AESByte o_part_mulByte_2_inst_09_rhs; + AESByte o_part_mulByte_2_inst_09_o; + AESByte o_part_mulByte_2_inst_10_rhs; + AESByte o_part_mulByte_2_inst_10_o; + AESByte o_part_mulByte_0_inst_05_rhs; + AESByte o_part_mulByte_0_inst_05_o; + AESByte o_part_mulByte_1_inst_05_rhs; + AESByte o_part_mulByte_1_inst_05_o; + AESByte o_part_mulByte_2_inst_11_rhs; + AESByte o_part_mulByte_2_inst_11_o; + AESByte o_part_mulByte_2_inst_12_rhs; + AESByte o_part_mulByte_2_inst_12_o; + AESByte o_part_mulByte_2_inst_13_rhs; + AESByte o_part_mulByte_2_inst_13_o; + AESByte o_part_mulByte_0_inst_06_rhs; + AESByte o_part_mulByte_0_inst_06_o; + AESByte o_part_mulByte_1_inst_06_rhs; + AESByte o_part_mulByte_1_inst_06_o; + AESByte o_part_mulByte_1_inst_07_rhs; + AESByte o_part_mulByte_1_inst_07_o; + AESByte o_part_mulByte_2_inst_14_rhs; + AESByte o_part_mulByte_2_inst_14_o; + AESByte o_part_mulByte_2_inst_15_rhs; + AESByte o_part_mulByte_2_inst_15_o; + AESByte o_part_mulByte_0_inst_07_rhs; + AESByte o_part_mulByte_0_inst_07_o; + AESByte o_part_mulByte_0_inst_08_rhs; + AESByte o_part_mulByte_0_inst_08_o; + AESByte o_part_mulByte_1_inst_08_rhs; + AESByte o_part_mulByte_1_inst_08_o; + AESByte o_part_mulByte_2_inst_16_rhs; + AESByte o_part_mulByte_2_inst_16_o; + AESByte o_part_mulByte_2_inst_17_rhs; + AESByte o_part_mulByte_2_inst_17_o; + AESByte o_part_mulByte_2_inst_18_rhs; + AESByte o_part_mulByte_2_inst_18_o; + AESByte o_part_mulByte_0_inst_09_rhs; + AESByte o_part_mulByte_0_inst_09_o; + AESByte o_part_mulByte_1_inst_09_rhs; + AESByte o_part_mulByte_1_inst_09_o; + AESByte o_part_mulByte_2_inst_19_rhs; + AESByte o_part_mulByte_2_inst_19_o; + AESByte o_part_mulByte_2_inst_20_rhs; + AESByte o_part_mulByte_2_inst_20_o; + AESByte o_part_mulByte_2_inst_21_rhs; + AESByte o_part_mulByte_2_inst_21_o; + AESByte o_part_mulByte_0_inst_10_rhs; + AESByte o_part_mulByte_0_inst_10_o; + AESByte o_part_mulByte_1_inst_10_rhs; + AESByte o_part_mulByte_1_inst_10_o; + AESByte o_part_mulByte_1_inst_11_rhs; + AESByte o_part_mulByte_1_inst_11_o; + AESByte o_part_mulByte_2_inst_22_rhs; + AESByte o_part_mulByte_2_inst_22_o; + AESByte o_part_mulByte_2_inst_23_rhs; + AESByte o_part_mulByte_2_inst_23_o; + AESByte o_part_mulByte_0_inst_11_rhs; + AESByte o_part_mulByte_0_inst_11_o; + AESByte o_part_mulByte_0_inst_12_rhs; + AESByte o_part_mulByte_0_inst_12_o; + AESByte o_part_mulByte_1_inst_12_rhs; + AESByte o_part_mulByte_1_inst_12_o; + AESByte o_part_mulByte_2_inst_24_rhs; + AESByte o_part_mulByte_2_inst_24_o; + AESByte o_part_mulByte_2_inst_25_rhs; + AESByte o_part_mulByte_2_inst_25_o; + AESByte o_part_mulByte_2_inst_26_rhs; + AESByte o_part_mulByte_2_inst_26_o; + AESByte o_part_mulByte_0_inst_13_rhs; + AESByte o_part_mulByte_0_inst_13_o; + AESByte o_part_mulByte_1_inst_13_rhs; + AESByte o_part_mulByte_1_inst_13_o; + AESByte o_part_mulByte_2_inst_27_rhs; + AESByte o_part_mulByte_2_inst_27_o; + AESByte o_part_mulByte_2_inst_28_rhs; + AESByte o_part_mulByte_2_inst_28_o; + AESByte o_part_mulByte_2_inst_29_rhs; + AESByte o_part_mulByte_2_inst_29_o; + AESByte o_part_mulByte_0_inst_14_rhs; + AESByte o_part_mulByte_0_inst_14_o; + AESByte o_part_mulByte_1_inst_14_rhs; + AESByte o_part_mulByte_1_inst_14_o; + AESByte o_part_mulByte_1_inst_15_rhs; + AESByte o_part_mulByte_1_inst_15_o; + AESByte o_part_mulByte_2_inst_30_rhs; + AESByte o_part_mulByte_2_inst_30_o; + AESByte o_part_mulByte_2_inst_31_rhs; + AESByte o_part_mulByte_2_inst_31_o; + AESByte o_part_mulByte_0_inst_15_rhs; + AESByte o_part_mulByte_0_inst_15_o; mulByte_0 #( .lhs (8'h02) ) o_part_mulByte_0_inst_00( diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_0.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_0.sv index e5a36783d..c8a1e0d1c 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_0.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_0.sv @@ -3,12 +3,12 @@ `include "CipherNoOpaques_defs.svh" module mulByte_0#(parameter logic [7:0] lhs = 8'hxx)( - input wire t_opaque_AESByte rhs, - output t_opaque_AESByte o + input wire AESByte rhs, + output AESByte o ); `include "dfhdl_defs.svh" - t_opaque_AESByte a_lhs; - t_opaque_AESByte a_o; + AESByte a_lhs; + AESByte a_o; xtime a( .lhs /*<--*/ (a_lhs), .o /*-->*/ (a_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_1.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_1.sv index 49857e954..fd24596bf 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_1.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_1.sv @@ -3,12 +3,12 @@ `include "CipherNoOpaques_defs.svh" module mulByte_1#(parameter logic [7:0] lhs = 8'hxx)( - input wire t_opaque_AESByte rhs, - output t_opaque_AESByte o + input wire AESByte rhs, + output AESByte o ); `include "dfhdl_defs.svh" - t_opaque_AESByte a_lhs; - t_opaque_AESByte a_o; + AESByte a_lhs; + AESByte a_o; xtime a( .lhs /*<--*/ (a_lhs), .o /*-->*/ (a_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_2.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_2.sv index 29a5b1904..2f709135d 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_2.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_2.sv @@ -3,8 +3,8 @@ `include "CipherNoOpaques_defs.svh" module mulByte_2#(parameter logic [7:0] lhs = 8'hxx)( - input wire t_opaque_AESByte rhs, - output t_opaque_AESByte o + input wire AESByte rhs, + output AESByte o ); `include "dfhdl_defs.svh" assign o = 8'h00 ^ rhs; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/rotWord.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/rotWord.sv index e35b45f98..359b4680a 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/rotWord.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/rotWord.sv @@ -3,8 +3,8 @@ `include "CipherNoOpaques_defs.svh" module rotWord( - input wire t_opaque_AESWord lhs, - output t_opaque_AESWord o + input wire AESWord lhs, + output AESWord o ); `include "dfhdl_defs.svh" assign o = '{0: lhs[1], 1: lhs[2], 2: lhs[3], 3: lhs[0]}; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/sbox.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/sbox.sv index 5ba68053f..ffd351ecc 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/sbox.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/sbox.sv @@ -3,8 +3,8 @@ `include "CipherNoOpaques_defs.svh" module sbox( - input wire t_opaque_AESByte lhs, - output t_opaque_AESByte o + input wire AESByte lhs, + output AESByte o ); `include "dfhdl_defs.svh" assign o = sboxLookupTable[lhs]; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/shiftRows.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/shiftRows.sv index 0001a070c..3c43da664 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/shiftRows.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/shiftRows.sv @@ -3,8 +3,8 @@ `include "CipherNoOpaques_defs.svh" module shiftRows( - input wire t_opaque_AESState state, - output t_opaque_AESState o + input wire AESState state, + output AESState o ); `include "dfhdl_defs.svh" assign o = '{ diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/subBytes.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/subBytes.sv index c8a1d8eb6..997012b30 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/subBytes.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/subBytes.sv @@ -3,42 +3,42 @@ `include "CipherNoOpaques_defs.svh" module subBytes( - input wire t_opaque_AESState state, - output t_opaque_AESState o + input wire AESState state, + output AESState o ); `include "dfhdl_defs.svh" - t_opaque_AESByte o_part_sbox_inst_00_lhs; - t_opaque_AESByte o_part_sbox_inst_00_o; - t_opaque_AESByte o_part_sbox_inst_01_lhs; - t_opaque_AESByte o_part_sbox_inst_01_o; - t_opaque_AESByte o_part_sbox_inst_02_lhs; - t_opaque_AESByte o_part_sbox_inst_02_o; - t_opaque_AESByte o_part_sbox_inst_03_lhs; - t_opaque_AESByte o_part_sbox_inst_03_o; - t_opaque_AESByte o_part_sbox_inst_04_lhs; - t_opaque_AESByte o_part_sbox_inst_04_o; - t_opaque_AESByte o_part_sbox_inst_05_lhs; - t_opaque_AESByte o_part_sbox_inst_05_o; - t_opaque_AESByte o_part_sbox_inst_06_lhs; - t_opaque_AESByte o_part_sbox_inst_06_o; - t_opaque_AESByte o_part_sbox_inst_07_lhs; - t_opaque_AESByte o_part_sbox_inst_07_o; - t_opaque_AESByte o_part_sbox_inst_08_lhs; - t_opaque_AESByte o_part_sbox_inst_08_o; - t_opaque_AESByte o_part_sbox_inst_09_lhs; - t_opaque_AESByte o_part_sbox_inst_09_o; - t_opaque_AESByte o_part_sbox_inst_10_lhs; - t_opaque_AESByte o_part_sbox_inst_10_o; - t_opaque_AESByte o_part_sbox_inst_11_lhs; - t_opaque_AESByte o_part_sbox_inst_11_o; - t_opaque_AESByte o_part_sbox_inst_12_lhs; - t_opaque_AESByte o_part_sbox_inst_12_o; - t_opaque_AESByte o_part_sbox_inst_13_lhs; - t_opaque_AESByte o_part_sbox_inst_13_o; - t_opaque_AESByte o_part_sbox_inst_14_lhs; - t_opaque_AESByte o_part_sbox_inst_14_o; - t_opaque_AESByte o_part_sbox_inst_15_lhs; - t_opaque_AESByte o_part_sbox_inst_15_o; + AESByte o_part_sbox_inst_00_lhs; + AESByte o_part_sbox_inst_00_o; + AESByte o_part_sbox_inst_01_lhs; + AESByte o_part_sbox_inst_01_o; + AESByte o_part_sbox_inst_02_lhs; + AESByte o_part_sbox_inst_02_o; + AESByte o_part_sbox_inst_03_lhs; + AESByte o_part_sbox_inst_03_o; + AESByte o_part_sbox_inst_04_lhs; + AESByte o_part_sbox_inst_04_o; + AESByte o_part_sbox_inst_05_lhs; + AESByte o_part_sbox_inst_05_o; + AESByte o_part_sbox_inst_06_lhs; + AESByte o_part_sbox_inst_06_o; + AESByte o_part_sbox_inst_07_lhs; + AESByte o_part_sbox_inst_07_o; + AESByte o_part_sbox_inst_08_lhs; + AESByte o_part_sbox_inst_08_o; + AESByte o_part_sbox_inst_09_lhs; + AESByte o_part_sbox_inst_09_o; + AESByte o_part_sbox_inst_10_lhs; + AESByte o_part_sbox_inst_10_o; + AESByte o_part_sbox_inst_11_lhs; + AESByte o_part_sbox_inst_11_o; + AESByte o_part_sbox_inst_12_lhs; + AESByte o_part_sbox_inst_12_o; + AESByte o_part_sbox_inst_13_lhs; + AESByte o_part_sbox_inst_13_o; + AESByte o_part_sbox_inst_14_lhs; + AESByte o_part_sbox_inst_14_o; + AESByte o_part_sbox_inst_15_lhs; + AESByte o_part_sbox_inst_15_o; sbox o_part_sbox_inst_00( .lhs /*<--*/ (o_part_sbox_inst_00_lhs), .o /*-->*/ (o_part_sbox_inst_00_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/subWord.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/subWord.sv index ec5bd522d..05a9aa702 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/subWord.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/subWord.sv @@ -3,18 +3,18 @@ `include "CipherNoOpaques_defs.svh" module subWord( - input wire t_opaque_AESWord lhs, - output t_opaque_AESWord o + input wire AESWord lhs, + output AESWord o ); `include "dfhdl_defs.svh" - t_opaque_AESByte o_part_sbox_inst_0_lhs; - t_opaque_AESByte o_part_sbox_inst_0_o; - t_opaque_AESByte o_part_sbox_inst_1_lhs; - t_opaque_AESByte o_part_sbox_inst_1_o; - t_opaque_AESByte o_part_sbox_inst_2_lhs; - t_opaque_AESByte o_part_sbox_inst_2_o; - t_opaque_AESByte o_part_sbox_inst_3_lhs; - t_opaque_AESByte o_part_sbox_inst_3_o; + AESByte o_part_sbox_inst_0_lhs; + AESByte o_part_sbox_inst_0_o; + AESByte o_part_sbox_inst_1_lhs; + AESByte o_part_sbox_inst_1_o; + AESByte o_part_sbox_inst_2_lhs; + AESByte o_part_sbox_inst_2_o; + AESByte o_part_sbox_inst_3_lhs; + AESByte o_part_sbox_inst_3_o; sbox o_part_sbox_inst_0( .lhs /*<--*/ (o_part_sbox_inst_0_lhs), .o /*-->*/ (o_part_sbox_inst_0_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/xtime.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/xtime.sv index f391d03d9..d20fe6ba9 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/xtime.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/verilog.sv2009/hdl/xtime.sv @@ -3,8 +3,8 @@ `include "CipherNoOpaques_defs.svh" module xtime( - input wire t_opaque_AESByte lhs, - output t_opaque_AESByte o + input wire AESByte lhs, + output AESByte o ); `include "dfhdl_defs.svh" logic [7:0] shifted; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/CipherNoOpaques.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/CipherNoOpaques.vhd index 3ea706028..3b07fcafd 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/CipherNoOpaques.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/CipherNoOpaques.vhd @@ -6,16 +6,16 @@ use work.CipherNoOpaques_pkg.all; entity CipherNoOpaques is port ( - key : in t_opaque_AESKey; - data : in t_opaque_AESData; - o : out t_opaque_AESData + key : in AESKey; + data : in AESData; + o : out AESData ); end CipherNoOpaques; architecture CipherNoOpaques_arch of CipherNoOpaques is - signal o_part_cipher_inst_data : t_opaque_AESData; - signal o_part_cipher_inst_key : t_opaque_AESKey; - signal o_part_cipher_inst_o : t_opaque_AESData; + signal o_part_cipher_inst_data : AESData; + signal o_part_cipher_inst_key : AESKey; + signal o_part_cipher_inst_o : AESData; begin o_part_cipher_inst : entity work.cipher(cipher_arch) port map ( data => o_part_cipher_inst_data, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/CipherNoOpaques_pkg.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/CipherNoOpaques_pkg.vhd index eb6555815..ad76e7787 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/CipherNoOpaques_pkg.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/CipherNoOpaques_pkg.vhd @@ -4,36 +4,36 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package CipherNoOpaques_pkg is -subtype t_opaque_AESByte is std_logic_vector(7 downto 0); -function to_t_opaque_AESByte(A: std_logic_vector) return t_opaque_AESByte; -type t_arrX1_t_opaque_AESByte is array (natural range <>) of t_opaque_AESByte; -function bitWidth(A : t_arrX1_t_opaque_AESByte) return integer; -function to_slv(A : t_arrX1_t_opaque_AESByte) return std_logic_vector; -function to_t_arrX1_t_opaque_AESByte(A : std_logic_vector; D1 : integer) return t_arrX1_t_opaque_AESByte; -function bool_sel(C : boolean; T : t_arrX1_t_opaque_AESByte; F : t_arrX1_t_opaque_AESByte) return t_arrX1_t_opaque_AESByte; -subtype t_opaque_AESWord is t_arrX1_t_opaque_AESByte(0 to 3); -function to_t_opaque_AESWord(A: std_logic_vector) return t_opaque_AESWord; -type t_arrX1_t_opaque_AESWord is array (natural range <>) of t_opaque_AESWord; -function bitWidth(A : t_arrX1_t_opaque_AESWord) return integer; -function to_slv(A : t_arrX1_t_opaque_AESWord) return std_logic_vector; -function to_t_arrX1_t_opaque_AESWord(A : std_logic_vector; D1 : integer) return t_arrX1_t_opaque_AESWord; -function bool_sel(C : boolean; T : t_arrX1_t_opaque_AESWord; F : t_arrX1_t_opaque_AESWord) return t_arrX1_t_opaque_AESWord; -subtype t_opaque_AESKey is t_arrX1_t_opaque_AESWord(0 to 3); -function to_t_opaque_AESKey(A: std_logic_vector) return t_opaque_AESKey; -subtype t_opaque_AESData is t_arrX1_t_opaque_AESWord(0 to 3); -function to_t_opaque_AESData(A: std_logic_vector) return t_opaque_AESData; -subtype t_opaque_AESKeySchedule is t_arrX1_t_opaque_AESWord(0 to 43); -function to_t_opaque_AESKeySchedule(A: std_logic_vector) return t_opaque_AESKeySchedule; +subtype AESByte is std_logic_vector(7 downto 0); +function to_AESByte(A: std_logic_vector) return AESByte; +type t_arrX1_AESByte is array (natural range <>) of AESByte; +function bitWidth(A : t_arrX1_AESByte) return integer; +function to_slv(A : t_arrX1_AESByte) return std_logic_vector; +function to_t_arrX1_AESByte(A : std_logic_vector; D1 : integer) return t_arrX1_AESByte; +function bool_sel(C : boolean; T : t_arrX1_AESByte; F : t_arrX1_AESByte) return t_arrX1_AESByte; +subtype AESWord is t_arrX1_AESByte(0 to 3); +function to_AESWord(A: std_logic_vector) return AESWord; +type t_arrX1_AESWord is array (natural range <>) of AESWord; +function bitWidth(A : t_arrX1_AESWord) return integer; +function to_slv(A : t_arrX1_AESWord) return std_logic_vector; +function to_t_arrX1_AESWord(A : std_logic_vector; D1 : integer) return t_arrX1_AESWord; +function bool_sel(C : boolean; T : t_arrX1_AESWord; F : t_arrX1_AESWord) return t_arrX1_AESWord; +subtype AESKey is t_arrX1_AESWord(0 to 3); +function to_AESKey(A: std_logic_vector) return AESKey; +subtype AESData is t_arrX1_AESWord(0 to 3); +function to_AESData(A: std_logic_vector) return AESData; +subtype AESKeySchedule is t_arrX1_AESWord(0 to 43); +function to_AESKeySchedule(A: std_logic_vector) return AESKeySchedule; type t_arrX1_std_logic_vector is array (natural range <>) of std_logic_vector; function bitWidth(A : t_arrX1_std_logic_vector) return integer; function to_slv(A : t_arrX1_std_logic_vector) return std_logic_vector; function to_t_arrX1_std_logic_vector(A : std_logic_vector; D1 : integer; D0 : integer) return t_arrX1_std_logic_vector; function bool_sel(C : boolean; T : t_arrX1_std_logic_vector; F : t_arrX1_std_logic_vector) return t_arrX1_std_logic_vector; -subtype t_opaque_AESState is t_arrX1_t_opaque_AESWord(0 to 3); -function to_t_opaque_AESState(A: std_logic_vector) return t_opaque_AESState; -subtype t_opaque_AESRoundKey is t_arrX1_t_opaque_AESWord(0 to 3); -function to_t_opaque_AESRoundKey(A: std_logic_vector) return t_opaque_AESRoundKey; -constant Rcon : t_arrX1_t_opaque_AESWord(0 to 10) := ( +subtype AESState is t_arrX1_AESWord(0 to 3); +function to_AESState(A: std_logic_vector) return AESState; +subtype AESRoundKey is t_arrX1_AESWord(0 to 3); +function to_AESRoundKey(A: std_logic_vector) return AESRoundKey; +constant Rcon : t_arrX1_AESWord(0 to 10) := ( 0 => (0 => x"00", 1 => x"00", 2 => x"00", 3 => x"00"), 1 => (0 => x"01", 1 => x"00", 2 => x"00", 3 => x"00"), 2 => (0 => x"02", 1 => x"00", 2 => x"00", 3 => x"00"), 3 => (0 => x"04", 1 => x"00", 2 => x"00", 3 => x"00"), 4 => (0 => x"08", 1 => x"00", 2 => x"00", 3 => x"00"), 5 => (0 => x"10", 1 => x"00", 2 => x"00", 3 => x"00"), @@ -78,53 +78,53 @@ constant sboxLookupTable : t_arrX1_std_logic_vector(0 to 255)(7 downto 0) := ( end package CipherNoOpaques_pkg; package body CipherNoOpaques_pkg is -function to_t_opaque_AESByte(A : std_logic_vector) return t_opaque_AESByte is +function to_AESByte(A : std_logic_vector) return AESByte is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; return A0; end; -function to_t_opaque_AESWord(A : std_logic_vector) return t_opaque_AESWord is +function to_AESWord(A : std_logic_vector) return AESWord is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESByte(A0, 4); + return to_t_arrX1_AESByte(A0, 4); end; -function to_t_opaque_AESKey(A : std_logic_vector) return t_opaque_AESKey is +function to_AESKey(A : std_logic_vector) return AESKey is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 4); + return to_t_arrX1_AESWord(A0, 4); end; -function to_t_opaque_AESData(A : std_logic_vector) return t_opaque_AESData is +function to_AESData(A : std_logic_vector) return AESData is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 4); + return to_t_arrX1_AESWord(A0, 4); end; -function to_t_opaque_AESKeySchedule(A : std_logic_vector) return t_opaque_AESKeySchedule is +function to_AESKeySchedule(A : std_logic_vector) return AESKeySchedule is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 44); + return to_t_arrX1_AESWord(A0, 44); end; -function to_t_opaque_AESState(A : std_logic_vector) return t_opaque_AESState is +function to_AESState(A : std_logic_vector) return AESState is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 4); + return to_t_arrX1_AESWord(A0, 4); end; -function to_t_opaque_AESRoundKey(A : std_logic_vector) return t_opaque_AESRoundKey is +function to_AESRoundKey(A : std_logic_vector) return AESRoundKey is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 4); + return to_t_arrX1_AESWord(A0, 4); end; -function bitWidth(A : t_arrX1_t_opaque_AESByte) return integer is +function bitWidth(A : t_arrX1_AESByte) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX1_t_opaque_AESByte) return std_logic_vector is +function to_slv(A : t_arrX1_AESByte) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -138,21 +138,21 @@ begin end loop; return ret; end; -function to_t_arrX1_t_opaque_AESByte(A : std_logic_vector; D1 : integer) return t_arrX1_t_opaque_AESByte is +function to_t_arrX1_AESByte(A : std_logic_vector; D1 : integer) return t_arrX1_AESByte is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX1_t_opaque_AESByte(0 to D1 - 1); + variable ret : t_arrX1_AESByte(0 to D1 - 1); begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESByte(A(hi downto lo)); + ret(i) := to_AESByte(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX1_t_opaque_AESByte; F : t_arrX1_t_opaque_AESByte) return t_arrX1_t_opaque_AESByte is +function bool_sel(C : boolean; T : t_arrX1_AESByte; F : t_arrX1_AESByte) return t_arrX1_AESByte is begin if C then return T; @@ -160,11 +160,11 @@ begin return F; end if; end; -function bitWidth(A : t_arrX1_t_opaque_AESWord) return integer is +function bitWidth(A : t_arrX1_AESWord) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX1_t_opaque_AESWord) return std_logic_vector is +function to_slv(A : t_arrX1_AESWord) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -178,21 +178,21 @@ begin end loop; return ret; end; -function to_t_arrX1_t_opaque_AESWord(A : std_logic_vector; D1 : integer) return t_arrX1_t_opaque_AESWord is +function to_t_arrX1_AESWord(A : std_logic_vector; D1 : integer) return t_arrX1_AESWord is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX1_t_opaque_AESWord(0 to D1 - 1); + variable ret : t_arrX1_AESWord(0 to D1 - 1); begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESWord(A(hi downto lo)); + ret(i) := to_AESWord(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX1_t_opaque_AESWord; F : t_arrX1_t_opaque_AESWord) return t_arrX1_t_opaque_AESWord is +function bool_sel(C : boolean; T : t_arrX1_AESWord; F : t_arrX1_AESWord) return t_arrX1_AESWord is begin if C then return T; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/addRoundKey.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/addRoundKey.vhd index 67d0ef687..b4c65f0a0 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/addRoundKey.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/addRoundKey.vhd @@ -6,9 +6,9 @@ use work.CipherNoOpaques_pkg.all; entity addRoundKey is port ( - state : in t_opaque_AESState; - key : in t_opaque_AESRoundKey; - o : out t_opaque_AESState + state : in AESState; + key : in AESRoundKey; + o : out AESState ); end addRoundKey; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/cipher.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/cipher.vhd index 21796f882..7f9543711 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/cipher.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/cipher.vhd @@ -6,106 +6,106 @@ use work.CipherNoOpaques_pkg.all; entity cipher is port ( - data : in t_opaque_AESData; - key : in t_opaque_AESKey; - o : out t_opaque_AESData + data : in AESData; + key : in AESKey; + o : out AESData ); end cipher; architecture cipher_arch of cipher is - signal keySchedule_key : t_opaque_AESKey; - signal keySchedule_o : t_opaque_AESKeySchedule; - signal state_00_state : t_opaque_AESState; - signal state_00_key : t_opaque_AESRoundKey; - signal state_00_o : t_opaque_AESState; - signal o_part_subBytes_inst_00_state : t_opaque_AESState; - signal o_part_subBytes_inst_00_o : t_opaque_AESState; - signal o_part_shiftRows_inst_00_state : t_opaque_AESState; - signal o_part_shiftRows_inst_00_o : t_opaque_AESState; - signal o_part_mixColumns_inst_0_state : t_opaque_AESState; - signal o_part_mixColumns_inst_0_o : t_opaque_AESState; - signal state_01_state : t_opaque_AESState; - signal state_01_key : t_opaque_AESRoundKey; - signal state_01_o : t_opaque_AESState; - signal o_part_subBytes_inst_01_state : t_opaque_AESState; - signal o_part_subBytes_inst_01_o : t_opaque_AESState; - signal o_part_shiftRows_inst_01_state : t_opaque_AESState; - signal o_part_shiftRows_inst_01_o : t_opaque_AESState; - signal o_part_mixColumns_inst_1_state : t_opaque_AESState; - signal o_part_mixColumns_inst_1_o : t_opaque_AESState; - signal state_02_state : t_opaque_AESState; - signal state_02_key : t_opaque_AESRoundKey; - signal state_02_o : t_opaque_AESState; - signal o_part_subBytes_inst_02_state : t_opaque_AESState; - signal o_part_subBytes_inst_02_o : t_opaque_AESState; - signal o_part_shiftRows_inst_02_state : t_opaque_AESState; - signal o_part_shiftRows_inst_02_o : t_opaque_AESState; - signal o_part_mixColumns_inst_2_state : t_opaque_AESState; - signal o_part_mixColumns_inst_2_o : t_opaque_AESState; - signal state_03_state : t_opaque_AESState; - signal state_03_key : t_opaque_AESRoundKey; - signal state_03_o : t_opaque_AESState; - signal o_part_subBytes_inst_03_state : t_opaque_AESState; - signal o_part_subBytes_inst_03_o : t_opaque_AESState; - signal o_part_shiftRows_inst_03_state : t_opaque_AESState; - signal o_part_shiftRows_inst_03_o : t_opaque_AESState; - signal o_part_mixColumns_inst_3_state : t_opaque_AESState; - signal o_part_mixColumns_inst_3_o : t_opaque_AESState; - signal state_04_state : t_opaque_AESState; - signal state_04_key : t_opaque_AESRoundKey; - signal state_04_o : t_opaque_AESState; - signal o_part_subBytes_inst_04_state : t_opaque_AESState; - signal o_part_subBytes_inst_04_o : t_opaque_AESState; - signal o_part_shiftRows_inst_04_state : t_opaque_AESState; - signal o_part_shiftRows_inst_04_o : t_opaque_AESState; - signal o_part_mixColumns_inst_4_state : t_opaque_AESState; - signal o_part_mixColumns_inst_4_o : t_opaque_AESState; - signal state_05_state : t_opaque_AESState; - signal state_05_key : t_opaque_AESRoundKey; - signal state_05_o : t_opaque_AESState; - signal o_part_subBytes_inst_05_state : t_opaque_AESState; - signal o_part_subBytes_inst_05_o : t_opaque_AESState; - signal o_part_shiftRows_inst_05_state : t_opaque_AESState; - signal o_part_shiftRows_inst_05_o : t_opaque_AESState; - signal o_part_mixColumns_inst_5_state : t_opaque_AESState; - signal o_part_mixColumns_inst_5_o : t_opaque_AESState; - signal state_06_state : t_opaque_AESState; - signal state_06_key : t_opaque_AESRoundKey; - signal state_06_o : t_opaque_AESState; - signal o_part_subBytes_inst_06_state : t_opaque_AESState; - signal o_part_subBytes_inst_06_o : t_opaque_AESState; - signal o_part_shiftRows_inst_06_state : t_opaque_AESState; - signal o_part_shiftRows_inst_06_o : t_opaque_AESState; - signal o_part_mixColumns_inst_6_state : t_opaque_AESState; - signal o_part_mixColumns_inst_6_o : t_opaque_AESState; - signal state_07_state : t_opaque_AESState; - signal state_07_key : t_opaque_AESRoundKey; - signal state_07_o : t_opaque_AESState; - signal o_part_subBytes_inst_07_state : t_opaque_AESState; - signal o_part_subBytes_inst_07_o : t_opaque_AESState; - signal o_part_shiftRows_inst_07_state : t_opaque_AESState; - signal o_part_shiftRows_inst_07_o : t_opaque_AESState; - signal o_part_mixColumns_inst_7_state : t_opaque_AESState; - signal o_part_mixColumns_inst_7_o : t_opaque_AESState; - signal state_08_state : t_opaque_AESState; - signal state_08_key : t_opaque_AESRoundKey; - signal state_08_o : t_opaque_AESState; - signal o_part_subBytes_inst_08_state : t_opaque_AESState; - signal o_part_subBytes_inst_08_o : t_opaque_AESState; - signal o_part_shiftRows_inst_08_state : t_opaque_AESState; - signal o_part_shiftRows_inst_08_o : t_opaque_AESState; - signal o_part_mixColumns_inst_8_state : t_opaque_AESState; - signal o_part_mixColumns_inst_8_o : t_opaque_AESState; - signal state_09_state : t_opaque_AESState; - signal state_09_key : t_opaque_AESRoundKey; - signal state_09_o : t_opaque_AESState; - signal o_part_subBytes_inst_09_state : t_opaque_AESState; - signal o_part_subBytes_inst_09_o : t_opaque_AESState; - signal o_part_shiftRows_inst_09_state : t_opaque_AESState; - signal o_part_shiftRows_inst_09_o : t_opaque_AESState; - signal state_10_state : t_opaque_AESState; - signal state_10_key : t_opaque_AESRoundKey; - signal state_10_o : t_opaque_AESState; + signal keySchedule_key : AESKey; + signal keySchedule_o : AESKeySchedule; + signal state_00_state : AESState; + signal state_00_key : AESRoundKey; + signal state_00_o : AESState; + signal o_part_subBytes_inst_00_state : AESState; + signal o_part_subBytes_inst_00_o : AESState; + signal o_part_shiftRows_inst_00_state : AESState; + signal o_part_shiftRows_inst_00_o : AESState; + signal o_part_mixColumns_inst_0_state : AESState; + signal o_part_mixColumns_inst_0_o : AESState; + signal state_01_state : AESState; + signal state_01_key : AESRoundKey; + signal state_01_o : AESState; + signal o_part_subBytes_inst_01_state : AESState; + signal o_part_subBytes_inst_01_o : AESState; + signal o_part_shiftRows_inst_01_state : AESState; + signal o_part_shiftRows_inst_01_o : AESState; + signal o_part_mixColumns_inst_1_state : AESState; + signal o_part_mixColumns_inst_1_o : AESState; + signal state_02_state : AESState; + signal state_02_key : AESRoundKey; + signal state_02_o : AESState; + signal o_part_subBytes_inst_02_state : AESState; + signal o_part_subBytes_inst_02_o : AESState; + signal o_part_shiftRows_inst_02_state : AESState; + signal o_part_shiftRows_inst_02_o : AESState; + signal o_part_mixColumns_inst_2_state : AESState; + signal o_part_mixColumns_inst_2_o : AESState; + signal state_03_state : AESState; + signal state_03_key : AESRoundKey; + signal state_03_o : AESState; + signal o_part_subBytes_inst_03_state : AESState; + signal o_part_subBytes_inst_03_o : AESState; + signal o_part_shiftRows_inst_03_state : AESState; + signal o_part_shiftRows_inst_03_o : AESState; + signal o_part_mixColumns_inst_3_state : AESState; + signal o_part_mixColumns_inst_3_o : AESState; + signal state_04_state : AESState; + signal state_04_key : AESRoundKey; + signal state_04_o : AESState; + signal o_part_subBytes_inst_04_state : AESState; + signal o_part_subBytes_inst_04_o : AESState; + signal o_part_shiftRows_inst_04_state : AESState; + signal o_part_shiftRows_inst_04_o : AESState; + signal o_part_mixColumns_inst_4_state : AESState; + signal o_part_mixColumns_inst_4_o : AESState; + signal state_05_state : AESState; + signal state_05_key : AESRoundKey; + signal state_05_o : AESState; + signal o_part_subBytes_inst_05_state : AESState; + signal o_part_subBytes_inst_05_o : AESState; + signal o_part_shiftRows_inst_05_state : AESState; + signal o_part_shiftRows_inst_05_o : AESState; + signal o_part_mixColumns_inst_5_state : AESState; + signal o_part_mixColumns_inst_5_o : AESState; + signal state_06_state : AESState; + signal state_06_key : AESRoundKey; + signal state_06_o : AESState; + signal o_part_subBytes_inst_06_state : AESState; + signal o_part_subBytes_inst_06_o : AESState; + signal o_part_shiftRows_inst_06_state : AESState; + signal o_part_shiftRows_inst_06_o : AESState; + signal o_part_mixColumns_inst_6_state : AESState; + signal o_part_mixColumns_inst_6_o : AESState; + signal state_07_state : AESState; + signal state_07_key : AESRoundKey; + signal state_07_o : AESState; + signal o_part_subBytes_inst_07_state : AESState; + signal o_part_subBytes_inst_07_o : AESState; + signal o_part_shiftRows_inst_07_state : AESState; + signal o_part_shiftRows_inst_07_o : AESState; + signal o_part_mixColumns_inst_7_state : AESState; + signal o_part_mixColumns_inst_7_o : AESState; + signal state_08_state : AESState; + signal state_08_key : AESRoundKey; + signal state_08_o : AESState; + signal o_part_subBytes_inst_08_state : AESState; + signal o_part_subBytes_inst_08_o : AESState; + signal o_part_shiftRows_inst_08_state : AESState; + signal o_part_shiftRows_inst_08_o : AESState; + signal o_part_mixColumns_inst_8_state : AESState; + signal o_part_mixColumns_inst_8_o : AESState; + signal state_09_state : AESState; + signal state_09_key : AESRoundKey; + signal state_09_o : AESState; + signal o_part_subBytes_inst_09_state : AESState; + signal o_part_subBytes_inst_09_o : AESState; + signal o_part_shiftRows_inst_09_state : AESState; + signal o_part_shiftRows_inst_09_o : AESState; + signal state_10_state : AESState; + signal state_10_key : AESRoundKey; + signal state_10_o : AESState; begin keySchedule : entity work.keyExpansion(keyExpansion_arch) port map ( key => keySchedule_key, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/keyExpansion.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/keyExpansion.vhd index 9323e93de..689a96ea3 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/keyExpansion.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/keyExpansion.vhd @@ -6,211 +6,211 @@ use work.CipherNoOpaques_pkg.all; entity keyExpansion is port ( - key : in t_opaque_AESKey; - o : out t_opaque_AESKeySchedule + key : in AESKey; + o : out AESKeySchedule ); end keyExpansion; architecture keyExpansion_arch of keyExpansion is - signal w_0 : t_opaque_AESWord; - signal w_1 : t_opaque_AESWord; - signal w_2 : t_opaque_AESWord; - signal w_3 : t_opaque_AESWord; - signal o_part_000 : t_opaque_AESByte; - signal o_part_001 : t_opaque_AESByte; - signal o_part_002 : t_opaque_AESByte; - signal o_part_003 : t_opaque_AESByte; - signal o_part_004 : t_opaque_AESByte; - signal o_part_005 : t_opaque_AESByte; - signal o_part_006 : t_opaque_AESByte; - signal o_part_007 : t_opaque_AESByte; - signal o_part_008 : t_opaque_AESByte; - signal o_part_009 : t_opaque_AESByte; - signal o_part_010 : t_opaque_AESByte; - signal o_part_011 : t_opaque_AESByte; - signal lhs_part_00 : t_opaque_AESByte; - signal lhs_part_01 : t_opaque_AESByte; - signal lhs_part_02 : t_opaque_AESByte; - signal lhs_part_03 : t_opaque_AESByte; - signal lhs_part_04 : t_opaque_AESWord; - signal o_part_012 : t_opaque_AESByte; - signal o_part_013 : t_opaque_AESByte; - signal o_part_014 : t_opaque_AESByte; - signal o_part_015 : t_opaque_AESByte; - signal o_part_016 : t_opaque_AESByte; - signal o_part_017 : t_opaque_AESByte; - signal o_part_018 : t_opaque_AESByte; - signal o_part_019 : t_opaque_AESByte; - signal o_part_020 : t_opaque_AESByte; - signal o_part_021 : t_opaque_AESByte; - signal o_part_022 : t_opaque_AESByte; - signal o_part_023 : t_opaque_AESByte; - signal lhs_part_05 : t_opaque_AESByte; - signal lhs_part_06 : t_opaque_AESByte; - signal lhs_part_07 : t_opaque_AESByte; - signal lhs_part_08 : t_opaque_AESByte; - signal lhs_part_09 : t_opaque_AESWord; - signal o_part_024 : t_opaque_AESByte; - signal o_part_025 : t_opaque_AESByte; - signal o_part_026 : t_opaque_AESByte; - signal o_part_027 : t_opaque_AESByte; - signal o_part_028 : t_opaque_AESByte; - signal o_part_029 : t_opaque_AESByte; - signal o_part_030 : t_opaque_AESByte; - signal o_part_031 : t_opaque_AESByte; - signal o_part_032 : t_opaque_AESByte; - signal o_part_033 : t_opaque_AESByte; - signal o_part_034 : t_opaque_AESByte; - signal o_part_035 : t_opaque_AESByte; - signal lhs_part_10 : t_opaque_AESByte; - signal lhs_part_11 : t_opaque_AESByte; - signal lhs_part_12 : t_opaque_AESByte; - signal lhs_part_13 : t_opaque_AESByte; - signal lhs_part_14 : t_opaque_AESWord; - signal o_part_036 : t_opaque_AESByte; - signal o_part_037 : t_opaque_AESByte; - signal o_part_038 : t_opaque_AESByte; - signal o_part_039 : t_opaque_AESByte; - signal o_part_040 : t_opaque_AESByte; - signal o_part_041 : t_opaque_AESByte; - signal o_part_042 : t_opaque_AESByte; - signal o_part_043 : t_opaque_AESByte; - signal o_part_044 : t_opaque_AESByte; - signal o_part_045 : t_opaque_AESByte; - signal o_part_046 : t_opaque_AESByte; - signal o_part_047 : t_opaque_AESByte; - signal lhs_part_15 : t_opaque_AESByte; - signal lhs_part_16 : t_opaque_AESByte; - signal lhs_part_17 : t_opaque_AESByte; - signal lhs_part_18 : t_opaque_AESByte; - signal lhs_part_19 : t_opaque_AESWord; - signal o_part_048 : t_opaque_AESByte; - signal o_part_049 : t_opaque_AESByte; - signal o_part_050 : t_opaque_AESByte; - signal o_part_051 : t_opaque_AESByte; - signal o_part_052 : t_opaque_AESByte; - signal o_part_053 : t_opaque_AESByte; - signal o_part_054 : t_opaque_AESByte; - signal o_part_055 : t_opaque_AESByte; - signal o_part_056 : t_opaque_AESByte; - signal o_part_057 : t_opaque_AESByte; - signal o_part_058 : t_opaque_AESByte; - signal o_part_059 : t_opaque_AESByte; - signal lhs_part_20 : t_opaque_AESByte; - signal lhs_part_21 : t_opaque_AESByte; - signal lhs_part_22 : t_opaque_AESByte; - signal lhs_part_23 : t_opaque_AESByte; - signal lhs_part_24 : t_opaque_AESWord; - signal o_part_060 : t_opaque_AESByte; - signal o_part_061 : t_opaque_AESByte; - signal o_part_062 : t_opaque_AESByte; - signal o_part_063 : t_opaque_AESByte; - signal o_part_064 : t_opaque_AESByte; - signal o_part_065 : t_opaque_AESByte; - signal o_part_066 : t_opaque_AESByte; - signal o_part_067 : t_opaque_AESByte; - signal o_part_068 : t_opaque_AESByte; - signal o_part_069 : t_opaque_AESByte; - signal o_part_070 : t_opaque_AESByte; - signal o_part_071 : t_opaque_AESByte; - signal lhs_part_25 : t_opaque_AESByte; - signal lhs_part_26 : t_opaque_AESByte; - signal lhs_part_27 : t_opaque_AESByte; - signal lhs_part_28 : t_opaque_AESByte; - signal lhs_part_29 : t_opaque_AESWord; - signal o_part_072 : t_opaque_AESByte; - signal o_part_073 : t_opaque_AESByte; - signal o_part_074 : t_opaque_AESByte; - signal o_part_075 : t_opaque_AESByte; - signal o_part_076 : t_opaque_AESByte; - signal o_part_077 : t_opaque_AESByte; - signal o_part_078 : t_opaque_AESByte; - signal o_part_079 : t_opaque_AESByte; - signal o_part_080 : t_opaque_AESByte; - signal o_part_081 : t_opaque_AESByte; - signal o_part_082 : t_opaque_AESByte; - signal o_part_083 : t_opaque_AESByte; - signal lhs_part_30 : t_opaque_AESByte; - signal lhs_part_31 : t_opaque_AESByte; - signal lhs_part_32 : t_opaque_AESByte; - signal lhs_part_33 : t_opaque_AESByte; - signal lhs_part_34 : t_opaque_AESWord; - signal o_part_084 : t_opaque_AESByte; - signal o_part_085 : t_opaque_AESByte; - signal o_part_086 : t_opaque_AESByte; - signal o_part_087 : t_opaque_AESByte; - signal o_part_088 : t_opaque_AESByte; - signal o_part_089 : t_opaque_AESByte; - signal o_part_090 : t_opaque_AESByte; - signal o_part_091 : t_opaque_AESByte; - signal o_part_092 : t_opaque_AESByte; - signal o_part_093 : t_opaque_AESByte; - signal o_part_094 : t_opaque_AESByte; - signal o_part_095 : t_opaque_AESByte; - signal lhs_part_35 : t_opaque_AESByte; - signal lhs_part_36 : t_opaque_AESByte; - signal lhs_part_37 : t_opaque_AESByte; - signal lhs_part_38 : t_opaque_AESByte; - signal lhs_part_39 : t_opaque_AESWord; - signal o_part_096 : t_opaque_AESByte; - signal o_part_097 : t_opaque_AESByte; - signal o_part_098 : t_opaque_AESByte; - signal o_part_099 : t_opaque_AESByte; - signal o_part_100 : t_opaque_AESByte; - signal o_part_101 : t_opaque_AESByte; - signal o_part_102 : t_opaque_AESByte; - signal o_part_103 : t_opaque_AESByte; - signal o_part_104 : t_opaque_AESByte; - signal o_part_105 : t_opaque_AESByte; - signal o_part_106 : t_opaque_AESByte; - signal o_part_107 : t_opaque_AESByte; - signal lhs_part_40 : t_opaque_AESByte; - signal lhs_part_41 : t_opaque_AESByte; - signal lhs_part_42 : t_opaque_AESByte; - signal lhs_part_43 : t_opaque_AESByte; - signal lhs_part_44 : t_opaque_AESWord; - signal o_part_108 : t_opaque_AESByte; - signal o_part_109 : t_opaque_AESByte; - signal o_part_110 : t_opaque_AESByte; - signal o_part_111 : t_opaque_AESByte; - signal o_part_112 : t_opaque_AESByte; - signal o_part_113 : t_opaque_AESByte; - signal o_part_114 : t_opaque_AESByte; - signal o_part_115 : t_opaque_AESByte; - signal o_part_116 : t_opaque_AESByte; - signal o_part_117 : t_opaque_AESByte; - signal o_part_118 : t_opaque_AESByte; - signal o_part_119 : t_opaque_AESByte; - signal o_part_rotWord_inst_00_o : t_opaque_AESWord; - signal o_part_subWord_inst_00_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_00_o : t_opaque_AESWord; - signal o_part_rotWord_inst_01_o : t_opaque_AESWord; - signal o_part_subWord_inst_01_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_01_o : t_opaque_AESWord; - signal o_part_rotWord_inst_02_o : t_opaque_AESWord; - signal o_part_subWord_inst_02_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_02_o : t_opaque_AESWord; - signal o_part_rotWord_inst_03_o : t_opaque_AESWord; - signal o_part_subWord_inst_03_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_03_o : t_opaque_AESWord; - signal o_part_rotWord_inst_04_o : t_opaque_AESWord; - signal o_part_subWord_inst_04_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_04_o : t_opaque_AESWord; - signal o_part_rotWord_inst_05_o : t_opaque_AESWord; - signal o_part_subWord_inst_05_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_05_o : t_opaque_AESWord; - signal o_part_rotWord_inst_06_o : t_opaque_AESWord; - signal o_part_subWord_inst_06_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_06_o : t_opaque_AESWord; - signal o_part_rotWord_inst_07_o : t_opaque_AESWord; - signal o_part_subWord_inst_07_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_07_o : t_opaque_AESWord; - signal o_part_rotWord_inst_08_o : t_opaque_AESWord; - signal o_part_subWord_inst_08_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_08_o : t_opaque_AESWord; - signal o_part_rotWord_inst_09_o : t_opaque_AESWord; - signal o_part_subWord_inst_09_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_09_o : t_opaque_AESWord; + signal w_0 : AESWord; + signal w_1 : AESWord; + signal w_2 : AESWord; + signal w_3 : AESWord; + signal o_part_000 : AESByte; + signal o_part_001 : AESByte; + signal o_part_002 : AESByte; + signal o_part_003 : AESByte; + signal o_part_004 : AESByte; + signal o_part_005 : AESByte; + signal o_part_006 : AESByte; + signal o_part_007 : AESByte; + signal o_part_008 : AESByte; + signal o_part_009 : AESByte; + signal o_part_010 : AESByte; + signal o_part_011 : AESByte; + signal lhs_part_00 : AESByte; + signal lhs_part_01 : AESByte; + signal lhs_part_02 : AESByte; + signal lhs_part_03 : AESByte; + signal lhs_part_04 : AESWord; + signal o_part_012 : AESByte; + signal o_part_013 : AESByte; + signal o_part_014 : AESByte; + signal o_part_015 : AESByte; + signal o_part_016 : AESByte; + signal o_part_017 : AESByte; + signal o_part_018 : AESByte; + signal o_part_019 : AESByte; + signal o_part_020 : AESByte; + signal o_part_021 : AESByte; + signal o_part_022 : AESByte; + signal o_part_023 : AESByte; + signal lhs_part_05 : AESByte; + signal lhs_part_06 : AESByte; + signal lhs_part_07 : AESByte; + signal lhs_part_08 : AESByte; + signal lhs_part_09 : AESWord; + signal o_part_024 : AESByte; + signal o_part_025 : AESByte; + signal o_part_026 : AESByte; + signal o_part_027 : AESByte; + signal o_part_028 : AESByte; + signal o_part_029 : AESByte; + signal o_part_030 : AESByte; + signal o_part_031 : AESByte; + signal o_part_032 : AESByte; + signal o_part_033 : AESByte; + signal o_part_034 : AESByte; + signal o_part_035 : AESByte; + signal lhs_part_10 : AESByte; + signal lhs_part_11 : AESByte; + signal lhs_part_12 : AESByte; + signal lhs_part_13 : AESByte; + signal lhs_part_14 : AESWord; + signal o_part_036 : AESByte; + signal o_part_037 : AESByte; + signal o_part_038 : AESByte; + signal o_part_039 : AESByte; + signal o_part_040 : AESByte; + signal o_part_041 : AESByte; + signal o_part_042 : AESByte; + signal o_part_043 : AESByte; + signal o_part_044 : AESByte; + signal o_part_045 : AESByte; + signal o_part_046 : AESByte; + signal o_part_047 : AESByte; + signal lhs_part_15 : AESByte; + signal lhs_part_16 : AESByte; + signal lhs_part_17 : AESByte; + signal lhs_part_18 : AESByte; + signal lhs_part_19 : AESWord; + signal o_part_048 : AESByte; + signal o_part_049 : AESByte; + signal o_part_050 : AESByte; + signal o_part_051 : AESByte; + signal o_part_052 : AESByte; + signal o_part_053 : AESByte; + signal o_part_054 : AESByte; + signal o_part_055 : AESByte; + signal o_part_056 : AESByte; + signal o_part_057 : AESByte; + signal o_part_058 : AESByte; + signal o_part_059 : AESByte; + signal lhs_part_20 : AESByte; + signal lhs_part_21 : AESByte; + signal lhs_part_22 : AESByte; + signal lhs_part_23 : AESByte; + signal lhs_part_24 : AESWord; + signal o_part_060 : AESByte; + signal o_part_061 : AESByte; + signal o_part_062 : AESByte; + signal o_part_063 : AESByte; + signal o_part_064 : AESByte; + signal o_part_065 : AESByte; + signal o_part_066 : AESByte; + signal o_part_067 : AESByte; + signal o_part_068 : AESByte; + signal o_part_069 : AESByte; + signal o_part_070 : AESByte; + signal o_part_071 : AESByte; + signal lhs_part_25 : AESByte; + signal lhs_part_26 : AESByte; + signal lhs_part_27 : AESByte; + signal lhs_part_28 : AESByte; + signal lhs_part_29 : AESWord; + signal o_part_072 : AESByte; + signal o_part_073 : AESByte; + signal o_part_074 : AESByte; + signal o_part_075 : AESByte; + signal o_part_076 : AESByte; + signal o_part_077 : AESByte; + signal o_part_078 : AESByte; + signal o_part_079 : AESByte; + signal o_part_080 : AESByte; + signal o_part_081 : AESByte; + signal o_part_082 : AESByte; + signal o_part_083 : AESByte; + signal lhs_part_30 : AESByte; + signal lhs_part_31 : AESByte; + signal lhs_part_32 : AESByte; + signal lhs_part_33 : AESByte; + signal lhs_part_34 : AESWord; + signal o_part_084 : AESByte; + signal o_part_085 : AESByte; + signal o_part_086 : AESByte; + signal o_part_087 : AESByte; + signal o_part_088 : AESByte; + signal o_part_089 : AESByte; + signal o_part_090 : AESByte; + signal o_part_091 : AESByte; + signal o_part_092 : AESByte; + signal o_part_093 : AESByte; + signal o_part_094 : AESByte; + signal o_part_095 : AESByte; + signal lhs_part_35 : AESByte; + signal lhs_part_36 : AESByte; + signal lhs_part_37 : AESByte; + signal lhs_part_38 : AESByte; + signal lhs_part_39 : AESWord; + signal o_part_096 : AESByte; + signal o_part_097 : AESByte; + signal o_part_098 : AESByte; + signal o_part_099 : AESByte; + signal o_part_100 : AESByte; + signal o_part_101 : AESByte; + signal o_part_102 : AESByte; + signal o_part_103 : AESByte; + signal o_part_104 : AESByte; + signal o_part_105 : AESByte; + signal o_part_106 : AESByte; + signal o_part_107 : AESByte; + signal lhs_part_40 : AESByte; + signal lhs_part_41 : AESByte; + signal lhs_part_42 : AESByte; + signal lhs_part_43 : AESByte; + signal lhs_part_44 : AESWord; + signal o_part_108 : AESByte; + signal o_part_109 : AESByte; + signal o_part_110 : AESByte; + signal o_part_111 : AESByte; + signal o_part_112 : AESByte; + signal o_part_113 : AESByte; + signal o_part_114 : AESByte; + signal o_part_115 : AESByte; + signal o_part_116 : AESByte; + signal o_part_117 : AESByte; + signal o_part_118 : AESByte; + signal o_part_119 : AESByte; + signal o_part_rotWord_inst_00_o : AESWord; + signal o_part_subWord_inst_00_lhs : AESWord; + signal o_part_subWord_inst_00_o : AESWord; + signal o_part_rotWord_inst_01_o : AESWord; + signal o_part_subWord_inst_01_lhs : AESWord; + signal o_part_subWord_inst_01_o : AESWord; + signal o_part_rotWord_inst_02_o : AESWord; + signal o_part_subWord_inst_02_lhs : AESWord; + signal o_part_subWord_inst_02_o : AESWord; + signal o_part_rotWord_inst_03_o : AESWord; + signal o_part_subWord_inst_03_lhs : AESWord; + signal o_part_subWord_inst_03_o : AESWord; + signal o_part_rotWord_inst_04_o : AESWord; + signal o_part_subWord_inst_04_lhs : AESWord; + signal o_part_subWord_inst_04_o : AESWord; + signal o_part_rotWord_inst_05_o : AESWord; + signal o_part_subWord_inst_05_lhs : AESWord; + signal o_part_subWord_inst_05_o : AESWord; + signal o_part_rotWord_inst_06_o : AESWord; + signal o_part_subWord_inst_06_lhs : AESWord; + signal o_part_subWord_inst_06_o : AESWord; + signal o_part_rotWord_inst_07_o : AESWord; + signal o_part_subWord_inst_07_lhs : AESWord; + signal o_part_subWord_inst_07_o : AESWord; + signal o_part_rotWord_inst_08_o : AESWord; + signal o_part_subWord_inst_08_lhs : AESWord; + signal o_part_subWord_inst_08_o : AESWord; + signal o_part_rotWord_inst_09_o : AESWord; + signal o_part_subWord_inst_09_lhs : AESWord; + signal o_part_subWord_inst_09_o : AESWord; begin o_part_rotWord_inst_00 : entity work.rotWord(rotWord_arch) port map ( o => o_part_rotWord_inst_00_o, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mixColumns.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mixColumns.vhd index 79a254972..d5a19b5b5 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mixColumns.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mixColumns.vhd @@ -6,140 +6,140 @@ use work.CipherNoOpaques_pkg.all; entity mixColumns is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end mixColumns; architecture mixColumns_arch of mixColumns is - signal o_part_mulByte_0_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_15_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_16_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_16_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_17_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_17_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_18_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_18_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_19_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_19_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_20_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_20_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_21_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_21_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_22_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_22_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_23_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_23_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_24_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_24_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_25_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_25_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_26_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_26_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_27_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_27_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_28_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_28_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_29_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_29_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_15_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_30_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_30_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_31_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_31_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_15_o : t_opaque_AESByte; + signal o_part_mulByte_0_inst_00_rhs : AESByte; + signal o_part_mulByte_0_inst_00_o : AESByte; + signal o_part_mulByte_1_inst_00_rhs : AESByte; + signal o_part_mulByte_1_inst_00_o : AESByte; + signal o_part_mulByte_2_inst_00_rhs : AESByte; + signal o_part_mulByte_2_inst_00_o : AESByte; + signal o_part_mulByte_2_inst_01_rhs : AESByte; + signal o_part_mulByte_2_inst_01_o : AESByte; + signal o_part_mulByte_2_inst_02_rhs : AESByte; + signal o_part_mulByte_2_inst_02_o : AESByte; + signal o_part_mulByte_0_inst_01_rhs : AESByte; + signal o_part_mulByte_0_inst_01_o : AESByte; + signal o_part_mulByte_1_inst_01_rhs : AESByte; + signal o_part_mulByte_1_inst_01_o : AESByte; + signal o_part_mulByte_2_inst_03_rhs : AESByte; + signal o_part_mulByte_2_inst_03_o : AESByte; + signal o_part_mulByte_2_inst_04_rhs : AESByte; + signal o_part_mulByte_2_inst_04_o : AESByte; + signal o_part_mulByte_2_inst_05_rhs : AESByte; + signal o_part_mulByte_2_inst_05_o : AESByte; + signal o_part_mulByte_0_inst_02_rhs : AESByte; + signal o_part_mulByte_0_inst_02_o : AESByte; + signal o_part_mulByte_1_inst_02_rhs : AESByte; + signal o_part_mulByte_1_inst_02_o : AESByte; + signal o_part_mulByte_1_inst_03_rhs : AESByte; + signal o_part_mulByte_1_inst_03_o : AESByte; + signal o_part_mulByte_2_inst_06_rhs : AESByte; + signal o_part_mulByte_2_inst_06_o : AESByte; + signal o_part_mulByte_2_inst_07_rhs : AESByte; + signal o_part_mulByte_2_inst_07_o : AESByte; + signal o_part_mulByte_0_inst_03_rhs : AESByte; + signal o_part_mulByte_0_inst_03_o : AESByte; + signal o_part_mulByte_0_inst_04_rhs : AESByte; + signal o_part_mulByte_0_inst_04_o : AESByte; + signal o_part_mulByte_1_inst_04_rhs : AESByte; + signal o_part_mulByte_1_inst_04_o : AESByte; + signal o_part_mulByte_2_inst_08_rhs : AESByte; + signal o_part_mulByte_2_inst_08_o : AESByte; + signal o_part_mulByte_2_inst_09_rhs : AESByte; + signal o_part_mulByte_2_inst_09_o : AESByte; + signal o_part_mulByte_2_inst_10_rhs : AESByte; + signal o_part_mulByte_2_inst_10_o : AESByte; + signal o_part_mulByte_0_inst_05_rhs : AESByte; + signal o_part_mulByte_0_inst_05_o : AESByte; + signal o_part_mulByte_1_inst_05_rhs : AESByte; + signal o_part_mulByte_1_inst_05_o : AESByte; + signal o_part_mulByte_2_inst_11_rhs : AESByte; + signal o_part_mulByte_2_inst_11_o : AESByte; + signal o_part_mulByte_2_inst_12_rhs : AESByte; + signal o_part_mulByte_2_inst_12_o : AESByte; + signal o_part_mulByte_2_inst_13_rhs : AESByte; + signal o_part_mulByte_2_inst_13_o : AESByte; + signal o_part_mulByte_0_inst_06_rhs : AESByte; + signal o_part_mulByte_0_inst_06_o : AESByte; + signal o_part_mulByte_1_inst_06_rhs : AESByte; + signal o_part_mulByte_1_inst_06_o : AESByte; + signal o_part_mulByte_1_inst_07_rhs : AESByte; + signal o_part_mulByte_1_inst_07_o : AESByte; + signal o_part_mulByte_2_inst_14_rhs : AESByte; + signal o_part_mulByte_2_inst_14_o : AESByte; + signal o_part_mulByte_2_inst_15_rhs : AESByte; + signal o_part_mulByte_2_inst_15_o : AESByte; + signal o_part_mulByte_0_inst_07_rhs : AESByte; + signal o_part_mulByte_0_inst_07_o : AESByte; + signal o_part_mulByte_0_inst_08_rhs : AESByte; + signal o_part_mulByte_0_inst_08_o : AESByte; + signal o_part_mulByte_1_inst_08_rhs : AESByte; + signal o_part_mulByte_1_inst_08_o : AESByte; + signal o_part_mulByte_2_inst_16_rhs : AESByte; + signal o_part_mulByte_2_inst_16_o : AESByte; + signal o_part_mulByte_2_inst_17_rhs : AESByte; + signal o_part_mulByte_2_inst_17_o : AESByte; + signal o_part_mulByte_2_inst_18_rhs : AESByte; + signal o_part_mulByte_2_inst_18_o : AESByte; + signal o_part_mulByte_0_inst_09_rhs : AESByte; + signal o_part_mulByte_0_inst_09_o : AESByte; + signal o_part_mulByte_1_inst_09_rhs : AESByte; + signal o_part_mulByte_1_inst_09_o : AESByte; + signal o_part_mulByte_2_inst_19_rhs : AESByte; + signal o_part_mulByte_2_inst_19_o : AESByte; + signal o_part_mulByte_2_inst_20_rhs : AESByte; + signal o_part_mulByte_2_inst_20_o : AESByte; + signal o_part_mulByte_2_inst_21_rhs : AESByte; + signal o_part_mulByte_2_inst_21_o : AESByte; + signal o_part_mulByte_0_inst_10_rhs : AESByte; + signal o_part_mulByte_0_inst_10_o : AESByte; + signal o_part_mulByte_1_inst_10_rhs : AESByte; + signal o_part_mulByte_1_inst_10_o : AESByte; + signal o_part_mulByte_1_inst_11_rhs : AESByte; + signal o_part_mulByte_1_inst_11_o : AESByte; + signal o_part_mulByte_2_inst_22_rhs : AESByte; + signal o_part_mulByte_2_inst_22_o : AESByte; + signal o_part_mulByte_2_inst_23_rhs : AESByte; + signal o_part_mulByte_2_inst_23_o : AESByte; + signal o_part_mulByte_0_inst_11_rhs : AESByte; + signal o_part_mulByte_0_inst_11_o : AESByte; + signal o_part_mulByte_0_inst_12_rhs : AESByte; + signal o_part_mulByte_0_inst_12_o : AESByte; + signal o_part_mulByte_1_inst_12_rhs : AESByte; + signal o_part_mulByte_1_inst_12_o : AESByte; + signal o_part_mulByte_2_inst_24_rhs : AESByte; + signal o_part_mulByte_2_inst_24_o : AESByte; + signal o_part_mulByte_2_inst_25_rhs : AESByte; + signal o_part_mulByte_2_inst_25_o : AESByte; + signal o_part_mulByte_2_inst_26_rhs : AESByte; + signal o_part_mulByte_2_inst_26_o : AESByte; + signal o_part_mulByte_0_inst_13_rhs : AESByte; + signal o_part_mulByte_0_inst_13_o : AESByte; + signal o_part_mulByte_1_inst_13_rhs : AESByte; + signal o_part_mulByte_1_inst_13_o : AESByte; + signal o_part_mulByte_2_inst_27_rhs : AESByte; + signal o_part_mulByte_2_inst_27_o : AESByte; + signal o_part_mulByte_2_inst_28_rhs : AESByte; + signal o_part_mulByte_2_inst_28_o : AESByte; + signal o_part_mulByte_2_inst_29_rhs : AESByte; + signal o_part_mulByte_2_inst_29_o : AESByte; + signal o_part_mulByte_0_inst_14_rhs : AESByte; + signal o_part_mulByte_0_inst_14_o : AESByte; + signal o_part_mulByte_1_inst_14_rhs : AESByte; + signal o_part_mulByte_1_inst_14_o : AESByte; + signal o_part_mulByte_1_inst_15_rhs : AESByte; + signal o_part_mulByte_1_inst_15_o : AESByte; + signal o_part_mulByte_2_inst_30_rhs : AESByte; + signal o_part_mulByte_2_inst_30_o : AESByte; + signal o_part_mulByte_2_inst_31_rhs : AESByte; + signal o_part_mulByte_2_inst_31_o : AESByte; + signal o_part_mulByte_0_inst_15_rhs : AESByte; + signal o_part_mulByte_0_inst_15_o : AESByte; begin o_part_mulByte_0_inst_00 : entity work.mulByte_0(mulByte_0_arch) generic map ( lhs => x"02" diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_0.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_0.vhd index b7763944b..fc2aa822d 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_0.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_0.vhd @@ -9,14 +9,14 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_0; architecture mulByte_0_arch of mulByte_0 is - signal a_lhs : t_opaque_AESByte; - signal a_o : t_opaque_AESByte; + signal a_lhs : AESByte; + signal a_o : AESByte; begin a : entity work.xtime(xtime_arch) port map ( lhs => a_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_1.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_1.vhd index 26260a4ca..7376ef24a 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_1.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_1.vhd @@ -9,14 +9,14 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_1; architecture mulByte_1_arch of mulByte_1 is - signal a_lhs : t_opaque_AESByte; - signal a_o : t_opaque_AESByte; + signal a_lhs : AESByte; + signal a_o : AESByte; begin a : entity work.xtime(xtime_arch) port map ( lhs => a_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_2.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_2.vhd index a6a3f3324..eeb6713e5 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_2.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_2.vhd @@ -9,8 +9,8 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_2; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/rotWord.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/rotWord.vhd index de8d0c084..5c4d543f0 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/rotWord.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/rotWord.vhd @@ -6,8 +6,8 @@ use work.CipherNoOpaques_pkg.all; entity rotWord is port ( - lhs : in t_opaque_AESWord; - o : out t_opaque_AESWord + lhs : in AESWord; + o : out AESWord ); end rotWord; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/sbox.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/sbox.vhd index 9142b8bbc..5ba37869c 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/sbox.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/sbox.vhd @@ -6,8 +6,8 @@ use work.CipherNoOpaques_pkg.all; entity sbox is port ( - lhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + lhs : in AESByte; + o : out AESByte ); end sbox; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/shiftRows.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/shiftRows.vhd index 3b839140b..d01966db4 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/shiftRows.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/shiftRows.vhd @@ -6,8 +6,8 @@ use work.CipherNoOpaques_pkg.all; entity shiftRows is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end shiftRows; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/subBytes.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/subBytes.vhd index 6ecb54c44..1dff4f8a2 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/subBytes.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/subBytes.vhd @@ -6,44 +6,44 @@ use work.CipherNoOpaques_pkg.all; entity subBytes is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end subBytes; architecture subBytes_arch of subBytes is - signal o_part_sbox_inst_00_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_00_o : t_opaque_AESByte; - signal o_part_sbox_inst_01_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_01_o : t_opaque_AESByte; - signal o_part_sbox_inst_02_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_02_o : t_opaque_AESByte; - signal o_part_sbox_inst_03_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_03_o : t_opaque_AESByte; - signal o_part_sbox_inst_04_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_04_o : t_opaque_AESByte; - signal o_part_sbox_inst_05_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_05_o : t_opaque_AESByte; - signal o_part_sbox_inst_06_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_06_o : t_opaque_AESByte; - signal o_part_sbox_inst_07_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_07_o : t_opaque_AESByte; - signal o_part_sbox_inst_08_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_08_o : t_opaque_AESByte; - signal o_part_sbox_inst_09_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_09_o : t_opaque_AESByte; - signal o_part_sbox_inst_10_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_10_o : t_opaque_AESByte; - signal o_part_sbox_inst_11_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_11_o : t_opaque_AESByte; - signal o_part_sbox_inst_12_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_12_o : t_opaque_AESByte; - signal o_part_sbox_inst_13_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_13_o : t_opaque_AESByte; - signal o_part_sbox_inst_14_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_14_o : t_opaque_AESByte; - signal o_part_sbox_inst_15_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_15_o : t_opaque_AESByte; + signal o_part_sbox_inst_00_lhs : AESByte; + signal o_part_sbox_inst_00_o : AESByte; + signal o_part_sbox_inst_01_lhs : AESByte; + signal o_part_sbox_inst_01_o : AESByte; + signal o_part_sbox_inst_02_lhs : AESByte; + signal o_part_sbox_inst_02_o : AESByte; + signal o_part_sbox_inst_03_lhs : AESByte; + signal o_part_sbox_inst_03_o : AESByte; + signal o_part_sbox_inst_04_lhs : AESByte; + signal o_part_sbox_inst_04_o : AESByte; + signal o_part_sbox_inst_05_lhs : AESByte; + signal o_part_sbox_inst_05_o : AESByte; + signal o_part_sbox_inst_06_lhs : AESByte; + signal o_part_sbox_inst_06_o : AESByte; + signal o_part_sbox_inst_07_lhs : AESByte; + signal o_part_sbox_inst_07_o : AESByte; + signal o_part_sbox_inst_08_lhs : AESByte; + signal o_part_sbox_inst_08_o : AESByte; + signal o_part_sbox_inst_09_lhs : AESByte; + signal o_part_sbox_inst_09_o : AESByte; + signal o_part_sbox_inst_10_lhs : AESByte; + signal o_part_sbox_inst_10_o : AESByte; + signal o_part_sbox_inst_11_lhs : AESByte; + signal o_part_sbox_inst_11_o : AESByte; + signal o_part_sbox_inst_12_lhs : AESByte; + signal o_part_sbox_inst_12_o : AESByte; + signal o_part_sbox_inst_13_lhs : AESByte; + signal o_part_sbox_inst_13_o : AESByte; + signal o_part_sbox_inst_14_lhs : AESByte; + signal o_part_sbox_inst_14_o : AESByte; + signal o_part_sbox_inst_15_lhs : AESByte; + signal o_part_sbox_inst_15_o : AESByte; begin o_part_sbox_inst_00 : entity work.sbox(sbox_arch) port map ( lhs => o_part_sbox_inst_00_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/subWord.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/subWord.vhd index 170d3afb8..cf14b8e9c 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/subWord.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/subWord.vhd @@ -6,20 +6,20 @@ use work.CipherNoOpaques_pkg.all; entity subWord is port ( - lhs : in t_opaque_AESWord; - o : out t_opaque_AESWord + lhs : in AESWord; + o : out AESWord ); end subWord; architecture subWord_arch of subWord is - signal o_part_sbox_inst_0_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_0_o : t_opaque_AESByte; - signal o_part_sbox_inst_1_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_1_o : t_opaque_AESByte; - signal o_part_sbox_inst_2_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_2_o : t_opaque_AESByte; - signal o_part_sbox_inst_3_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_3_o : t_opaque_AESByte; + signal o_part_sbox_inst_0_lhs : AESByte; + signal o_part_sbox_inst_0_o : AESByte; + signal o_part_sbox_inst_1_lhs : AESByte; + signal o_part_sbox_inst_1_o : AESByte; + signal o_part_sbox_inst_2_lhs : AESByte; + signal o_part_sbox_inst_2_o : AESByte; + signal o_part_sbox_inst_3_lhs : AESByte; + signal o_part_sbox_inst_3_o : AESByte; begin o_part_sbox_inst_0 : entity work.sbox(sbox_arch) port map ( lhs => o_part_sbox_inst_0_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/xtime.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/xtime.vhd index e02ba48bd..c7415057e 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/xtime.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v2008/hdl/xtime.vhd @@ -6,8 +6,8 @@ use work.CipherNoOpaques_pkg.all; entity xtime is port ( - lhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + lhs : in AESByte; + o : out AESByte ); end xtime; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/CipherNoOpaques.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/CipherNoOpaques.vhd index 3ea706028..3b07fcafd 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/CipherNoOpaques.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/CipherNoOpaques.vhd @@ -6,16 +6,16 @@ use work.CipherNoOpaques_pkg.all; entity CipherNoOpaques is port ( - key : in t_opaque_AESKey; - data : in t_opaque_AESData; - o : out t_opaque_AESData + key : in AESKey; + data : in AESData; + o : out AESData ); end CipherNoOpaques; architecture CipherNoOpaques_arch of CipherNoOpaques is - signal o_part_cipher_inst_data : t_opaque_AESData; - signal o_part_cipher_inst_key : t_opaque_AESKey; - signal o_part_cipher_inst_o : t_opaque_AESData; + signal o_part_cipher_inst_data : AESData; + signal o_part_cipher_inst_key : AESKey; + signal o_part_cipher_inst_o : AESData; begin o_part_cipher_inst : entity work.cipher(cipher_arch) port map ( data => o_part_cipher_inst_data, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/CipherNoOpaques_pkg.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/CipherNoOpaques_pkg.vhd index 23b32f616..a9846d7a6 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/CipherNoOpaques_pkg.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/CipherNoOpaques_pkg.vhd @@ -4,46 +4,46 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package CipherNoOpaques_pkg is -subtype t_opaque_AESByte is std_logic_vector(7 downto 0); -function to_t_opaque_AESByte(A: std_logic_vector) return t_opaque_AESByte; -type t_arrX4_t_opaque_AESByte is array (0 to 4 - 1) of t_opaque_AESByte; -function bitWidth(A : t_arrX4_t_opaque_AESByte) return integer; -function to_slv(A : t_arrX4_t_opaque_AESByte) return std_logic_vector; -function to_t_arrX4_t_opaque_AESByte(A : std_logic_vector) return t_arrX4_t_opaque_AESByte; -function bool_sel(C : boolean; T : t_arrX4_t_opaque_AESByte; F : t_arrX4_t_opaque_AESByte) return t_arrX4_t_opaque_AESByte; -subtype t_opaque_AESWord is t_arrX4_t_opaque_AESByte; -function to_t_opaque_AESWord(A: std_logic_vector) return t_opaque_AESWord; -type t_arrX4_t_opaque_AESWord is array (0 to 4 - 1) of t_opaque_AESWord; -function bitWidth(A : t_arrX4_t_opaque_AESWord) return integer; -function to_slv(A : t_arrX4_t_opaque_AESWord) return std_logic_vector; -function to_t_arrX4_t_opaque_AESWord(A : std_logic_vector) return t_arrX4_t_opaque_AESWord; -function bool_sel(C : boolean; T : t_arrX4_t_opaque_AESWord; F : t_arrX4_t_opaque_AESWord) return t_arrX4_t_opaque_AESWord; -subtype t_opaque_AESKey is t_arrX4_t_opaque_AESWord; -function to_t_opaque_AESKey(A: std_logic_vector) return t_opaque_AESKey; -subtype t_opaque_AESData is t_arrX4_t_opaque_AESWord; -function to_t_opaque_AESData(A: std_logic_vector) return t_opaque_AESData; -type t_arrX11_t_opaque_AESWord is array (0 to 11 - 1) of t_opaque_AESWord; -function bitWidth(A : t_arrX11_t_opaque_AESWord) return integer; -function to_slv(A : t_arrX11_t_opaque_AESWord) return std_logic_vector; -function to_t_arrX11_t_opaque_AESWord(A : std_logic_vector) return t_arrX11_t_opaque_AESWord; -function bool_sel(C : boolean; T : t_arrX11_t_opaque_AESWord; F : t_arrX11_t_opaque_AESWord) return t_arrX11_t_opaque_AESWord; -type t_arrX44_t_opaque_AESWord is array (0 to 44 - 1) of t_opaque_AESWord; -function bitWidth(A : t_arrX44_t_opaque_AESWord) return integer; -function to_slv(A : t_arrX44_t_opaque_AESWord) return std_logic_vector; -function to_t_arrX44_t_opaque_AESWord(A : std_logic_vector) return t_arrX44_t_opaque_AESWord; -function bool_sel(C : boolean; T : t_arrX44_t_opaque_AESWord; F : t_arrX44_t_opaque_AESWord) return t_arrX44_t_opaque_AESWord; -subtype t_opaque_AESKeySchedule is t_arrX44_t_opaque_AESWord; -function to_t_opaque_AESKeySchedule(A: std_logic_vector) return t_opaque_AESKeySchedule; +subtype AESByte is std_logic_vector(7 downto 0); +function to_AESByte(A: std_logic_vector) return AESByte; +type t_arrX4_AESByte is array (0 to 4 - 1) of AESByte; +function bitWidth(A : t_arrX4_AESByte) return integer; +function to_slv(A : t_arrX4_AESByte) return std_logic_vector; +function to_t_arrX4_AESByte(A : std_logic_vector) return t_arrX4_AESByte; +function bool_sel(C : boolean; T : t_arrX4_AESByte; F : t_arrX4_AESByte) return t_arrX4_AESByte; +subtype AESWord is t_arrX4_AESByte; +function to_AESWord(A: std_logic_vector) return AESWord; +type t_arrX4_AESWord is array (0 to 4 - 1) of AESWord; +function bitWidth(A : t_arrX4_AESWord) return integer; +function to_slv(A : t_arrX4_AESWord) return std_logic_vector; +function to_t_arrX4_AESWord(A : std_logic_vector) return t_arrX4_AESWord; +function bool_sel(C : boolean; T : t_arrX4_AESWord; F : t_arrX4_AESWord) return t_arrX4_AESWord; +subtype AESKey is t_arrX4_AESWord; +function to_AESKey(A: std_logic_vector) return AESKey; +subtype AESData is t_arrX4_AESWord; +function to_AESData(A: std_logic_vector) return AESData; +type t_arrX11_AESWord is array (0 to 11 - 1) of AESWord; +function bitWidth(A : t_arrX11_AESWord) return integer; +function to_slv(A : t_arrX11_AESWord) return std_logic_vector; +function to_t_arrX11_AESWord(A : std_logic_vector) return t_arrX11_AESWord; +function bool_sel(C : boolean; T : t_arrX11_AESWord; F : t_arrX11_AESWord) return t_arrX11_AESWord; +type t_arrX44_AESWord is array (0 to 44 - 1) of AESWord; +function bitWidth(A : t_arrX44_AESWord) return integer; +function to_slv(A : t_arrX44_AESWord) return std_logic_vector; +function to_t_arrX44_AESWord(A : std_logic_vector) return t_arrX44_AESWord; +function bool_sel(C : boolean; T : t_arrX44_AESWord; F : t_arrX44_AESWord) return t_arrX44_AESWord; +subtype AESKeySchedule is t_arrX44_AESWord; +function to_AESKeySchedule(A: std_logic_vector) return AESKeySchedule; type t_arrX256_slv8 is array (0 to 256 - 1) of std_logic_vector(7 downto 0); function bitWidth(A : t_arrX256_slv8) return integer; function to_slv(A : t_arrX256_slv8) return std_logic_vector; function to_t_arrX256_slv8(A : std_logic_vector) return t_arrX256_slv8; function bool_sel(C : boolean; T : t_arrX256_slv8; F : t_arrX256_slv8) return t_arrX256_slv8; -subtype t_opaque_AESState is t_arrX4_t_opaque_AESWord; -function to_t_opaque_AESState(A: std_logic_vector) return t_opaque_AESState; -subtype t_opaque_AESRoundKey is t_arrX4_t_opaque_AESWord; -function to_t_opaque_AESRoundKey(A: std_logic_vector) return t_opaque_AESRoundKey; -constant Rcon : t_arrX11_t_opaque_AESWord := ( +subtype AESState is t_arrX4_AESWord; +function to_AESState(A: std_logic_vector) return AESState; +subtype AESRoundKey is t_arrX4_AESWord; +function to_AESRoundKey(A: std_logic_vector) return AESRoundKey; +constant Rcon : t_arrX11_AESWord := ( 0 => (0 => x"00", 1 => x"00", 2 => x"00", 3 => x"00"), 1 => (0 => x"01", 1 => x"00", 2 => x"00", 3 => x"00"), 2 => (0 => x"02", 1 => x"00", 2 => x"00", 3 => x"00"), 3 => (0 => x"04", 1 => x"00", 2 => x"00", 3 => x"00"), 4 => (0 => x"08", 1 => x"00", 2 => x"00", 3 => x"00"), 5 => (0 => x"10", 1 => x"00", 2 => x"00", 3 => x"00"), @@ -88,53 +88,53 @@ constant sboxLookupTable : t_arrX256_slv8 := ( end package CipherNoOpaques_pkg; package body CipherNoOpaques_pkg is -function to_t_opaque_AESByte(A : std_logic_vector) return t_opaque_AESByte is +function to_AESByte(A : std_logic_vector) return AESByte is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; return A0; end; -function to_t_opaque_AESWord(A : std_logic_vector) return t_opaque_AESWord is +function to_AESWord(A : std_logic_vector) return AESWord is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESByte(A0); + return to_t_arrX4_AESByte(A0); end; -function to_t_opaque_AESKey(A : std_logic_vector) return t_opaque_AESKey is +function to_AESKey(A : std_logic_vector) return AESKey is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESWord(A0); + return to_t_arrX4_AESWord(A0); end; -function to_t_opaque_AESData(A : std_logic_vector) return t_opaque_AESData is +function to_AESData(A : std_logic_vector) return AESData is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESWord(A0); + return to_t_arrX4_AESWord(A0); end; -function to_t_opaque_AESKeySchedule(A : std_logic_vector) return t_opaque_AESKeySchedule is +function to_AESKeySchedule(A : std_logic_vector) return AESKeySchedule is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX44_t_opaque_AESWord(A0); + return to_t_arrX44_AESWord(A0); end; -function to_t_opaque_AESState(A : std_logic_vector) return t_opaque_AESState is +function to_AESState(A : std_logic_vector) return AESState is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESWord(A0); + return to_t_arrX4_AESWord(A0); end; -function to_t_opaque_AESRoundKey(A : std_logic_vector) return t_opaque_AESRoundKey is +function to_AESRoundKey(A : std_logic_vector) return AESRoundKey is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESWord(A0); + return to_t_arrX4_AESWord(A0); end; -function bitWidth(A : t_arrX4_t_opaque_AESByte) return integer is +function bitWidth(A : t_arrX4_AESByte) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX4_t_opaque_AESByte) return std_logic_vector is +function to_slv(A : t_arrX4_AESByte) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -148,21 +148,21 @@ begin end loop; return ret; end; -function to_t_arrX4_t_opaque_AESByte(A : std_logic_vector) return t_arrX4_t_opaque_AESByte is +function to_t_arrX4_AESByte(A : std_logic_vector) return t_arrX4_AESByte is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX4_t_opaque_AESByte; + variable ret : t_arrX4_AESByte; begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESByte(A(hi downto lo)); + ret(i) := to_AESByte(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX4_t_opaque_AESByte; F : t_arrX4_t_opaque_AESByte) return t_arrX4_t_opaque_AESByte is +function bool_sel(C : boolean; T : t_arrX4_AESByte; F : t_arrX4_AESByte) return t_arrX4_AESByte is begin if C then return T; @@ -170,11 +170,11 @@ begin return F; end if; end; -function bitWidth(A : t_arrX4_t_opaque_AESWord) return integer is +function bitWidth(A : t_arrX4_AESWord) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX4_t_opaque_AESWord) return std_logic_vector is +function to_slv(A : t_arrX4_AESWord) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -188,21 +188,21 @@ begin end loop; return ret; end; -function to_t_arrX4_t_opaque_AESWord(A : std_logic_vector) return t_arrX4_t_opaque_AESWord is +function to_t_arrX4_AESWord(A : std_logic_vector) return t_arrX4_AESWord is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX4_t_opaque_AESWord; + variable ret : t_arrX4_AESWord; begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESWord(A(hi downto lo)); + ret(i) := to_AESWord(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX4_t_opaque_AESWord; F : t_arrX4_t_opaque_AESWord) return t_arrX4_t_opaque_AESWord is +function bool_sel(C : boolean; T : t_arrX4_AESWord; F : t_arrX4_AESWord) return t_arrX4_AESWord is begin if C then return T; @@ -210,11 +210,11 @@ begin return F; end if; end; -function bitWidth(A : t_arrX11_t_opaque_AESWord) return integer is +function bitWidth(A : t_arrX11_AESWord) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX11_t_opaque_AESWord) return std_logic_vector is +function to_slv(A : t_arrX11_AESWord) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -228,21 +228,21 @@ begin end loop; return ret; end; -function to_t_arrX11_t_opaque_AESWord(A : std_logic_vector) return t_arrX11_t_opaque_AESWord is +function to_t_arrX11_AESWord(A : std_logic_vector) return t_arrX11_AESWord is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX11_t_opaque_AESWord; + variable ret : t_arrX11_AESWord; begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESWord(A(hi downto lo)); + ret(i) := to_AESWord(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX11_t_opaque_AESWord; F : t_arrX11_t_opaque_AESWord) return t_arrX11_t_opaque_AESWord is +function bool_sel(C : boolean; T : t_arrX11_AESWord; F : t_arrX11_AESWord) return t_arrX11_AESWord is begin if C then return T; @@ -250,11 +250,11 @@ begin return F; end if; end; -function bitWidth(A : t_arrX44_t_opaque_AESWord) return integer is +function bitWidth(A : t_arrX44_AESWord) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX44_t_opaque_AESWord) return std_logic_vector is +function to_slv(A : t_arrX44_AESWord) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -268,21 +268,21 @@ begin end loop; return ret; end; -function to_t_arrX44_t_opaque_AESWord(A : std_logic_vector) return t_arrX44_t_opaque_AESWord is +function to_t_arrX44_AESWord(A : std_logic_vector) return t_arrX44_AESWord is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX44_t_opaque_AESWord; + variable ret : t_arrX44_AESWord; begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESWord(A(hi downto lo)); + ret(i) := to_AESWord(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX44_t_opaque_AESWord; F : t_arrX44_t_opaque_AESWord) return t_arrX44_t_opaque_AESWord is +function bool_sel(C : boolean; T : t_arrX44_AESWord; F : t_arrX44_AESWord) return t_arrX44_AESWord is begin if C then return T; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/addRoundKey.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/addRoundKey.vhd index 67d0ef687..b4c65f0a0 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/addRoundKey.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/addRoundKey.vhd @@ -6,9 +6,9 @@ use work.CipherNoOpaques_pkg.all; entity addRoundKey is port ( - state : in t_opaque_AESState; - key : in t_opaque_AESRoundKey; - o : out t_opaque_AESState + state : in AESState; + key : in AESRoundKey; + o : out AESState ); end addRoundKey; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/cipher.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/cipher.vhd index 21796f882..7f9543711 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/cipher.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/cipher.vhd @@ -6,106 +6,106 @@ use work.CipherNoOpaques_pkg.all; entity cipher is port ( - data : in t_opaque_AESData; - key : in t_opaque_AESKey; - o : out t_opaque_AESData + data : in AESData; + key : in AESKey; + o : out AESData ); end cipher; architecture cipher_arch of cipher is - signal keySchedule_key : t_opaque_AESKey; - signal keySchedule_o : t_opaque_AESKeySchedule; - signal state_00_state : t_opaque_AESState; - signal state_00_key : t_opaque_AESRoundKey; - signal state_00_o : t_opaque_AESState; - signal o_part_subBytes_inst_00_state : t_opaque_AESState; - signal o_part_subBytes_inst_00_o : t_opaque_AESState; - signal o_part_shiftRows_inst_00_state : t_opaque_AESState; - signal o_part_shiftRows_inst_00_o : t_opaque_AESState; - signal o_part_mixColumns_inst_0_state : t_opaque_AESState; - signal o_part_mixColumns_inst_0_o : t_opaque_AESState; - signal state_01_state : t_opaque_AESState; - signal state_01_key : t_opaque_AESRoundKey; - signal state_01_o : t_opaque_AESState; - signal o_part_subBytes_inst_01_state : t_opaque_AESState; - signal o_part_subBytes_inst_01_o : t_opaque_AESState; - signal o_part_shiftRows_inst_01_state : t_opaque_AESState; - signal o_part_shiftRows_inst_01_o : t_opaque_AESState; - signal o_part_mixColumns_inst_1_state : t_opaque_AESState; - signal o_part_mixColumns_inst_1_o : t_opaque_AESState; - signal state_02_state : t_opaque_AESState; - signal state_02_key : t_opaque_AESRoundKey; - signal state_02_o : t_opaque_AESState; - signal o_part_subBytes_inst_02_state : t_opaque_AESState; - signal o_part_subBytes_inst_02_o : t_opaque_AESState; - signal o_part_shiftRows_inst_02_state : t_opaque_AESState; - signal o_part_shiftRows_inst_02_o : t_opaque_AESState; - signal o_part_mixColumns_inst_2_state : t_opaque_AESState; - signal o_part_mixColumns_inst_2_o : t_opaque_AESState; - signal state_03_state : t_opaque_AESState; - signal state_03_key : t_opaque_AESRoundKey; - signal state_03_o : t_opaque_AESState; - signal o_part_subBytes_inst_03_state : t_opaque_AESState; - signal o_part_subBytes_inst_03_o : t_opaque_AESState; - signal o_part_shiftRows_inst_03_state : t_opaque_AESState; - signal o_part_shiftRows_inst_03_o : t_opaque_AESState; - signal o_part_mixColumns_inst_3_state : t_opaque_AESState; - signal o_part_mixColumns_inst_3_o : t_opaque_AESState; - signal state_04_state : t_opaque_AESState; - signal state_04_key : t_opaque_AESRoundKey; - signal state_04_o : t_opaque_AESState; - signal o_part_subBytes_inst_04_state : t_opaque_AESState; - signal o_part_subBytes_inst_04_o : t_opaque_AESState; - signal o_part_shiftRows_inst_04_state : t_opaque_AESState; - signal o_part_shiftRows_inst_04_o : t_opaque_AESState; - signal o_part_mixColumns_inst_4_state : t_opaque_AESState; - signal o_part_mixColumns_inst_4_o : t_opaque_AESState; - signal state_05_state : t_opaque_AESState; - signal state_05_key : t_opaque_AESRoundKey; - signal state_05_o : t_opaque_AESState; - signal o_part_subBytes_inst_05_state : t_opaque_AESState; - signal o_part_subBytes_inst_05_o : t_opaque_AESState; - signal o_part_shiftRows_inst_05_state : t_opaque_AESState; - signal o_part_shiftRows_inst_05_o : t_opaque_AESState; - signal o_part_mixColumns_inst_5_state : t_opaque_AESState; - signal o_part_mixColumns_inst_5_o : t_opaque_AESState; - signal state_06_state : t_opaque_AESState; - signal state_06_key : t_opaque_AESRoundKey; - signal state_06_o : t_opaque_AESState; - signal o_part_subBytes_inst_06_state : t_opaque_AESState; - signal o_part_subBytes_inst_06_o : t_opaque_AESState; - signal o_part_shiftRows_inst_06_state : t_opaque_AESState; - signal o_part_shiftRows_inst_06_o : t_opaque_AESState; - signal o_part_mixColumns_inst_6_state : t_opaque_AESState; - signal o_part_mixColumns_inst_6_o : t_opaque_AESState; - signal state_07_state : t_opaque_AESState; - signal state_07_key : t_opaque_AESRoundKey; - signal state_07_o : t_opaque_AESState; - signal o_part_subBytes_inst_07_state : t_opaque_AESState; - signal o_part_subBytes_inst_07_o : t_opaque_AESState; - signal o_part_shiftRows_inst_07_state : t_opaque_AESState; - signal o_part_shiftRows_inst_07_o : t_opaque_AESState; - signal o_part_mixColumns_inst_7_state : t_opaque_AESState; - signal o_part_mixColumns_inst_7_o : t_opaque_AESState; - signal state_08_state : t_opaque_AESState; - signal state_08_key : t_opaque_AESRoundKey; - signal state_08_o : t_opaque_AESState; - signal o_part_subBytes_inst_08_state : t_opaque_AESState; - signal o_part_subBytes_inst_08_o : t_opaque_AESState; - signal o_part_shiftRows_inst_08_state : t_opaque_AESState; - signal o_part_shiftRows_inst_08_o : t_opaque_AESState; - signal o_part_mixColumns_inst_8_state : t_opaque_AESState; - signal o_part_mixColumns_inst_8_o : t_opaque_AESState; - signal state_09_state : t_opaque_AESState; - signal state_09_key : t_opaque_AESRoundKey; - signal state_09_o : t_opaque_AESState; - signal o_part_subBytes_inst_09_state : t_opaque_AESState; - signal o_part_subBytes_inst_09_o : t_opaque_AESState; - signal o_part_shiftRows_inst_09_state : t_opaque_AESState; - signal o_part_shiftRows_inst_09_o : t_opaque_AESState; - signal state_10_state : t_opaque_AESState; - signal state_10_key : t_opaque_AESRoundKey; - signal state_10_o : t_opaque_AESState; + signal keySchedule_key : AESKey; + signal keySchedule_o : AESKeySchedule; + signal state_00_state : AESState; + signal state_00_key : AESRoundKey; + signal state_00_o : AESState; + signal o_part_subBytes_inst_00_state : AESState; + signal o_part_subBytes_inst_00_o : AESState; + signal o_part_shiftRows_inst_00_state : AESState; + signal o_part_shiftRows_inst_00_o : AESState; + signal o_part_mixColumns_inst_0_state : AESState; + signal o_part_mixColumns_inst_0_o : AESState; + signal state_01_state : AESState; + signal state_01_key : AESRoundKey; + signal state_01_o : AESState; + signal o_part_subBytes_inst_01_state : AESState; + signal o_part_subBytes_inst_01_o : AESState; + signal o_part_shiftRows_inst_01_state : AESState; + signal o_part_shiftRows_inst_01_o : AESState; + signal o_part_mixColumns_inst_1_state : AESState; + signal o_part_mixColumns_inst_1_o : AESState; + signal state_02_state : AESState; + signal state_02_key : AESRoundKey; + signal state_02_o : AESState; + signal o_part_subBytes_inst_02_state : AESState; + signal o_part_subBytes_inst_02_o : AESState; + signal o_part_shiftRows_inst_02_state : AESState; + signal o_part_shiftRows_inst_02_o : AESState; + signal o_part_mixColumns_inst_2_state : AESState; + signal o_part_mixColumns_inst_2_o : AESState; + signal state_03_state : AESState; + signal state_03_key : AESRoundKey; + signal state_03_o : AESState; + signal o_part_subBytes_inst_03_state : AESState; + signal o_part_subBytes_inst_03_o : AESState; + signal o_part_shiftRows_inst_03_state : AESState; + signal o_part_shiftRows_inst_03_o : AESState; + signal o_part_mixColumns_inst_3_state : AESState; + signal o_part_mixColumns_inst_3_o : AESState; + signal state_04_state : AESState; + signal state_04_key : AESRoundKey; + signal state_04_o : AESState; + signal o_part_subBytes_inst_04_state : AESState; + signal o_part_subBytes_inst_04_o : AESState; + signal o_part_shiftRows_inst_04_state : AESState; + signal o_part_shiftRows_inst_04_o : AESState; + signal o_part_mixColumns_inst_4_state : AESState; + signal o_part_mixColumns_inst_4_o : AESState; + signal state_05_state : AESState; + signal state_05_key : AESRoundKey; + signal state_05_o : AESState; + signal o_part_subBytes_inst_05_state : AESState; + signal o_part_subBytes_inst_05_o : AESState; + signal o_part_shiftRows_inst_05_state : AESState; + signal o_part_shiftRows_inst_05_o : AESState; + signal o_part_mixColumns_inst_5_state : AESState; + signal o_part_mixColumns_inst_5_o : AESState; + signal state_06_state : AESState; + signal state_06_key : AESRoundKey; + signal state_06_o : AESState; + signal o_part_subBytes_inst_06_state : AESState; + signal o_part_subBytes_inst_06_o : AESState; + signal o_part_shiftRows_inst_06_state : AESState; + signal o_part_shiftRows_inst_06_o : AESState; + signal o_part_mixColumns_inst_6_state : AESState; + signal o_part_mixColumns_inst_6_o : AESState; + signal state_07_state : AESState; + signal state_07_key : AESRoundKey; + signal state_07_o : AESState; + signal o_part_subBytes_inst_07_state : AESState; + signal o_part_subBytes_inst_07_o : AESState; + signal o_part_shiftRows_inst_07_state : AESState; + signal o_part_shiftRows_inst_07_o : AESState; + signal o_part_mixColumns_inst_7_state : AESState; + signal o_part_mixColumns_inst_7_o : AESState; + signal state_08_state : AESState; + signal state_08_key : AESRoundKey; + signal state_08_o : AESState; + signal o_part_subBytes_inst_08_state : AESState; + signal o_part_subBytes_inst_08_o : AESState; + signal o_part_shiftRows_inst_08_state : AESState; + signal o_part_shiftRows_inst_08_o : AESState; + signal o_part_mixColumns_inst_8_state : AESState; + signal o_part_mixColumns_inst_8_o : AESState; + signal state_09_state : AESState; + signal state_09_key : AESRoundKey; + signal state_09_o : AESState; + signal o_part_subBytes_inst_09_state : AESState; + signal o_part_subBytes_inst_09_o : AESState; + signal o_part_shiftRows_inst_09_state : AESState; + signal o_part_shiftRows_inst_09_o : AESState; + signal state_10_state : AESState; + signal state_10_key : AESRoundKey; + signal state_10_o : AESState; begin keySchedule : entity work.keyExpansion(keyExpansion_arch) port map ( key => keySchedule_key, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/keyExpansion.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/keyExpansion.vhd index 9323e93de..689a96ea3 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/keyExpansion.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/keyExpansion.vhd @@ -6,211 +6,211 @@ use work.CipherNoOpaques_pkg.all; entity keyExpansion is port ( - key : in t_opaque_AESKey; - o : out t_opaque_AESKeySchedule + key : in AESKey; + o : out AESKeySchedule ); end keyExpansion; architecture keyExpansion_arch of keyExpansion is - signal w_0 : t_opaque_AESWord; - signal w_1 : t_opaque_AESWord; - signal w_2 : t_opaque_AESWord; - signal w_3 : t_opaque_AESWord; - signal o_part_000 : t_opaque_AESByte; - signal o_part_001 : t_opaque_AESByte; - signal o_part_002 : t_opaque_AESByte; - signal o_part_003 : t_opaque_AESByte; - signal o_part_004 : t_opaque_AESByte; - signal o_part_005 : t_opaque_AESByte; - signal o_part_006 : t_opaque_AESByte; - signal o_part_007 : t_opaque_AESByte; - signal o_part_008 : t_opaque_AESByte; - signal o_part_009 : t_opaque_AESByte; - signal o_part_010 : t_opaque_AESByte; - signal o_part_011 : t_opaque_AESByte; - signal lhs_part_00 : t_opaque_AESByte; - signal lhs_part_01 : t_opaque_AESByte; - signal lhs_part_02 : t_opaque_AESByte; - signal lhs_part_03 : t_opaque_AESByte; - signal lhs_part_04 : t_opaque_AESWord; - signal o_part_012 : t_opaque_AESByte; - signal o_part_013 : t_opaque_AESByte; - signal o_part_014 : t_opaque_AESByte; - signal o_part_015 : t_opaque_AESByte; - signal o_part_016 : t_opaque_AESByte; - signal o_part_017 : t_opaque_AESByte; - signal o_part_018 : t_opaque_AESByte; - signal o_part_019 : t_opaque_AESByte; - signal o_part_020 : t_opaque_AESByte; - signal o_part_021 : t_opaque_AESByte; - signal o_part_022 : t_opaque_AESByte; - signal o_part_023 : t_opaque_AESByte; - signal lhs_part_05 : t_opaque_AESByte; - signal lhs_part_06 : t_opaque_AESByte; - signal lhs_part_07 : t_opaque_AESByte; - signal lhs_part_08 : t_opaque_AESByte; - signal lhs_part_09 : t_opaque_AESWord; - signal o_part_024 : t_opaque_AESByte; - signal o_part_025 : t_opaque_AESByte; - signal o_part_026 : t_opaque_AESByte; - signal o_part_027 : t_opaque_AESByte; - signal o_part_028 : t_opaque_AESByte; - signal o_part_029 : t_opaque_AESByte; - signal o_part_030 : t_opaque_AESByte; - signal o_part_031 : t_opaque_AESByte; - signal o_part_032 : t_opaque_AESByte; - signal o_part_033 : t_opaque_AESByte; - signal o_part_034 : t_opaque_AESByte; - signal o_part_035 : t_opaque_AESByte; - signal lhs_part_10 : t_opaque_AESByte; - signal lhs_part_11 : t_opaque_AESByte; - signal lhs_part_12 : t_opaque_AESByte; - signal lhs_part_13 : t_opaque_AESByte; - signal lhs_part_14 : t_opaque_AESWord; - signal o_part_036 : t_opaque_AESByte; - signal o_part_037 : t_opaque_AESByte; - signal o_part_038 : t_opaque_AESByte; - signal o_part_039 : t_opaque_AESByte; - signal o_part_040 : t_opaque_AESByte; - signal o_part_041 : t_opaque_AESByte; - signal o_part_042 : t_opaque_AESByte; - signal o_part_043 : t_opaque_AESByte; - signal o_part_044 : t_opaque_AESByte; - signal o_part_045 : t_opaque_AESByte; - signal o_part_046 : t_opaque_AESByte; - signal o_part_047 : t_opaque_AESByte; - signal lhs_part_15 : t_opaque_AESByte; - signal lhs_part_16 : t_opaque_AESByte; - signal lhs_part_17 : t_opaque_AESByte; - signal lhs_part_18 : t_opaque_AESByte; - signal lhs_part_19 : t_opaque_AESWord; - signal o_part_048 : t_opaque_AESByte; - signal o_part_049 : t_opaque_AESByte; - signal o_part_050 : t_opaque_AESByte; - signal o_part_051 : t_opaque_AESByte; - signal o_part_052 : t_opaque_AESByte; - signal o_part_053 : t_opaque_AESByte; - signal o_part_054 : t_opaque_AESByte; - signal o_part_055 : t_opaque_AESByte; - signal o_part_056 : t_opaque_AESByte; - signal o_part_057 : t_opaque_AESByte; - signal o_part_058 : t_opaque_AESByte; - signal o_part_059 : t_opaque_AESByte; - signal lhs_part_20 : t_opaque_AESByte; - signal lhs_part_21 : t_opaque_AESByte; - signal lhs_part_22 : t_opaque_AESByte; - signal lhs_part_23 : t_opaque_AESByte; - signal lhs_part_24 : t_opaque_AESWord; - signal o_part_060 : t_opaque_AESByte; - signal o_part_061 : t_opaque_AESByte; - signal o_part_062 : t_opaque_AESByte; - signal o_part_063 : t_opaque_AESByte; - signal o_part_064 : t_opaque_AESByte; - signal o_part_065 : t_opaque_AESByte; - signal o_part_066 : t_opaque_AESByte; - signal o_part_067 : t_opaque_AESByte; - signal o_part_068 : t_opaque_AESByte; - signal o_part_069 : t_opaque_AESByte; - signal o_part_070 : t_opaque_AESByte; - signal o_part_071 : t_opaque_AESByte; - signal lhs_part_25 : t_opaque_AESByte; - signal lhs_part_26 : t_opaque_AESByte; - signal lhs_part_27 : t_opaque_AESByte; - signal lhs_part_28 : t_opaque_AESByte; - signal lhs_part_29 : t_opaque_AESWord; - signal o_part_072 : t_opaque_AESByte; - signal o_part_073 : t_opaque_AESByte; - signal o_part_074 : t_opaque_AESByte; - signal o_part_075 : t_opaque_AESByte; - signal o_part_076 : t_opaque_AESByte; - signal o_part_077 : t_opaque_AESByte; - signal o_part_078 : t_opaque_AESByte; - signal o_part_079 : t_opaque_AESByte; - signal o_part_080 : t_opaque_AESByte; - signal o_part_081 : t_opaque_AESByte; - signal o_part_082 : t_opaque_AESByte; - signal o_part_083 : t_opaque_AESByte; - signal lhs_part_30 : t_opaque_AESByte; - signal lhs_part_31 : t_opaque_AESByte; - signal lhs_part_32 : t_opaque_AESByte; - signal lhs_part_33 : t_opaque_AESByte; - signal lhs_part_34 : t_opaque_AESWord; - signal o_part_084 : t_opaque_AESByte; - signal o_part_085 : t_opaque_AESByte; - signal o_part_086 : t_opaque_AESByte; - signal o_part_087 : t_opaque_AESByte; - signal o_part_088 : t_opaque_AESByte; - signal o_part_089 : t_opaque_AESByte; - signal o_part_090 : t_opaque_AESByte; - signal o_part_091 : t_opaque_AESByte; - signal o_part_092 : t_opaque_AESByte; - signal o_part_093 : t_opaque_AESByte; - signal o_part_094 : t_opaque_AESByte; - signal o_part_095 : t_opaque_AESByte; - signal lhs_part_35 : t_opaque_AESByte; - signal lhs_part_36 : t_opaque_AESByte; - signal lhs_part_37 : t_opaque_AESByte; - signal lhs_part_38 : t_opaque_AESByte; - signal lhs_part_39 : t_opaque_AESWord; - signal o_part_096 : t_opaque_AESByte; - signal o_part_097 : t_opaque_AESByte; - signal o_part_098 : t_opaque_AESByte; - signal o_part_099 : t_opaque_AESByte; - signal o_part_100 : t_opaque_AESByte; - signal o_part_101 : t_opaque_AESByte; - signal o_part_102 : t_opaque_AESByte; - signal o_part_103 : t_opaque_AESByte; - signal o_part_104 : t_opaque_AESByte; - signal o_part_105 : t_opaque_AESByte; - signal o_part_106 : t_opaque_AESByte; - signal o_part_107 : t_opaque_AESByte; - signal lhs_part_40 : t_opaque_AESByte; - signal lhs_part_41 : t_opaque_AESByte; - signal lhs_part_42 : t_opaque_AESByte; - signal lhs_part_43 : t_opaque_AESByte; - signal lhs_part_44 : t_opaque_AESWord; - signal o_part_108 : t_opaque_AESByte; - signal o_part_109 : t_opaque_AESByte; - signal o_part_110 : t_opaque_AESByte; - signal o_part_111 : t_opaque_AESByte; - signal o_part_112 : t_opaque_AESByte; - signal o_part_113 : t_opaque_AESByte; - signal o_part_114 : t_opaque_AESByte; - signal o_part_115 : t_opaque_AESByte; - signal o_part_116 : t_opaque_AESByte; - signal o_part_117 : t_opaque_AESByte; - signal o_part_118 : t_opaque_AESByte; - signal o_part_119 : t_opaque_AESByte; - signal o_part_rotWord_inst_00_o : t_opaque_AESWord; - signal o_part_subWord_inst_00_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_00_o : t_opaque_AESWord; - signal o_part_rotWord_inst_01_o : t_opaque_AESWord; - signal o_part_subWord_inst_01_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_01_o : t_opaque_AESWord; - signal o_part_rotWord_inst_02_o : t_opaque_AESWord; - signal o_part_subWord_inst_02_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_02_o : t_opaque_AESWord; - signal o_part_rotWord_inst_03_o : t_opaque_AESWord; - signal o_part_subWord_inst_03_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_03_o : t_opaque_AESWord; - signal o_part_rotWord_inst_04_o : t_opaque_AESWord; - signal o_part_subWord_inst_04_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_04_o : t_opaque_AESWord; - signal o_part_rotWord_inst_05_o : t_opaque_AESWord; - signal o_part_subWord_inst_05_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_05_o : t_opaque_AESWord; - signal o_part_rotWord_inst_06_o : t_opaque_AESWord; - signal o_part_subWord_inst_06_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_06_o : t_opaque_AESWord; - signal o_part_rotWord_inst_07_o : t_opaque_AESWord; - signal o_part_subWord_inst_07_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_07_o : t_opaque_AESWord; - signal o_part_rotWord_inst_08_o : t_opaque_AESWord; - signal o_part_subWord_inst_08_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_08_o : t_opaque_AESWord; - signal o_part_rotWord_inst_09_o : t_opaque_AESWord; - signal o_part_subWord_inst_09_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_09_o : t_opaque_AESWord; + signal w_0 : AESWord; + signal w_1 : AESWord; + signal w_2 : AESWord; + signal w_3 : AESWord; + signal o_part_000 : AESByte; + signal o_part_001 : AESByte; + signal o_part_002 : AESByte; + signal o_part_003 : AESByte; + signal o_part_004 : AESByte; + signal o_part_005 : AESByte; + signal o_part_006 : AESByte; + signal o_part_007 : AESByte; + signal o_part_008 : AESByte; + signal o_part_009 : AESByte; + signal o_part_010 : AESByte; + signal o_part_011 : AESByte; + signal lhs_part_00 : AESByte; + signal lhs_part_01 : AESByte; + signal lhs_part_02 : AESByte; + signal lhs_part_03 : AESByte; + signal lhs_part_04 : AESWord; + signal o_part_012 : AESByte; + signal o_part_013 : AESByte; + signal o_part_014 : AESByte; + signal o_part_015 : AESByte; + signal o_part_016 : AESByte; + signal o_part_017 : AESByte; + signal o_part_018 : AESByte; + signal o_part_019 : AESByte; + signal o_part_020 : AESByte; + signal o_part_021 : AESByte; + signal o_part_022 : AESByte; + signal o_part_023 : AESByte; + signal lhs_part_05 : AESByte; + signal lhs_part_06 : AESByte; + signal lhs_part_07 : AESByte; + signal lhs_part_08 : AESByte; + signal lhs_part_09 : AESWord; + signal o_part_024 : AESByte; + signal o_part_025 : AESByte; + signal o_part_026 : AESByte; + signal o_part_027 : AESByte; + signal o_part_028 : AESByte; + signal o_part_029 : AESByte; + signal o_part_030 : AESByte; + signal o_part_031 : AESByte; + signal o_part_032 : AESByte; + signal o_part_033 : AESByte; + signal o_part_034 : AESByte; + signal o_part_035 : AESByte; + signal lhs_part_10 : AESByte; + signal lhs_part_11 : AESByte; + signal lhs_part_12 : AESByte; + signal lhs_part_13 : AESByte; + signal lhs_part_14 : AESWord; + signal o_part_036 : AESByte; + signal o_part_037 : AESByte; + signal o_part_038 : AESByte; + signal o_part_039 : AESByte; + signal o_part_040 : AESByte; + signal o_part_041 : AESByte; + signal o_part_042 : AESByte; + signal o_part_043 : AESByte; + signal o_part_044 : AESByte; + signal o_part_045 : AESByte; + signal o_part_046 : AESByte; + signal o_part_047 : AESByte; + signal lhs_part_15 : AESByte; + signal lhs_part_16 : AESByte; + signal lhs_part_17 : AESByte; + signal lhs_part_18 : AESByte; + signal lhs_part_19 : AESWord; + signal o_part_048 : AESByte; + signal o_part_049 : AESByte; + signal o_part_050 : AESByte; + signal o_part_051 : AESByte; + signal o_part_052 : AESByte; + signal o_part_053 : AESByte; + signal o_part_054 : AESByte; + signal o_part_055 : AESByte; + signal o_part_056 : AESByte; + signal o_part_057 : AESByte; + signal o_part_058 : AESByte; + signal o_part_059 : AESByte; + signal lhs_part_20 : AESByte; + signal lhs_part_21 : AESByte; + signal lhs_part_22 : AESByte; + signal lhs_part_23 : AESByte; + signal lhs_part_24 : AESWord; + signal o_part_060 : AESByte; + signal o_part_061 : AESByte; + signal o_part_062 : AESByte; + signal o_part_063 : AESByte; + signal o_part_064 : AESByte; + signal o_part_065 : AESByte; + signal o_part_066 : AESByte; + signal o_part_067 : AESByte; + signal o_part_068 : AESByte; + signal o_part_069 : AESByte; + signal o_part_070 : AESByte; + signal o_part_071 : AESByte; + signal lhs_part_25 : AESByte; + signal lhs_part_26 : AESByte; + signal lhs_part_27 : AESByte; + signal lhs_part_28 : AESByte; + signal lhs_part_29 : AESWord; + signal o_part_072 : AESByte; + signal o_part_073 : AESByte; + signal o_part_074 : AESByte; + signal o_part_075 : AESByte; + signal o_part_076 : AESByte; + signal o_part_077 : AESByte; + signal o_part_078 : AESByte; + signal o_part_079 : AESByte; + signal o_part_080 : AESByte; + signal o_part_081 : AESByte; + signal o_part_082 : AESByte; + signal o_part_083 : AESByte; + signal lhs_part_30 : AESByte; + signal lhs_part_31 : AESByte; + signal lhs_part_32 : AESByte; + signal lhs_part_33 : AESByte; + signal lhs_part_34 : AESWord; + signal o_part_084 : AESByte; + signal o_part_085 : AESByte; + signal o_part_086 : AESByte; + signal o_part_087 : AESByte; + signal o_part_088 : AESByte; + signal o_part_089 : AESByte; + signal o_part_090 : AESByte; + signal o_part_091 : AESByte; + signal o_part_092 : AESByte; + signal o_part_093 : AESByte; + signal o_part_094 : AESByte; + signal o_part_095 : AESByte; + signal lhs_part_35 : AESByte; + signal lhs_part_36 : AESByte; + signal lhs_part_37 : AESByte; + signal lhs_part_38 : AESByte; + signal lhs_part_39 : AESWord; + signal o_part_096 : AESByte; + signal o_part_097 : AESByte; + signal o_part_098 : AESByte; + signal o_part_099 : AESByte; + signal o_part_100 : AESByte; + signal o_part_101 : AESByte; + signal o_part_102 : AESByte; + signal o_part_103 : AESByte; + signal o_part_104 : AESByte; + signal o_part_105 : AESByte; + signal o_part_106 : AESByte; + signal o_part_107 : AESByte; + signal lhs_part_40 : AESByte; + signal lhs_part_41 : AESByte; + signal lhs_part_42 : AESByte; + signal lhs_part_43 : AESByte; + signal lhs_part_44 : AESWord; + signal o_part_108 : AESByte; + signal o_part_109 : AESByte; + signal o_part_110 : AESByte; + signal o_part_111 : AESByte; + signal o_part_112 : AESByte; + signal o_part_113 : AESByte; + signal o_part_114 : AESByte; + signal o_part_115 : AESByte; + signal o_part_116 : AESByte; + signal o_part_117 : AESByte; + signal o_part_118 : AESByte; + signal o_part_119 : AESByte; + signal o_part_rotWord_inst_00_o : AESWord; + signal o_part_subWord_inst_00_lhs : AESWord; + signal o_part_subWord_inst_00_o : AESWord; + signal o_part_rotWord_inst_01_o : AESWord; + signal o_part_subWord_inst_01_lhs : AESWord; + signal o_part_subWord_inst_01_o : AESWord; + signal o_part_rotWord_inst_02_o : AESWord; + signal o_part_subWord_inst_02_lhs : AESWord; + signal o_part_subWord_inst_02_o : AESWord; + signal o_part_rotWord_inst_03_o : AESWord; + signal o_part_subWord_inst_03_lhs : AESWord; + signal o_part_subWord_inst_03_o : AESWord; + signal o_part_rotWord_inst_04_o : AESWord; + signal o_part_subWord_inst_04_lhs : AESWord; + signal o_part_subWord_inst_04_o : AESWord; + signal o_part_rotWord_inst_05_o : AESWord; + signal o_part_subWord_inst_05_lhs : AESWord; + signal o_part_subWord_inst_05_o : AESWord; + signal o_part_rotWord_inst_06_o : AESWord; + signal o_part_subWord_inst_06_lhs : AESWord; + signal o_part_subWord_inst_06_o : AESWord; + signal o_part_rotWord_inst_07_o : AESWord; + signal o_part_subWord_inst_07_lhs : AESWord; + signal o_part_subWord_inst_07_o : AESWord; + signal o_part_rotWord_inst_08_o : AESWord; + signal o_part_subWord_inst_08_lhs : AESWord; + signal o_part_subWord_inst_08_o : AESWord; + signal o_part_rotWord_inst_09_o : AESWord; + signal o_part_subWord_inst_09_lhs : AESWord; + signal o_part_subWord_inst_09_o : AESWord; begin o_part_rotWord_inst_00 : entity work.rotWord(rotWord_arch) port map ( o => o_part_rotWord_inst_00_o, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mixColumns.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mixColumns.vhd index 79a254972..d5a19b5b5 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mixColumns.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mixColumns.vhd @@ -6,140 +6,140 @@ use work.CipherNoOpaques_pkg.all; entity mixColumns is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end mixColumns; architecture mixColumns_arch of mixColumns is - signal o_part_mulByte_0_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_15_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_16_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_16_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_17_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_17_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_18_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_18_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_19_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_19_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_20_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_20_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_21_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_21_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_22_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_22_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_23_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_23_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_24_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_24_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_25_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_25_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_26_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_26_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_27_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_27_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_28_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_28_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_29_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_29_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_15_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_30_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_30_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_31_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_31_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_15_o : t_opaque_AESByte; + signal o_part_mulByte_0_inst_00_rhs : AESByte; + signal o_part_mulByte_0_inst_00_o : AESByte; + signal o_part_mulByte_1_inst_00_rhs : AESByte; + signal o_part_mulByte_1_inst_00_o : AESByte; + signal o_part_mulByte_2_inst_00_rhs : AESByte; + signal o_part_mulByte_2_inst_00_o : AESByte; + signal o_part_mulByte_2_inst_01_rhs : AESByte; + signal o_part_mulByte_2_inst_01_o : AESByte; + signal o_part_mulByte_2_inst_02_rhs : AESByte; + signal o_part_mulByte_2_inst_02_o : AESByte; + signal o_part_mulByte_0_inst_01_rhs : AESByte; + signal o_part_mulByte_0_inst_01_o : AESByte; + signal o_part_mulByte_1_inst_01_rhs : AESByte; + signal o_part_mulByte_1_inst_01_o : AESByte; + signal o_part_mulByte_2_inst_03_rhs : AESByte; + signal o_part_mulByte_2_inst_03_o : AESByte; + signal o_part_mulByte_2_inst_04_rhs : AESByte; + signal o_part_mulByte_2_inst_04_o : AESByte; + signal o_part_mulByte_2_inst_05_rhs : AESByte; + signal o_part_mulByte_2_inst_05_o : AESByte; + signal o_part_mulByte_0_inst_02_rhs : AESByte; + signal o_part_mulByte_0_inst_02_o : AESByte; + signal o_part_mulByte_1_inst_02_rhs : AESByte; + signal o_part_mulByte_1_inst_02_o : AESByte; + signal o_part_mulByte_1_inst_03_rhs : AESByte; + signal o_part_mulByte_1_inst_03_o : AESByte; + signal o_part_mulByte_2_inst_06_rhs : AESByte; + signal o_part_mulByte_2_inst_06_o : AESByte; + signal o_part_mulByte_2_inst_07_rhs : AESByte; + signal o_part_mulByte_2_inst_07_o : AESByte; + signal o_part_mulByte_0_inst_03_rhs : AESByte; + signal o_part_mulByte_0_inst_03_o : AESByte; + signal o_part_mulByte_0_inst_04_rhs : AESByte; + signal o_part_mulByte_0_inst_04_o : AESByte; + signal o_part_mulByte_1_inst_04_rhs : AESByte; + signal o_part_mulByte_1_inst_04_o : AESByte; + signal o_part_mulByte_2_inst_08_rhs : AESByte; + signal o_part_mulByte_2_inst_08_o : AESByte; + signal o_part_mulByte_2_inst_09_rhs : AESByte; + signal o_part_mulByte_2_inst_09_o : AESByte; + signal o_part_mulByte_2_inst_10_rhs : AESByte; + signal o_part_mulByte_2_inst_10_o : AESByte; + signal o_part_mulByte_0_inst_05_rhs : AESByte; + signal o_part_mulByte_0_inst_05_o : AESByte; + signal o_part_mulByte_1_inst_05_rhs : AESByte; + signal o_part_mulByte_1_inst_05_o : AESByte; + signal o_part_mulByte_2_inst_11_rhs : AESByte; + signal o_part_mulByte_2_inst_11_o : AESByte; + signal o_part_mulByte_2_inst_12_rhs : AESByte; + signal o_part_mulByte_2_inst_12_o : AESByte; + signal o_part_mulByte_2_inst_13_rhs : AESByte; + signal o_part_mulByte_2_inst_13_o : AESByte; + signal o_part_mulByte_0_inst_06_rhs : AESByte; + signal o_part_mulByte_0_inst_06_o : AESByte; + signal o_part_mulByte_1_inst_06_rhs : AESByte; + signal o_part_mulByte_1_inst_06_o : AESByte; + signal o_part_mulByte_1_inst_07_rhs : AESByte; + signal o_part_mulByte_1_inst_07_o : AESByte; + signal o_part_mulByte_2_inst_14_rhs : AESByte; + signal o_part_mulByte_2_inst_14_o : AESByte; + signal o_part_mulByte_2_inst_15_rhs : AESByte; + signal o_part_mulByte_2_inst_15_o : AESByte; + signal o_part_mulByte_0_inst_07_rhs : AESByte; + signal o_part_mulByte_0_inst_07_o : AESByte; + signal o_part_mulByte_0_inst_08_rhs : AESByte; + signal o_part_mulByte_0_inst_08_o : AESByte; + signal o_part_mulByte_1_inst_08_rhs : AESByte; + signal o_part_mulByte_1_inst_08_o : AESByte; + signal o_part_mulByte_2_inst_16_rhs : AESByte; + signal o_part_mulByte_2_inst_16_o : AESByte; + signal o_part_mulByte_2_inst_17_rhs : AESByte; + signal o_part_mulByte_2_inst_17_o : AESByte; + signal o_part_mulByte_2_inst_18_rhs : AESByte; + signal o_part_mulByte_2_inst_18_o : AESByte; + signal o_part_mulByte_0_inst_09_rhs : AESByte; + signal o_part_mulByte_0_inst_09_o : AESByte; + signal o_part_mulByte_1_inst_09_rhs : AESByte; + signal o_part_mulByte_1_inst_09_o : AESByte; + signal o_part_mulByte_2_inst_19_rhs : AESByte; + signal o_part_mulByte_2_inst_19_o : AESByte; + signal o_part_mulByte_2_inst_20_rhs : AESByte; + signal o_part_mulByte_2_inst_20_o : AESByte; + signal o_part_mulByte_2_inst_21_rhs : AESByte; + signal o_part_mulByte_2_inst_21_o : AESByte; + signal o_part_mulByte_0_inst_10_rhs : AESByte; + signal o_part_mulByte_0_inst_10_o : AESByte; + signal o_part_mulByte_1_inst_10_rhs : AESByte; + signal o_part_mulByte_1_inst_10_o : AESByte; + signal o_part_mulByte_1_inst_11_rhs : AESByte; + signal o_part_mulByte_1_inst_11_o : AESByte; + signal o_part_mulByte_2_inst_22_rhs : AESByte; + signal o_part_mulByte_2_inst_22_o : AESByte; + signal o_part_mulByte_2_inst_23_rhs : AESByte; + signal o_part_mulByte_2_inst_23_o : AESByte; + signal o_part_mulByte_0_inst_11_rhs : AESByte; + signal o_part_mulByte_0_inst_11_o : AESByte; + signal o_part_mulByte_0_inst_12_rhs : AESByte; + signal o_part_mulByte_0_inst_12_o : AESByte; + signal o_part_mulByte_1_inst_12_rhs : AESByte; + signal o_part_mulByte_1_inst_12_o : AESByte; + signal o_part_mulByte_2_inst_24_rhs : AESByte; + signal o_part_mulByte_2_inst_24_o : AESByte; + signal o_part_mulByte_2_inst_25_rhs : AESByte; + signal o_part_mulByte_2_inst_25_o : AESByte; + signal o_part_mulByte_2_inst_26_rhs : AESByte; + signal o_part_mulByte_2_inst_26_o : AESByte; + signal o_part_mulByte_0_inst_13_rhs : AESByte; + signal o_part_mulByte_0_inst_13_o : AESByte; + signal o_part_mulByte_1_inst_13_rhs : AESByte; + signal o_part_mulByte_1_inst_13_o : AESByte; + signal o_part_mulByte_2_inst_27_rhs : AESByte; + signal o_part_mulByte_2_inst_27_o : AESByte; + signal o_part_mulByte_2_inst_28_rhs : AESByte; + signal o_part_mulByte_2_inst_28_o : AESByte; + signal o_part_mulByte_2_inst_29_rhs : AESByte; + signal o_part_mulByte_2_inst_29_o : AESByte; + signal o_part_mulByte_0_inst_14_rhs : AESByte; + signal o_part_mulByte_0_inst_14_o : AESByte; + signal o_part_mulByte_1_inst_14_rhs : AESByte; + signal o_part_mulByte_1_inst_14_o : AESByte; + signal o_part_mulByte_1_inst_15_rhs : AESByte; + signal o_part_mulByte_1_inst_15_o : AESByte; + signal o_part_mulByte_2_inst_30_rhs : AESByte; + signal o_part_mulByte_2_inst_30_o : AESByte; + signal o_part_mulByte_2_inst_31_rhs : AESByte; + signal o_part_mulByte_2_inst_31_o : AESByte; + signal o_part_mulByte_0_inst_15_rhs : AESByte; + signal o_part_mulByte_0_inst_15_o : AESByte; begin o_part_mulByte_0_inst_00 : entity work.mulByte_0(mulByte_0_arch) generic map ( lhs => x"02" diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_0.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_0.vhd index b7763944b..fc2aa822d 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_0.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_0.vhd @@ -9,14 +9,14 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_0; architecture mulByte_0_arch of mulByte_0 is - signal a_lhs : t_opaque_AESByte; - signal a_o : t_opaque_AESByte; + signal a_lhs : AESByte; + signal a_o : AESByte; begin a : entity work.xtime(xtime_arch) port map ( lhs => a_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_1.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_1.vhd index 26260a4ca..7376ef24a 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_1.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_1.vhd @@ -9,14 +9,14 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_1; architecture mulByte_1_arch of mulByte_1 is - signal a_lhs : t_opaque_AESByte; - signal a_o : t_opaque_AESByte; + signal a_lhs : AESByte; + signal a_o : AESByte; begin a : entity work.xtime(xtime_arch) port map ( lhs => a_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_2.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_2.vhd index a6a3f3324..eeb6713e5 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_2.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_2.vhd @@ -9,8 +9,8 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_2; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/rotWord.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/rotWord.vhd index de8d0c084..5c4d543f0 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/rotWord.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/rotWord.vhd @@ -6,8 +6,8 @@ use work.CipherNoOpaques_pkg.all; entity rotWord is port ( - lhs : in t_opaque_AESWord; - o : out t_opaque_AESWord + lhs : in AESWord; + o : out AESWord ); end rotWord; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/sbox.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/sbox.vhd index 9142b8bbc..5ba37869c 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/sbox.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/sbox.vhd @@ -6,8 +6,8 @@ use work.CipherNoOpaques_pkg.all; entity sbox is port ( - lhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + lhs : in AESByte; + o : out AESByte ); end sbox; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/shiftRows.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/shiftRows.vhd index 3b839140b..d01966db4 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/shiftRows.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/shiftRows.vhd @@ -6,8 +6,8 @@ use work.CipherNoOpaques_pkg.all; entity shiftRows is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end shiftRows; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/subBytes.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/subBytes.vhd index 6ecb54c44..1dff4f8a2 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/subBytes.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/subBytes.vhd @@ -6,44 +6,44 @@ use work.CipherNoOpaques_pkg.all; entity subBytes is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end subBytes; architecture subBytes_arch of subBytes is - signal o_part_sbox_inst_00_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_00_o : t_opaque_AESByte; - signal o_part_sbox_inst_01_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_01_o : t_opaque_AESByte; - signal o_part_sbox_inst_02_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_02_o : t_opaque_AESByte; - signal o_part_sbox_inst_03_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_03_o : t_opaque_AESByte; - signal o_part_sbox_inst_04_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_04_o : t_opaque_AESByte; - signal o_part_sbox_inst_05_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_05_o : t_opaque_AESByte; - signal o_part_sbox_inst_06_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_06_o : t_opaque_AESByte; - signal o_part_sbox_inst_07_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_07_o : t_opaque_AESByte; - signal o_part_sbox_inst_08_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_08_o : t_opaque_AESByte; - signal o_part_sbox_inst_09_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_09_o : t_opaque_AESByte; - signal o_part_sbox_inst_10_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_10_o : t_opaque_AESByte; - signal o_part_sbox_inst_11_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_11_o : t_opaque_AESByte; - signal o_part_sbox_inst_12_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_12_o : t_opaque_AESByte; - signal o_part_sbox_inst_13_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_13_o : t_opaque_AESByte; - signal o_part_sbox_inst_14_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_14_o : t_opaque_AESByte; - signal o_part_sbox_inst_15_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_15_o : t_opaque_AESByte; + signal o_part_sbox_inst_00_lhs : AESByte; + signal o_part_sbox_inst_00_o : AESByte; + signal o_part_sbox_inst_01_lhs : AESByte; + signal o_part_sbox_inst_01_o : AESByte; + signal o_part_sbox_inst_02_lhs : AESByte; + signal o_part_sbox_inst_02_o : AESByte; + signal o_part_sbox_inst_03_lhs : AESByte; + signal o_part_sbox_inst_03_o : AESByte; + signal o_part_sbox_inst_04_lhs : AESByte; + signal o_part_sbox_inst_04_o : AESByte; + signal o_part_sbox_inst_05_lhs : AESByte; + signal o_part_sbox_inst_05_o : AESByte; + signal o_part_sbox_inst_06_lhs : AESByte; + signal o_part_sbox_inst_06_o : AESByte; + signal o_part_sbox_inst_07_lhs : AESByte; + signal o_part_sbox_inst_07_o : AESByte; + signal o_part_sbox_inst_08_lhs : AESByte; + signal o_part_sbox_inst_08_o : AESByte; + signal o_part_sbox_inst_09_lhs : AESByte; + signal o_part_sbox_inst_09_o : AESByte; + signal o_part_sbox_inst_10_lhs : AESByte; + signal o_part_sbox_inst_10_o : AESByte; + signal o_part_sbox_inst_11_lhs : AESByte; + signal o_part_sbox_inst_11_o : AESByte; + signal o_part_sbox_inst_12_lhs : AESByte; + signal o_part_sbox_inst_12_o : AESByte; + signal o_part_sbox_inst_13_lhs : AESByte; + signal o_part_sbox_inst_13_o : AESByte; + signal o_part_sbox_inst_14_lhs : AESByte; + signal o_part_sbox_inst_14_o : AESByte; + signal o_part_sbox_inst_15_lhs : AESByte; + signal o_part_sbox_inst_15_o : AESByte; begin o_part_sbox_inst_00 : entity work.sbox(sbox_arch) port map ( lhs => o_part_sbox_inst_00_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/subWord.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/subWord.vhd index 170d3afb8..cf14b8e9c 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/subWord.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/subWord.vhd @@ -6,20 +6,20 @@ use work.CipherNoOpaques_pkg.all; entity subWord is port ( - lhs : in t_opaque_AESWord; - o : out t_opaque_AESWord + lhs : in AESWord; + o : out AESWord ); end subWord; architecture subWord_arch of subWord is - signal o_part_sbox_inst_0_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_0_o : t_opaque_AESByte; - signal o_part_sbox_inst_1_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_1_o : t_opaque_AESByte; - signal o_part_sbox_inst_2_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_2_o : t_opaque_AESByte; - signal o_part_sbox_inst_3_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_3_o : t_opaque_AESByte; + signal o_part_sbox_inst_0_lhs : AESByte; + signal o_part_sbox_inst_0_o : AESByte; + signal o_part_sbox_inst_1_lhs : AESByte; + signal o_part_sbox_inst_1_o : AESByte; + signal o_part_sbox_inst_2_lhs : AESByte; + signal o_part_sbox_inst_2_o : AESByte; + signal o_part_sbox_inst_3_lhs : AESByte; + signal o_part_sbox_inst_3_o : AESByte; begin o_part_sbox_inst_0 : entity work.sbox(sbox_arch) port map ( lhs => o_part_sbox_inst_0_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/xtime.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/xtime.vhd index 68c62886c..3e68c9d37 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/xtime.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecNoOpaques/vhdl.v93/hdl/xtime.vhd @@ -6,8 +6,8 @@ use work.CipherNoOpaques_pkg.all; entity xtime is port ( - lhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + lhs : in AESByte; + o : out AESByte ); end xtime; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/Cipher.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/Cipher.sv index ebed10ffb..8745b71e4 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/Cipher.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/Cipher.sv @@ -3,14 +3,14 @@ `include "Cipher_defs.svh" module Cipher( - input wire t_opaque_AESKey key, - input wire t_opaque_AESData data, - output t_opaque_AESData o + input wire AESKey key, + input wire AESData data, + output AESData o ); `include "dfhdl_defs.svh" - t_opaque_AESData o_part_cipher_inst_data; - t_opaque_AESKey o_part_cipher_inst_key; - t_opaque_AESData o_part_cipher_inst_o; + AESData o_part_cipher_inst_data; + AESKey o_part_cipher_inst_key; + AESData o_part_cipher_inst_o; cipher_0 o_part_cipher_inst( .data /*<--*/ (o_part_cipher_inst_data), .key /*<--*/ (o_part_cipher_inst_key), diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/Cipher_defs.svh b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/Cipher_defs.svh index 29aa77dd0..904a214fe 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/Cipher_defs.svh +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/Cipher_defs.svh @@ -1,13 +1,13 @@ `ifndef CIPHER_DEFS `define CIPHER_DEFS -typedef logic [7:0] t_opaque_AESByte; -typedef t_opaque_AESByte t_opaque_AESWord [0:3]; -typedef t_opaque_AESWord t_opaque_AESKey [0:3]; -typedef t_opaque_AESWord t_opaque_AESData [0:3]; -typedef t_opaque_AESWord t_opaque_AESKeySchedule [0:43]; -typedef t_opaque_AESWord t_opaque_AESState [0:3]; -typedef t_opaque_AESWord t_opaque_AESRoundKey [0:3]; -parameter t_opaque_AESWord Rcon [0:10] = '{ +typedef logic [7:0] AESByte; +typedef AESByte AESWord [0:3]; +typedef AESWord AESKey [0:3]; +typedef AESWord AESData [0:3]; +typedef AESWord AESKeySchedule [0:43]; +typedef AESWord AESState [0:3]; +typedef AESWord AESRoundKey [0:3]; +parameter AESWord Rcon [0:10] = '{ 0: '{0: 8'h00, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 1: '{0: 8'h01, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 2: '{0: 8'h02, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 3: '{0: 8'h04, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 4: '{0: 8'h08, 1: 8'h00, 2: 8'h00, 3: 8'h00}, 5: '{0: 8'h10, 1: 8'h00, 2: 8'h00, 3: 8'h00}, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/addRoundKey.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/addRoundKey.sv index e0acb556f..1ea2a7504 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/addRoundKey.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/addRoundKey.sv @@ -3,9 +3,9 @@ `include "Cipher_defs.svh" module addRoundKey( - input wire t_opaque_AESState state, - input wire t_opaque_AESRoundKey key, - output t_opaque_AESState o + input wire AESState state, + input wire AESRoundKey key, + output AESState o ); `include "dfhdl_defs.svh" assign o = '{ diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/cipher_0.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/cipher_0.sv index efa0f8f7f..35fa1979c 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/cipher_0.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/cipher_0.sv @@ -3,104 +3,104 @@ `include "Cipher_defs.svh" module cipher_0( - input wire t_opaque_AESData data, - input wire t_opaque_AESKey key, - output t_opaque_AESData o + input wire AESData data, + input wire AESKey key, + output AESData o ); `include "dfhdl_defs.svh" - t_opaque_AESKey keySchedule_key; - t_opaque_AESKeySchedule keySchedule_o; - t_opaque_AESState state_00_state; - t_opaque_AESRoundKey state_00_key; - t_opaque_AESState state_00_o; - t_opaque_AESState o_part_subBytes_inst_00_state; - t_opaque_AESState o_part_subBytes_inst_00_o; - t_opaque_AESState o_part_shiftRows_inst_00_state; - t_opaque_AESState o_part_shiftRows_inst_00_o; - t_opaque_AESState o_part_mixColumns_inst_0_state; - t_opaque_AESState o_part_mixColumns_inst_0_o; - t_opaque_AESState state_01_state; - t_opaque_AESRoundKey state_01_key; - t_opaque_AESState state_01_o; - t_opaque_AESState o_part_subBytes_inst_01_state; - t_opaque_AESState o_part_subBytes_inst_01_o; - t_opaque_AESState o_part_shiftRows_inst_01_state; - t_opaque_AESState o_part_shiftRows_inst_01_o; - t_opaque_AESState o_part_mixColumns_inst_1_state; - t_opaque_AESState o_part_mixColumns_inst_1_o; - t_opaque_AESState state_02_state; - t_opaque_AESRoundKey state_02_key; - t_opaque_AESState state_02_o; - t_opaque_AESState o_part_subBytes_inst_02_state; - t_opaque_AESState o_part_subBytes_inst_02_o; - t_opaque_AESState o_part_shiftRows_inst_02_state; - t_opaque_AESState o_part_shiftRows_inst_02_o; - t_opaque_AESState o_part_mixColumns_inst_2_state; - t_opaque_AESState o_part_mixColumns_inst_2_o; - t_opaque_AESState state_03_state; - t_opaque_AESRoundKey state_03_key; - t_opaque_AESState state_03_o; - t_opaque_AESState o_part_subBytes_inst_03_state; - t_opaque_AESState o_part_subBytes_inst_03_o; - t_opaque_AESState o_part_shiftRows_inst_03_state; - t_opaque_AESState o_part_shiftRows_inst_03_o; - t_opaque_AESState o_part_mixColumns_inst_3_state; - t_opaque_AESState o_part_mixColumns_inst_3_o; - t_opaque_AESState state_04_state; - t_opaque_AESRoundKey state_04_key; - t_opaque_AESState state_04_o; - t_opaque_AESState o_part_subBytes_inst_04_state; - t_opaque_AESState o_part_subBytes_inst_04_o; - t_opaque_AESState o_part_shiftRows_inst_04_state; - t_opaque_AESState o_part_shiftRows_inst_04_o; - t_opaque_AESState o_part_mixColumns_inst_4_state; - t_opaque_AESState o_part_mixColumns_inst_4_o; - t_opaque_AESState state_05_state; - t_opaque_AESRoundKey state_05_key; - t_opaque_AESState state_05_o; - t_opaque_AESState o_part_subBytes_inst_05_state; - t_opaque_AESState o_part_subBytes_inst_05_o; - t_opaque_AESState o_part_shiftRows_inst_05_state; - t_opaque_AESState o_part_shiftRows_inst_05_o; - t_opaque_AESState o_part_mixColumns_inst_5_state; - t_opaque_AESState o_part_mixColumns_inst_5_o; - t_opaque_AESState state_06_state; - t_opaque_AESRoundKey state_06_key; - t_opaque_AESState state_06_o; - t_opaque_AESState o_part_subBytes_inst_06_state; - t_opaque_AESState o_part_subBytes_inst_06_o; - t_opaque_AESState o_part_shiftRows_inst_06_state; - t_opaque_AESState o_part_shiftRows_inst_06_o; - t_opaque_AESState o_part_mixColumns_inst_6_state; - t_opaque_AESState o_part_mixColumns_inst_6_o; - t_opaque_AESState state_07_state; - t_opaque_AESRoundKey state_07_key; - t_opaque_AESState state_07_o; - t_opaque_AESState o_part_subBytes_inst_07_state; - t_opaque_AESState o_part_subBytes_inst_07_o; - t_opaque_AESState o_part_shiftRows_inst_07_state; - t_opaque_AESState o_part_shiftRows_inst_07_o; - t_opaque_AESState o_part_mixColumns_inst_7_state; - t_opaque_AESState o_part_mixColumns_inst_7_o; - t_opaque_AESState state_08_state; - t_opaque_AESRoundKey state_08_key; - t_opaque_AESState state_08_o; - t_opaque_AESState o_part_subBytes_inst_08_state; - t_opaque_AESState o_part_subBytes_inst_08_o; - t_opaque_AESState o_part_shiftRows_inst_08_state; - t_opaque_AESState o_part_shiftRows_inst_08_o; - t_opaque_AESState o_part_mixColumns_inst_8_state; - t_opaque_AESState o_part_mixColumns_inst_8_o; - t_opaque_AESState state_09_state; - t_opaque_AESRoundKey state_09_key; - t_opaque_AESState state_09_o; - t_opaque_AESState o_part_subBytes_inst_09_state; - t_opaque_AESState o_part_subBytes_inst_09_o; - t_opaque_AESState o_part_shiftRows_inst_09_state; - t_opaque_AESState o_part_shiftRows_inst_09_o; - t_opaque_AESState state_10_state; - t_opaque_AESRoundKey state_10_key; - t_opaque_AESState state_10_o; + AESKey keySchedule_key; + AESKeySchedule keySchedule_o; + AESState state_00_state; + AESRoundKey state_00_key; + AESState state_00_o; + AESState o_part_subBytes_inst_00_state; + AESState o_part_subBytes_inst_00_o; + AESState o_part_shiftRows_inst_00_state; + AESState o_part_shiftRows_inst_00_o; + AESState o_part_mixColumns_inst_0_state; + AESState o_part_mixColumns_inst_0_o; + AESState state_01_state; + AESRoundKey state_01_key; + AESState state_01_o; + AESState o_part_subBytes_inst_01_state; + AESState o_part_subBytes_inst_01_o; + AESState o_part_shiftRows_inst_01_state; + AESState o_part_shiftRows_inst_01_o; + AESState o_part_mixColumns_inst_1_state; + AESState o_part_mixColumns_inst_1_o; + AESState state_02_state; + AESRoundKey state_02_key; + AESState state_02_o; + AESState o_part_subBytes_inst_02_state; + AESState o_part_subBytes_inst_02_o; + AESState o_part_shiftRows_inst_02_state; + AESState o_part_shiftRows_inst_02_o; + AESState o_part_mixColumns_inst_2_state; + AESState o_part_mixColumns_inst_2_o; + AESState state_03_state; + AESRoundKey state_03_key; + AESState state_03_o; + AESState o_part_subBytes_inst_03_state; + AESState o_part_subBytes_inst_03_o; + AESState o_part_shiftRows_inst_03_state; + AESState o_part_shiftRows_inst_03_o; + AESState o_part_mixColumns_inst_3_state; + AESState o_part_mixColumns_inst_3_o; + AESState state_04_state; + AESRoundKey state_04_key; + AESState state_04_o; + AESState o_part_subBytes_inst_04_state; + AESState o_part_subBytes_inst_04_o; + AESState o_part_shiftRows_inst_04_state; + AESState o_part_shiftRows_inst_04_o; + AESState o_part_mixColumns_inst_4_state; + AESState o_part_mixColumns_inst_4_o; + AESState state_05_state; + AESRoundKey state_05_key; + AESState state_05_o; + AESState o_part_subBytes_inst_05_state; + AESState o_part_subBytes_inst_05_o; + AESState o_part_shiftRows_inst_05_state; + AESState o_part_shiftRows_inst_05_o; + AESState o_part_mixColumns_inst_5_state; + AESState o_part_mixColumns_inst_5_o; + AESState state_06_state; + AESRoundKey state_06_key; + AESState state_06_o; + AESState o_part_subBytes_inst_06_state; + AESState o_part_subBytes_inst_06_o; + AESState o_part_shiftRows_inst_06_state; + AESState o_part_shiftRows_inst_06_o; + AESState o_part_mixColumns_inst_6_state; + AESState o_part_mixColumns_inst_6_o; + AESState state_07_state; + AESRoundKey state_07_key; + AESState state_07_o; + AESState o_part_subBytes_inst_07_state; + AESState o_part_subBytes_inst_07_o; + AESState o_part_shiftRows_inst_07_state; + AESState o_part_shiftRows_inst_07_o; + AESState o_part_mixColumns_inst_7_state; + AESState o_part_mixColumns_inst_7_o; + AESState state_08_state; + AESRoundKey state_08_key; + AESState state_08_o; + AESState o_part_subBytes_inst_08_state; + AESState o_part_subBytes_inst_08_o; + AESState o_part_shiftRows_inst_08_state; + AESState o_part_shiftRows_inst_08_o; + AESState o_part_mixColumns_inst_8_state; + AESState o_part_mixColumns_inst_8_o; + AESState state_09_state; + AESRoundKey state_09_key; + AESState state_09_o; + AESState o_part_subBytes_inst_09_state; + AESState o_part_subBytes_inst_09_o; + AESState o_part_shiftRows_inst_09_state; + AESState o_part_shiftRows_inst_09_o; + AESState state_10_state; + AESRoundKey state_10_key; + AESState state_10_o; keyExpansion keySchedule( .key /*<--*/ (keySchedule_key), .o /*-->*/ (keySchedule_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/keyExpansion.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/keyExpansion.sv index 5671ad023..42c592ddd 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/keyExpansion.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/keyExpansion.sv @@ -3,209 +3,209 @@ `include "Cipher_defs.svh" module keyExpansion( - input wire t_opaque_AESKey key, - output t_opaque_AESKeySchedule o + input wire AESKey key, + output AESKeySchedule o ); `include "dfhdl_defs.svh" - t_opaque_AESWord w_0; - t_opaque_AESWord w_1; - t_opaque_AESWord w_2; - t_opaque_AESWord w_3; - t_opaque_AESByte o_part_000; - t_opaque_AESByte o_part_001; - t_opaque_AESByte o_part_002; - t_opaque_AESByte o_part_003; - t_opaque_AESByte o_part_004; - t_opaque_AESByte o_part_005; - t_opaque_AESByte o_part_006; - t_opaque_AESByte o_part_007; - t_opaque_AESByte o_part_008; - t_opaque_AESByte o_part_009; - t_opaque_AESByte o_part_010; - t_opaque_AESByte o_part_011; - t_opaque_AESByte lhs_part_00; - t_opaque_AESByte lhs_part_01; - t_opaque_AESByte lhs_part_02; - t_opaque_AESByte lhs_part_03; - t_opaque_AESWord lhs_part_04; - t_opaque_AESByte o_part_012; - t_opaque_AESByte o_part_013; - t_opaque_AESByte o_part_014; - t_opaque_AESByte o_part_015; - t_opaque_AESByte o_part_016; - t_opaque_AESByte o_part_017; - t_opaque_AESByte o_part_018; - t_opaque_AESByte o_part_019; - t_opaque_AESByte o_part_020; - t_opaque_AESByte o_part_021; - t_opaque_AESByte o_part_022; - t_opaque_AESByte o_part_023; - t_opaque_AESByte lhs_part_05; - t_opaque_AESByte lhs_part_06; - t_opaque_AESByte lhs_part_07; - t_opaque_AESByte lhs_part_08; - t_opaque_AESWord lhs_part_09; - t_opaque_AESByte o_part_024; - t_opaque_AESByte o_part_025; - t_opaque_AESByte o_part_026; - t_opaque_AESByte o_part_027; - t_opaque_AESByte o_part_028; - t_opaque_AESByte o_part_029; - t_opaque_AESByte o_part_030; - t_opaque_AESByte o_part_031; - t_opaque_AESByte o_part_032; - t_opaque_AESByte o_part_033; - t_opaque_AESByte o_part_034; - t_opaque_AESByte o_part_035; - t_opaque_AESByte lhs_part_10; - t_opaque_AESByte lhs_part_11; - t_opaque_AESByte lhs_part_12; - t_opaque_AESByte lhs_part_13; - t_opaque_AESWord lhs_part_14; - t_opaque_AESByte o_part_036; - t_opaque_AESByte o_part_037; - t_opaque_AESByte o_part_038; - t_opaque_AESByte o_part_039; - t_opaque_AESByte o_part_040; - t_opaque_AESByte o_part_041; - t_opaque_AESByte o_part_042; - t_opaque_AESByte o_part_043; - t_opaque_AESByte o_part_044; - t_opaque_AESByte o_part_045; - t_opaque_AESByte o_part_046; - t_opaque_AESByte o_part_047; - t_opaque_AESByte lhs_part_15; - t_opaque_AESByte lhs_part_16; - t_opaque_AESByte lhs_part_17; - t_opaque_AESByte lhs_part_18; - t_opaque_AESWord lhs_part_19; - t_opaque_AESByte o_part_048; - t_opaque_AESByte o_part_049; - t_opaque_AESByte o_part_050; - t_opaque_AESByte o_part_051; - t_opaque_AESByte o_part_052; - t_opaque_AESByte o_part_053; - t_opaque_AESByte o_part_054; - t_opaque_AESByte o_part_055; - t_opaque_AESByte o_part_056; - t_opaque_AESByte o_part_057; - t_opaque_AESByte o_part_058; - t_opaque_AESByte o_part_059; - t_opaque_AESByte lhs_part_20; - t_opaque_AESByte lhs_part_21; - t_opaque_AESByte lhs_part_22; - t_opaque_AESByte lhs_part_23; - t_opaque_AESWord lhs_part_24; - t_opaque_AESByte o_part_060; - t_opaque_AESByte o_part_061; - t_opaque_AESByte o_part_062; - t_opaque_AESByte o_part_063; - t_opaque_AESByte o_part_064; - t_opaque_AESByte o_part_065; - t_opaque_AESByte o_part_066; - t_opaque_AESByte o_part_067; - t_opaque_AESByte o_part_068; - t_opaque_AESByte o_part_069; - t_opaque_AESByte o_part_070; - t_opaque_AESByte o_part_071; - t_opaque_AESByte lhs_part_25; - t_opaque_AESByte lhs_part_26; - t_opaque_AESByte lhs_part_27; - t_opaque_AESByte lhs_part_28; - t_opaque_AESWord lhs_part_29; - t_opaque_AESByte o_part_072; - t_opaque_AESByte o_part_073; - t_opaque_AESByte o_part_074; - t_opaque_AESByte o_part_075; - t_opaque_AESByte o_part_076; - t_opaque_AESByte o_part_077; - t_opaque_AESByte o_part_078; - t_opaque_AESByte o_part_079; - t_opaque_AESByte o_part_080; - t_opaque_AESByte o_part_081; - t_opaque_AESByte o_part_082; - t_opaque_AESByte o_part_083; - t_opaque_AESByte lhs_part_30; - t_opaque_AESByte lhs_part_31; - t_opaque_AESByte lhs_part_32; - t_opaque_AESByte lhs_part_33; - t_opaque_AESWord lhs_part_34; - t_opaque_AESByte o_part_084; - t_opaque_AESByte o_part_085; - t_opaque_AESByte o_part_086; - t_opaque_AESByte o_part_087; - t_opaque_AESByte o_part_088; - t_opaque_AESByte o_part_089; - t_opaque_AESByte o_part_090; - t_opaque_AESByte o_part_091; - t_opaque_AESByte o_part_092; - t_opaque_AESByte o_part_093; - t_opaque_AESByte o_part_094; - t_opaque_AESByte o_part_095; - t_opaque_AESByte lhs_part_35; - t_opaque_AESByte lhs_part_36; - t_opaque_AESByte lhs_part_37; - t_opaque_AESByte lhs_part_38; - t_opaque_AESWord lhs_part_39; - t_opaque_AESByte o_part_096; - t_opaque_AESByte o_part_097; - t_opaque_AESByte o_part_098; - t_opaque_AESByte o_part_099; - t_opaque_AESByte o_part_100; - t_opaque_AESByte o_part_101; - t_opaque_AESByte o_part_102; - t_opaque_AESByte o_part_103; - t_opaque_AESByte o_part_104; - t_opaque_AESByte o_part_105; - t_opaque_AESByte o_part_106; - t_opaque_AESByte o_part_107; - t_opaque_AESByte lhs_part_40; - t_opaque_AESByte lhs_part_41; - t_opaque_AESByte lhs_part_42; - t_opaque_AESByte lhs_part_43; - t_opaque_AESWord lhs_part_44; - t_opaque_AESByte o_part_108; - t_opaque_AESByte o_part_109; - t_opaque_AESByte o_part_110; - t_opaque_AESByte o_part_111; - t_opaque_AESByte o_part_112; - t_opaque_AESByte o_part_113; - t_opaque_AESByte o_part_114; - t_opaque_AESByte o_part_115; - t_opaque_AESByte o_part_116; - t_opaque_AESByte o_part_117; - t_opaque_AESByte o_part_118; - t_opaque_AESByte o_part_119; - t_opaque_AESWord o_part_rotWord_inst_00_o; - t_opaque_AESWord o_part_subWord_inst_00_lhs; - t_opaque_AESWord o_part_subWord_inst_00_o; - t_opaque_AESWord o_part_rotWord_inst_01_o; - t_opaque_AESWord o_part_subWord_inst_01_lhs; - t_opaque_AESWord o_part_subWord_inst_01_o; - t_opaque_AESWord o_part_rotWord_inst_02_o; - t_opaque_AESWord o_part_subWord_inst_02_lhs; - t_opaque_AESWord o_part_subWord_inst_02_o; - t_opaque_AESWord o_part_rotWord_inst_03_o; - t_opaque_AESWord o_part_subWord_inst_03_lhs; - t_opaque_AESWord o_part_subWord_inst_03_o; - t_opaque_AESWord o_part_rotWord_inst_04_o; - t_opaque_AESWord o_part_subWord_inst_04_lhs; - t_opaque_AESWord o_part_subWord_inst_04_o; - t_opaque_AESWord o_part_rotWord_inst_05_o; - t_opaque_AESWord o_part_subWord_inst_05_lhs; - t_opaque_AESWord o_part_subWord_inst_05_o; - t_opaque_AESWord o_part_rotWord_inst_06_o; - t_opaque_AESWord o_part_subWord_inst_06_lhs; - t_opaque_AESWord o_part_subWord_inst_06_o; - t_opaque_AESWord o_part_rotWord_inst_07_o; - t_opaque_AESWord o_part_subWord_inst_07_lhs; - t_opaque_AESWord o_part_subWord_inst_07_o; - t_opaque_AESWord o_part_rotWord_inst_08_o; - t_opaque_AESWord o_part_subWord_inst_08_lhs; - t_opaque_AESWord o_part_subWord_inst_08_o; - t_opaque_AESWord o_part_rotWord_inst_09_o; - t_opaque_AESWord o_part_subWord_inst_09_lhs; - t_opaque_AESWord o_part_subWord_inst_09_o; + AESWord w_0; + AESWord w_1; + AESWord w_2; + AESWord w_3; + AESByte o_part_000; + AESByte o_part_001; + AESByte o_part_002; + AESByte o_part_003; + AESByte o_part_004; + AESByte o_part_005; + AESByte o_part_006; + AESByte o_part_007; + AESByte o_part_008; + AESByte o_part_009; + AESByte o_part_010; + AESByte o_part_011; + AESByte lhs_part_00; + AESByte lhs_part_01; + AESByte lhs_part_02; + AESByte lhs_part_03; + AESWord lhs_part_04; + AESByte o_part_012; + AESByte o_part_013; + AESByte o_part_014; + AESByte o_part_015; + AESByte o_part_016; + AESByte o_part_017; + AESByte o_part_018; + AESByte o_part_019; + AESByte o_part_020; + AESByte o_part_021; + AESByte o_part_022; + AESByte o_part_023; + AESByte lhs_part_05; + AESByte lhs_part_06; + AESByte lhs_part_07; + AESByte lhs_part_08; + AESWord lhs_part_09; + AESByte o_part_024; + AESByte o_part_025; + AESByte o_part_026; + AESByte o_part_027; + AESByte o_part_028; + AESByte o_part_029; + AESByte o_part_030; + AESByte o_part_031; + AESByte o_part_032; + AESByte o_part_033; + AESByte o_part_034; + AESByte o_part_035; + AESByte lhs_part_10; + AESByte lhs_part_11; + AESByte lhs_part_12; + AESByte lhs_part_13; + AESWord lhs_part_14; + AESByte o_part_036; + AESByte o_part_037; + AESByte o_part_038; + AESByte o_part_039; + AESByte o_part_040; + AESByte o_part_041; + AESByte o_part_042; + AESByte o_part_043; + AESByte o_part_044; + AESByte o_part_045; + AESByte o_part_046; + AESByte o_part_047; + AESByte lhs_part_15; + AESByte lhs_part_16; + AESByte lhs_part_17; + AESByte lhs_part_18; + AESWord lhs_part_19; + AESByte o_part_048; + AESByte o_part_049; + AESByte o_part_050; + AESByte o_part_051; + AESByte o_part_052; + AESByte o_part_053; + AESByte o_part_054; + AESByte o_part_055; + AESByte o_part_056; + AESByte o_part_057; + AESByte o_part_058; + AESByte o_part_059; + AESByte lhs_part_20; + AESByte lhs_part_21; + AESByte lhs_part_22; + AESByte lhs_part_23; + AESWord lhs_part_24; + AESByte o_part_060; + AESByte o_part_061; + AESByte o_part_062; + AESByte o_part_063; + AESByte o_part_064; + AESByte o_part_065; + AESByte o_part_066; + AESByte o_part_067; + AESByte o_part_068; + AESByte o_part_069; + AESByte o_part_070; + AESByte o_part_071; + AESByte lhs_part_25; + AESByte lhs_part_26; + AESByte lhs_part_27; + AESByte lhs_part_28; + AESWord lhs_part_29; + AESByte o_part_072; + AESByte o_part_073; + AESByte o_part_074; + AESByte o_part_075; + AESByte o_part_076; + AESByte o_part_077; + AESByte o_part_078; + AESByte o_part_079; + AESByte o_part_080; + AESByte o_part_081; + AESByte o_part_082; + AESByte o_part_083; + AESByte lhs_part_30; + AESByte lhs_part_31; + AESByte lhs_part_32; + AESByte lhs_part_33; + AESWord lhs_part_34; + AESByte o_part_084; + AESByte o_part_085; + AESByte o_part_086; + AESByte o_part_087; + AESByte o_part_088; + AESByte o_part_089; + AESByte o_part_090; + AESByte o_part_091; + AESByte o_part_092; + AESByte o_part_093; + AESByte o_part_094; + AESByte o_part_095; + AESByte lhs_part_35; + AESByte lhs_part_36; + AESByte lhs_part_37; + AESByte lhs_part_38; + AESWord lhs_part_39; + AESByte o_part_096; + AESByte o_part_097; + AESByte o_part_098; + AESByte o_part_099; + AESByte o_part_100; + AESByte o_part_101; + AESByte o_part_102; + AESByte o_part_103; + AESByte o_part_104; + AESByte o_part_105; + AESByte o_part_106; + AESByte o_part_107; + AESByte lhs_part_40; + AESByte lhs_part_41; + AESByte lhs_part_42; + AESByte lhs_part_43; + AESWord lhs_part_44; + AESByte o_part_108; + AESByte o_part_109; + AESByte o_part_110; + AESByte o_part_111; + AESByte o_part_112; + AESByte o_part_113; + AESByte o_part_114; + AESByte o_part_115; + AESByte o_part_116; + AESByte o_part_117; + AESByte o_part_118; + AESByte o_part_119; + AESWord o_part_rotWord_inst_00_o; + AESWord o_part_subWord_inst_00_lhs; + AESWord o_part_subWord_inst_00_o; + AESWord o_part_rotWord_inst_01_o; + AESWord o_part_subWord_inst_01_lhs; + AESWord o_part_subWord_inst_01_o; + AESWord o_part_rotWord_inst_02_o; + AESWord o_part_subWord_inst_02_lhs; + AESWord o_part_subWord_inst_02_o; + AESWord o_part_rotWord_inst_03_o; + AESWord o_part_subWord_inst_03_lhs; + AESWord o_part_subWord_inst_03_o; + AESWord o_part_rotWord_inst_04_o; + AESWord o_part_subWord_inst_04_lhs; + AESWord o_part_subWord_inst_04_o; + AESWord o_part_rotWord_inst_05_o; + AESWord o_part_subWord_inst_05_lhs; + AESWord o_part_subWord_inst_05_o; + AESWord o_part_rotWord_inst_06_o; + AESWord o_part_subWord_inst_06_lhs; + AESWord o_part_subWord_inst_06_o; + AESWord o_part_rotWord_inst_07_o; + AESWord o_part_subWord_inst_07_lhs; + AESWord o_part_subWord_inst_07_o; + AESWord o_part_rotWord_inst_08_o; + AESWord o_part_subWord_inst_08_lhs; + AESWord o_part_subWord_inst_08_o; + AESWord o_part_rotWord_inst_09_o; + AESWord o_part_subWord_inst_09_lhs; + AESWord o_part_subWord_inst_09_o; rotWord o_part_rotWord_inst_00( .o /*-->*/ (o_part_rotWord_inst_00_o), .lhs /*<--*/ (w_3) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mixColumns.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mixColumns.sv index c0a6c22e2..c989deb24 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mixColumns.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mixColumns.sv @@ -3,138 +3,138 @@ `include "Cipher_defs.svh" module mixColumns( - input wire t_opaque_AESState state, - output t_opaque_AESState o + input wire AESState state, + output AESState o ); `include "dfhdl_defs.svh" - t_opaque_AESByte o_part_mulByte_0_inst_00_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_00_o; - t_opaque_AESByte o_part_mulByte_1_inst_00_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_00_o; - t_opaque_AESByte o_part_mulByte_2_inst_00_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_00_o; - t_opaque_AESByte o_part_mulByte_2_inst_01_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_01_o; - t_opaque_AESByte o_part_mulByte_2_inst_02_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_02_o; - t_opaque_AESByte o_part_mulByte_0_inst_01_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_01_o; - t_opaque_AESByte o_part_mulByte_1_inst_01_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_01_o; - t_opaque_AESByte o_part_mulByte_2_inst_03_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_03_o; - t_opaque_AESByte o_part_mulByte_2_inst_04_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_04_o; - t_opaque_AESByte o_part_mulByte_2_inst_05_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_05_o; - t_opaque_AESByte o_part_mulByte_0_inst_02_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_02_o; - t_opaque_AESByte o_part_mulByte_1_inst_02_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_02_o; - t_opaque_AESByte o_part_mulByte_1_inst_03_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_03_o; - t_opaque_AESByte o_part_mulByte_2_inst_06_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_06_o; - t_opaque_AESByte o_part_mulByte_2_inst_07_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_07_o; - t_opaque_AESByte o_part_mulByte_0_inst_03_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_03_o; - t_opaque_AESByte o_part_mulByte_0_inst_04_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_04_o; - t_opaque_AESByte o_part_mulByte_1_inst_04_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_04_o; - t_opaque_AESByte o_part_mulByte_2_inst_08_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_08_o; - t_opaque_AESByte o_part_mulByte_2_inst_09_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_09_o; - t_opaque_AESByte o_part_mulByte_2_inst_10_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_10_o; - t_opaque_AESByte o_part_mulByte_0_inst_05_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_05_o; - t_opaque_AESByte o_part_mulByte_1_inst_05_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_05_o; - t_opaque_AESByte o_part_mulByte_2_inst_11_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_11_o; - t_opaque_AESByte o_part_mulByte_2_inst_12_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_12_o; - t_opaque_AESByte o_part_mulByte_2_inst_13_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_13_o; - t_opaque_AESByte o_part_mulByte_0_inst_06_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_06_o; - t_opaque_AESByte o_part_mulByte_1_inst_06_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_06_o; - t_opaque_AESByte o_part_mulByte_1_inst_07_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_07_o; - t_opaque_AESByte o_part_mulByte_2_inst_14_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_14_o; - t_opaque_AESByte o_part_mulByte_2_inst_15_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_15_o; - t_opaque_AESByte o_part_mulByte_0_inst_07_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_07_o; - t_opaque_AESByte o_part_mulByte_0_inst_08_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_08_o; - t_opaque_AESByte o_part_mulByte_1_inst_08_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_08_o; - t_opaque_AESByte o_part_mulByte_2_inst_16_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_16_o; - t_opaque_AESByte o_part_mulByte_2_inst_17_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_17_o; - t_opaque_AESByte o_part_mulByte_2_inst_18_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_18_o; - t_opaque_AESByte o_part_mulByte_0_inst_09_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_09_o; - t_opaque_AESByte o_part_mulByte_1_inst_09_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_09_o; - t_opaque_AESByte o_part_mulByte_2_inst_19_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_19_o; - t_opaque_AESByte o_part_mulByte_2_inst_20_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_20_o; - t_opaque_AESByte o_part_mulByte_2_inst_21_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_21_o; - t_opaque_AESByte o_part_mulByte_0_inst_10_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_10_o; - t_opaque_AESByte o_part_mulByte_1_inst_10_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_10_o; - t_opaque_AESByte o_part_mulByte_1_inst_11_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_11_o; - t_opaque_AESByte o_part_mulByte_2_inst_22_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_22_o; - t_opaque_AESByte o_part_mulByte_2_inst_23_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_23_o; - t_opaque_AESByte o_part_mulByte_0_inst_11_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_11_o; - t_opaque_AESByte o_part_mulByte_0_inst_12_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_12_o; - t_opaque_AESByte o_part_mulByte_1_inst_12_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_12_o; - t_opaque_AESByte o_part_mulByte_2_inst_24_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_24_o; - t_opaque_AESByte o_part_mulByte_2_inst_25_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_25_o; - t_opaque_AESByte o_part_mulByte_2_inst_26_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_26_o; - t_opaque_AESByte o_part_mulByte_0_inst_13_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_13_o; - t_opaque_AESByte o_part_mulByte_1_inst_13_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_13_o; - t_opaque_AESByte o_part_mulByte_2_inst_27_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_27_o; - t_opaque_AESByte o_part_mulByte_2_inst_28_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_28_o; - t_opaque_AESByte o_part_mulByte_2_inst_29_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_29_o; - t_opaque_AESByte o_part_mulByte_0_inst_14_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_14_o; - t_opaque_AESByte o_part_mulByte_1_inst_14_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_14_o; - t_opaque_AESByte o_part_mulByte_1_inst_15_rhs; - t_opaque_AESByte o_part_mulByte_1_inst_15_o; - t_opaque_AESByte o_part_mulByte_2_inst_30_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_30_o; - t_opaque_AESByte o_part_mulByte_2_inst_31_rhs; - t_opaque_AESByte o_part_mulByte_2_inst_31_o; - t_opaque_AESByte o_part_mulByte_0_inst_15_rhs; - t_opaque_AESByte o_part_mulByte_0_inst_15_o; + AESByte o_part_mulByte_0_inst_00_rhs; + AESByte o_part_mulByte_0_inst_00_o; + AESByte o_part_mulByte_1_inst_00_rhs; + AESByte o_part_mulByte_1_inst_00_o; + AESByte o_part_mulByte_2_inst_00_rhs; + AESByte o_part_mulByte_2_inst_00_o; + AESByte o_part_mulByte_2_inst_01_rhs; + AESByte o_part_mulByte_2_inst_01_o; + AESByte o_part_mulByte_2_inst_02_rhs; + AESByte o_part_mulByte_2_inst_02_o; + AESByte o_part_mulByte_0_inst_01_rhs; + AESByte o_part_mulByte_0_inst_01_o; + AESByte o_part_mulByte_1_inst_01_rhs; + AESByte o_part_mulByte_1_inst_01_o; + AESByte o_part_mulByte_2_inst_03_rhs; + AESByte o_part_mulByte_2_inst_03_o; + AESByte o_part_mulByte_2_inst_04_rhs; + AESByte o_part_mulByte_2_inst_04_o; + AESByte o_part_mulByte_2_inst_05_rhs; + AESByte o_part_mulByte_2_inst_05_o; + AESByte o_part_mulByte_0_inst_02_rhs; + AESByte o_part_mulByte_0_inst_02_o; + AESByte o_part_mulByte_1_inst_02_rhs; + AESByte o_part_mulByte_1_inst_02_o; + AESByte o_part_mulByte_1_inst_03_rhs; + AESByte o_part_mulByte_1_inst_03_o; + AESByte o_part_mulByte_2_inst_06_rhs; + AESByte o_part_mulByte_2_inst_06_o; + AESByte o_part_mulByte_2_inst_07_rhs; + AESByte o_part_mulByte_2_inst_07_o; + AESByte o_part_mulByte_0_inst_03_rhs; + AESByte o_part_mulByte_0_inst_03_o; + AESByte o_part_mulByte_0_inst_04_rhs; + AESByte o_part_mulByte_0_inst_04_o; + AESByte o_part_mulByte_1_inst_04_rhs; + AESByte o_part_mulByte_1_inst_04_o; + AESByte o_part_mulByte_2_inst_08_rhs; + AESByte o_part_mulByte_2_inst_08_o; + AESByte o_part_mulByte_2_inst_09_rhs; + AESByte o_part_mulByte_2_inst_09_o; + AESByte o_part_mulByte_2_inst_10_rhs; + AESByte o_part_mulByte_2_inst_10_o; + AESByte o_part_mulByte_0_inst_05_rhs; + AESByte o_part_mulByte_0_inst_05_o; + AESByte o_part_mulByte_1_inst_05_rhs; + AESByte o_part_mulByte_1_inst_05_o; + AESByte o_part_mulByte_2_inst_11_rhs; + AESByte o_part_mulByte_2_inst_11_o; + AESByte o_part_mulByte_2_inst_12_rhs; + AESByte o_part_mulByte_2_inst_12_o; + AESByte o_part_mulByte_2_inst_13_rhs; + AESByte o_part_mulByte_2_inst_13_o; + AESByte o_part_mulByte_0_inst_06_rhs; + AESByte o_part_mulByte_0_inst_06_o; + AESByte o_part_mulByte_1_inst_06_rhs; + AESByte o_part_mulByte_1_inst_06_o; + AESByte o_part_mulByte_1_inst_07_rhs; + AESByte o_part_mulByte_1_inst_07_o; + AESByte o_part_mulByte_2_inst_14_rhs; + AESByte o_part_mulByte_2_inst_14_o; + AESByte o_part_mulByte_2_inst_15_rhs; + AESByte o_part_mulByte_2_inst_15_o; + AESByte o_part_mulByte_0_inst_07_rhs; + AESByte o_part_mulByte_0_inst_07_o; + AESByte o_part_mulByte_0_inst_08_rhs; + AESByte o_part_mulByte_0_inst_08_o; + AESByte o_part_mulByte_1_inst_08_rhs; + AESByte o_part_mulByte_1_inst_08_o; + AESByte o_part_mulByte_2_inst_16_rhs; + AESByte o_part_mulByte_2_inst_16_o; + AESByte o_part_mulByte_2_inst_17_rhs; + AESByte o_part_mulByte_2_inst_17_o; + AESByte o_part_mulByte_2_inst_18_rhs; + AESByte o_part_mulByte_2_inst_18_o; + AESByte o_part_mulByte_0_inst_09_rhs; + AESByte o_part_mulByte_0_inst_09_o; + AESByte o_part_mulByte_1_inst_09_rhs; + AESByte o_part_mulByte_1_inst_09_o; + AESByte o_part_mulByte_2_inst_19_rhs; + AESByte o_part_mulByte_2_inst_19_o; + AESByte o_part_mulByte_2_inst_20_rhs; + AESByte o_part_mulByte_2_inst_20_o; + AESByte o_part_mulByte_2_inst_21_rhs; + AESByte o_part_mulByte_2_inst_21_o; + AESByte o_part_mulByte_0_inst_10_rhs; + AESByte o_part_mulByte_0_inst_10_o; + AESByte o_part_mulByte_1_inst_10_rhs; + AESByte o_part_mulByte_1_inst_10_o; + AESByte o_part_mulByte_1_inst_11_rhs; + AESByte o_part_mulByte_1_inst_11_o; + AESByte o_part_mulByte_2_inst_22_rhs; + AESByte o_part_mulByte_2_inst_22_o; + AESByte o_part_mulByte_2_inst_23_rhs; + AESByte o_part_mulByte_2_inst_23_o; + AESByte o_part_mulByte_0_inst_11_rhs; + AESByte o_part_mulByte_0_inst_11_o; + AESByte o_part_mulByte_0_inst_12_rhs; + AESByte o_part_mulByte_0_inst_12_o; + AESByte o_part_mulByte_1_inst_12_rhs; + AESByte o_part_mulByte_1_inst_12_o; + AESByte o_part_mulByte_2_inst_24_rhs; + AESByte o_part_mulByte_2_inst_24_o; + AESByte o_part_mulByte_2_inst_25_rhs; + AESByte o_part_mulByte_2_inst_25_o; + AESByte o_part_mulByte_2_inst_26_rhs; + AESByte o_part_mulByte_2_inst_26_o; + AESByte o_part_mulByte_0_inst_13_rhs; + AESByte o_part_mulByte_0_inst_13_o; + AESByte o_part_mulByte_1_inst_13_rhs; + AESByte o_part_mulByte_1_inst_13_o; + AESByte o_part_mulByte_2_inst_27_rhs; + AESByte o_part_mulByte_2_inst_27_o; + AESByte o_part_mulByte_2_inst_28_rhs; + AESByte o_part_mulByte_2_inst_28_o; + AESByte o_part_mulByte_2_inst_29_rhs; + AESByte o_part_mulByte_2_inst_29_o; + AESByte o_part_mulByte_0_inst_14_rhs; + AESByte o_part_mulByte_0_inst_14_o; + AESByte o_part_mulByte_1_inst_14_rhs; + AESByte o_part_mulByte_1_inst_14_o; + AESByte o_part_mulByte_1_inst_15_rhs; + AESByte o_part_mulByte_1_inst_15_o; + AESByte o_part_mulByte_2_inst_30_rhs; + AESByte o_part_mulByte_2_inst_30_o; + AESByte o_part_mulByte_2_inst_31_rhs; + AESByte o_part_mulByte_2_inst_31_o; + AESByte o_part_mulByte_0_inst_15_rhs; + AESByte o_part_mulByte_0_inst_15_o; mulByte_0 #( .lhs (8'h02) ) o_part_mulByte_0_inst_00( diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_0.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_0.sv index e6e1f4ee5..f02dc1eeb 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_0.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_0.sv @@ -3,12 +3,12 @@ `include "Cipher_defs.svh" module mulByte_0#(parameter logic [7:0] lhs = 8'hxx)( - input wire t_opaque_AESByte rhs, - output t_opaque_AESByte o + input wire AESByte rhs, + output AESByte o ); `include "dfhdl_defs.svh" - t_opaque_AESByte a_lhs; - t_opaque_AESByte a_o; + AESByte a_lhs; + AESByte a_o; xtime a( .lhs /*<--*/ (a_lhs), .o /*-->*/ (a_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_1.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_1.sv index 8412c6bf9..e7845a4c9 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_1.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_1.sv @@ -3,12 +3,12 @@ `include "Cipher_defs.svh" module mulByte_1#(parameter logic [7:0] lhs = 8'hxx)( - input wire t_opaque_AESByte rhs, - output t_opaque_AESByte o + input wire AESByte rhs, + output AESByte o ); `include "dfhdl_defs.svh" - t_opaque_AESByte a_lhs; - t_opaque_AESByte a_o; + AESByte a_lhs; + AESByte a_o; xtime a( .lhs /*<--*/ (a_lhs), .o /*-->*/ (a_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_2.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_2.sv index 608299974..84b634d96 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_2.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_2.sv @@ -3,8 +3,8 @@ `include "Cipher_defs.svh" module mulByte_2#(parameter logic [7:0] lhs = 8'hxx)( - input wire t_opaque_AESByte rhs, - output t_opaque_AESByte o + input wire AESByte rhs, + output AESByte o ); `include "dfhdl_defs.svh" assign o = 8'h00 ^ rhs; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/rotWord.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/rotWord.sv index 3441e67ca..4f5575942 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/rotWord.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/rotWord.sv @@ -3,8 +3,8 @@ `include "Cipher_defs.svh" module rotWord( - input wire t_opaque_AESWord lhs, - output t_opaque_AESWord o + input wire AESWord lhs, + output AESWord o ); `include "dfhdl_defs.svh" assign o = '{0: lhs[1], 1: lhs[2], 2: lhs[3], 3: lhs[0]}; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/sbox.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/sbox.sv index 766be4c1d..172cc8a8b 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/sbox.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/sbox.sv @@ -3,8 +3,8 @@ `include "Cipher_defs.svh" module sbox( - input wire t_opaque_AESByte lhs, - output t_opaque_AESByte o + input wire AESByte lhs, + output AESByte o ); `include "dfhdl_defs.svh" assign o = sboxLookupTable[lhs]; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/shiftRows.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/shiftRows.sv index 7ef75fdaa..e790907c2 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/shiftRows.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/shiftRows.sv @@ -3,8 +3,8 @@ `include "Cipher_defs.svh" module shiftRows( - input wire t_opaque_AESState state, - output t_opaque_AESState o + input wire AESState state, + output AESState o ); `include "dfhdl_defs.svh" assign o = '{ diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/subBytes.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/subBytes.sv index 10b5cd21a..a07e51e93 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/subBytes.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/subBytes.sv @@ -3,42 +3,42 @@ `include "Cipher_defs.svh" module subBytes( - input wire t_opaque_AESState state, - output t_opaque_AESState o + input wire AESState state, + output AESState o ); `include "dfhdl_defs.svh" - t_opaque_AESByte o_part_sbox_inst_00_lhs; - t_opaque_AESByte o_part_sbox_inst_00_o; - t_opaque_AESByte o_part_sbox_inst_01_lhs; - t_opaque_AESByte o_part_sbox_inst_01_o; - t_opaque_AESByte o_part_sbox_inst_02_lhs; - t_opaque_AESByte o_part_sbox_inst_02_o; - t_opaque_AESByte o_part_sbox_inst_03_lhs; - t_opaque_AESByte o_part_sbox_inst_03_o; - t_opaque_AESByte o_part_sbox_inst_04_lhs; - t_opaque_AESByte o_part_sbox_inst_04_o; - t_opaque_AESByte o_part_sbox_inst_05_lhs; - t_opaque_AESByte o_part_sbox_inst_05_o; - t_opaque_AESByte o_part_sbox_inst_06_lhs; - t_opaque_AESByte o_part_sbox_inst_06_o; - t_opaque_AESByte o_part_sbox_inst_07_lhs; - t_opaque_AESByte o_part_sbox_inst_07_o; - t_opaque_AESByte o_part_sbox_inst_08_lhs; - t_opaque_AESByte o_part_sbox_inst_08_o; - t_opaque_AESByte o_part_sbox_inst_09_lhs; - t_opaque_AESByte o_part_sbox_inst_09_o; - t_opaque_AESByte o_part_sbox_inst_10_lhs; - t_opaque_AESByte o_part_sbox_inst_10_o; - t_opaque_AESByte o_part_sbox_inst_11_lhs; - t_opaque_AESByte o_part_sbox_inst_11_o; - t_opaque_AESByte o_part_sbox_inst_12_lhs; - t_opaque_AESByte o_part_sbox_inst_12_o; - t_opaque_AESByte o_part_sbox_inst_13_lhs; - t_opaque_AESByte o_part_sbox_inst_13_o; - t_opaque_AESByte o_part_sbox_inst_14_lhs; - t_opaque_AESByte o_part_sbox_inst_14_o; - t_opaque_AESByte o_part_sbox_inst_15_lhs; - t_opaque_AESByte o_part_sbox_inst_15_o; + AESByte o_part_sbox_inst_00_lhs; + AESByte o_part_sbox_inst_00_o; + AESByte o_part_sbox_inst_01_lhs; + AESByte o_part_sbox_inst_01_o; + AESByte o_part_sbox_inst_02_lhs; + AESByte o_part_sbox_inst_02_o; + AESByte o_part_sbox_inst_03_lhs; + AESByte o_part_sbox_inst_03_o; + AESByte o_part_sbox_inst_04_lhs; + AESByte o_part_sbox_inst_04_o; + AESByte o_part_sbox_inst_05_lhs; + AESByte o_part_sbox_inst_05_o; + AESByte o_part_sbox_inst_06_lhs; + AESByte o_part_sbox_inst_06_o; + AESByte o_part_sbox_inst_07_lhs; + AESByte o_part_sbox_inst_07_o; + AESByte o_part_sbox_inst_08_lhs; + AESByte o_part_sbox_inst_08_o; + AESByte o_part_sbox_inst_09_lhs; + AESByte o_part_sbox_inst_09_o; + AESByte o_part_sbox_inst_10_lhs; + AESByte o_part_sbox_inst_10_o; + AESByte o_part_sbox_inst_11_lhs; + AESByte o_part_sbox_inst_11_o; + AESByte o_part_sbox_inst_12_lhs; + AESByte o_part_sbox_inst_12_o; + AESByte o_part_sbox_inst_13_lhs; + AESByte o_part_sbox_inst_13_o; + AESByte o_part_sbox_inst_14_lhs; + AESByte o_part_sbox_inst_14_o; + AESByte o_part_sbox_inst_15_lhs; + AESByte o_part_sbox_inst_15_o; sbox o_part_sbox_inst_00( .lhs /*<--*/ (o_part_sbox_inst_00_lhs), .o /*-->*/ (o_part_sbox_inst_00_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/subWord.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/subWord.sv index 4ad1d7a22..6478aac08 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/subWord.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/subWord.sv @@ -3,18 +3,18 @@ `include "Cipher_defs.svh" module subWord( - input wire t_opaque_AESWord lhs, - output t_opaque_AESWord o + input wire AESWord lhs, + output AESWord o ); `include "dfhdl_defs.svh" - t_opaque_AESByte o_part_sbox_inst_0_lhs; - t_opaque_AESByte o_part_sbox_inst_0_o; - t_opaque_AESByte o_part_sbox_inst_1_lhs; - t_opaque_AESByte o_part_sbox_inst_1_o; - t_opaque_AESByte o_part_sbox_inst_2_lhs; - t_opaque_AESByte o_part_sbox_inst_2_o; - t_opaque_AESByte o_part_sbox_inst_3_lhs; - t_opaque_AESByte o_part_sbox_inst_3_o; + AESByte o_part_sbox_inst_0_lhs; + AESByte o_part_sbox_inst_0_o; + AESByte o_part_sbox_inst_1_lhs; + AESByte o_part_sbox_inst_1_o; + AESByte o_part_sbox_inst_2_lhs; + AESByte o_part_sbox_inst_2_o; + AESByte o_part_sbox_inst_3_lhs; + AESByte o_part_sbox_inst_3_o; sbox o_part_sbox_inst_0( .lhs /*<--*/ (o_part_sbox_inst_0_lhs), .o /*-->*/ (o_part_sbox_inst_0_o) diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/xtime.sv b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/xtime.sv index 2588c778b..457b0e18d 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/xtime.sv +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/verilog.sv2009/hdl/xtime.sv @@ -3,8 +3,8 @@ `include "Cipher_defs.svh" module xtime( - input wire t_opaque_AESByte lhs, - output t_opaque_AESByte o + input wire AESByte lhs, + output AESByte o ); `include "dfhdl_defs.svh" logic [7:0] shifted; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/Cipher.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/Cipher.vhd index 201f3c09c..b425a1006 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/Cipher.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/Cipher.vhd @@ -6,16 +6,16 @@ use work.Cipher_pkg.all; entity Cipher is port ( - key : in t_opaque_AESKey; - data : in t_opaque_AESData; - o : out t_opaque_AESData + key : in AESKey; + data : in AESData; + o : out AESData ); end Cipher; architecture Cipher_arch of Cipher is - signal o_part_cipher_inst_data : t_opaque_AESData; - signal o_part_cipher_inst_key : t_opaque_AESKey; - signal o_part_cipher_inst_o : t_opaque_AESData; + signal o_part_cipher_inst_data : AESData; + signal o_part_cipher_inst_key : AESKey; + signal o_part_cipher_inst_o : AESData; begin o_part_cipher_inst : entity work.cipher_0(cipher_0_arch) port map ( data => o_part_cipher_inst_data, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/Cipher_pkg.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/Cipher_pkg.vhd index 6f19bef16..2af1166c1 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/Cipher_pkg.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/Cipher_pkg.vhd @@ -4,36 +4,36 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package Cipher_pkg is -subtype t_opaque_AESByte is std_logic_vector(7 downto 0); -function to_t_opaque_AESByte(A: std_logic_vector) return t_opaque_AESByte; -type t_arrX1_t_opaque_AESByte is array (natural range <>) of t_opaque_AESByte; -function bitWidth(A : t_arrX1_t_opaque_AESByte) return integer; -function to_slv(A : t_arrX1_t_opaque_AESByte) return std_logic_vector; -function to_t_arrX1_t_opaque_AESByte(A : std_logic_vector; D1 : integer) return t_arrX1_t_opaque_AESByte; -function bool_sel(C : boolean; T : t_arrX1_t_opaque_AESByte; F : t_arrX1_t_opaque_AESByte) return t_arrX1_t_opaque_AESByte; -subtype t_opaque_AESWord is t_arrX1_t_opaque_AESByte(0 to 3); -function to_t_opaque_AESWord(A: std_logic_vector) return t_opaque_AESWord; -type t_arrX1_t_opaque_AESWord is array (natural range <>) of t_opaque_AESWord; -function bitWidth(A : t_arrX1_t_opaque_AESWord) return integer; -function to_slv(A : t_arrX1_t_opaque_AESWord) return std_logic_vector; -function to_t_arrX1_t_opaque_AESWord(A : std_logic_vector; D1 : integer) return t_arrX1_t_opaque_AESWord; -function bool_sel(C : boolean; T : t_arrX1_t_opaque_AESWord; F : t_arrX1_t_opaque_AESWord) return t_arrX1_t_opaque_AESWord; -subtype t_opaque_AESKey is t_arrX1_t_opaque_AESWord(0 to 3); -function to_t_opaque_AESKey(A: std_logic_vector) return t_opaque_AESKey; -subtype t_opaque_AESData is t_arrX1_t_opaque_AESWord(0 to 3); -function to_t_opaque_AESData(A: std_logic_vector) return t_opaque_AESData; -subtype t_opaque_AESKeySchedule is t_arrX1_t_opaque_AESWord(0 to 43); -function to_t_opaque_AESKeySchedule(A: std_logic_vector) return t_opaque_AESKeySchedule; +subtype AESByte is std_logic_vector(7 downto 0); +function to_AESByte(A: std_logic_vector) return AESByte; +type t_arrX1_AESByte is array (natural range <>) of AESByte; +function bitWidth(A : t_arrX1_AESByte) return integer; +function to_slv(A : t_arrX1_AESByte) return std_logic_vector; +function to_t_arrX1_AESByte(A : std_logic_vector; D1 : integer) return t_arrX1_AESByte; +function bool_sel(C : boolean; T : t_arrX1_AESByte; F : t_arrX1_AESByte) return t_arrX1_AESByte; +subtype AESWord is t_arrX1_AESByte(0 to 3); +function to_AESWord(A: std_logic_vector) return AESWord; +type t_arrX1_AESWord is array (natural range <>) of AESWord; +function bitWidth(A : t_arrX1_AESWord) return integer; +function to_slv(A : t_arrX1_AESWord) return std_logic_vector; +function to_t_arrX1_AESWord(A : std_logic_vector; D1 : integer) return t_arrX1_AESWord; +function bool_sel(C : boolean; T : t_arrX1_AESWord; F : t_arrX1_AESWord) return t_arrX1_AESWord; +subtype AESKey is t_arrX1_AESWord(0 to 3); +function to_AESKey(A: std_logic_vector) return AESKey; +subtype AESData is t_arrX1_AESWord(0 to 3); +function to_AESData(A: std_logic_vector) return AESData; +subtype AESKeySchedule is t_arrX1_AESWord(0 to 43); +function to_AESKeySchedule(A: std_logic_vector) return AESKeySchedule; type t_arrX1_std_logic_vector is array (natural range <>) of std_logic_vector; function bitWidth(A : t_arrX1_std_logic_vector) return integer; function to_slv(A : t_arrX1_std_logic_vector) return std_logic_vector; function to_t_arrX1_std_logic_vector(A : std_logic_vector; D1 : integer; D0 : integer) return t_arrX1_std_logic_vector; function bool_sel(C : boolean; T : t_arrX1_std_logic_vector; F : t_arrX1_std_logic_vector) return t_arrX1_std_logic_vector; -subtype t_opaque_AESState is t_arrX1_t_opaque_AESWord(0 to 3); -function to_t_opaque_AESState(A: std_logic_vector) return t_opaque_AESState; -subtype t_opaque_AESRoundKey is t_arrX1_t_opaque_AESWord(0 to 3); -function to_t_opaque_AESRoundKey(A: std_logic_vector) return t_opaque_AESRoundKey; -constant Rcon : t_arrX1_t_opaque_AESWord(0 to 10) := ( +subtype AESState is t_arrX1_AESWord(0 to 3); +function to_AESState(A: std_logic_vector) return AESState; +subtype AESRoundKey is t_arrX1_AESWord(0 to 3); +function to_AESRoundKey(A: std_logic_vector) return AESRoundKey; +constant Rcon : t_arrX1_AESWord(0 to 10) := ( 0 => (0 => x"00", 1 => x"00", 2 => x"00", 3 => x"00"), 1 => (0 => x"01", 1 => x"00", 2 => x"00", 3 => x"00"), 2 => (0 => x"02", 1 => x"00", 2 => x"00", 3 => x"00"), 3 => (0 => x"04", 1 => x"00", 2 => x"00", 3 => x"00"), 4 => (0 => x"08", 1 => x"00", 2 => x"00", 3 => x"00"), 5 => (0 => x"10", 1 => x"00", 2 => x"00", 3 => x"00"), @@ -78,53 +78,53 @@ constant sboxLookupTable : t_arrX1_std_logic_vector(0 to 255)(7 downto 0) := ( end package Cipher_pkg; package body Cipher_pkg is -function to_t_opaque_AESByte(A : std_logic_vector) return t_opaque_AESByte is +function to_AESByte(A : std_logic_vector) return AESByte is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; return A0; end; -function to_t_opaque_AESWord(A : std_logic_vector) return t_opaque_AESWord is +function to_AESWord(A : std_logic_vector) return AESWord is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESByte(A0, 4); + return to_t_arrX1_AESByte(A0, 4); end; -function to_t_opaque_AESKey(A : std_logic_vector) return t_opaque_AESKey is +function to_AESKey(A : std_logic_vector) return AESKey is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 4); + return to_t_arrX1_AESWord(A0, 4); end; -function to_t_opaque_AESData(A : std_logic_vector) return t_opaque_AESData is +function to_AESData(A : std_logic_vector) return AESData is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 4); + return to_t_arrX1_AESWord(A0, 4); end; -function to_t_opaque_AESKeySchedule(A : std_logic_vector) return t_opaque_AESKeySchedule is +function to_AESKeySchedule(A : std_logic_vector) return AESKeySchedule is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 44); + return to_t_arrX1_AESWord(A0, 44); end; -function to_t_opaque_AESState(A : std_logic_vector) return t_opaque_AESState is +function to_AESState(A : std_logic_vector) return AESState is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 4); + return to_t_arrX1_AESWord(A0, 4); end; -function to_t_opaque_AESRoundKey(A : std_logic_vector) return t_opaque_AESRoundKey is +function to_AESRoundKey(A : std_logic_vector) return AESRoundKey is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX1_t_opaque_AESWord(A0, 4); + return to_t_arrX1_AESWord(A0, 4); end; -function bitWidth(A : t_arrX1_t_opaque_AESByte) return integer is +function bitWidth(A : t_arrX1_AESByte) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX1_t_opaque_AESByte) return std_logic_vector is +function to_slv(A : t_arrX1_AESByte) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -138,21 +138,21 @@ begin end loop; return ret; end; -function to_t_arrX1_t_opaque_AESByte(A : std_logic_vector; D1 : integer) return t_arrX1_t_opaque_AESByte is +function to_t_arrX1_AESByte(A : std_logic_vector; D1 : integer) return t_arrX1_AESByte is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX1_t_opaque_AESByte(0 to D1 - 1); + variable ret : t_arrX1_AESByte(0 to D1 - 1); begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESByte(A(hi downto lo)); + ret(i) := to_AESByte(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX1_t_opaque_AESByte; F : t_arrX1_t_opaque_AESByte) return t_arrX1_t_opaque_AESByte is +function bool_sel(C : boolean; T : t_arrX1_AESByte; F : t_arrX1_AESByte) return t_arrX1_AESByte is begin if C then return T; @@ -160,11 +160,11 @@ begin return F; end if; end; -function bitWidth(A : t_arrX1_t_opaque_AESWord) return integer is +function bitWidth(A : t_arrX1_AESWord) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX1_t_opaque_AESWord) return std_logic_vector is +function to_slv(A : t_arrX1_AESWord) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -178,21 +178,21 @@ begin end loop; return ret; end; -function to_t_arrX1_t_opaque_AESWord(A : std_logic_vector; D1 : integer) return t_arrX1_t_opaque_AESWord is +function to_t_arrX1_AESWord(A : std_logic_vector; D1 : integer) return t_arrX1_AESWord is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX1_t_opaque_AESWord(0 to D1 - 1); + variable ret : t_arrX1_AESWord(0 to D1 - 1); begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESWord(A(hi downto lo)); + ret(i) := to_AESWord(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX1_t_opaque_AESWord; F : t_arrX1_t_opaque_AESWord) return t_arrX1_t_opaque_AESWord is +function bool_sel(C : boolean; T : t_arrX1_AESWord; F : t_arrX1_AESWord) return t_arrX1_AESWord is begin if C then return T; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/addRoundKey.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/addRoundKey.vhd index 4a329d2b5..0d40524e3 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/addRoundKey.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/addRoundKey.vhd @@ -6,9 +6,9 @@ use work.Cipher_pkg.all; entity addRoundKey is port ( - state : in t_opaque_AESState; - key : in t_opaque_AESRoundKey; - o : out t_opaque_AESState + state : in AESState; + key : in AESRoundKey; + o : out AESState ); end addRoundKey; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/cipher_0.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/cipher_0.vhd index a0f38a530..6156588e5 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/cipher_0.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/cipher_0.vhd @@ -6,106 +6,106 @@ use work.Cipher_pkg.all; entity cipher_0 is port ( - data : in t_opaque_AESData; - key : in t_opaque_AESKey; - o : out t_opaque_AESData + data : in AESData; + key : in AESKey; + o : out AESData ); end cipher_0; architecture cipher_0_arch of cipher_0 is - signal keySchedule_key : t_opaque_AESKey; - signal keySchedule_o : t_opaque_AESKeySchedule; - signal state_00_state : t_opaque_AESState; - signal state_00_key : t_opaque_AESRoundKey; - signal state_00_o : t_opaque_AESState; - signal o_part_subBytes_inst_00_state : t_opaque_AESState; - signal o_part_subBytes_inst_00_o : t_opaque_AESState; - signal o_part_shiftRows_inst_00_state : t_opaque_AESState; - signal o_part_shiftRows_inst_00_o : t_opaque_AESState; - signal o_part_mixColumns_inst_0_state : t_opaque_AESState; - signal o_part_mixColumns_inst_0_o : t_opaque_AESState; - signal state_01_state : t_opaque_AESState; - signal state_01_key : t_opaque_AESRoundKey; - signal state_01_o : t_opaque_AESState; - signal o_part_subBytes_inst_01_state : t_opaque_AESState; - signal o_part_subBytes_inst_01_o : t_opaque_AESState; - signal o_part_shiftRows_inst_01_state : t_opaque_AESState; - signal o_part_shiftRows_inst_01_o : t_opaque_AESState; - signal o_part_mixColumns_inst_1_state : t_opaque_AESState; - signal o_part_mixColumns_inst_1_o : t_opaque_AESState; - signal state_02_state : t_opaque_AESState; - signal state_02_key : t_opaque_AESRoundKey; - signal state_02_o : t_opaque_AESState; - signal o_part_subBytes_inst_02_state : t_opaque_AESState; - signal o_part_subBytes_inst_02_o : t_opaque_AESState; - signal o_part_shiftRows_inst_02_state : t_opaque_AESState; - signal o_part_shiftRows_inst_02_o : t_opaque_AESState; - signal o_part_mixColumns_inst_2_state : t_opaque_AESState; - signal o_part_mixColumns_inst_2_o : t_opaque_AESState; - signal state_03_state : t_opaque_AESState; - signal state_03_key : t_opaque_AESRoundKey; - signal state_03_o : t_opaque_AESState; - signal o_part_subBytes_inst_03_state : t_opaque_AESState; - signal o_part_subBytes_inst_03_o : t_opaque_AESState; - signal o_part_shiftRows_inst_03_state : t_opaque_AESState; - signal o_part_shiftRows_inst_03_o : t_opaque_AESState; - signal o_part_mixColumns_inst_3_state : t_opaque_AESState; - signal o_part_mixColumns_inst_3_o : t_opaque_AESState; - signal state_04_state : t_opaque_AESState; - signal state_04_key : t_opaque_AESRoundKey; - signal state_04_o : t_opaque_AESState; - signal o_part_subBytes_inst_04_state : t_opaque_AESState; - signal o_part_subBytes_inst_04_o : t_opaque_AESState; - signal o_part_shiftRows_inst_04_state : t_opaque_AESState; - signal o_part_shiftRows_inst_04_o : t_opaque_AESState; - signal o_part_mixColumns_inst_4_state : t_opaque_AESState; - signal o_part_mixColumns_inst_4_o : t_opaque_AESState; - signal state_05_state : t_opaque_AESState; - signal state_05_key : t_opaque_AESRoundKey; - signal state_05_o : t_opaque_AESState; - signal o_part_subBytes_inst_05_state : t_opaque_AESState; - signal o_part_subBytes_inst_05_o : t_opaque_AESState; - signal o_part_shiftRows_inst_05_state : t_opaque_AESState; - signal o_part_shiftRows_inst_05_o : t_opaque_AESState; - signal o_part_mixColumns_inst_5_state : t_opaque_AESState; - signal o_part_mixColumns_inst_5_o : t_opaque_AESState; - signal state_06_state : t_opaque_AESState; - signal state_06_key : t_opaque_AESRoundKey; - signal state_06_o : t_opaque_AESState; - signal o_part_subBytes_inst_06_state : t_opaque_AESState; - signal o_part_subBytes_inst_06_o : t_opaque_AESState; - signal o_part_shiftRows_inst_06_state : t_opaque_AESState; - signal o_part_shiftRows_inst_06_o : t_opaque_AESState; - signal o_part_mixColumns_inst_6_state : t_opaque_AESState; - signal o_part_mixColumns_inst_6_o : t_opaque_AESState; - signal state_07_state : t_opaque_AESState; - signal state_07_key : t_opaque_AESRoundKey; - signal state_07_o : t_opaque_AESState; - signal o_part_subBytes_inst_07_state : t_opaque_AESState; - signal o_part_subBytes_inst_07_o : t_opaque_AESState; - signal o_part_shiftRows_inst_07_state : t_opaque_AESState; - signal o_part_shiftRows_inst_07_o : t_opaque_AESState; - signal o_part_mixColumns_inst_7_state : t_opaque_AESState; - signal o_part_mixColumns_inst_7_o : t_opaque_AESState; - signal state_08_state : t_opaque_AESState; - signal state_08_key : t_opaque_AESRoundKey; - signal state_08_o : t_opaque_AESState; - signal o_part_subBytes_inst_08_state : t_opaque_AESState; - signal o_part_subBytes_inst_08_o : t_opaque_AESState; - signal o_part_shiftRows_inst_08_state : t_opaque_AESState; - signal o_part_shiftRows_inst_08_o : t_opaque_AESState; - signal o_part_mixColumns_inst_8_state : t_opaque_AESState; - signal o_part_mixColumns_inst_8_o : t_opaque_AESState; - signal state_09_state : t_opaque_AESState; - signal state_09_key : t_opaque_AESRoundKey; - signal state_09_o : t_opaque_AESState; - signal o_part_subBytes_inst_09_state : t_opaque_AESState; - signal o_part_subBytes_inst_09_o : t_opaque_AESState; - signal o_part_shiftRows_inst_09_state : t_opaque_AESState; - signal o_part_shiftRows_inst_09_o : t_opaque_AESState; - signal state_10_state : t_opaque_AESState; - signal state_10_key : t_opaque_AESRoundKey; - signal state_10_o : t_opaque_AESState; + signal keySchedule_key : AESKey; + signal keySchedule_o : AESKeySchedule; + signal state_00_state : AESState; + signal state_00_key : AESRoundKey; + signal state_00_o : AESState; + signal o_part_subBytes_inst_00_state : AESState; + signal o_part_subBytes_inst_00_o : AESState; + signal o_part_shiftRows_inst_00_state : AESState; + signal o_part_shiftRows_inst_00_o : AESState; + signal o_part_mixColumns_inst_0_state : AESState; + signal o_part_mixColumns_inst_0_o : AESState; + signal state_01_state : AESState; + signal state_01_key : AESRoundKey; + signal state_01_o : AESState; + signal o_part_subBytes_inst_01_state : AESState; + signal o_part_subBytes_inst_01_o : AESState; + signal o_part_shiftRows_inst_01_state : AESState; + signal o_part_shiftRows_inst_01_o : AESState; + signal o_part_mixColumns_inst_1_state : AESState; + signal o_part_mixColumns_inst_1_o : AESState; + signal state_02_state : AESState; + signal state_02_key : AESRoundKey; + signal state_02_o : AESState; + signal o_part_subBytes_inst_02_state : AESState; + signal o_part_subBytes_inst_02_o : AESState; + signal o_part_shiftRows_inst_02_state : AESState; + signal o_part_shiftRows_inst_02_o : AESState; + signal o_part_mixColumns_inst_2_state : AESState; + signal o_part_mixColumns_inst_2_o : AESState; + signal state_03_state : AESState; + signal state_03_key : AESRoundKey; + signal state_03_o : AESState; + signal o_part_subBytes_inst_03_state : AESState; + signal o_part_subBytes_inst_03_o : AESState; + signal o_part_shiftRows_inst_03_state : AESState; + signal o_part_shiftRows_inst_03_o : AESState; + signal o_part_mixColumns_inst_3_state : AESState; + signal o_part_mixColumns_inst_3_o : AESState; + signal state_04_state : AESState; + signal state_04_key : AESRoundKey; + signal state_04_o : AESState; + signal o_part_subBytes_inst_04_state : AESState; + signal o_part_subBytes_inst_04_o : AESState; + signal o_part_shiftRows_inst_04_state : AESState; + signal o_part_shiftRows_inst_04_o : AESState; + signal o_part_mixColumns_inst_4_state : AESState; + signal o_part_mixColumns_inst_4_o : AESState; + signal state_05_state : AESState; + signal state_05_key : AESRoundKey; + signal state_05_o : AESState; + signal o_part_subBytes_inst_05_state : AESState; + signal o_part_subBytes_inst_05_o : AESState; + signal o_part_shiftRows_inst_05_state : AESState; + signal o_part_shiftRows_inst_05_o : AESState; + signal o_part_mixColumns_inst_5_state : AESState; + signal o_part_mixColumns_inst_5_o : AESState; + signal state_06_state : AESState; + signal state_06_key : AESRoundKey; + signal state_06_o : AESState; + signal o_part_subBytes_inst_06_state : AESState; + signal o_part_subBytes_inst_06_o : AESState; + signal o_part_shiftRows_inst_06_state : AESState; + signal o_part_shiftRows_inst_06_o : AESState; + signal o_part_mixColumns_inst_6_state : AESState; + signal o_part_mixColumns_inst_6_o : AESState; + signal state_07_state : AESState; + signal state_07_key : AESRoundKey; + signal state_07_o : AESState; + signal o_part_subBytes_inst_07_state : AESState; + signal o_part_subBytes_inst_07_o : AESState; + signal o_part_shiftRows_inst_07_state : AESState; + signal o_part_shiftRows_inst_07_o : AESState; + signal o_part_mixColumns_inst_7_state : AESState; + signal o_part_mixColumns_inst_7_o : AESState; + signal state_08_state : AESState; + signal state_08_key : AESRoundKey; + signal state_08_o : AESState; + signal o_part_subBytes_inst_08_state : AESState; + signal o_part_subBytes_inst_08_o : AESState; + signal o_part_shiftRows_inst_08_state : AESState; + signal o_part_shiftRows_inst_08_o : AESState; + signal o_part_mixColumns_inst_8_state : AESState; + signal o_part_mixColumns_inst_8_o : AESState; + signal state_09_state : AESState; + signal state_09_key : AESRoundKey; + signal state_09_o : AESState; + signal o_part_subBytes_inst_09_state : AESState; + signal o_part_subBytes_inst_09_o : AESState; + signal o_part_shiftRows_inst_09_state : AESState; + signal o_part_shiftRows_inst_09_o : AESState; + signal state_10_state : AESState; + signal state_10_key : AESRoundKey; + signal state_10_o : AESState; begin keySchedule : entity work.keyExpansion(keyExpansion_arch) port map ( key => keySchedule_key, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/keyExpansion.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/keyExpansion.vhd index fc42d4f18..b4bbf60e6 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/keyExpansion.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/keyExpansion.vhd @@ -6,211 +6,211 @@ use work.Cipher_pkg.all; entity keyExpansion is port ( - key : in t_opaque_AESKey; - o : out t_opaque_AESKeySchedule + key : in AESKey; + o : out AESKeySchedule ); end keyExpansion; architecture keyExpansion_arch of keyExpansion is - signal w_0 : t_opaque_AESWord; - signal w_1 : t_opaque_AESWord; - signal w_2 : t_opaque_AESWord; - signal w_3 : t_opaque_AESWord; - signal o_part_000 : t_opaque_AESByte; - signal o_part_001 : t_opaque_AESByte; - signal o_part_002 : t_opaque_AESByte; - signal o_part_003 : t_opaque_AESByte; - signal o_part_004 : t_opaque_AESByte; - signal o_part_005 : t_opaque_AESByte; - signal o_part_006 : t_opaque_AESByte; - signal o_part_007 : t_opaque_AESByte; - signal o_part_008 : t_opaque_AESByte; - signal o_part_009 : t_opaque_AESByte; - signal o_part_010 : t_opaque_AESByte; - signal o_part_011 : t_opaque_AESByte; - signal lhs_part_00 : t_opaque_AESByte; - signal lhs_part_01 : t_opaque_AESByte; - signal lhs_part_02 : t_opaque_AESByte; - signal lhs_part_03 : t_opaque_AESByte; - signal lhs_part_04 : t_opaque_AESWord; - signal o_part_012 : t_opaque_AESByte; - signal o_part_013 : t_opaque_AESByte; - signal o_part_014 : t_opaque_AESByte; - signal o_part_015 : t_opaque_AESByte; - signal o_part_016 : t_opaque_AESByte; - signal o_part_017 : t_opaque_AESByte; - signal o_part_018 : t_opaque_AESByte; - signal o_part_019 : t_opaque_AESByte; - signal o_part_020 : t_opaque_AESByte; - signal o_part_021 : t_opaque_AESByte; - signal o_part_022 : t_opaque_AESByte; - signal o_part_023 : t_opaque_AESByte; - signal lhs_part_05 : t_opaque_AESByte; - signal lhs_part_06 : t_opaque_AESByte; - signal lhs_part_07 : t_opaque_AESByte; - signal lhs_part_08 : t_opaque_AESByte; - signal lhs_part_09 : t_opaque_AESWord; - signal o_part_024 : t_opaque_AESByte; - signal o_part_025 : t_opaque_AESByte; - signal o_part_026 : t_opaque_AESByte; - signal o_part_027 : t_opaque_AESByte; - signal o_part_028 : t_opaque_AESByte; - signal o_part_029 : t_opaque_AESByte; - signal o_part_030 : t_opaque_AESByte; - signal o_part_031 : t_opaque_AESByte; - signal o_part_032 : t_opaque_AESByte; - signal o_part_033 : t_opaque_AESByte; - signal o_part_034 : t_opaque_AESByte; - signal o_part_035 : t_opaque_AESByte; - signal lhs_part_10 : t_opaque_AESByte; - signal lhs_part_11 : t_opaque_AESByte; - signal lhs_part_12 : t_opaque_AESByte; - signal lhs_part_13 : t_opaque_AESByte; - signal lhs_part_14 : t_opaque_AESWord; - signal o_part_036 : t_opaque_AESByte; - signal o_part_037 : t_opaque_AESByte; - signal o_part_038 : t_opaque_AESByte; - signal o_part_039 : t_opaque_AESByte; - signal o_part_040 : t_opaque_AESByte; - signal o_part_041 : t_opaque_AESByte; - signal o_part_042 : t_opaque_AESByte; - signal o_part_043 : t_opaque_AESByte; - signal o_part_044 : t_opaque_AESByte; - signal o_part_045 : t_opaque_AESByte; - signal o_part_046 : t_opaque_AESByte; - signal o_part_047 : t_opaque_AESByte; - signal lhs_part_15 : t_opaque_AESByte; - signal lhs_part_16 : t_opaque_AESByte; - signal lhs_part_17 : t_opaque_AESByte; - signal lhs_part_18 : t_opaque_AESByte; - signal lhs_part_19 : t_opaque_AESWord; - signal o_part_048 : t_opaque_AESByte; - signal o_part_049 : t_opaque_AESByte; - signal o_part_050 : t_opaque_AESByte; - signal o_part_051 : t_opaque_AESByte; - signal o_part_052 : t_opaque_AESByte; - signal o_part_053 : t_opaque_AESByte; - signal o_part_054 : t_opaque_AESByte; - signal o_part_055 : t_opaque_AESByte; - signal o_part_056 : t_opaque_AESByte; - signal o_part_057 : t_opaque_AESByte; - signal o_part_058 : t_opaque_AESByte; - signal o_part_059 : t_opaque_AESByte; - signal lhs_part_20 : t_opaque_AESByte; - signal lhs_part_21 : t_opaque_AESByte; - signal lhs_part_22 : t_opaque_AESByte; - signal lhs_part_23 : t_opaque_AESByte; - signal lhs_part_24 : t_opaque_AESWord; - signal o_part_060 : t_opaque_AESByte; - signal o_part_061 : t_opaque_AESByte; - signal o_part_062 : t_opaque_AESByte; - signal o_part_063 : t_opaque_AESByte; - signal o_part_064 : t_opaque_AESByte; - signal o_part_065 : t_opaque_AESByte; - signal o_part_066 : t_opaque_AESByte; - signal o_part_067 : t_opaque_AESByte; - signal o_part_068 : t_opaque_AESByte; - signal o_part_069 : t_opaque_AESByte; - signal o_part_070 : t_opaque_AESByte; - signal o_part_071 : t_opaque_AESByte; - signal lhs_part_25 : t_opaque_AESByte; - signal lhs_part_26 : t_opaque_AESByte; - signal lhs_part_27 : t_opaque_AESByte; - signal lhs_part_28 : t_opaque_AESByte; - signal lhs_part_29 : t_opaque_AESWord; - signal o_part_072 : t_opaque_AESByte; - signal o_part_073 : t_opaque_AESByte; - signal o_part_074 : t_opaque_AESByte; - signal o_part_075 : t_opaque_AESByte; - signal o_part_076 : t_opaque_AESByte; - signal o_part_077 : t_opaque_AESByte; - signal o_part_078 : t_opaque_AESByte; - signal o_part_079 : t_opaque_AESByte; - signal o_part_080 : t_opaque_AESByte; - signal o_part_081 : t_opaque_AESByte; - signal o_part_082 : t_opaque_AESByte; - signal o_part_083 : t_opaque_AESByte; - signal lhs_part_30 : t_opaque_AESByte; - signal lhs_part_31 : t_opaque_AESByte; - signal lhs_part_32 : t_opaque_AESByte; - signal lhs_part_33 : t_opaque_AESByte; - signal lhs_part_34 : t_opaque_AESWord; - signal o_part_084 : t_opaque_AESByte; - signal o_part_085 : t_opaque_AESByte; - signal o_part_086 : t_opaque_AESByte; - signal o_part_087 : t_opaque_AESByte; - signal o_part_088 : t_opaque_AESByte; - signal o_part_089 : t_opaque_AESByte; - signal o_part_090 : t_opaque_AESByte; - signal o_part_091 : t_opaque_AESByte; - signal o_part_092 : t_opaque_AESByte; - signal o_part_093 : t_opaque_AESByte; - signal o_part_094 : t_opaque_AESByte; - signal o_part_095 : t_opaque_AESByte; - signal lhs_part_35 : t_opaque_AESByte; - signal lhs_part_36 : t_opaque_AESByte; - signal lhs_part_37 : t_opaque_AESByte; - signal lhs_part_38 : t_opaque_AESByte; - signal lhs_part_39 : t_opaque_AESWord; - signal o_part_096 : t_opaque_AESByte; - signal o_part_097 : t_opaque_AESByte; - signal o_part_098 : t_opaque_AESByte; - signal o_part_099 : t_opaque_AESByte; - signal o_part_100 : t_opaque_AESByte; - signal o_part_101 : t_opaque_AESByte; - signal o_part_102 : t_opaque_AESByte; - signal o_part_103 : t_opaque_AESByte; - signal o_part_104 : t_opaque_AESByte; - signal o_part_105 : t_opaque_AESByte; - signal o_part_106 : t_opaque_AESByte; - signal o_part_107 : t_opaque_AESByte; - signal lhs_part_40 : t_opaque_AESByte; - signal lhs_part_41 : t_opaque_AESByte; - signal lhs_part_42 : t_opaque_AESByte; - signal lhs_part_43 : t_opaque_AESByte; - signal lhs_part_44 : t_opaque_AESWord; - signal o_part_108 : t_opaque_AESByte; - signal o_part_109 : t_opaque_AESByte; - signal o_part_110 : t_opaque_AESByte; - signal o_part_111 : t_opaque_AESByte; - signal o_part_112 : t_opaque_AESByte; - signal o_part_113 : t_opaque_AESByte; - signal o_part_114 : t_opaque_AESByte; - signal o_part_115 : t_opaque_AESByte; - signal o_part_116 : t_opaque_AESByte; - signal o_part_117 : t_opaque_AESByte; - signal o_part_118 : t_opaque_AESByte; - signal o_part_119 : t_opaque_AESByte; - signal o_part_rotWord_inst_00_o : t_opaque_AESWord; - signal o_part_subWord_inst_00_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_00_o : t_opaque_AESWord; - signal o_part_rotWord_inst_01_o : t_opaque_AESWord; - signal o_part_subWord_inst_01_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_01_o : t_opaque_AESWord; - signal o_part_rotWord_inst_02_o : t_opaque_AESWord; - signal o_part_subWord_inst_02_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_02_o : t_opaque_AESWord; - signal o_part_rotWord_inst_03_o : t_opaque_AESWord; - signal o_part_subWord_inst_03_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_03_o : t_opaque_AESWord; - signal o_part_rotWord_inst_04_o : t_opaque_AESWord; - signal o_part_subWord_inst_04_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_04_o : t_opaque_AESWord; - signal o_part_rotWord_inst_05_o : t_opaque_AESWord; - signal o_part_subWord_inst_05_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_05_o : t_opaque_AESWord; - signal o_part_rotWord_inst_06_o : t_opaque_AESWord; - signal o_part_subWord_inst_06_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_06_o : t_opaque_AESWord; - signal o_part_rotWord_inst_07_o : t_opaque_AESWord; - signal o_part_subWord_inst_07_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_07_o : t_opaque_AESWord; - signal o_part_rotWord_inst_08_o : t_opaque_AESWord; - signal o_part_subWord_inst_08_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_08_o : t_opaque_AESWord; - signal o_part_rotWord_inst_09_o : t_opaque_AESWord; - signal o_part_subWord_inst_09_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_09_o : t_opaque_AESWord; + signal w_0 : AESWord; + signal w_1 : AESWord; + signal w_2 : AESWord; + signal w_3 : AESWord; + signal o_part_000 : AESByte; + signal o_part_001 : AESByte; + signal o_part_002 : AESByte; + signal o_part_003 : AESByte; + signal o_part_004 : AESByte; + signal o_part_005 : AESByte; + signal o_part_006 : AESByte; + signal o_part_007 : AESByte; + signal o_part_008 : AESByte; + signal o_part_009 : AESByte; + signal o_part_010 : AESByte; + signal o_part_011 : AESByte; + signal lhs_part_00 : AESByte; + signal lhs_part_01 : AESByte; + signal lhs_part_02 : AESByte; + signal lhs_part_03 : AESByte; + signal lhs_part_04 : AESWord; + signal o_part_012 : AESByte; + signal o_part_013 : AESByte; + signal o_part_014 : AESByte; + signal o_part_015 : AESByte; + signal o_part_016 : AESByte; + signal o_part_017 : AESByte; + signal o_part_018 : AESByte; + signal o_part_019 : AESByte; + signal o_part_020 : AESByte; + signal o_part_021 : AESByte; + signal o_part_022 : AESByte; + signal o_part_023 : AESByte; + signal lhs_part_05 : AESByte; + signal lhs_part_06 : AESByte; + signal lhs_part_07 : AESByte; + signal lhs_part_08 : AESByte; + signal lhs_part_09 : AESWord; + signal o_part_024 : AESByte; + signal o_part_025 : AESByte; + signal o_part_026 : AESByte; + signal o_part_027 : AESByte; + signal o_part_028 : AESByte; + signal o_part_029 : AESByte; + signal o_part_030 : AESByte; + signal o_part_031 : AESByte; + signal o_part_032 : AESByte; + signal o_part_033 : AESByte; + signal o_part_034 : AESByte; + signal o_part_035 : AESByte; + signal lhs_part_10 : AESByte; + signal lhs_part_11 : AESByte; + signal lhs_part_12 : AESByte; + signal lhs_part_13 : AESByte; + signal lhs_part_14 : AESWord; + signal o_part_036 : AESByte; + signal o_part_037 : AESByte; + signal o_part_038 : AESByte; + signal o_part_039 : AESByte; + signal o_part_040 : AESByte; + signal o_part_041 : AESByte; + signal o_part_042 : AESByte; + signal o_part_043 : AESByte; + signal o_part_044 : AESByte; + signal o_part_045 : AESByte; + signal o_part_046 : AESByte; + signal o_part_047 : AESByte; + signal lhs_part_15 : AESByte; + signal lhs_part_16 : AESByte; + signal lhs_part_17 : AESByte; + signal lhs_part_18 : AESByte; + signal lhs_part_19 : AESWord; + signal o_part_048 : AESByte; + signal o_part_049 : AESByte; + signal o_part_050 : AESByte; + signal o_part_051 : AESByte; + signal o_part_052 : AESByte; + signal o_part_053 : AESByte; + signal o_part_054 : AESByte; + signal o_part_055 : AESByte; + signal o_part_056 : AESByte; + signal o_part_057 : AESByte; + signal o_part_058 : AESByte; + signal o_part_059 : AESByte; + signal lhs_part_20 : AESByte; + signal lhs_part_21 : AESByte; + signal lhs_part_22 : AESByte; + signal lhs_part_23 : AESByte; + signal lhs_part_24 : AESWord; + signal o_part_060 : AESByte; + signal o_part_061 : AESByte; + signal o_part_062 : AESByte; + signal o_part_063 : AESByte; + signal o_part_064 : AESByte; + signal o_part_065 : AESByte; + signal o_part_066 : AESByte; + signal o_part_067 : AESByte; + signal o_part_068 : AESByte; + signal o_part_069 : AESByte; + signal o_part_070 : AESByte; + signal o_part_071 : AESByte; + signal lhs_part_25 : AESByte; + signal lhs_part_26 : AESByte; + signal lhs_part_27 : AESByte; + signal lhs_part_28 : AESByte; + signal lhs_part_29 : AESWord; + signal o_part_072 : AESByte; + signal o_part_073 : AESByte; + signal o_part_074 : AESByte; + signal o_part_075 : AESByte; + signal o_part_076 : AESByte; + signal o_part_077 : AESByte; + signal o_part_078 : AESByte; + signal o_part_079 : AESByte; + signal o_part_080 : AESByte; + signal o_part_081 : AESByte; + signal o_part_082 : AESByte; + signal o_part_083 : AESByte; + signal lhs_part_30 : AESByte; + signal lhs_part_31 : AESByte; + signal lhs_part_32 : AESByte; + signal lhs_part_33 : AESByte; + signal lhs_part_34 : AESWord; + signal o_part_084 : AESByte; + signal o_part_085 : AESByte; + signal o_part_086 : AESByte; + signal o_part_087 : AESByte; + signal o_part_088 : AESByte; + signal o_part_089 : AESByte; + signal o_part_090 : AESByte; + signal o_part_091 : AESByte; + signal o_part_092 : AESByte; + signal o_part_093 : AESByte; + signal o_part_094 : AESByte; + signal o_part_095 : AESByte; + signal lhs_part_35 : AESByte; + signal lhs_part_36 : AESByte; + signal lhs_part_37 : AESByte; + signal lhs_part_38 : AESByte; + signal lhs_part_39 : AESWord; + signal o_part_096 : AESByte; + signal o_part_097 : AESByte; + signal o_part_098 : AESByte; + signal o_part_099 : AESByte; + signal o_part_100 : AESByte; + signal o_part_101 : AESByte; + signal o_part_102 : AESByte; + signal o_part_103 : AESByte; + signal o_part_104 : AESByte; + signal o_part_105 : AESByte; + signal o_part_106 : AESByte; + signal o_part_107 : AESByte; + signal lhs_part_40 : AESByte; + signal lhs_part_41 : AESByte; + signal lhs_part_42 : AESByte; + signal lhs_part_43 : AESByte; + signal lhs_part_44 : AESWord; + signal o_part_108 : AESByte; + signal o_part_109 : AESByte; + signal o_part_110 : AESByte; + signal o_part_111 : AESByte; + signal o_part_112 : AESByte; + signal o_part_113 : AESByte; + signal o_part_114 : AESByte; + signal o_part_115 : AESByte; + signal o_part_116 : AESByte; + signal o_part_117 : AESByte; + signal o_part_118 : AESByte; + signal o_part_119 : AESByte; + signal o_part_rotWord_inst_00_o : AESWord; + signal o_part_subWord_inst_00_lhs : AESWord; + signal o_part_subWord_inst_00_o : AESWord; + signal o_part_rotWord_inst_01_o : AESWord; + signal o_part_subWord_inst_01_lhs : AESWord; + signal o_part_subWord_inst_01_o : AESWord; + signal o_part_rotWord_inst_02_o : AESWord; + signal o_part_subWord_inst_02_lhs : AESWord; + signal o_part_subWord_inst_02_o : AESWord; + signal o_part_rotWord_inst_03_o : AESWord; + signal o_part_subWord_inst_03_lhs : AESWord; + signal o_part_subWord_inst_03_o : AESWord; + signal o_part_rotWord_inst_04_o : AESWord; + signal o_part_subWord_inst_04_lhs : AESWord; + signal o_part_subWord_inst_04_o : AESWord; + signal o_part_rotWord_inst_05_o : AESWord; + signal o_part_subWord_inst_05_lhs : AESWord; + signal o_part_subWord_inst_05_o : AESWord; + signal o_part_rotWord_inst_06_o : AESWord; + signal o_part_subWord_inst_06_lhs : AESWord; + signal o_part_subWord_inst_06_o : AESWord; + signal o_part_rotWord_inst_07_o : AESWord; + signal o_part_subWord_inst_07_lhs : AESWord; + signal o_part_subWord_inst_07_o : AESWord; + signal o_part_rotWord_inst_08_o : AESWord; + signal o_part_subWord_inst_08_lhs : AESWord; + signal o_part_subWord_inst_08_o : AESWord; + signal o_part_rotWord_inst_09_o : AESWord; + signal o_part_subWord_inst_09_lhs : AESWord; + signal o_part_subWord_inst_09_o : AESWord; begin o_part_rotWord_inst_00 : entity work.rotWord(rotWord_arch) port map ( o => o_part_rotWord_inst_00_o, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mixColumns.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mixColumns.vhd index 4f5cb477f..b9e69ecbe 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mixColumns.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mixColumns.vhd @@ -6,140 +6,140 @@ use work.Cipher_pkg.all; entity mixColumns is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end mixColumns; architecture mixColumns_arch of mixColumns is - signal o_part_mulByte_0_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_15_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_16_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_16_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_17_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_17_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_18_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_18_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_19_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_19_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_20_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_20_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_21_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_21_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_22_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_22_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_23_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_23_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_24_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_24_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_25_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_25_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_26_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_26_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_27_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_27_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_28_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_28_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_29_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_29_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_15_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_30_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_30_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_31_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_31_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_15_o : t_opaque_AESByte; + signal o_part_mulByte_0_inst_00_rhs : AESByte; + signal o_part_mulByte_0_inst_00_o : AESByte; + signal o_part_mulByte_1_inst_00_rhs : AESByte; + signal o_part_mulByte_1_inst_00_o : AESByte; + signal o_part_mulByte_2_inst_00_rhs : AESByte; + signal o_part_mulByte_2_inst_00_o : AESByte; + signal o_part_mulByte_2_inst_01_rhs : AESByte; + signal o_part_mulByte_2_inst_01_o : AESByte; + signal o_part_mulByte_2_inst_02_rhs : AESByte; + signal o_part_mulByte_2_inst_02_o : AESByte; + signal o_part_mulByte_0_inst_01_rhs : AESByte; + signal o_part_mulByte_0_inst_01_o : AESByte; + signal o_part_mulByte_1_inst_01_rhs : AESByte; + signal o_part_mulByte_1_inst_01_o : AESByte; + signal o_part_mulByte_2_inst_03_rhs : AESByte; + signal o_part_mulByte_2_inst_03_o : AESByte; + signal o_part_mulByte_2_inst_04_rhs : AESByte; + signal o_part_mulByte_2_inst_04_o : AESByte; + signal o_part_mulByte_2_inst_05_rhs : AESByte; + signal o_part_mulByte_2_inst_05_o : AESByte; + signal o_part_mulByte_0_inst_02_rhs : AESByte; + signal o_part_mulByte_0_inst_02_o : AESByte; + signal o_part_mulByte_1_inst_02_rhs : AESByte; + signal o_part_mulByte_1_inst_02_o : AESByte; + signal o_part_mulByte_1_inst_03_rhs : AESByte; + signal o_part_mulByte_1_inst_03_o : AESByte; + signal o_part_mulByte_2_inst_06_rhs : AESByte; + signal o_part_mulByte_2_inst_06_o : AESByte; + signal o_part_mulByte_2_inst_07_rhs : AESByte; + signal o_part_mulByte_2_inst_07_o : AESByte; + signal o_part_mulByte_0_inst_03_rhs : AESByte; + signal o_part_mulByte_0_inst_03_o : AESByte; + signal o_part_mulByte_0_inst_04_rhs : AESByte; + signal o_part_mulByte_0_inst_04_o : AESByte; + signal o_part_mulByte_1_inst_04_rhs : AESByte; + signal o_part_mulByte_1_inst_04_o : AESByte; + signal o_part_mulByte_2_inst_08_rhs : AESByte; + signal o_part_mulByte_2_inst_08_o : AESByte; + signal o_part_mulByte_2_inst_09_rhs : AESByte; + signal o_part_mulByte_2_inst_09_o : AESByte; + signal o_part_mulByte_2_inst_10_rhs : AESByte; + signal o_part_mulByte_2_inst_10_o : AESByte; + signal o_part_mulByte_0_inst_05_rhs : AESByte; + signal o_part_mulByte_0_inst_05_o : AESByte; + signal o_part_mulByte_1_inst_05_rhs : AESByte; + signal o_part_mulByte_1_inst_05_o : AESByte; + signal o_part_mulByte_2_inst_11_rhs : AESByte; + signal o_part_mulByte_2_inst_11_o : AESByte; + signal o_part_mulByte_2_inst_12_rhs : AESByte; + signal o_part_mulByte_2_inst_12_o : AESByte; + signal o_part_mulByte_2_inst_13_rhs : AESByte; + signal o_part_mulByte_2_inst_13_o : AESByte; + signal o_part_mulByte_0_inst_06_rhs : AESByte; + signal o_part_mulByte_0_inst_06_o : AESByte; + signal o_part_mulByte_1_inst_06_rhs : AESByte; + signal o_part_mulByte_1_inst_06_o : AESByte; + signal o_part_mulByte_1_inst_07_rhs : AESByte; + signal o_part_mulByte_1_inst_07_o : AESByte; + signal o_part_mulByte_2_inst_14_rhs : AESByte; + signal o_part_mulByte_2_inst_14_o : AESByte; + signal o_part_mulByte_2_inst_15_rhs : AESByte; + signal o_part_mulByte_2_inst_15_o : AESByte; + signal o_part_mulByte_0_inst_07_rhs : AESByte; + signal o_part_mulByte_0_inst_07_o : AESByte; + signal o_part_mulByte_0_inst_08_rhs : AESByte; + signal o_part_mulByte_0_inst_08_o : AESByte; + signal o_part_mulByte_1_inst_08_rhs : AESByte; + signal o_part_mulByte_1_inst_08_o : AESByte; + signal o_part_mulByte_2_inst_16_rhs : AESByte; + signal o_part_mulByte_2_inst_16_o : AESByte; + signal o_part_mulByte_2_inst_17_rhs : AESByte; + signal o_part_mulByte_2_inst_17_o : AESByte; + signal o_part_mulByte_2_inst_18_rhs : AESByte; + signal o_part_mulByte_2_inst_18_o : AESByte; + signal o_part_mulByte_0_inst_09_rhs : AESByte; + signal o_part_mulByte_0_inst_09_o : AESByte; + signal o_part_mulByte_1_inst_09_rhs : AESByte; + signal o_part_mulByte_1_inst_09_o : AESByte; + signal o_part_mulByte_2_inst_19_rhs : AESByte; + signal o_part_mulByte_2_inst_19_o : AESByte; + signal o_part_mulByte_2_inst_20_rhs : AESByte; + signal o_part_mulByte_2_inst_20_o : AESByte; + signal o_part_mulByte_2_inst_21_rhs : AESByte; + signal o_part_mulByte_2_inst_21_o : AESByte; + signal o_part_mulByte_0_inst_10_rhs : AESByte; + signal o_part_mulByte_0_inst_10_o : AESByte; + signal o_part_mulByte_1_inst_10_rhs : AESByte; + signal o_part_mulByte_1_inst_10_o : AESByte; + signal o_part_mulByte_1_inst_11_rhs : AESByte; + signal o_part_mulByte_1_inst_11_o : AESByte; + signal o_part_mulByte_2_inst_22_rhs : AESByte; + signal o_part_mulByte_2_inst_22_o : AESByte; + signal o_part_mulByte_2_inst_23_rhs : AESByte; + signal o_part_mulByte_2_inst_23_o : AESByte; + signal o_part_mulByte_0_inst_11_rhs : AESByte; + signal o_part_mulByte_0_inst_11_o : AESByte; + signal o_part_mulByte_0_inst_12_rhs : AESByte; + signal o_part_mulByte_0_inst_12_o : AESByte; + signal o_part_mulByte_1_inst_12_rhs : AESByte; + signal o_part_mulByte_1_inst_12_o : AESByte; + signal o_part_mulByte_2_inst_24_rhs : AESByte; + signal o_part_mulByte_2_inst_24_o : AESByte; + signal o_part_mulByte_2_inst_25_rhs : AESByte; + signal o_part_mulByte_2_inst_25_o : AESByte; + signal o_part_mulByte_2_inst_26_rhs : AESByte; + signal o_part_mulByte_2_inst_26_o : AESByte; + signal o_part_mulByte_0_inst_13_rhs : AESByte; + signal o_part_mulByte_0_inst_13_o : AESByte; + signal o_part_mulByte_1_inst_13_rhs : AESByte; + signal o_part_mulByte_1_inst_13_o : AESByte; + signal o_part_mulByte_2_inst_27_rhs : AESByte; + signal o_part_mulByte_2_inst_27_o : AESByte; + signal o_part_mulByte_2_inst_28_rhs : AESByte; + signal o_part_mulByte_2_inst_28_o : AESByte; + signal o_part_mulByte_2_inst_29_rhs : AESByte; + signal o_part_mulByte_2_inst_29_o : AESByte; + signal o_part_mulByte_0_inst_14_rhs : AESByte; + signal o_part_mulByte_0_inst_14_o : AESByte; + signal o_part_mulByte_1_inst_14_rhs : AESByte; + signal o_part_mulByte_1_inst_14_o : AESByte; + signal o_part_mulByte_1_inst_15_rhs : AESByte; + signal o_part_mulByte_1_inst_15_o : AESByte; + signal o_part_mulByte_2_inst_30_rhs : AESByte; + signal o_part_mulByte_2_inst_30_o : AESByte; + signal o_part_mulByte_2_inst_31_rhs : AESByte; + signal o_part_mulByte_2_inst_31_o : AESByte; + signal o_part_mulByte_0_inst_15_rhs : AESByte; + signal o_part_mulByte_0_inst_15_o : AESByte; begin o_part_mulByte_0_inst_00 : entity work.mulByte_0(mulByte_0_arch) generic map ( lhs => x"02" diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_0.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_0.vhd index 42c2ec0ab..6662783ac 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_0.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_0.vhd @@ -9,14 +9,14 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_0; architecture mulByte_0_arch of mulByte_0 is - signal a_lhs : t_opaque_AESByte; - signal a_o : t_opaque_AESByte; + signal a_lhs : AESByte; + signal a_o : AESByte; begin a : entity work.xtime(xtime_arch) port map ( lhs => a_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_1.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_1.vhd index 8db42f698..f6567b48c 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_1.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_1.vhd @@ -9,14 +9,14 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_1; architecture mulByte_1_arch of mulByte_1 is - signal a_lhs : t_opaque_AESByte; - signal a_o : t_opaque_AESByte; + signal a_lhs : AESByte; + signal a_o : AESByte; begin a : entity work.xtime(xtime_arch) port map ( lhs => a_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_2.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_2.vhd index 0b3b4ae1d..fbd334417 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_2.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_2.vhd @@ -9,8 +9,8 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_2; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/rotWord.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/rotWord.vhd index 47ac6f4ec..bd82b9bfc 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/rotWord.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/rotWord.vhd @@ -6,8 +6,8 @@ use work.Cipher_pkg.all; entity rotWord is port ( - lhs : in t_opaque_AESWord; - o : out t_opaque_AESWord + lhs : in AESWord; + o : out AESWord ); end rotWord; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/sbox.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/sbox.vhd index ba5bbbbf1..d6439f006 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/sbox.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/sbox.vhd @@ -6,8 +6,8 @@ use work.Cipher_pkg.all; entity sbox is port ( - lhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + lhs : in AESByte; + o : out AESByte ); end sbox; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/shiftRows.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/shiftRows.vhd index b3393ab1c..a463980ee 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/shiftRows.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/shiftRows.vhd @@ -6,8 +6,8 @@ use work.Cipher_pkg.all; entity shiftRows is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end shiftRows; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/subBytes.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/subBytes.vhd index 11474f315..473b2b723 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/subBytes.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/subBytes.vhd @@ -6,44 +6,44 @@ use work.Cipher_pkg.all; entity subBytes is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end subBytes; architecture subBytes_arch of subBytes is - signal o_part_sbox_inst_00_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_00_o : t_opaque_AESByte; - signal o_part_sbox_inst_01_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_01_o : t_opaque_AESByte; - signal o_part_sbox_inst_02_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_02_o : t_opaque_AESByte; - signal o_part_sbox_inst_03_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_03_o : t_opaque_AESByte; - signal o_part_sbox_inst_04_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_04_o : t_opaque_AESByte; - signal o_part_sbox_inst_05_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_05_o : t_opaque_AESByte; - signal o_part_sbox_inst_06_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_06_o : t_opaque_AESByte; - signal o_part_sbox_inst_07_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_07_o : t_opaque_AESByte; - signal o_part_sbox_inst_08_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_08_o : t_opaque_AESByte; - signal o_part_sbox_inst_09_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_09_o : t_opaque_AESByte; - signal o_part_sbox_inst_10_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_10_o : t_opaque_AESByte; - signal o_part_sbox_inst_11_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_11_o : t_opaque_AESByte; - signal o_part_sbox_inst_12_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_12_o : t_opaque_AESByte; - signal o_part_sbox_inst_13_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_13_o : t_opaque_AESByte; - signal o_part_sbox_inst_14_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_14_o : t_opaque_AESByte; - signal o_part_sbox_inst_15_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_15_o : t_opaque_AESByte; + signal o_part_sbox_inst_00_lhs : AESByte; + signal o_part_sbox_inst_00_o : AESByte; + signal o_part_sbox_inst_01_lhs : AESByte; + signal o_part_sbox_inst_01_o : AESByte; + signal o_part_sbox_inst_02_lhs : AESByte; + signal o_part_sbox_inst_02_o : AESByte; + signal o_part_sbox_inst_03_lhs : AESByte; + signal o_part_sbox_inst_03_o : AESByte; + signal o_part_sbox_inst_04_lhs : AESByte; + signal o_part_sbox_inst_04_o : AESByte; + signal o_part_sbox_inst_05_lhs : AESByte; + signal o_part_sbox_inst_05_o : AESByte; + signal o_part_sbox_inst_06_lhs : AESByte; + signal o_part_sbox_inst_06_o : AESByte; + signal o_part_sbox_inst_07_lhs : AESByte; + signal o_part_sbox_inst_07_o : AESByte; + signal o_part_sbox_inst_08_lhs : AESByte; + signal o_part_sbox_inst_08_o : AESByte; + signal o_part_sbox_inst_09_lhs : AESByte; + signal o_part_sbox_inst_09_o : AESByte; + signal o_part_sbox_inst_10_lhs : AESByte; + signal o_part_sbox_inst_10_o : AESByte; + signal o_part_sbox_inst_11_lhs : AESByte; + signal o_part_sbox_inst_11_o : AESByte; + signal o_part_sbox_inst_12_lhs : AESByte; + signal o_part_sbox_inst_12_o : AESByte; + signal o_part_sbox_inst_13_lhs : AESByte; + signal o_part_sbox_inst_13_o : AESByte; + signal o_part_sbox_inst_14_lhs : AESByte; + signal o_part_sbox_inst_14_o : AESByte; + signal o_part_sbox_inst_15_lhs : AESByte; + signal o_part_sbox_inst_15_o : AESByte; begin o_part_sbox_inst_00 : entity work.sbox(sbox_arch) port map ( lhs => o_part_sbox_inst_00_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/subWord.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/subWord.vhd index b3f99a7e0..7d8597fdd 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/subWord.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/subWord.vhd @@ -6,20 +6,20 @@ use work.Cipher_pkg.all; entity subWord is port ( - lhs : in t_opaque_AESWord; - o : out t_opaque_AESWord + lhs : in AESWord; + o : out AESWord ); end subWord; architecture subWord_arch of subWord is - signal o_part_sbox_inst_0_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_0_o : t_opaque_AESByte; - signal o_part_sbox_inst_1_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_1_o : t_opaque_AESByte; - signal o_part_sbox_inst_2_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_2_o : t_opaque_AESByte; - signal o_part_sbox_inst_3_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_3_o : t_opaque_AESByte; + signal o_part_sbox_inst_0_lhs : AESByte; + signal o_part_sbox_inst_0_o : AESByte; + signal o_part_sbox_inst_1_lhs : AESByte; + signal o_part_sbox_inst_1_o : AESByte; + signal o_part_sbox_inst_2_lhs : AESByte; + signal o_part_sbox_inst_2_o : AESByte; + signal o_part_sbox_inst_3_lhs : AESByte; + signal o_part_sbox_inst_3_o : AESByte; begin o_part_sbox_inst_0 : entity work.sbox(sbox_arch) port map ( lhs => o_part_sbox_inst_0_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/xtime.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/xtime.vhd index 3c9dc8d6a..a6a318581 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/xtime.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v2008/hdl/xtime.vhd @@ -6,8 +6,8 @@ use work.Cipher_pkg.all; entity xtime is port ( - lhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + lhs : in AESByte; + o : out AESByte ); end xtime; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/Cipher.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/Cipher.vhd index 201f3c09c..b425a1006 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/Cipher.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/Cipher.vhd @@ -6,16 +6,16 @@ use work.Cipher_pkg.all; entity Cipher is port ( - key : in t_opaque_AESKey; - data : in t_opaque_AESData; - o : out t_opaque_AESData + key : in AESKey; + data : in AESData; + o : out AESData ); end Cipher; architecture Cipher_arch of Cipher is - signal o_part_cipher_inst_data : t_opaque_AESData; - signal o_part_cipher_inst_key : t_opaque_AESKey; - signal o_part_cipher_inst_o : t_opaque_AESData; + signal o_part_cipher_inst_data : AESData; + signal o_part_cipher_inst_key : AESKey; + signal o_part_cipher_inst_o : AESData; begin o_part_cipher_inst : entity work.cipher_0(cipher_0_arch) port map ( data => o_part_cipher_inst_data, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/Cipher_pkg.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/Cipher_pkg.vhd index 0b047853d..0ee2d8c5a 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/Cipher_pkg.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/Cipher_pkg.vhd @@ -4,46 +4,46 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package Cipher_pkg is -subtype t_opaque_AESByte is std_logic_vector(7 downto 0); -function to_t_opaque_AESByte(A: std_logic_vector) return t_opaque_AESByte; -type t_arrX4_t_opaque_AESByte is array (0 to 4 - 1) of t_opaque_AESByte; -function bitWidth(A : t_arrX4_t_opaque_AESByte) return integer; -function to_slv(A : t_arrX4_t_opaque_AESByte) return std_logic_vector; -function to_t_arrX4_t_opaque_AESByte(A : std_logic_vector) return t_arrX4_t_opaque_AESByte; -function bool_sel(C : boolean; T : t_arrX4_t_opaque_AESByte; F : t_arrX4_t_opaque_AESByte) return t_arrX4_t_opaque_AESByte; -subtype t_opaque_AESWord is t_arrX4_t_opaque_AESByte; -function to_t_opaque_AESWord(A: std_logic_vector) return t_opaque_AESWord; -type t_arrX4_t_opaque_AESWord is array (0 to 4 - 1) of t_opaque_AESWord; -function bitWidth(A : t_arrX4_t_opaque_AESWord) return integer; -function to_slv(A : t_arrX4_t_opaque_AESWord) return std_logic_vector; -function to_t_arrX4_t_opaque_AESWord(A : std_logic_vector) return t_arrX4_t_opaque_AESWord; -function bool_sel(C : boolean; T : t_arrX4_t_opaque_AESWord; F : t_arrX4_t_opaque_AESWord) return t_arrX4_t_opaque_AESWord; -subtype t_opaque_AESKey is t_arrX4_t_opaque_AESWord; -function to_t_opaque_AESKey(A: std_logic_vector) return t_opaque_AESKey; -subtype t_opaque_AESData is t_arrX4_t_opaque_AESWord; -function to_t_opaque_AESData(A: std_logic_vector) return t_opaque_AESData; -type t_arrX11_t_opaque_AESWord is array (0 to 11 - 1) of t_opaque_AESWord; -function bitWidth(A : t_arrX11_t_opaque_AESWord) return integer; -function to_slv(A : t_arrX11_t_opaque_AESWord) return std_logic_vector; -function to_t_arrX11_t_opaque_AESWord(A : std_logic_vector) return t_arrX11_t_opaque_AESWord; -function bool_sel(C : boolean; T : t_arrX11_t_opaque_AESWord; F : t_arrX11_t_opaque_AESWord) return t_arrX11_t_opaque_AESWord; -type t_arrX44_t_opaque_AESWord is array (0 to 44 - 1) of t_opaque_AESWord; -function bitWidth(A : t_arrX44_t_opaque_AESWord) return integer; -function to_slv(A : t_arrX44_t_opaque_AESWord) return std_logic_vector; -function to_t_arrX44_t_opaque_AESWord(A : std_logic_vector) return t_arrX44_t_opaque_AESWord; -function bool_sel(C : boolean; T : t_arrX44_t_opaque_AESWord; F : t_arrX44_t_opaque_AESWord) return t_arrX44_t_opaque_AESWord; -subtype t_opaque_AESKeySchedule is t_arrX44_t_opaque_AESWord; -function to_t_opaque_AESKeySchedule(A: std_logic_vector) return t_opaque_AESKeySchedule; +subtype AESByte is std_logic_vector(7 downto 0); +function to_AESByte(A: std_logic_vector) return AESByte; +type t_arrX4_AESByte is array (0 to 4 - 1) of AESByte; +function bitWidth(A : t_arrX4_AESByte) return integer; +function to_slv(A : t_arrX4_AESByte) return std_logic_vector; +function to_t_arrX4_AESByte(A : std_logic_vector) return t_arrX4_AESByte; +function bool_sel(C : boolean; T : t_arrX4_AESByte; F : t_arrX4_AESByte) return t_arrX4_AESByte; +subtype AESWord is t_arrX4_AESByte; +function to_AESWord(A: std_logic_vector) return AESWord; +type t_arrX4_AESWord is array (0 to 4 - 1) of AESWord; +function bitWidth(A : t_arrX4_AESWord) return integer; +function to_slv(A : t_arrX4_AESWord) return std_logic_vector; +function to_t_arrX4_AESWord(A : std_logic_vector) return t_arrX4_AESWord; +function bool_sel(C : boolean; T : t_arrX4_AESWord; F : t_arrX4_AESWord) return t_arrX4_AESWord; +subtype AESKey is t_arrX4_AESWord; +function to_AESKey(A: std_logic_vector) return AESKey; +subtype AESData is t_arrX4_AESWord; +function to_AESData(A: std_logic_vector) return AESData; +type t_arrX11_AESWord is array (0 to 11 - 1) of AESWord; +function bitWidth(A : t_arrX11_AESWord) return integer; +function to_slv(A : t_arrX11_AESWord) return std_logic_vector; +function to_t_arrX11_AESWord(A : std_logic_vector) return t_arrX11_AESWord; +function bool_sel(C : boolean; T : t_arrX11_AESWord; F : t_arrX11_AESWord) return t_arrX11_AESWord; +type t_arrX44_AESWord is array (0 to 44 - 1) of AESWord; +function bitWidth(A : t_arrX44_AESWord) return integer; +function to_slv(A : t_arrX44_AESWord) return std_logic_vector; +function to_t_arrX44_AESWord(A : std_logic_vector) return t_arrX44_AESWord; +function bool_sel(C : boolean; T : t_arrX44_AESWord; F : t_arrX44_AESWord) return t_arrX44_AESWord; +subtype AESKeySchedule is t_arrX44_AESWord; +function to_AESKeySchedule(A: std_logic_vector) return AESKeySchedule; type t_arrX256_slv8 is array (0 to 256 - 1) of std_logic_vector(7 downto 0); function bitWidth(A : t_arrX256_slv8) return integer; function to_slv(A : t_arrX256_slv8) return std_logic_vector; function to_t_arrX256_slv8(A : std_logic_vector) return t_arrX256_slv8; function bool_sel(C : boolean; T : t_arrX256_slv8; F : t_arrX256_slv8) return t_arrX256_slv8; -subtype t_opaque_AESState is t_arrX4_t_opaque_AESWord; -function to_t_opaque_AESState(A: std_logic_vector) return t_opaque_AESState; -subtype t_opaque_AESRoundKey is t_arrX4_t_opaque_AESWord; -function to_t_opaque_AESRoundKey(A: std_logic_vector) return t_opaque_AESRoundKey; -constant Rcon : t_arrX11_t_opaque_AESWord := ( +subtype AESState is t_arrX4_AESWord; +function to_AESState(A: std_logic_vector) return AESState; +subtype AESRoundKey is t_arrX4_AESWord; +function to_AESRoundKey(A: std_logic_vector) return AESRoundKey; +constant Rcon : t_arrX11_AESWord := ( 0 => (0 => x"00", 1 => x"00", 2 => x"00", 3 => x"00"), 1 => (0 => x"01", 1 => x"00", 2 => x"00", 3 => x"00"), 2 => (0 => x"02", 1 => x"00", 2 => x"00", 3 => x"00"), 3 => (0 => x"04", 1 => x"00", 2 => x"00", 3 => x"00"), 4 => (0 => x"08", 1 => x"00", 2 => x"00", 3 => x"00"), 5 => (0 => x"10", 1 => x"00", 2 => x"00", 3 => x"00"), @@ -88,53 +88,53 @@ constant sboxLookupTable : t_arrX256_slv8 := ( end package Cipher_pkg; package body Cipher_pkg is -function to_t_opaque_AESByte(A : std_logic_vector) return t_opaque_AESByte is +function to_AESByte(A : std_logic_vector) return AESByte is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; return A0; end; -function to_t_opaque_AESWord(A : std_logic_vector) return t_opaque_AESWord is +function to_AESWord(A : std_logic_vector) return AESWord is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESByte(A0); + return to_t_arrX4_AESByte(A0); end; -function to_t_opaque_AESKey(A : std_logic_vector) return t_opaque_AESKey is +function to_AESKey(A : std_logic_vector) return AESKey is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESWord(A0); + return to_t_arrX4_AESWord(A0); end; -function to_t_opaque_AESData(A : std_logic_vector) return t_opaque_AESData is +function to_AESData(A : std_logic_vector) return AESData is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESWord(A0); + return to_t_arrX4_AESWord(A0); end; -function to_t_opaque_AESKeySchedule(A : std_logic_vector) return t_opaque_AESKeySchedule is +function to_AESKeySchedule(A : std_logic_vector) return AESKeySchedule is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX44_t_opaque_AESWord(A0); + return to_t_arrX44_AESWord(A0); end; -function to_t_opaque_AESState(A : std_logic_vector) return t_opaque_AESState is +function to_AESState(A : std_logic_vector) return AESState is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESWord(A0); + return to_t_arrX4_AESWord(A0); end; -function to_t_opaque_AESRoundKey(A : std_logic_vector) return t_opaque_AESRoundKey is +function to_AESRoundKey(A : std_logic_vector) return AESRoundKey is variable A0 : std_logic_vector(A'length - 1 downto 0); begin A0 := A; - return to_t_arrX4_t_opaque_AESWord(A0); + return to_t_arrX4_AESWord(A0); end; -function bitWidth(A : t_arrX4_t_opaque_AESByte) return integer is +function bitWidth(A : t_arrX4_AESByte) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX4_t_opaque_AESByte) return std_logic_vector is +function to_slv(A : t_arrX4_AESByte) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -148,21 +148,21 @@ begin end loop; return ret; end; -function to_t_arrX4_t_opaque_AESByte(A : std_logic_vector) return t_arrX4_t_opaque_AESByte is +function to_t_arrX4_AESByte(A : std_logic_vector) return t_arrX4_AESByte is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX4_t_opaque_AESByte; + variable ret : t_arrX4_AESByte; begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESByte(A(hi downto lo)); + ret(i) := to_AESByte(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX4_t_opaque_AESByte; F : t_arrX4_t_opaque_AESByte) return t_arrX4_t_opaque_AESByte is +function bool_sel(C : boolean; T : t_arrX4_AESByte; F : t_arrX4_AESByte) return t_arrX4_AESByte is begin if C then return T; @@ -170,11 +170,11 @@ begin return F; end if; end; -function bitWidth(A : t_arrX4_t_opaque_AESWord) return integer is +function bitWidth(A : t_arrX4_AESWord) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX4_t_opaque_AESWord) return std_logic_vector is +function to_slv(A : t_arrX4_AESWord) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -188,21 +188,21 @@ begin end loop; return ret; end; -function to_t_arrX4_t_opaque_AESWord(A : std_logic_vector) return t_arrX4_t_opaque_AESWord is +function to_t_arrX4_AESWord(A : std_logic_vector) return t_arrX4_AESWord is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX4_t_opaque_AESWord; + variable ret : t_arrX4_AESWord; begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESWord(A(hi downto lo)); + ret(i) := to_AESWord(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX4_t_opaque_AESWord; F : t_arrX4_t_opaque_AESWord) return t_arrX4_t_opaque_AESWord is +function bool_sel(C : boolean; T : t_arrX4_AESWord; F : t_arrX4_AESWord) return t_arrX4_AESWord is begin if C then return T; @@ -210,11 +210,11 @@ begin return F; end if; end; -function bitWidth(A : t_arrX11_t_opaque_AESWord) return integer is +function bitWidth(A : t_arrX11_AESWord) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX11_t_opaque_AESWord) return std_logic_vector is +function to_slv(A : t_arrX11_AESWord) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -228,21 +228,21 @@ begin end loop; return ret; end; -function to_t_arrX11_t_opaque_AESWord(A : std_logic_vector) return t_arrX11_t_opaque_AESWord is +function to_t_arrX11_AESWord(A : std_logic_vector) return t_arrX11_AESWord is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX11_t_opaque_AESWord; + variable ret : t_arrX11_AESWord; begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESWord(A(hi downto lo)); + ret(i) := to_AESWord(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX11_t_opaque_AESWord; F : t_arrX11_t_opaque_AESWord) return t_arrX11_t_opaque_AESWord is +function bool_sel(C : boolean; T : t_arrX11_AESWord; F : t_arrX11_AESWord) return t_arrX11_AESWord is begin if C then return T; @@ -250,11 +250,11 @@ begin return F; end if; end; -function bitWidth(A : t_arrX44_t_opaque_AESWord) return integer is +function bitWidth(A : t_arrX44_AESWord) return integer is begin return A'length * bitWidth(A(0)); end; -function to_slv(A : t_arrX44_t_opaque_AESWord) return std_logic_vector is +function to_slv(A : t_arrX44_AESWord) return std_logic_vector is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; @@ -268,21 +268,21 @@ begin end loop; return ret; end; -function to_t_arrX44_t_opaque_AESWord(A : std_logic_vector) return t_arrX44_t_opaque_AESWord is +function to_t_arrX44_AESWord(A : std_logic_vector) return t_arrX44_AESWord is variable hi : integer; variable lo : integer; variable cellBitWidth: integer; - variable ret : t_arrX44_t_opaque_AESWord; + variable ret : t_arrX44_AESWord; begin cellBitWidth := bitWidth(ret(0)); lo := A'high + 1; for i in 0 to ret'length - 1 loop hi := lo - 1; lo := hi - cellBitWidth + 1; - ret(i) := to_t_opaque_AESWord(A(hi downto lo)); + ret(i) := to_AESWord(A(hi downto lo)); end loop; return ret; end; -function bool_sel(C : boolean; T : t_arrX44_t_opaque_AESWord; F : t_arrX44_t_opaque_AESWord) return t_arrX44_t_opaque_AESWord is +function bool_sel(C : boolean; T : t_arrX44_AESWord; F : t_arrX44_AESWord) return t_arrX44_AESWord is begin if C then return T; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/addRoundKey.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/addRoundKey.vhd index 4a329d2b5..0d40524e3 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/addRoundKey.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/addRoundKey.vhd @@ -6,9 +6,9 @@ use work.Cipher_pkg.all; entity addRoundKey is port ( - state : in t_opaque_AESState; - key : in t_opaque_AESRoundKey; - o : out t_opaque_AESState + state : in AESState; + key : in AESRoundKey; + o : out AESState ); end addRoundKey; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/cipher_0.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/cipher_0.vhd index a0f38a530..6156588e5 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/cipher_0.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/cipher_0.vhd @@ -6,106 +6,106 @@ use work.Cipher_pkg.all; entity cipher_0 is port ( - data : in t_opaque_AESData; - key : in t_opaque_AESKey; - o : out t_opaque_AESData + data : in AESData; + key : in AESKey; + o : out AESData ); end cipher_0; architecture cipher_0_arch of cipher_0 is - signal keySchedule_key : t_opaque_AESKey; - signal keySchedule_o : t_opaque_AESKeySchedule; - signal state_00_state : t_opaque_AESState; - signal state_00_key : t_opaque_AESRoundKey; - signal state_00_o : t_opaque_AESState; - signal o_part_subBytes_inst_00_state : t_opaque_AESState; - signal o_part_subBytes_inst_00_o : t_opaque_AESState; - signal o_part_shiftRows_inst_00_state : t_opaque_AESState; - signal o_part_shiftRows_inst_00_o : t_opaque_AESState; - signal o_part_mixColumns_inst_0_state : t_opaque_AESState; - signal o_part_mixColumns_inst_0_o : t_opaque_AESState; - signal state_01_state : t_opaque_AESState; - signal state_01_key : t_opaque_AESRoundKey; - signal state_01_o : t_opaque_AESState; - signal o_part_subBytes_inst_01_state : t_opaque_AESState; - signal o_part_subBytes_inst_01_o : t_opaque_AESState; - signal o_part_shiftRows_inst_01_state : t_opaque_AESState; - signal o_part_shiftRows_inst_01_o : t_opaque_AESState; - signal o_part_mixColumns_inst_1_state : t_opaque_AESState; - signal o_part_mixColumns_inst_1_o : t_opaque_AESState; - signal state_02_state : t_opaque_AESState; - signal state_02_key : t_opaque_AESRoundKey; - signal state_02_o : t_opaque_AESState; - signal o_part_subBytes_inst_02_state : t_opaque_AESState; - signal o_part_subBytes_inst_02_o : t_opaque_AESState; - signal o_part_shiftRows_inst_02_state : t_opaque_AESState; - signal o_part_shiftRows_inst_02_o : t_opaque_AESState; - signal o_part_mixColumns_inst_2_state : t_opaque_AESState; - signal o_part_mixColumns_inst_2_o : t_opaque_AESState; - signal state_03_state : t_opaque_AESState; - signal state_03_key : t_opaque_AESRoundKey; - signal state_03_o : t_opaque_AESState; - signal o_part_subBytes_inst_03_state : t_opaque_AESState; - signal o_part_subBytes_inst_03_o : t_opaque_AESState; - signal o_part_shiftRows_inst_03_state : t_opaque_AESState; - signal o_part_shiftRows_inst_03_o : t_opaque_AESState; - signal o_part_mixColumns_inst_3_state : t_opaque_AESState; - signal o_part_mixColumns_inst_3_o : t_opaque_AESState; - signal state_04_state : t_opaque_AESState; - signal state_04_key : t_opaque_AESRoundKey; - signal state_04_o : t_opaque_AESState; - signal o_part_subBytes_inst_04_state : t_opaque_AESState; - signal o_part_subBytes_inst_04_o : t_opaque_AESState; - signal o_part_shiftRows_inst_04_state : t_opaque_AESState; - signal o_part_shiftRows_inst_04_o : t_opaque_AESState; - signal o_part_mixColumns_inst_4_state : t_opaque_AESState; - signal o_part_mixColumns_inst_4_o : t_opaque_AESState; - signal state_05_state : t_opaque_AESState; - signal state_05_key : t_opaque_AESRoundKey; - signal state_05_o : t_opaque_AESState; - signal o_part_subBytes_inst_05_state : t_opaque_AESState; - signal o_part_subBytes_inst_05_o : t_opaque_AESState; - signal o_part_shiftRows_inst_05_state : t_opaque_AESState; - signal o_part_shiftRows_inst_05_o : t_opaque_AESState; - signal o_part_mixColumns_inst_5_state : t_opaque_AESState; - signal o_part_mixColumns_inst_5_o : t_opaque_AESState; - signal state_06_state : t_opaque_AESState; - signal state_06_key : t_opaque_AESRoundKey; - signal state_06_o : t_opaque_AESState; - signal o_part_subBytes_inst_06_state : t_opaque_AESState; - signal o_part_subBytes_inst_06_o : t_opaque_AESState; - signal o_part_shiftRows_inst_06_state : t_opaque_AESState; - signal o_part_shiftRows_inst_06_o : t_opaque_AESState; - signal o_part_mixColumns_inst_6_state : t_opaque_AESState; - signal o_part_mixColumns_inst_6_o : t_opaque_AESState; - signal state_07_state : t_opaque_AESState; - signal state_07_key : t_opaque_AESRoundKey; - signal state_07_o : t_opaque_AESState; - signal o_part_subBytes_inst_07_state : t_opaque_AESState; - signal o_part_subBytes_inst_07_o : t_opaque_AESState; - signal o_part_shiftRows_inst_07_state : t_opaque_AESState; - signal o_part_shiftRows_inst_07_o : t_opaque_AESState; - signal o_part_mixColumns_inst_7_state : t_opaque_AESState; - signal o_part_mixColumns_inst_7_o : t_opaque_AESState; - signal state_08_state : t_opaque_AESState; - signal state_08_key : t_opaque_AESRoundKey; - signal state_08_o : t_opaque_AESState; - signal o_part_subBytes_inst_08_state : t_opaque_AESState; - signal o_part_subBytes_inst_08_o : t_opaque_AESState; - signal o_part_shiftRows_inst_08_state : t_opaque_AESState; - signal o_part_shiftRows_inst_08_o : t_opaque_AESState; - signal o_part_mixColumns_inst_8_state : t_opaque_AESState; - signal o_part_mixColumns_inst_8_o : t_opaque_AESState; - signal state_09_state : t_opaque_AESState; - signal state_09_key : t_opaque_AESRoundKey; - signal state_09_o : t_opaque_AESState; - signal o_part_subBytes_inst_09_state : t_opaque_AESState; - signal o_part_subBytes_inst_09_o : t_opaque_AESState; - signal o_part_shiftRows_inst_09_state : t_opaque_AESState; - signal o_part_shiftRows_inst_09_o : t_opaque_AESState; - signal state_10_state : t_opaque_AESState; - signal state_10_key : t_opaque_AESRoundKey; - signal state_10_o : t_opaque_AESState; + signal keySchedule_key : AESKey; + signal keySchedule_o : AESKeySchedule; + signal state_00_state : AESState; + signal state_00_key : AESRoundKey; + signal state_00_o : AESState; + signal o_part_subBytes_inst_00_state : AESState; + signal o_part_subBytes_inst_00_o : AESState; + signal o_part_shiftRows_inst_00_state : AESState; + signal o_part_shiftRows_inst_00_o : AESState; + signal o_part_mixColumns_inst_0_state : AESState; + signal o_part_mixColumns_inst_0_o : AESState; + signal state_01_state : AESState; + signal state_01_key : AESRoundKey; + signal state_01_o : AESState; + signal o_part_subBytes_inst_01_state : AESState; + signal o_part_subBytes_inst_01_o : AESState; + signal o_part_shiftRows_inst_01_state : AESState; + signal o_part_shiftRows_inst_01_o : AESState; + signal o_part_mixColumns_inst_1_state : AESState; + signal o_part_mixColumns_inst_1_o : AESState; + signal state_02_state : AESState; + signal state_02_key : AESRoundKey; + signal state_02_o : AESState; + signal o_part_subBytes_inst_02_state : AESState; + signal o_part_subBytes_inst_02_o : AESState; + signal o_part_shiftRows_inst_02_state : AESState; + signal o_part_shiftRows_inst_02_o : AESState; + signal o_part_mixColumns_inst_2_state : AESState; + signal o_part_mixColumns_inst_2_o : AESState; + signal state_03_state : AESState; + signal state_03_key : AESRoundKey; + signal state_03_o : AESState; + signal o_part_subBytes_inst_03_state : AESState; + signal o_part_subBytes_inst_03_o : AESState; + signal o_part_shiftRows_inst_03_state : AESState; + signal o_part_shiftRows_inst_03_o : AESState; + signal o_part_mixColumns_inst_3_state : AESState; + signal o_part_mixColumns_inst_3_o : AESState; + signal state_04_state : AESState; + signal state_04_key : AESRoundKey; + signal state_04_o : AESState; + signal o_part_subBytes_inst_04_state : AESState; + signal o_part_subBytes_inst_04_o : AESState; + signal o_part_shiftRows_inst_04_state : AESState; + signal o_part_shiftRows_inst_04_o : AESState; + signal o_part_mixColumns_inst_4_state : AESState; + signal o_part_mixColumns_inst_4_o : AESState; + signal state_05_state : AESState; + signal state_05_key : AESRoundKey; + signal state_05_o : AESState; + signal o_part_subBytes_inst_05_state : AESState; + signal o_part_subBytes_inst_05_o : AESState; + signal o_part_shiftRows_inst_05_state : AESState; + signal o_part_shiftRows_inst_05_o : AESState; + signal o_part_mixColumns_inst_5_state : AESState; + signal o_part_mixColumns_inst_5_o : AESState; + signal state_06_state : AESState; + signal state_06_key : AESRoundKey; + signal state_06_o : AESState; + signal o_part_subBytes_inst_06_state : AESState; + signal o_part_subBytes_inst_06_o : AESState; + signal o_part_shiftRows_inst_06_state : AESState; + signal o_part_shiftRows_inst_06_o : AESState; + signal o_part_mixColumns_inst_6_state : AESState; + signal o_part_mixColumns_inst_6_o : AESState; + signal state_07_state : AESState; + signal state_07_key : AESRoundKey; + signal state_07_o : AESState; + signal o_part_subBytes_inst_07_state : AESState; + signal o_part_subBytes_inst_07_o : AESState; + signal o_part_shiftRows_inst_07_state : AESState; + signal o_part_shiftRows_inst_07_o : AESState; + signal o_part_mixColumns_inst_7_state : AESState; + signal o_part_mixColumns_inst_7_o : AESState; + signal state_08_state : AESState; + signal state_08_key : AESRoundKey; + signal state_08_o : AESState; + signal o_part_subBytes_inst_08_state : AESState; + signal o_part_subBytes_inst_08_o : AESState; + signal o_part_shiftRows_inst_08_state : AESState; + signal o_part_shiftRows_inst_08_o : AESState; + signal o_part_mixColumns_inst_8_state : AESState; + signal o_part_mixColumns_inst_8_o : AESState; + signal state_09_state : AESState; + signal state_09_key : AESRoundKey; + signal state_09_o : AESState; + signal o_part_subBytes_inst_09_state : AESState; + signal o_part_subBytes_inst_09_o : AESState; + signal o_part_shiftRows_inst_09_state : AESState; + signal o_part_shiftRows_inst_09_o : AESState; + signal state_10_state : AESState; + signal state_10_key : AESRoundKey; + signal state_10_o : AESState; begin keySchedule : entity work.keyExpansion(keyExpansion_arch) port map ( key => keySchedule_key, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/keyExpansion.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/keyExpansion.vhd index fc42d4f18..b4bbf60e6 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/keyExpansion.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/keyExpansion.vhd @@ -6,211 +6,211 @@ use work.Cipher_pkg.all; entity keyExpansion is port ( - key : in t_opaque_AESKey; - o : out t_opaque_AESKeySchedule + key : in AESKey; + o : out AESKeySchedule ); end keyExpansion; architecture keyExpansion_arch of keyExpansion is - signal w_0 : t_opaque_AESWord; - signal w_1 : t_opaque_AESWord; - signal w_2 : t_opaque_AESWord; - signal w_3 : t_opaque_AESWord; - signal o_part_000 : t_opaque_AESByte; - signal o_part_001 : t_opaque_AESByte; - signal o_part_002 : t_opaque_AESByte; - signal o_part_003 : t_opaque_AESByte; - signal o_part_004 : t_opaque_AESByte; - signal o_part_005 : t_opaque_AESByte; - signal o_part_006 : t_opaque_AESByte; - signal o_part_007 : t_opaque_AESByte; - signal o_part_008 : t_opaque_AESByte; - signal o_part_009 : t_opaque_AESByte; - signal o_part_010 : t_opaque_AESByte; - signal o_part_011 : t_opaque_AESByte; - signal lhs_part_00 : t_opaque_AESByte; - signal lhs_part_01 : t_opaque_AESByte; - signal lhs_part_02 : t_opaque_AESByte; - signal lhs_part_03 : t_opaque_AESByte; - signal lhs_part_04 : t_opaque_AESWord; - signal o_part_012 : t_opaque_AESByte; - signal o_part_013 : t_opaque_AESByte; - signal o_part_014 : t_opaque_AESByte; - signal o_part_015 : t_opaque_AESByte; - signal o_part_016 : t_opaque_AESByte; - signal o_part_017 : t_opaque_AESByte; - signal o_part_018 : t_opaque_AESByte; - signal o_part_019 : t_opaque_AESByte; - signal o_part_020 : t_opaque_AESByte; - signal o_part_021 : t_opaque_AESByte; - signal o_part_022 : t_opaque_AESByte; - signal o_part_023 : t_opaque_AESByte; - signal lhs_part_05 : t_opaque_AESByte; - signal lhs_part_06 : t_opaque_AESByte; - signal lhs_part_07 : t_opaque_AESByte; - signal lhs_part_08 : t_opaque_AESByte; - signal lhs_part_09 : t_opaque_AESWord; - signal o_part_024 : t_opaque_AESByte; - signal o_part_025 : t_opaque_AESByte; - signal o_part_026 : t_opaque_AESByte; - signal o_part_027 : t_opaque_AESByte; - signal o_part_028 : t_opaque_AESByte; - signal o_part_029 : t_opaque_AESByte; - signal o_part_030 : t_opaque_AESByte; - signal o_part_031 : t_opaque_AESByte; - signal o_part_032 : t_opaque_AESByte; - signal o_part_033 : t_opaque_AESByte; - signal o_part_034 : t_opaque_AESByte; - signal o_part_035 : t_opaque_AESByte; - signal lhs_part_10 : t_opaque_AESByte; - signal lhs_part_11 : t_opaque_AESByte; - signal lhs_part_12 : t_opaque_AESByte; - signal lhs_part_13 : t_opaque_AESByte; - signal lhs_part_14 : t_opaque_AESWord; - signal o_part_036 : t_opaque_AESByte; - signal o_part_037 : t_opaque_AESByte; - signal o_part_038 : t_opaque_AESByte; - signal o_part_039 : t_opaque_AESByte; - signal o_part_040 : t_opaque_AESByte; - signal o_part_041 : t_opaque_AESByte; - signal o_part_042 : t_opaque_AESByte; - signal o_part_043 : t_opaque_AESByte; - signal o_part_044 : t_opaque_AESByte; - signal o_part_045 : t_opaque_AESByte; - signal o_part_046 : t_opaque_AESByte; - signal o_part_047 : t_opaque_AESByte; - signal lhs_part_15 : t_opaque_AESByte; - signal lhs_part_16 : t_opaque_AESByte; - signal lhs_part_17 : t_opaque_AESByte; - signal lhs_part_18 : t_opaque_AESByte; - signal lhs_part_19 : t_opaque_AESWord; - signal o_part_048 : t_opaque_AESByte; - signal o_part_049 : t_opaque_AESByte; - signal o_part_050 : t_opaque_AESByte; - signal o_part_051 : t_opaque_AESByte; - signal o_part_052 : t_opaque_AESByte; - signal o_part_053 : t_opaque_AESByte; - signal o_part_054 : t_opaque_AESByte; - signal o_part_055 : t_opaque_AESByte; - signal o_part_056 : t_opaque_AESByte; - signal o_part_057 : t_opaque_AESByte; - signal o_part_058 : t_opaque_AESByte; - signal o_part_059 : t_opaque_AESByte; - signal lhs_part_20 : t_opaque_AESByte; - signal lhs_part_21 : t_opaque_AESByte; - signal lhs_part_22 : t_opaque_AESByte; - signal lhs_part_23 : t_opaque_AESByte; - signal lhs_part_24 : t_opaque_AESWord; - signal o_part_060 : t_opaque_AESByte; - signal o_part_061 : t_opaque_AESByte; - signal o_part_062 : t_opaque_AESByte; - signal o_part_063 : t_opaque_AESByte; - signal o_part_064 : t_opaque_AESByte; - signal o_part_065 : t_opaque_AESByte; - signal o_part_066 : t_opaque_AESByte; - signal o_part_067 : t_opaque_AESByte; - signal o_part_068 : t_opaque_AESByte; - signal o_part_069 : t_opaque_AESByte; - signal o_part_070 : t_opaque_AESByte; - signal o_part_071 : t_opaque_AESByte; - signal lhs_part_25 : t_opaque_AESByte; - signal lhs_part_26 : t_opaque_AESByte; - signal lhs_part_27 : t_opaque_AESByte; - signal lhs_part_28 : t_opaque_AESByte; - signal lhs_part_29 : t_opaque_AESWord; - signal o_part_072 : t_opaque_AESByte; - signal o_part_073 : t_opaque_AESByte; - signal o_part_074 : t_opaque_AESByte; - signal o_part_075 : t_opaque_AESByte; - signal o_part_076 : t_opaque_AESByte; - signal o_part_077 : t_opaque_AESByte; - signal o_part_078 : t_opaque_AESByte; - signal o_part_079 : t_opaque_AESByte; - signal o_part_080 : t_opaque_AESByte; - signal o_part_081 : t_opaque_AESByte; - signal o_part_082 : t_opaque_AESByte; - signal o_part_083 : t_opaque_AESByte; - signal lhs_part_30 : t_opaque_AESByte; - signal lhs_part_31 : t_opaque_AESByte; - signal lhs_part_32 : t_opaque_AESByte; - signal lhs_part_33 : t_opaque_AESByte; - signal lhs_part_34 : t_opaque_AESWord; - signal o_part_084 : t_opaque_AESByte; - signal o_part_085 : t_opaque_AESByte; - signal o_part_086 : t_opaque_AESByte; - signal o_part_087 : t_opaque_AESByte; - signal o_part_088 : t_opaque_AESByte; - signal o_part_089 : t_opaque_AESByte; - signal o_part_090 : t_opaque_AESByte; - signal o_part_091 : t_opaque_AESByte; - signal o_part_092 : t_opaque_AESByte; - signal o_part_093 : t_opaque_AESByte; - signal o_part_094 : t_opaque_AESByte; - signal o_part_095 : t_opaque_AESByte; - signal lhs_part_35 : t_opaque_AESByte; - signal lhs_part_36 : t_opaque_AESByte; - signal lhs_part_37 : t_opaque_AESByte; - signal lhs_part_38 : t_opaque_AESByte; - signal lhs_part_39 : t_opaque_AESWord; - signal o_part_096 : t_opaque_AESByte; - signal o_part_097 : t_opaque_AESByte; - signal o_part_098 : t_opaque_AESByte; - signal o_part_099 : t_opaque_AESByte; - signal o_part_100 : t_opaque_AESByte; - signal o_part_101 : t_opaque_AESByte; - signal o_part_102 : t_opaque_AESByte; - signal o_part_103 : t_opaque_AESByte; - signal o_part_104 : t_opaque_AESByte; - signal o_part_105 : t_opaque_AESByte; - signal o_part_106 : t_opaque_AESByte; - signal o_part_107 : t_opaque_AESByte; - signal lhs_part_40 : t_opaque_AESByte; - signal lhs_part_41 : t_opaque_AESByte; - signal lhs_part_42 : t_opaque_AESByte; - signal lhs_part_43 : t_opaque_AESByte; - signal lhs_part_44 : t_opaque_AESWord; - signal o_part_108 : t_opaque_AESByte; - signal o_part_109 : t_opaque_AESByte; - signal o_part_110 : t_opaque_AESByte; - signal o_part_111 : t_opaque_AESByte; - signal o_part_112 : t_opaque_AESByte; - signal o_part_113 : t_opaque_AESByte; - signal o_part_114 : t_opaque_AESByte; - signal o_part_115 : t_opaque_AESByte; - signal o_part_116 : t_opaque_AESByte; - signal o_part_117 : t_opaque_AESByte; - signal o_part_118 : t_opaque_AESByte; - signal o_part_119 : t_opaque_AESByte; - signal o_part_rotWord_inst_00_o : t_opaque_AESWord; - signal o_part_subWord_inst_00_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_00_o : t_opaque_AESWord; - signal o_part_rotWord_inst_01_o : t_opaque_AESWord; - signal o_part_subWord_inst_01_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_01_o : t_opaque_AESWord; - signal o_part_rotWord_inst_02_o : t_opaque_AESWord; - signal o_part_subWord_inst_02_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_02_o : t_opaque_AESWord; - signal o_part_rotWord_inst_03_o : t_opaque_AESWord; - signal o_part_subWord_inst_03_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_03_o : t_opaque_AESWord; - signal o_part_rotWord_inst_04_o : t_opaque_AESWord; - signal o_part_subWord_inst_04_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_04_o : t_opaque_AESWord; - signal o_part_rotWord_inst_05_o : t_opaque_AESWord; - signal o_part_subWord_inst_05_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_05_o : t_opaque_AESWord; - signal o_part_rotWord_inst_06_o : t_opaque_AESWord; - signal o_part_subWord_inst_06_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_06_o : t_opaque_AESWord; - signal o_part_rotWord_inst_07_o : t_opaque_AESWord; - signal o_part_subWord_inst_07_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_07_o : t_opaque_AESWord; - signal o_part_rotWord_inst_08_o : t_opaque_AESWord; - signal o_part_subWord_inst_08_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_08_o : t_opaque_AESWord; - signal o_part_rotWord_inst_09_o : t_opaque_AESWord; - signal o_part_subWord_inst_09_lhs : t_opaque_AESWord; - signal o_part_subWord_inst_09_o : t_opaque_AESWord; + signal w_0 : AESWord; + signal w_1 : AESWord; + signal w_2 : AESWord; + signal w_3 : AESWord; + signal o_part_000 : AESByte; + signal o_part_001 : AESByte; + signal o_part_002 : AESByte; + signal o_part_003 : AESByte; + signal o_part_004 : AESByte; + signal o_part_005 : AESByte; + signal o_part_006 : AESByte; + signal o_part_007 : AESByte; + signal o_part_008 : AESByte; + signal o_part_009 : AESByte; + signal o_part_010 : AESByte; + signal o_part_011 : AESByte; + signal lhs_part_00 : AESByte; + signal lhs_part_01 : AESByte; + signal lhs_part_02 : AESByte; + signal lhs_part_03 : AESByte; + signal lhs_part_04 : AESWord; + signal o_part_012 : AESByte; + signal o_part_013 : AESByte; + signal o_part_014 : AESByte; + signal o_part_015 : AESByte; + signal o_part_016 : AESByte; + signal o_part_017 : AESByte; + signal o_part_018 : AESByte; + signal o_part_019 : AESByte; + signal o_part_020 : AESByte; + signal o_part_021 : AESByte; + signal o_part_022 : AESByte; + signal o_part_023 : AESByte; + signal lhs_part_05 : AESByte; + signal lhs_part_06 : AESByte; + signal lhs_part_07 : AESByte; + signal lhs_part_08 : AESByte; + signal lhs_part_09 : AESWord; + signal o_part_024 : AESByte; + signal o_part_025 : AESByte; + signal o_part_026 : AESByte; + signal o_part_027 : AESByte; + signal o_part_028 : AESByte; + signal o_part_029 : AESByte; + signal o_part_030 : AESByte; + signal o_part_031 : AESByte; + signal o_part_032 : AESByte; + signal o_part_033 : AESByte; + signal o_part_034 : AESByte; + signal o_part_035 : AESByte; + signal lhs_part_10 : AESByte; + signal lhs_part_11 : AESByte; + signal lhs_part_12 : AESByte; + signal lhs_part_13 : AESByte; + signal lhs_part_14 : AESWord; + signal o_part_036 : AESByte; + signal o_part_037 : AESByte; + signal o_part_038 : AESByte; + signal o_part_039 : AESByte; + signal o_part_040 : AESByte; + signal o_part_041 : AESByte; + signal o_part_042 : AESByte; + signal o_part_043 : AESByte; + signal o_part_044 : AESByte; + signal o_part_045 : AESByte; + signal o_part_046 : AESByte; + signal o_part_047 : AESByte; + signal lhs_part_15 : AESByte; + signal lhs_part_16 : AESByte; + signal lhs_part_17 : AESByte; + signal lhs_part_18 : AESByte; + signal lhs_part_19 : AESWord; + signal o_part_048 : AESByte; + signal o_part_049 : AESByte; + signal o_part_050 : AESByte; + signal o_part_051 : AESByte; + signal o_part_052 : AESByte; + signal o_part_053 : AESByte; + signal o_part_054 : AESByte; + signal o_part_055 : AESByte; + signal o_part_056 : AESByte; + signal o_part_057 : AESByte; + signal o_part_058 : AESByte; + signal o_part_059 : AESByte; + signal lhs_part_20 : AESByte; + signal lhs_part_21 : AESByte; + signal lhs_part_22 : AESByte; + signal lhs_part_23 : AESByte; + signal lhs_part_24 : AESWord; + signal o_part_060 : AESByte; + signal o_part_061 : AESByte; + signal o_part_062 : AESByte; + signal o_part_063 : AESByte; + signal o_part_064 : AESByte; + signal o_part_065 : AESByte; + signal o_part_066 : AESByte; + signal o_part_067 : AESByte; + signal o_part_068 : AESByte; + signal o_part_069 : AESByte; + signal o_part_070 : AESByte; + signal o_part_071 : AESByte; + signal lhs_part_25 : AESByte; + signal lhs_part_26 : AESByte; + signal lhs_part_27 : AESByte; + signal lhs_part_28 : AESByte; + signal lhs_part_29 : AESWord; + signal o_part_072 : AESByte; + signal o_part_073 : AESByte; + signal o_part_074 : AESByte; + signal o_part_075 : AESByte; + signal o_part_076 : AESByte; + signal o_part_077 : AESByte; + signal o_part_078 : AESByte; + signal o_part_079 : AESByte; + signal o_part_080 : AESByte; + signal o_part_081 : AESByte; + signal o_part_082 : AESByte; + signal o_part_083 : AESByte; + signal lhs_part_30 : AESByte; + signal lhs_part_31 : AESByte; + signal lhs_part_32 : AESByte; + signal lhs_part_33 : AESByte; + signal lhs_part_34 : AESWord; + signal o_part_084 : AESByte; + signal o_part_085 : AESByte; + signal o_part_086 : AESByte; + signal o_part_087 : AESByte; + signal o_part_088 : AESByte; + signal o_part_089 : AESByte; + signal o_part_090 : AESByte; + signal o_part_091 : AESByte; + signal o_part_092 : AESByte; + signal o_part_093 : AESByte; + signal o_part_094 : AESByte; + signal o_part_095 : AESByte; + signal lhs_part_35 : AESByte; + signal lhs_part_36 : AESByte; + signal lhs_part_37 : AESByte; + signal lhs_part_38 : AESByte; + signal lhs_part_39 : AESWord; + signal o_part_096 : AESByte; + signal o_part_097 : AESByte; + signal o_part_098 : AESByte; + signal o_part_099 : AESByte; + signal o_part_100 : AESByte; + signal o_part_101 : AESByte; + signal o_part_102 : AESByte; + signal o_part_103 : AESByte; + signal o_part_104 : AESByte; + signal o_part_105 : AESByte; + signal o_part_106 : AESByte; + signal o_part_107 : AESByte; + signal lhs_part_40 : AESByte; + signal lhs_part_41 : AESByte; + signal lhs_part_42 : AESByte; + signal lhs_part_43 : AESByte; + signal lhs_part_44 : AESWord; + signal o_part_108 : AESByte; + signal o_part_109 : AESByte; + signal o_part_110 : AESByte; + signal o_part_111 : AESByte; + signal o_part_112 : AESByte; + signal o_part_113 : AESByte; + signal o_part_114 : AESByte; + signal o_part_115 : AESByte; + signal o_part_116 : AESByte; + signal o_part_117 : AESByte; + signal o_part_118 : AESByte; + signal o_part_119 : AESByte; + signal o_part_rotWord_inst_00_o : AESWord; + signal o_part_subWord_inst_00_lhs : AESWord; + signal o_part_subWord_inst_00_o : AESWord; + signal o_part_rotWord_inst_01_o : AESWord; + signal o_part_subWord_inst_01_lhs : AESWord; + signal o_part_subWord_inst_01_o : AESWord; + signal o_part_rotWord_inst_02_o : AESWord; + signal o_part_subWord_inst_02_lhs : AESWord; + signal o_part_subWord_inst_02_o : AESWord; + signal o_part_rotWord_inst_03_o : AESWord; + signal o_part_subWord_inst_03_lhs : AESWord; + signal o_part_subWord_inst_03_o : AESWord; + signal o_part_rotWord_inst_04_o : AESWord; + signal o_part_subWord_inst_04_lhs : AESWord; + signal o_part_subWord_inst_04_o : AESWord; + signal o_part_rotWord_inst_05_o : AESWord; + signal o_part_subWord_inst_05_lhs : AESWord; + signal o_part_subWord_inst_05_o : AESWord; + signal o_part_rotWord_inst_06_o : AESWord; + signal o_part_subWord_inst_06_lhs : AESWord; + signal o_part_subWord_inst_06_o : AESWord; + signal o_part_rotWord_inst_07_o : AESWord; + signal o_part_subWord_inst_07_lhs : AESWord; + signal o_part_subWord_inst_07_o : AESWord; + signal o_part_rotWord_inst_08_o : AESWord; + signal o_part_subWord_inst_08_lhs : AESWord; + signal o_part_subWord_inst_08_o : AESWord; + signal o_part_rotWord_inst_09_o : AESWord; + signal o_part_subWord_inst_09_lhs : AESWord; + signal o_part_subWord_inst_09_o : AESWord; begin o_part_rotWord_inst_00 : entity work.rotWord(rotWord_arch) port map ( o => o_part_rotWord_inst_00_o, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mixColumns.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mixColumns.vhd index 4f5cb477f..b9e69ecbe 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mixColumns.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mixColumns.vhd @@ -6,140 +6,140 @@ use work.Cipher_pkg.all; entity mixColumns is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end mixColumns; architecture mixColumns_arch of mixColumns is - signal o_part_mulByte_0_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_00_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_00_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_01_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_01_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_02_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_02_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_03_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_03_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_04_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_04_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_05_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_05_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_06_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_06_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_15_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_07_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_07_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_08_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_08_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_16_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_16_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_17_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_17_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_18_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_18_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_09_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_09_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_19_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_19_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_20_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_20_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_21_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_21_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_10_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_10_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_22_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_22_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_23_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_23_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_11_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_11_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_12_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_12_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_24_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_24_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_25_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_25_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_26_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_26_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_13_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_13_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_27_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_27_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_28_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_28_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_29_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_29_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_14_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_14_o : t_opaque_AESByte; - signal o_part_mulByte_1_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_1_inst_15_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_30_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_30_o : t_opaque_AESByte; - signal o_part_mulByte_2_inst_31_rhs : t_opaque_AESByte; - signal o_part_mulByte_2_inst_31_o : t_opaque_AESByte; - signal o_part_mulByte_0_inst_15_rhs : t_opaque_AESByte; - signal o_part_mulByte_0_inst_15_o : t_opaque_AESByte; + signal o_part_mulByte_0_inst_00_rhs : AESByte; + signal o_part_mulByte_0_inst_00_o : AESByte; + signal o_part_mulByte_1_inst_00_rhs : AESByte; + signal o_part_mulByte_1_inst_00_o : AESByte; + signal o_part_mulByte_2_inst_00_rhs : AESByte; + signal o_part_mulByte_2_inst_00_o : AESByte; + signal o_part_mulByte_2_inst_01_rhs : AESByte; + signal o_part_mulByte_2_inst_01_o : AESByte; + signal o_part_mulByte_2_inst_02_rhs : AESByte; + signal o_part_mulByte_2_inst_02_o : AESByte; + signal o_part_mulByte_0_inst_01_rhs : AESByte; + signal o_part_mulByte_0_inst_01_o : AESByte; + signal o_part_mulByte_1_inst_01_rhs : AESByte; + signal o_part_mulByte_1_inst_01_o : AESByte; + signal o_part_mulByte_2_inst_03_rhs : AESByte; + signal o_part_mulByte_2_inst_03_o : AESByte; + signal o_part_mulByte_2_inst_04_rhs : AESByte; + signal o_part_mulByte_2_inst_04_o : AESByte; + signal o_part_mulByte_2_inst_05_rhs : AESByte; + signal o_part_mulByte_2_inst_05_o : AESByte; + signal o_part_mulByte_0_inst_02_rhs : AESByte; + signal o_part_mulByte_0_inst_02_o : AESByte; + signal o_part_mulByte_1_inst_02_rhs : AESByte; + signal o_part_mulByte_1_inst_02_o : AESByte; + signal o_part_mulByte_1_inst_03_rhs : AESByte; + signal o_part_mulByte_1_inst_03_o : AESByte; + signal o_part_mulByte_2_inst_06_rhs : AESByte; + signal o_part_mulByte_2_inst_06_o : AESByte; + signal o_part_mulByte_2_inst_07_rhs : AESByte; + signal o_part_mulByte_2_inst_07_o : AESByte; + signal o_part_mulByte_0_inst_03_rhs : AESByte; + signal o_part_mulByte_0_inst_03_o : AESByte; + signal o_part_mulByte_0_inst_04_rhs : AESByte; + signal o_part_mulByte_0_inst_04_o : AESByte; + signal o_part_mulByte_1_inst_04_rhs : AESByte; + signal o_part_mulByte_1_inst_04_o : AESByte; + signal o_part_mulByte_2_inst_08_rhs : AESByte; + signal o_part_mulByte_2_inst_08_o : AESByte; + signal o_part_mulByte_2_inst_09_rhs : AESByte; + signal o_part_mulByte_2_inst_09_o : AESByte; + signal o_part_mulByte_2_inst_10_rhs : AESByte; + signal o_part_mulByte_2_inst_10_o : AESByte; + signal o_part_mulByte_0_inst_05_rhs : AESByte; + signal o_part_mulByte_0_inst_05_o : AESByte; + signal o_part_mulByte_1_inst_05_rhs : AESByte; + signal o_part_mulByte_1_inst_05_o : AESByte; + signal o_part_mulByte_2_inst_11_rhs : AESByte; + signal o_part_mulByte_2_inst_11_o : AESByte; + signal o_part_mulByte_2_inst_12_rhs : AESByte; + signal o_part_mulByte_2_inst_12_o : AESByte; + signal o_part_mulByte_2_inst_13_rhs : AESByte; + signal o_part_mulByte_2_inst_13_o : AESByte; + signal o_part_mulByte_0_inst_06_rhs : AESByte; + signal o_part_mulByte_0_inst_06_o : AESByte; + signal o_part_mulByte_1_inst_06_rhs : AESByte; + signal o_part_mulByte_1_inst_06_o : AESByte; + signal o_part_mulByte_1_inst_07_rhs : AESByte; + signal o_part_mulByte_1_inst_07_o : AESByte; + signal o_part_mulByte_2_inst_14_rhs : AESByte; + signal o_part_mulByte_2_inst_14_o : AESByte; + signal o_part_mulByte_2_inst_15_rhs : AESByte; + signal o_part_mulByte_2_inst_15_o : AESByte; + signal o_part_mulByte_0_inst_07_rhs : AESByte; + signal o_part_mulByte_0_inst_07_o : AESByte; + signal o_part_mulByte_0_inst_08_rhs : AESByte; + signal o_part_mulByte_0_inst_08_o : AESByte; + signal o_part_mulByte_1_inst_08_rhs : AESByte; + signal o_part_mulByte_1_inst_08_o : AESByte; + signal o_part_mulByte_2_inst_16_rhs : AESByte; + signal o_part_mulByte_2_inst_16_o : AESByte; + signal o_part_mulByte_2_inst_17_rhs : AESByte; + signal o_part_mulByte_2_inst_17_o : AESByte; + signal o_part_mulByte_2_inst_18_rhs : AESByte; + signal o_part_mulByte_2_inst_18_o : AESByte; + signal o_part_mulByte_0_inst_09_rhs : AESByte; + signal o_part_mulByte_0_inst_09_o : AESByte; + signal o_part_mulByte_1_inst_09_rhs : AESByte; + signal o_part_mulByte_1_inst_09_o : AESByte; + signal o_part_mulByte_2_inst_19_rhs : AESByte; + signal o_part_mulByte_2_inst_19_o : AESByte; + signal o_part_mulByte_2_inst_20_rhs : AESByte; + signal o_part_mulByte_2_inst_20_o : AESByte; + signal o_part_mulByte_2_inst_21_rhs : AESByte; + signal o_part_mulByte_2_inst_21_o : AESByte; + signal o_part_mulByte_0_inst_10_rhs : AESByte; + signal o_part_mulByte_0_inst_10_o : AESByte; + signal o_part_mulByte_1_inst_10_rhs : AESByte; + signal o_part_mulByte_1_inst_10_o : AESByte; + signal o_part_mulByte_1_inst_11_rhs : AESByte; + signal o_part_mulByte_1_inst_11_o : AESByte; + signal o_part_mulByte_2_inst_22_rhs : AESByte; + signal o_part_mulByte_2_inst_22_o : AESByte; + signal o_part_mulByte_2_inst_23_rhs : AESByte; + signal o_part_mulByte_2_inst_23_o : AESByte; + signal o_part_mulByte_0_inst_11_rhs : AESByte; + signal o_part_mulByte_0_inst_11_o : AESByte; + signal o_part_mulByte_0_inst_12_rhs : AESByte; + signal o_part_mulByte_0_inst_12_o : AESByte; + signal o_part_mulByte_1_inst_12_rhs : AESByte; + signal o_part_mulByte_1_inst_12_o : AESByte; + signal o_part_mulByte_2_inst_24_rhs : AESByte; + signal o_part_mulByte_2_inst_24_o : AESByte; + signal o_part_mulByte_2_inst_25_rhs : AESByte; + signal o_part_mulByte_2_inst_25_o : AESByte; + signal o_part_mulByte_2_inst_26_rhs : AESByte; + signal o_part_mulByte_2_inst_26_o : AESByte; + signal o_part_mulByte_0_inst_13_rhs : AESByte; + signal o_part_mulByte_0_inst_13_o : AESByte; + signal o_part_mulByte_1_inst_13_rhs : AESByte; + signal o_part_mulByte_1_inst_13_o : AESByte; + signal o_part_mulByte_2_inst_27_rhs : AESByte; + signal o_part_mulByte_2_inst_27_o : AESByte; + signal o_part_mulByte_2_inst_28_rhs : AESByte; + signal o_part_mulByte_2_inst_28_o : AESByte; + signal o_part_mulByte_2_inst_29_rhs : AESByte; + signal o_part_mulByte_2_inst_29_o : AESByte; + signal o_part_mulByte_0_inst_14_rhs : AESByte; + signal o_part_mulByte_0_inst_14_o : AESByte; + signal o_part_mulByte_1_inst_14_rhs : AESByte; + signal o_part_mulByte_1_inst_14_o : AESByte; + signal o_part_mulByte_1_inst_15_rhs : AESByte; + signal o_part_mulByte_1_inst_15_o : AESByte; + signal o_part_mulByte_2_inst_30_rhs : AESByte; + signal o_part_mulByte_2_inst_30_o : AESByte; + signal o_part_mulByte_2_inst_31_rhs : AESByte; + signal o_part_mulByte_2_inst_31_o : AESByte; + signal o_part_mulByte_0_inst_15_rhs : AESByte; + signal o_part_mulByte_0_inst_15_o : AESByte; begin o_part_mulByte_0_inst_00 : entity work.mulByte_0(mulByte_0_arch) generic map ( lhs => x"02" diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_0.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_0.vhd index 42c2ec0ab..6662783ac 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_0.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_0.vhd @@ -9,14 +9,14 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_0; architecture mulByte_0_arch of mulByte_0 is - signal a_lhs : t_opaque_AESByte; - signal a_o : t_opaque_AESByte; + signal a_lhs : AESByte; + signal a_o : AESByte; begin a : entity work.xtime(xtime_arch) port map ( lhs => a_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_1.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_1.vhd index 8db42f698..f6567b48c 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_1.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_1.vhd @@ -9,14 +9,14 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_1; architecture mulByte_1_arch of mulByte_1 is - signal a_lhs : t_opaque_AESByte; - signal a_o : t_opaque_AESByte; + signal a_lhs : AESByte; + signal a_o : AESByte; begin a : entity work.xtime(xtime_arch) port map ( lhs => a_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_2.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_2.vhd index 0b3b4ae1d..fbd334417 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_2.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_2.vhd @@ -9,8 +9,8 @@ generic ( lhs : std_logic_vector(7 downto 0) ); port ( - rhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + rhs : in AESByte; + o : out AESByte ); end mulByte_2; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/rotWord.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/rotWord.vhd index 47ac6f4ec..bd82b9bfc 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/rotWord.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/rotWord.vhd @@ -6,8 +6,8 @@ use work.Cipher_pkg.all; entity rotWord is port ( - lhs : in t_opaque_AESWord; - o : out t_opaque_AESWord + lhs : in AESWord; + o : out AESWord ); end rotWord; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/sbox.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/sbox.vhd index ba5bbbbf1..d6439f006 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/sbox.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/sbox.vhd @@ -6,8 +6,8 @@ use work.Cipher_pkg.all; entity sbox is port ( - lhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + lhs : in AESByte; + o : out AESByte ); end sbox; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/shiftRows.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/shiftRows.vhd index b3393ab1c..a463980ee 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/shiftRows.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/shiftRows.vhd @@ -6,8 +6,8 @@ use work.Cipher_pkg.all; entity shiftRows is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end shiftRows; diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/subBytes.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/subBytes.vhd index 11474f315..473b2b723 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/subBytes.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/subBytes.vhd @@ -6,44 +6,44 @@ use work.Cipher_pkg.all; entity subBytes is port ( - state : in t_opaque_AESState; - o : out t_opaque_AESState + state : in AESState; + o : out AESState ); end subBytes; architecture subBytes_arch of subBytes is - signal o_part_sbox_inst_00_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_00_o : t_opaque_AESByte; - signal o_part_sbox_inst_01_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_01_o : t_opaque_AESByte; - signal o_part_sbox_inst_02_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_02_o : t_opaque_AESByte; - signal o_part_sbox_inst_03_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_03_o : t_opaque_AESByte; - signal o_part_sbox_inst_04_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_04_o : t_opaque_AESByte; - signal o_part_sbox_inst_05_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_05_o : t_opaque_AESByte; - signal o_part_sbox_inst_06_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_06_o : t_opaque_AESByte; - signal o_part_sbox_inst_07_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_07_o : t_opaque_AESByte; - signal o_part_sbox_inst_08_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_08_o : t_opaque_AESByte; - signal o_part_sbox_inst_09_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_09_o : t_opaque_AESByte; - signal o_part_sbox_inst_10_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_10_o : t_opaque_AESByte; - signal o_part_sbox_inst_11_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_11_o : t_opaque_AESByte; - signal o_part_sbox_inst_12_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_12_o : t_opaque_AESByte; - signal o_part_sbox_inst_13_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_13_o : t_opaque_AESByte; - signal o_part_sbox_inst_14_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_14_o : t_opaque_AESByte; - signal o_part_sbox_inst_15_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_15_o : t_opaque_AESByte; + signal o_part_sbox_inst_00_lhs : AESByte; + signal o_part_sbox_inst_00_o : AESByte; + signal o_part_sbox_inst_01_lhs : AESByte; + signal o_part_sbox_inst_01_o : AESByte; + signal o_part_sbox_inst_02_lhs : AESByte; + signal o_part_sbox_inst_02_o : AESByte; + signal o_part_sbox_inst_03_lhs : AESByte; + signal o_part_sbox_inst_03_o : AESByte; + signal o_part_sbox_inst_04_lhs : AESByte; + signal o_part_sbox_inst_04_o : AESByte; + signal o_part_sbox_inst_05_lhs : AESByte; + signal o_part_sbox_inst_05_o : AESByte; + signal o_part_sbox_inst_06_lhs : AESByte; + signal o_part_sbox_inst_06_o : AESByte; + signal o_part_sbox_inst_07_lhs : AESByte; + signal o_part_sbox_inst_07_o : AESByte; + signal o_part_sbox_inst_08_lhs : AESByte; + signal o_part_sbox_inst_08_o : AESByte; + signal o_part_sbox_inst_09_lhs : AESByte; + signal o_part_sbox_inst_09_o : AESByte; + signal o_part_sbox_inst_10_lhs : AESByte; + signal o_part_sbox_inst_10_o : AESByte; + signal o_part_sbox_inst_11_lhs : AESByte; + signal o_part_sbox_inst_11_o : AESByte; + signal o_part_sbox_inst_12_lhs : AESByte; + signal o_part_sbox_inst_12_o : AESByte; + signal o_part_sbox_inst_13_lhs : AESByte; + signal o_part_sbox_inst_13_o : AESByte; + signal o_part_sbox_inst_14_lhs : AESByte; + signal o_part_sbox_inst_14_o : AESByte; + signal o_part_sbox_inst_15_lhs : AESByte; + signal o_part_sbox_inst_15_o : AESByte; begin o_part_sbox_inst_00 : entity work.sbox(sbox_arch) port map ( lhs => o_part_sbox_inst_00_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/subWord.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/subWord.vhd index b3f99a7e0..7d8597fdd 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/subWord.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/subWord.vhd @@ -6,20 +6,20 @@ use work.Cipher_pkg.all; entity subWord is port ( - lhs : in t_opaque_AESWord; - o : out t_opaque_AESWord + lhs : in AESWord; + o : out AESWord ); end subWord; architecture subWord_arch of subWord is - signal o_part_sbox_inst_0_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_0_o : t_opaque_AESByte; - signal o_part_sbox_inst_1_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_1_o : t_opaque_AESByte; - signal o_part_sbox_inst_2_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_2_o : t_opaque_AESByte; - signal o_part_sbox_inst_3_lhs : t_opaque_AESByte; - signal o_part_sbox_inst_3_o : t_opaque_AESByte; + signal o_part_sbox_inst_0_lhs : AESByte; + signal o_part_sbox_inst_0_o : AESByte; + signal o_part_sbox_inst_1_lhs : AESByte; + signal o_part_sbox_inst_1_o : AESByte; + signal o_part_sbox_inst_2_lhs : AESByte; + signal o_part_sbox_inst_2_o : AESByte; + signal o_part_sbox_inst_3_lhs : AESByte; + signal o_part_sbox_inst_3_o : AESByte; begin o_part_sbox_inst_0 : entity work.sbox(sbox_arch) port map ( lhs => o_part_sbox_inst_0_lhs, diff --git a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/xtime.vhd b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/xtime.vhd index 7e8ddd2a1..007931a00 100644 --- a/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/xtime.vhd +++ b/lib/src/test/resources/ref/dfhdl.AES.CipherSpecWithOpaques/vhdl.v93/hdl/xtime.vhd @@ -6,8 +6,8 @@ use work.Cipher_pkg.all; entity xtime is port ( - lhs : in t_opaque_AESByte; - o : out t_opaque_AESByte + lhs : in AESByte; + o : out AESByte ); end xtime; diff --git a/lib/src/test/resources/ref/docExamples.ALUSpec/verilog.sv2009/hdl/ALU.sv b/lib/src/test/resources/ref/docExamples.ALUSpec/verilog.sv2009/hdl/ALU.sv index f3ece4974..fd9d5077c 100644 --- a/lib/src/test/resources/ref/docExamples.ALUSpec/verilog.sv2009/hdl/ALU.sv +++ b/lib/src/test/resources/ref/docExamples.ALUSpec/verilog.sv2009/hdl/ALU.sv @@ -3,10 +3,10 @@ `include "ALU_defs.svh" module ALU( - input wire logic [31:0] op1, - input wire logic [31:0] op2, - input wire t_enum_ALUSel aluSel, - output logic [31:0] aluOut + input wire logic [31:0] op1, + input wire logic [31:0] op2, + input wire ALUSel aluSel, + output logic [31:0] aluOut ); `include "dfhdl_defs.svh" logic [4:0] shamt; diff --git a/lib/src/test/resources/ref/docExamples.ALUSpec/verilog.sv2009/hdl/ALU_defs.svh b/lib/src/test/resources/ref/docExamples.ALUSpec/verilog.sv2009/hdl/ALU_defs.svh index 4b32703fc..0fdbad0ca 100644 --- a/lib/src/test/resources/ref/docExamples.ALUSpec/verilog.sv2009/hdl/ALU_defs.svh +++ b/lib/src/test/resources/ref/docExamples.ALUSpec/verilog.sv2009/hdl/ALU_defs.svh @@ -12,5 +12,5 @@ typedef enum logic [3:0] { ALUSel_SLT = 8, ALUSel_SLTU = 9, ALUSel_COPY1 = 10 -} t_enum_ALUSel; +} ALUSel; `endif diff --git a/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v2008/hdl/ALU.vhd b/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v2008/hdl/ALU.vhd index bdf393d39..a5ba22d72 100644 --- a/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v2008/hdl/ALU.vhd +++ b/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v2008/hdl/ALU.vhd @@ -6,10 +6,10 @@ use work.ALU_pkg.all; entity ALU is port ( - op1 : in std_logic_vector(31 downto 0); - op2 : in std_logic_vector(31 downto 0); - aluSel : in t_enum_ALUSel; - aluOut : out std_logic_vector(31 downto 0) + op1 : in std_logic_vector(31 downto 0); + op2 : in std_logic_vector(31 downto 0); + aluSel_0 : in ALUSel; + aluOut : out std_logic_vector(31 downto 0) ); end ALU; @@ -18,7 +18,7 @@ architecture ALU_arch of ALU is begin process (all) begin - case aluSel is + case aluSel_0 is when ALUSel_ADD => aluOut <= to_slv(unsigned(op1) + unsigned(op2)); when ALUSel_SUB => aluOut <= to_slv(unsigned(op1) - unsigned(op2)); when ALUSel_AND => aluOut <= op1 and op2; diff --git a/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v2008/hdl/ALU_pkg.vhd b/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v2008/hdl/ALU_pkg.vhd index 8e4d730e8..888026d42 100644 --- a/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v2008/hdl/ALU_pkg.vhd +++ b/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v2008/hdl/ALU_pkg.vhd @@ -4,21 +4,21 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package ALU_pkg is -type t_enum_ALUSel is ( +type ALUSel is ( ALUSel_ADD, ALUSel_SUB, ALUSel_SLL, ALUSel_SRL, ALUSel_SRA, ALUSel_AND, ALUSel_OR, ALUSel_XOR, ALUSel_SLT, ALUSel_SLTU, ALUSel_COPY1 ); -function bitWidth(A: t_enum_ALUSel) return integer; -function to_slv(A: t_enum_ALUSel) return std_logic_vector; -function to_t_enum_ALUSel(A: std_logic_vector) return t_enum_ALUSel; -function bool_sel(C : boolean; T : t_enum_ALUSel; F : t_enum_ALUSel) return t_enum_ALUSel; +function bitWidth(A: ALUSel) return integer; +function to_slv(A: ALUSel) return std_logic_vector; +function to_ALUSel(A: std_logic_vector) return ALUSel; +function bool_sel(C : boolean; T : ALUSel; F : ALUSel) return ALUSel; end package ALU_pkg; package body ALU_pkg is -function bitWidth(A : t_enum_ALUSel) return integer is +function bitWidth(A : ALUSel) return integer is begin return 4; end; -function to_slv(A : t_enum_ALUSel) return std_logic_vector is +function to_slv(A : ALUSel) return std_logic_vector is variable int_val : integer; begin case A is @@ -36,7 +36,7 @@ begin end case; return resize(to_slv(int_val), 4); end; -function to_t_enum_ALUSel(A : std_logic_vector) return t_enum_ALUSel is +function to_ALUSel(A : std_logic_vector) return ALUSel is begin case to_integer(unsigned(A)) is when 0 => return ALUSel_ADD; @@ -55,7 +55,7 @@ begin return ALUSel_ADD; end case; end; -function bool_sel(C : boolean; T : t_enum_ALUSel; F : t_enum_ALUSel) return t_enum_ALUSel is +function bool_sel(C : boolean; T : ALUSel; F : ALUSel) return ALUSel is begin if C then return T; diff --git a/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v93/hdl/ALU.vhd b/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v93/hdl/ALU.vhd index ba7065c3d..24bb66478 100644 --- a/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v93/hdl/ALU.vhd +++ b/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v93/hdl/ALU.vhd @@ -6,19 +6,19 @@ use work.ALU_pkg.all; entity ALU is port ( - op1 : in std_logic_vector(31 downto 0); - op2 : in std_logic_vector(31 downto 0); - aluSel : in t_enum_ALUSel; - aluOut : out std_logic_vector(31 downto 0) + op1 : in std_logic_vector(31 downto 0); + op2 : in std_logic_vector(31 downto 0); + aluSel_0 : in ALUSel; + aluOut : out std_logic_vector(31 downto 0) ); end ALU; architecture ALU_arch of ALU is signal shamt : std_logic_vector(4 downto 0); begin - process (aluSel, op1, op2, shamt) + process (aluSel_0, op1, op2, shamt) begin - case aluSel is + case aluSel_0 is when ALUSel_ADD => aluOut <= to_slv(unsigned(op1) + unsigned(op2)); when ALUSel_SUB => aluOut <= to_slv(unsigned(op1) - unsigned(op2)); when ALUSel_AND => aluOut <= op1 and op2; diff --git a/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v93/hdl/ALU_pkg.vhd b/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v93/hdl/ALU_pkg.vhd index 8e4d730e8..888026d42 100644 --- a/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v93/hdl/ALU_pkg.vhd +++ b/lib/src/test/resources/ref/docExamples.ALUSpec/vhdl.v93/hdl/ALU_pkg.vhd @@ -4,21 +4,21 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package ALU_pkg is -type t_enum_ALUSel is ( +type ALUSel is ( ALUSel_ADD, ALUSel_SUB, ALUSel_SLL, ALUSel_SRL, ALUSel_SRA, ALUSel_AND, ALUSel_OR, ALUSel_XOR, ALUSel_SLT, ALUSel_SLTU, ALUSel_COPY1 ); -function bitWidth(A: t_enum_ALUSel) return integer; -function to_slv(A: t_enum_ALUSel) return std_logic_vector; -function to_t_enum_ALUSel(A: std_logic_vector) return t_enum_ALUSel; -function bool_sel(C : boolean; T : t_enum_ALUSel; F : t_enum_ALUSel) return t_enum_ALUSel; +function bitWidth(A: ALUSel) return integer; +function to_slv(A: ALUSel) return std_logic_vector; +function to_ALUSel(A: std_logic_vector) return ALUSel; +function bool_sel(C : boolean; T : ALUSel; F : ALUSel) return ALUSel; end package ALU_pkg; package body ALU_pkg is -function bitWidth(A : t_enum_ALUSel) return integer is +function bitWidth(A : ALUSel) return integer is begin return 4; end; -function to_slv(A : t_enum_ALUSel) return std_logic_vector is +function to_slv(A : ALUSel) return std_logic_vector is variable int_val : integer; begin case A is @@ -36,7 +36,7 @@ begin end case; return resize(to_slv(int_val), 4); end; -function to_t_enum_ALUSel(A : std_logic_vector) return t_enum_ALUSel is +function to_ALUSel(A : std_logic_vector) return ALUSel is begin case to_integer(unsigned(A)) is when 0 => return ALUSel_ADD; @@ -55,7 +55,7 @@ begin return ALUSel_ADD; end case; end; -function bool_sel(C : boolean; T : t_enum_ALUSel; F : t_enum_ALUSel) return t_enum_ALUSel is +function bool_sel(C : boolean; T : ALUSel; F : ALUSel) return ALUSel is begin if C then return T; 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 11f0ca75e..e600adddf 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 @@ -21,11 +21,11 @@ module UART_Tx#( Status_DataBits = 4, Status_StopBit = 8, Status_Finalize = 16 - } t_enum_Status; - t_enum_Status status; + } Status; + Status status; logic [$clog2(BIT_CLOCKS) - 1:0] bitClkCnt; - logic [2:0] dataBitCnt; - logic [7:0] shiftData; + logic [2:0] dataBitCnt; + logic [7:0] shiftData; if (!((BIT_CLOCKS - 1) >= 0)) begin : constraint_0 $fatal(1, "Design parameter violation found. Expected: (BIT_CLOCKS - 1) >= 0"); end 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 24ab5edf9..a7c39549b 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 @@ -21,10 +21,10 @@ end UART_Tx; architecture UART_Tx_arch of UART_Tx is constant BIT_CLOCKS : integer := (CLK_FREQ_KHz * 1000) / BAUD_RATE_BPS; - type t_enum_Status is ( + type Status is ( Status_Idle, Status_StartBit, Status_DataBits, Status_StopBit, Status_Finalize ); - signal status : t_enum_Status; + signal status_0 : Status; signal bitClkCnt : unsigned(clog2(BIT_CLOCKS) - 1 downto 0); signal dataBitCnt : unsigned(2 downto 0); signal shiftData : std_logic_vector(7 downto 0); @@ -35,11 +35,11 @@ begin begin if rising_edge(clk) then if rst = '1' then - status <= Status_Idle; + status_0 <= Status_Idle; bitClkCnt <= resize(1d"0", clog2(BIT_CLOCKS)); dataBitCnt <= 3d"0"; else - case status is + case status_0 is when Status_Idle => tx_en <= '0'; tx <= '1'; @@ -48,14 +48,14 @@ begin dataBitCnt <= 3d"0"; if data_en then shiftData <= data; - status <= Status_StartBit; + status_0 <= Status_StartBit; end if; when Status_StartBit => tx_en <= '1'; tx <= '0'; if bitClkCnt = to_unsigned(BIT_CLOCKS - 1, clog2(BIT_CLOCKS)) then bitClkCnt <= resize(1d"0", clog2(BIT_CLOCKS)); - status <= Status_DataBits; + status_0 <= Status_DataBits; else bitClkCnt <= bitClkCnt + resize(1d"1", clog2(BIT_CLOCKS)); end if; when Status_DataBits => @@ -65,7 +65,7 @@ begin shiftData <= slv_srl(shiftData, 1); if dataBitCnt = 3d"7" then dataBitCnt <= 3d"0"; - status <= Status_StopBit; + status_0 <= Status_StopBit; else dataBitCnt <= dataBitCnt + 3d"1"; end if; else bitClkCnt <= bitClkCnt + resize(1d"1", clog2(BIT_CLOCKS)); @@ -75,13 +75,13 @@ begin if bitClkCnt = to_unsigned(BIT_CLOCKS - 1, clog2(BIT_CLOCKS)) then bitClkCnt <= resize(1d"0", clog2(BIT_CLOCKS)); tx_done <= '1'; - status <= Status_Finalize; + status_0 <= Status_Finalize; else bitClkCnt <= bitClkCnt + resize(1d"1", clog2(BIT_CLOCKS)); end if; when Status_Finalize => tx_en <= '0'; tx_done <= '1'; - status <= Status_Idle; + status_0 <= Status_Idle; end case; end if; end if; 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 8f27d75db..b4a4ef309 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 @@ -21,10 +21,10 @@ end UART_Tx; architecture UART_Tx_arch of UART_Tx is constant BIT_CLOCKS : integer := (CLK_FREQ_KHz * 1000) / BAUD_RATE_BPS; - type t_enum_Status is ( + type Status is ( Status_Idle, Status_StartBit, Status_DataBits, Status_StopBit, Status_Finalize ); - signal status : t_enum_Status; + signal status_0 : Status; signal bitClkCnt : unsigned(clog2(BIT_CLOCKS) - 1 downto 0); signal dataBitCnt : unsigned(2 downto 0); signal shiftData : std_logic_vector(7 downto 0); @@ -35,11 +35,11 @@ begin begin if rising_edge(clk) then if rst = '1' then - status <= Status_Idle; + status_0 <= Status_Idle; bitClkCnt <= resize(to_unsigned(0, 1), clog2(BIT_CLOCKS)); dataBitCnt <= to_unsigned(0, 3); else - case status is + case status_0 is when Status_Idle => tx_en <= '0'; tx <= '1'; @@ -48,14 +48,14 @@ begin dataBitCnt <= to_unsigned(0, 3); if to_bool(data_en) then shiftData <= data; - status <= Status_StartBit; + status_0 <= Status_StartBit; end if; when Status_StartBit => tx_en <= '1'; tx <= '0'; if bitClkCnt = to_unsigned(BIT_CLOCKS - 1, clog2(BIT_CLOCKS)) then bitClkCnt <= resize(to_unsigned(0, 1), clog2(BIT_CLOCKS)); - status <= Status_DataBits; + status_0 <= Status_DataBits; else bitClkCnt <= bitClkCnt + resize(to_unsigned(1, 1), clog2(BIT_CLOCKS)); end if; when Status_DataBits => @@ -65,7 +65,7 @@ begin shiftData <= slv_srl(shiftData, 1); if dataBitCnt = to_unsigned(7, 3) then dataBitCnt <= to_unsigned(0, 3); - status <= Status_StopBit; + status_0 <= Status_StopBit; else dataBitCnt <= dataBitCnt + to_unsigned(1, 3); end if; else bitClkCnt <= bitClkCnt + resize(to_unsigned(1, 1), clog2(BIT_CLOCKS)); @@ -75,13 +75,13 @@ begin if bitClkCnt = to_unsigned(BIT_CLOCKS - 1, clog2(BIT_CLOCKS)) then bitClkCnt <= resize(to_unsigned(0, 1), clog2(BIT_CLOCKS)); tx_done <= '1'; - status <= Status_Finalize; + status_0 <= Status_Finalize; else bitClkCnt <= bitClkCnt + resize(to_unsigned(1, 1), clog2(BIT_CLOCKS)); end if; when Status_Finalize => tx_en <= '0'; tx_done <= '1'; - status <= Status_Idle; + status_0 <= Status_Idle; end case; end if; end if; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/verilog.sv2009/hdl/LRShiftFlat.sv b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/verilog.sv2009/hdl/LRShiftFlat.sv index fec610d8b..43dbf6d36 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/verilog.sv2009/hdl/LRShiftFlat.sv +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/verilog.sv2009/hdl/LRShiftFlat.sv @@ -13,7 +13,7 @@ module LRShiftFlat#(parameter int width = 8)( /* requested shift */ input wire logic [$clog2(width) - 1:0] shift, /* direction of shift */ - input wire t_enum_ShiftDir dir, + input wire ShiftDir dir, /* bits output */ output logic [width - 1:0] oBits ); diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/verilog.sv2009/hdl/LRShiftFlat_defs.svh b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/verilog.sv2009/hdl/LRShiftFlat_defs.svh index 48f385198..fcf573799 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/verilog.sv2009/hdl/LRShiftFlat_defs.svh +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/verilog.sv2009/hdl/LRShiftFlat_defs.svh @@ -3,5 +3,5 @@ typedef enum logic [0:0] { ShiftDir_Left = 0, ShiftDir_Right = 1 -} t_enum_ShiftDir; +} ShiftDir; `endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v2008/hdl/LRShiftFlat.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v2008/hdl/LRShiftFlat.vhd index 79866d1f7..160852fa7 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v2008/hdl/LRShiftFlat.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v2008/hdl/LRShiftFlat.vhd @@ -18,7 +18,7 @@ port ( -- requested shift shift : in unsigned(clog2(width) - 1 downto 0); -- direction of shift - dir : in t_enum_ShiftDir; + dir : in ShiftDir; -- bits output oBits : out std_logic_vector(width - 1 downto 0) ); diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v2008/hdl/LRShiftFlat_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v2008/hdl/LRShiftFlat_pkg.vhd index bece2f07f..58decafb2 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v2008/hdl/LRShiftFlat_pkg.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v2008/hdl/LRShiftFlat_pkg.vhd @@ -4,26 +4,26 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package LRShiftFlat_pkg is -type t_enum_ShiftDir is ( +type ShiftDir is ( ShiftDir_Left, ShiftDir_Right ); -function bitWidth(A: t_enum_ShiftDir) return integer; -function to_slv(A: t_enum_ShiftDir) return std_logic_vector; -function to_t_enum_ShiftDir(A: std_logic_vector) return t_enum_ShiftDir; -function bool_sel(C : boolean; T : t_enum_ShiftDir; F : t_enum_ShiftDir) return t_enum_ShiftDir; -function to_bool(A: t_enum_ShiftDir) return boolean; -function to_sl(A: t_enum_ShiftDir) return std_logic; -function to_t_enum_ShiftDir(A: boolean) return t_enum_ShiftDir; -function to_t_enum_ShiftDir(A: std_logic) return t_enum_ShiftDir; -function toggle(A: t_enum_ShiftDir) return t_enum_ShiftDir; +function bitWidth(A: ShiftDir) return integer; +function to_slv(A: ShiftDir) return std_logic_vector; +function to_ShiftDir(A: std_logic_vector) return ShiftDir; +function bool_sel(C : boolean; T : ShiftDir; F : ShiftDir) return ShiftDir; +function to_bool(A: ShiftDir) return boolean; +function to_sl(A: ShiftDir) return std_logic; +function to_ShiftDir(A: boolean) return ShiftDir; +function to_ShiftDir(A: std_logic) return ShiftDir; +function toggle(A: ShiftDir) return ShiftDir; end package LRShiftFlat_pkg; package body LRShiftFlat_pkg is -function bitWidth(A : t_enum_ShiftDir) return integer is +function bitWidth(A : ShiftDir) return integer is begin return 1; end; -function to_slv(A : t_enum_ShiftDir) return std_logic_vector is +function to_slv(A : ShiftDir) return std_logic_vector is variable int_val : integer; begin case A is @@ -32,7 +32,7 @@ begin end case; return resize(to_slv(int_val), 1); end; -function to_t_enum_ShiftDir(A : std_logic_vector) return t_enum_ShiftDir is +function to_ShiftDir(A : std_logic_vector) return ShiftDir is begin case to_integer(unsigned(A)) is when 0 => return ShiftDir_Left; @@ -42,7 +42,7 @@ begin return ShiftDir_Left; end case; end; -function bool_sel(C : boolean; T : t_enum_ShiftDir; F : t_enum_ShiftDir) return t_enum_ShiftDir is +function bool_sel(C : boolean; T : ShiftDir; F : ShiftDir) return ShiftDir is begin if C then return T; @@ -50,33 +50,33 @@ begin return F; end if; end; -function to_bool(A : t_enum_ShiftDir) return boolean is +function to_bool(A : ShiftDir) return boolean is begin case A is when ShiftDir_Left => return false; when ShiftDir_Right => return true; end case; end; -function to_sl(A : t_enum_ShiftDir) return std_logic is +function to_sl(A : ShiftDir) return std_logic is begin case A is when ShiftDir_Left => return '0'; when ShiftDir_Right => return '1'; end case; end; -function to_t_enum_ShiftDir(A : boolean) return t_enum_ShiftDir is +function to_ShiftDir(A : boolean) return ShiftDir is begin if A then return ShiftDir_Right; else return ShiftDir_Left; end if; end; -function to_t_enum_ShiftDir(A : std_logic) return t_enum_ShiftDir is +function to_ShiftDir(A : std_logic) return ShiftDir is begin if A = '1' then return ShiftDir_Right; else return ShiftDir_Left; end if; end; -function toggle(A : t_enum_ShiftDir) return t_enum_ShiftDir is +function toggle(A : ShiftDir) return ShiftDir is begin case A is when ShiftDir_Left => return ShiftDir_Right; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v93/hdl/LRShiftFlat.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v93/hdl/LRShiftFlat.vhd index 2029f833d..8e0409f0a 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v93/hdl/LRShiftFlat.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v93/hdl/LRShiftFlat.vhd @@ -18,7 +18,7 @@ port ( -- requested shift shift : in unsigned(clog2(width) - 1 downto 0); -- direction of shift - dir : in t_enum_ShiftDir; + dir : in ShiftDir; -- bits output oBits : out std_logic_vector(width - 1 downto 0) ); diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v93/hdl/LRShiftFlat_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v93/hdl/LRShiftFlat_pkg.vhd index bece2f07f..58decafb2 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v93/hdl/LRShiftFlat_pkg.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo4.LRShiftFlatSpec/vhdl.v93/hdl/LRShiftFlat_pkg.vhd @@ -4,26 +4,26 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package LRShiftFlat_pkg is -type t_enum_ShiftDir is ( +type ShiftDir is ( ShiftDir_Left, ShiftDir_Right ); -function bitWidth(A: t_enum_ShiftDir) return integer; -function to_slv(A: t_enum_ShiftDir) return std_logic_vector; -function to_t_enum_ShiftDir(A: std_logic_vector) return t_enum_ShiftDir; -function bool_sel(C : boolean; T : t_enum_ShiftDir; F : t_enum_ShiftDir) return t_enum_ShiftDir; -function to_bool(A: t_enum_ShiftDir) return boolean; -function to_sl(A: t_enum_ShiftDir) return std_logic; -function to_t_enum_ShiftDir(A: boolean) return t_enum_ShiftDir; -function to_t_enum_ShiftDir(A: std_logic) return t_enum_ShiftDir; -function toggle(A: t_enum_ShiftDir) return t_enum_ShiftDir; +function bitWidth(A: ShiftDir) return integer; +function to_slv(A: ShiftDir) return std_logic_vector; +function to_ShiftDir(A: std_logic_vector) return ShiftDir; +function bool_sel(C : boolean; T : ShiftDir; F : ShiftDir) return ShiftDir; +function to_bool(A: ShiftDir) return boolean; +function to_sl(A: ShiftDir) return std_logic; +function to_ShiftDir(A: boolean) return ShiftDir; +function to_ShiftDir(A: std_logic) return ShiftDir; +function toggle(A: ShiftDir) return ShiftDir; end package LRShiftFlat_pkg; package body LRShiftFlat_pkg is -function bitWidth(A : t_enum_ShiftDir) return integer is +function bitWidth(A : ShiftDir) return integer is begin return 1; end; -function to_slv(A : t_enum_ShiftDir) return std_logic_vector is +function to_slv(A : ShiftDir) return std_logic_vector is variable int_val : integer; begin case A is @@ -32,7 +32,7 @@ begin end case; return resize(to_slv(int_val), 1); end; -function to_t_enum_ShiftDir(A : std_logic_vector) return t_enum_ShiftDir is +function to_ShiftDir(A : std_logic_vector) return ShiftDir is begin case to_integer(unsigned(A)) is when 0 => return ShiftDir_Left; @@ -42,7 +42,7 @@ begin return ShiftDir_Left; end case; end; -function bool_sel(C : boolean; T : t_enum_ShiftDir; F : t_enum_ShiftDir) return t_enum_ShiftDir is +function bool_sel(C : boolean; T : ShiftDir; F : ShiftDir) return ShiftDir is begin if C then return T; @@ -50,33 +50,33 @@ begin return F; end if; end; -function to_bool(A : t_enum_ShiftDir) return boolean is +function to_bool(A : ShiftDir) return boolean is begin case A is when ShiftDir_Left => return false; when ShiftDir_Right => return true; end case; end; -function to_sl(A : t_enum_ShiftDir) return std_logic is +function to_sl(A : ShiftDir) return std_logic is begin case A is when ShiftDir_Left => return '0'; when ShiftDir_Right => return '1'; end case; end; -function to_t_enum_ShiftDir(A : boolean) return t_enum_ShiftDir is +function to_ShiftDir(A : boolean) return ShiftDir is begin if A then return ShiftDir_Right; else return ShiftDir_Left; end if; end; -function to_t_enum_ShiftDir(A : std_logic) return t_enum_ShiftDir is +function to_ShiftDir(A : std_logic) return ShiftDir is begin if A = '1' then return ShiftDir_Right; else return ShiftDir_Left; end if; end; -function toggle(A : t_enum_ShiftDir) return t_enum_ShiftDir is +function toggle(A : ShiftDir) return ShiftDir is begin case A is when ShiftDir_Left => return ShiftDir_Right; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/verilog.sv2009/hdl/LRShiftDirect.sv b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/verilog.sv2009/hdl/LRShiftDirect.sv index 63a81126c..d7c48bab6 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/verilog.sv2009/hdl/LRShiftDirect.sv +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/verilog.sv2009/hdl/LRShiftDirect.sv @@ -15,7 +15,7 @@ module LRShiftDirect#(parameter int width = 8)( /* bits output */ output logic [width - 1:0] oBits, /* direction of shift */ - input wire t_enum_ShiftDir dir + input wire ShiftDir dir ); `include "dfhdl_defs.svh" logic [width - 1:0] lshifter_iBits; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/verilog.sv2009/hdl/LRShiftDirect_defs.svh b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/verilog.sv2009/hdl/LRShiftDirect_defs.svh index 88875792f..321c1d48e 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/verilog.sv2009/hdl/LRShiftDirect_defs.svh +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/verilog.sv2009/hdl/LRShiftDirect_defs.svh @@ -3,5 +3,5 @@ typedef enum logic [0:0] { ShiftDir_Left = 0, ShiftDir_Right = 1 -} t_enum_ShiftDir; +} ShiftDir; `endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v2008/hdl/LRShiftDirect.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v2008/hdl/LRShiftDirect.vhd index 139d5dbbb..f5ab0517a 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v2008/hdl/LRShiftDirect.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v2008/hdl/LRShiftDirect.vhd @@ -20,7 +20,7 @@ port ( -- bits output oBits : out std_logic_vector(width - 1 downto 0); -- direction of shift - dir : in t_enum_ShiftDir + dir : in ShiftDir ); end LRShiftDirect; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v2008/hdl/LRShiftDirect_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v2008/hdl/LRShiftDirect_pkg.vhd index 66c004d1f..b2cacb03f 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v2008/hdl/LRShiftDirect_pkg.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v2008/hdl/LRShiftDirect_pkg.vhd @@ -4,26 +4,26 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package LRShiftDirect_pkg is -type t_enum_ShiftDir is ( +type ShiftDir is ( ShiftDir_Left, ShiftDir_Right ); -function bitWidth(A: t_enum_ShiftDir) return integer; -function to_slv(A: t_enum_ShiftDir) return std_logic_vector; -function to_t_enum_ShiftDir(A: std_logic_vector) return t_enum_ShiftDir; -function bool_sel(C : boolean; T : t_enum_ShiftDir; F : t_enum_ShiftDir) return t_enum_ShiftDir; -function to_bool(A: t_enum_ShiftDir) return boolean; -function to_sl(A: t_enum_ShiftDir) return std_logic; -function to_t_enum_ShiftDir(A: boolean) return t_enum_ShiftDir; -function to_t_enum_ShiftDir(A: std_logic) return t_enum_ShiftDir; -function toggle(A: t_enum_ShiftDir) return t_enum_ShiftDir; +function bitWidth(A: ShiftDir) return integer; +function to_slv(A: ShiftDir) return std_logic_vector; +function to_ShiftDir(A: std_logic_vector) return ShiftDir; +function bool_sel(C : boolean; T : ShiftDir; F : ShiftDir) return ShiftDir; +function to_bool(A: ShiftDir) return boolean; +function to_sl(A: ShiftDir) return std_logic; +function to_ShiftDir(A: boolean) return ShiftDir; +function to_ShiftDir(A: std_logic) return ShiftDir; +function toggle(A: ShiftDir) return ShiftDir; end package LRShiftDirect_pkg; package body LRShiftDirect_pkg is -function bitWidth(A : t_enum_ShiftDir) return integer is +function bitWidth(A : ShiftDir) return integer is begin return 1; end; -function to_slv(A : t_enum_ShiftDir) return std_logic_vector is +function to_slv(A : ShiftDir) return std_logic_vector is variable int_val : integer; begin case A is @@ -32,7 +32,7 @@ begin end case; return resize(to_slv(int_val), 1); end; -function to_t_enum_ShiftDir(A : std_logic_vector) return t_enum_ShiftDir is +function to_ShiftDir(A : std_logic_vector) return ShiftDir is begin case to_integer(unsigned(A)) is when 0 => return ShiftDir_Left; @@ -42,7 +42,7 @@ begin return ShiftDir_Left; end case; end; -function bool_sel(C : boolean; T : t_enum_ShiftDir; F : t_enum_ShiftDir) return t_enum_ShiftDir is +function bool_sel(C : boolean; T : ShiftDir; F : ShiftDir) return ShiftDir is begin if C then return T; @@ -50,33 +50,33 @@ begin return F; end if; end; -function to_bool(A : t_enum_ShiftDir) return boolean is +function to_bool(A : ShiftDir) return boolean is begin case A is when ShiftDir_Left => return false; when ShiftDir_Right => return true; end case; end; -function to_sl(A : t_enum_ShiftDir) return std_logic is +function to_sl(A : ShiftDir) return std_logic is begin case A is when ShiftDir_Left => return '0'; when ShiftDir_Right => return '1'; end case; end; -function to_t_enum_ShiftDir(A : boolean) return t_enum_ShiftDir is +function to_ShiftDir(A : boolean) return ShiftDir is begin if A then return ShiftDir_Right; else return ShiftDir_Left; end if; end; -function to_t_enum_ShiftDir(A : std_logic) return t_enum_ShiftDir is +function to_ShiftDir(A : std_logic) return ShiftDir is begin if A = '1' then return ShiftDir_Right; else return ShiftDir_Left; end if; end; -function toggle(A : t_enum_ShiftDir) return t_enum_ShiftDir is +function toggle(A : ShiftDir) return ShiftDir is begin case A is when ShiftDir_Left => return ShiftDir_Right; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v93/hdl/LRShiftDirect.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v93/hdl/LRShiftDirect.vhd index e69337cbb..2699ec39e 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v93/hdl/LRShiftDirect.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v93/hdl/LRShiftDirect.vhd @@ -20,7 +20,7 @@ port ( -- bits output oBits : out std_logic_vector(width - 1 downto 0); -- direction of shift - dir : in t_enum_ShiftDir + dir : in ShiftDir ); end LRShiftDirect; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v93/hdl/LRShiftDirect_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v93/hdl/LRShiftDirect_pkg.vhd index 66c004d1f..b2cacb03f 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v93/hdl/LRShiftDirect_pkg.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo5.LRShiftDirectSpec/vhdl.v93/hdl/LRShiftDirect_pkg.vhd @@ -4,26 +4,26 @@ use ieee.numeric_std.all; use work.dfhdl_pkg.all; package LRShiftDirect_pkg is -type t_enum_ShiftDir is ( +type ShiftDir is ( ShiftDir_Left, ShiftDir_Right ); -function bitWidth(A: t_enum_ShiftDir) return integer; -function to_slv(A: t_enum_ShiftDir) return std_logic_vector; -function to_t_enum_ShiftDir(A: std_logic_vector) return t_enum_ShiftDir; -function bool_sel(C : boolean; T : t_enum_ShiftDir; F : t_enum_ShiftDir) return t_enum_ShiftDir; -function to_bool(A: t_enum_ShiftDir) return boolean; -function to_sl(A: t_enum_ShiftDir) return std_logic; -function to_t_enum_ShiftDir(A: boolean) return t_enum_ShiftDir; -function to_t_enum_ShiftDir(A: std_logic) return t_enum_ShiftDir; -function toggle(A: t_enum_ShiftDir) return t_enum_ShiftDir; +function bitWidth(A: ShiftDir) return integer; +function to_slv(A: ShiftDir) return std_logic_vector; +function to_ShiftDir(A: std_logic_vector) return ShiftDir; +function bool_sel(C : boolean; T : ShiftDir; F : ShiftDir) return ShiftDir; +function to_bool(A: ShiftDir) return boolean; +function to_sl(A: ShiftDir) return std_logic; +function to_ShiftDir(A: boolean) return ShiftDir; +function to_ShiftDir(A: std_logic) return ShiftDir; +function toggle(A: ShiftDir) return ShiftDir; end package LRShiftDirect_pkg; package body LRShiftDirect_pkg is -function bitWidth(A : t_enum_ShiftDir) return integer is +function bitWidth(A : ShiftDir) return integer is begin return 1; end; -function to_slv(A : t_enum_ShiftDir) return std_logic_vector is +function to_slv(A : ShiftDir) return std_logic_vector is variable int_val : integer; begin case A is @@ -32,7 +32,7 @@ begin end case; return resize(to_slv(int_val), 1); end; -function to_t_enum_ShiftDir(A : std_logic_vector) return t_enum_ShiftDir is +function to_ShiftDir(A : std_logic_vector) return ShiftDir is begin case to_integer(unsigned(A)) is when 0 => return ShiftDir_Left; @@ -42,7 +42,7 @@ begin return ShiftDir_Left; end case; end; -function bool_sel(C : boolean; T : t_enum_ShiftDir; F : t_enum_ShiftDir) return t_enum_ShiftDir is +function bool_sel(C : boolean; T : ShiftDir; F : ShiftDir) return ShiftDir is begin if C then return T; @@ -50,33 +50,33 @@ begin return F; end if; end; -function to_bool(A : t_enum_ShiftDir) return boolean is +function to_bool(A : ShiftDir) return boolean is begin case A is when ShiftDir_Left => return false; when ShiftDir_Right => return true; end case; end; -function to_sl(A : t_enum_ShiftDir) return std_logic is +function to_sl(A : ShiftDir) return std_logic is begin case A is when ShiftDir_Left => return '0'; when ShiftDir_Right => return '1'; end case; end; -function to_t_enum_ShiftDir(A : boolean) return t_enum_ShiftDir is +function to_ShiftDir(A : boolean) return ShiftDir is begin if A then return ShiftDir_Right; else return ShiftDir_Left; end if; end; -function to_t_enum_ShiftDir(A : std_logic) return t_enum_ShiftDir is +function to_ShiftDir(A : std_logic) return ShiftDir is begin if A = '1' then return ShiftDir_Right; else return ShiftDir_Left; end if; end; -function toggle(A : t_enum_ShiftDir) return t_enum_ShiftDir is +function toggle(A : ShiftDir) return ShiftDir is begin case A is when ShiftDir_Left => return ShiftDir_Right; From 37aeebe786bf6ef7122ad3210260510a900cf373 Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 04:30:13 +0300 Subject: [PATCH 51/57] packages: VHDL namespace-derived package emission Each namespace-derived package emits as a full VHDL package unit: the spec holds the type declarations with their conversion-function prototypes, then the constants and method prototypes in dependency order; the body holds the conversion-function and method bodies. Visibility is by `use` clauses (no qualification in VHDL): a package uses the general package plus every package preceding it in the cross-package topological order (dependencies are guaranteed to precede), and design files use all emitted packages. The general `_pkg` now carries only global-placed content: packaged named types are excluded from its type collection, conversion functions, spec decls, and method bodies (which also pick up the same-declaration dedup), while design-local types hoisted by packaged references are included. Architecture declarative regions no longer re-declare packaged types (they arrive through the `use` clause; a local re-declaration would shadow and break type identity). The shared printing layer exposes the typed per-package entries (packagedGlobalDeclEntries + csPackagedGlobalDecl) so VHDL can assemble its spec/body split; Verilog keeps consuming the flat rendered form. Pinned end-to-end in PrintVHDLCodeSpec "Namespace-derived type packages" over the same PkgFixtures design as the Verilog and DFHDL pins: typespkg1/typespkg2 package units with records, enums, subtypes, conversion functions, constants, and the static function, cross-package use-visibility, and the entity ports typed by packaged records. Co-Authored-By: Claude Fable 5 --- .../dfhdl/compiler/printing/Printer.scala | 34 +-- .../stages/vhdl/VHDLOwnerPrinter.scala | 10 +- .../compiler/stages/vhdl/VHDLPrinter.scala | 77 ++++++- .../scala/StagesSpec/PrintVHDLCodeSpec.scala | 215 ++++++++++++++++++ 4 files changed, 313 insertions(+), 23 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 1a0393cc4..a6c672abf 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala @@ -579,7 +579,7 @@ trait Printer true case _ => true } - private def globalDeclPlacementOf(decl: GlobalDecl): Option[String] = decl match + protected final def globalDeclPlacementOf(decl: GlobalDecl): Option[String] = decl match case GlobalDecl.Const(c) => memberPlacementOf(c.meta.namespace) case GlobalDecl.Method(b) => memberPlacementOf(b.dclMeta.namespace) private def globalDeclNamespaceOf(decl: GlobalDecl): String = decl match @@ -596,9 +596,9 @@ trait Printer protected final def csGlobalDecls: String = globalDeclsDeduped.filter(globalDeclPlacementOf(_).isEmpty) .map(csGlobalDecl).filter(_.nonEmpty).mkString("\n") - // packaged global constants/methods: (package, namespace, content) with the - // dependency order preserved within each package - protected final def packagedGlobalDecls: List[(String, String, String)] = + // packaged global constants/methods per package, dependency order preserved: + // the typed entries (for backends assembling their own spec/body structure) + protected final def packagedGlobalDeclEntries: List[(String, String, List[GlobalDecl])] = val perPkg = collection.mutable.LinkedHashMap .empty[String, (String, collection.mutable.ListBuffer[GlobalDecl])] @@ -613,16 +613,22 @@ trait Printer buf += decl } } - perPkg.view.map { case (pkg, (ns, decls)) => - val content = decls.map { decl => - val p = globalDeclPrinterOf(decl) - p.currentPackage = Some(pkg) - try csGlobalDecl(decl) - finally p.currentPackage = None - }.filter(_.nonEmpty).mkString("\n") - (pkg, ns, content) - }.toList - end packagedGlobalDecls + perPkg.view.map((pkg, entry) => (pkg, entry._1, entry._2.toList)).toList + end packagedGlobalDeclEntries + // renders one packaged declaration under its package context + protected final def csPackagedGlobalDecl(pkg: String, decl: GlobalDecl): String = + val p = globalDeclPrinterOf(decl) + p.currentPackage = Some(pkg) + try csGlobalDecl(decl) + finally p.currentPackage = None + // ...and the rendered per-package form (Verilog-shaped flat content) + protected final def packagedGlobalDecls: List[(String, String, String)] = + packagedGlobalDeclEntries.map { (pkg, ns, decls) => + (pkg, ns, decls.map(csPackagedGlobalDecl(pkg, _)).filter(_.nonEmpty).mkString("\n")) + } + // the per-package METHOD prototype rendering seam (VHDL splits spec from body) + protected final def globalMethodPrinterFor(b: DFDesignBlock): TPrinter = + globalMethodPrinterOf(b) // every packaged content group (types first, then constants/methods), merged per // package: type-dependency order first, decl-only packages appended final def packagedContents: List[(String, String, 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 b43a5f5e3..a32152be4 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 @@ -25,13 +25,15 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: |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 (printer.hasGlobalContent) s"use work.$packageName.all;" else ""} + |${printer.packagedContents.map((p, _, _) => s"use work.$p.all;").mkString("\n")}""" if (useStdSimLibrary && inSimulation) s"""$default | |library std; |use std.env.all;""".stripMargin else default + end csLibrary def entityName(design: DFDesignBlock): String = design.dclName def csEntityDcl(design: DFDesignBlock, asComponent: Boolean = false): String = val designMembers = design.members(MemberView.Folded) @@ -101,8 +103,10 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: case localVar @ DclVar() => localVar.dfType case localConst @ DclConst() => localConst.dfType }.flatMap(_.decompose[DFVector | NamedDFType] { - case dt: DFVector => dt - case dt: NamedDFType if !globalNamedDFTypes.contains(dt) => dt + case dt: DFVector => dt + // packaged types come from their package's `use` clause, never re-declared here + case dt: NamedDFType + if !globalNamedDFTypes.contains(dt) && printer.typePlacementOf(dt).isEmpty => dt })) // declarations of the types and relevant functions val namedTypeConvFuncsDcl = namedDFTypes.view 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 11e2c08d0..75a78bd4d 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 @@ -167,6 +167,61 @@ 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 + override def supportPackages: Boolean = true + override def packageFileName(pkgName: String): String = s"$pkgName.vhd" + // A namespace-derived package: spec (type dcls + conv-func protos, then constants and + // method protos in dependency order) and body (conv-func and method bodies). VHDL has + // no reference qualification here: visibility comes from `use` clauses, and a package + // uses the general package plus every package PRECEDING it in the cross-package + // topological order (its dependencies are guaranteed to precede it). + override def csPackageFileContent(pkgName: String, namespace: String, typeDcls: String): String = + val precedingPkgs = packagedContents.map(_._1).takeWhile(_ != pkgName) + val typeEntries = packagedTypeEntries.collectFirst { + case (`pkgName`, _, entries) => entries + }.getOrElse(Nil) + val declEntries = packagedGlobalDeclEntries.collectFirst { + case (`pkgName`, _, decls) => decls + }.getOrElse(Nil) + def underPkg[T](p: TPrinter)(block: => T): T = + p.currentPackage = Some(pkgName) + try block + finally p.currentPackage = None + val typeSpecDcls = typeEntries.map { (p, t) => + underPkg(p) { + sn"""|${p.csNamedDFTypeDcl(t, global = true)} + |${p.csNamedDFTypeConvFuncsDcl(t)}""" + } + }.mkString("\n") + val declSpecDcls = declEntries.map { + case GlobalDecl.Const(c) => csPackagedGlobalDecl(pkgName, GlobalDecl.Const(c)) + case GlobalDecl.Method(b) => + val p = globalMethodPrinterFor(b) + underPkg(p)(p.csMethodProto(b)) + }.filter(_.nonEmpty).mkString("\n") + val typeBodyDcls = typeEntries.map { (p, t) => + underPkg(p)(p.csNamedDFTypeConvFuncsBody(t)) + }.filter(_.nonEmpty).mkString("\n") + val declBodyDcls = declEntries.collect { case GlobalDecl.Method(b) => + csPackagedGlobalDecl(pkgName, GlobalDecl.Method(b)) + }.filter(_.nonEmpty).mkString("\n") + sn"""|library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + |${if (hasGlobalContent) s"use work.${printer.packageName}.all;" else ""} + |${precedingPkgs.map(p => s"use work.$p.all;").mkString("\n")} + | + |package $pkgName is + |$typeSpecDcls + |$declSpecDcls + |end package $pkgName; + | + |package body $pkgName is + |$typeBodyDcls + |$declBodyDcls + |end package body $pkgName; + |""" + end csPackageFileContent def globalFileName: String = val name = printerOptions.globalDefsFileName if (name.nonEmpty && name.contains('.')) name @@ -224,11 +279,16 @@ class VHDLPrinter(val dialect: VHDLDialect)(using printer.globalVectorTypes.view.map { case (tpName, (vecType, depth)) => printer.csDFVectorDclsGlobal(DclScope.PkgBody)(tpName, vecType, depth) }.mkString("\n") - // collect the global named types, including vectors + // collect the global named types, including vectors: packaged named types are + // excluded (each lives in its own package file), while design-local types that + // packaged content references are hoisted in 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 })) + }.flatMap(_.decompose { case dt: (DFVector | NamedDFType) => dt }).filter { + case dt: NamedDFType => printer.typePlacementOf(dt).isEmpty + case _ => true + }) ++ packagedHoistedTypes.map(_._2) // declarations of the types and relevant functions val namedTypeConvFuncsDcl = namedDFTypes.view .flatMap { @@ -248,8 +308,10 @@ class VHDLPrinter(val dialect: VHDLDialect)(using } .mkString("\n") val namedTypeConvFuncsBody = - getSet.designDB.getGlobalNamedDFTypes.view - .collect { case dfType: NamedDFType => printer.csNamedDFTypeConvFuncsBody(dfType) } + (getSet.designDB.getGlobalNamedDFTypes.view + .filter(dfType => printer.typePlacementOf(dfType).isEmpty) ++ + packagedHoistedTypes.view.map(_._2)) + .map(dfType => printer.csNamedDFTypeConvFuncsBody(dfType)) .mkString("\n") val usesMathReal = getSet.designDB.membersGlobals.exists { _.dfType.decompose { case dt: DFDouble => dt }.nonEmpty @@ -262,11 +324,14 @@ class VHDLPrinter(val dialect: VHDLDialect)(using // dependency order (a constant may call a method, a method may read a constant); the // bodies follow in the package body, where every spec name is already visible val protoOf = printer.globalMethodPrinters.toMap - val globalSpecDcls = globalDeclsOrdered.map { + val globalSpecDcls = globalDeclsDeduped.filter(globalDeclPlacementOf(_).isEmpty).map { case GlobalDecl.Const(c) => printer.csDFMembers(List(c)) case GlobalDecl.Method(b) => protoOf(b).csMethodProto(b) }.filter(_.nonEmpty).mkString("\n") - val globalMethodBodies = csGlobalMethodDcls + val globalMethodBodies = globalDeclsDeduped + .filter(globalDeclPlacementOf(_).isEmpty) + .collect { case decl @ GlobalDecl.Method(_) => csGlobalDecl(decl) } + .filter(_.nonEmpty).mkString("\n\n") sn"""|library ieee; |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index 962b07365..efbb66927 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -3916,4 +3916,219 @@ class PrintVHDLCodeSpec extends StageSpec: |end DocTop_arch; |""".stripMargin ) + test("Namespace-derived type packages"): + class PkgTop extends EDDesign: + val sp = typespkg1.PkgStruct <> IN + val so = typespkg1.PkgStruct <> OUT + val e = typespkg1.PkgEnum <> VAR + val o = typespkg1.PkgOpaque <> VAR + val w = typespkg2.PkgWrap <> VAR + val u = UInt(8) <> VAR init typespkg2.PkgWide + so <> sp + val top = (new PkgTop).getCompiledCodeString + assertNoDiff( + top, + """|type GlbNsStruct is record + | g : std_logic_vector(1 downto 0); + |end record; + |constant GlbNsConst : unsigned(7 downto 0) := 8d"3"; + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + |use work.PkgTop_pkg.all; + | + |package typespkg1 is + |type PkgStruct is record + | a : std_logic_vector(7 downto 0); + | b : std_logic; + | g : GlbNsStruct; + |end record; + |function bitWidth(A: PkgStruct) return integer; + |function to_slv(A: PkgStruct) return std_logic_vector; + |function to_PkgStruct(A: std_logic_vector) return PkgStruct; + |function bool_sel(C : boolean; T : PkgStruct; F : PkgStruct) return PkgStruct; + |type PkgEnum is ( + | PkgEnum_P0, PkgEnum_P1, PkgEnum_P2 + |); + |function bitWidth(A: PkgEnum) return integer; + |function to_slv(A: PkgEnum) return std_logic_vector; + |function to_PkgEnum(A: std_logic_vector) return PkgEnum; + |function bool_sel(C : boolean; T : PkgEnum; F : PkgEnum) return PkgEnum; + |subtype PkgOpaque is std_logic_vector(3 downto 0); + |function to_PkgOpaque(A: std_logic_vector) return PkgOpaque; + |constant PkgConst : unsigned(7 downto 0) := GlbNsConst + 8d"39"; + |pure function pkgCalc(arg : unsigned(7 downto 0)) return unsigned; + |constant PkgDerived : unsigned(7 downto 0) := pkgCalc(PkgConst); + |end package typespkg1; + | + |package body typespkg1 is + |function bitWidth(A : PkgStruct) return integer is + | variable width : integer; + |begin + | width := 0; + | width := width + bitWidth(A.a); + | width := width + bitWidth(A.b); + | width := width + bitWidth(A.g); + | return width; + |end; + |function to_slv(A : PkgStruct) return std_logic_vector is + | variable hi : integer; + | variable lo : integer; + | variable ret : std_logic_vector(bitWidth(A) - 1 downto 0); + |begin + | lo := bitWidth(A); + | hi := lo - 1; lo := hi - bitWidth(A.a) + 1; ret(hi downto lo) := A.a; + | hi := lo - 1; lo := hi - bitWidth(A.b) + 1; ret(hi downto lo) := to_slv(A.b); + | hi := lo - 1; lo := hi - bitWidth(A.g) + 1; ret(hi downto lo) := to_slv(A.g); + | return ret; + |end; + |function to_PkgStruct(A : std_logic_vector) return PkgStruct is + | variable hi : integer; + | variable lo : integer; + | variable ret : PkgStruct; + |begin + | lo := A'length; + | hi := lo - 1; lo := hi - bitWidth(ret.a) + 1; ret.a := A(hi downto lo); + | hi := lo - 1; lo := hi - bitWidth(ret.b) + 1; ret.b := to_sl(A(hi downto lo)); + | hi := lo - 1; lo := hi - bitWidth(ret.g) + 1; ret.g := to_GlbNsStruct(A(hi downto lo)); + | return ret; + |end; + |function bool_sel(C : boolean; T : PkgStruct; F : PkgStruct) return PkgStruct is + |begin + | if C then + | return T; + | else + | return F; + | end if; + |end; + |function bitWidth(A : PkgEnum) return integer is + |begin + | return 2; + |end; + |function to_slv(A : PkgEnum) return std_logic_vector is + | variable int_val : integer; + |begin + | case A is + | when PkgEnum_P0 => int_val := 0; + | when PkgEnum_P1 => int_val := 1; + | when PkgEnum_P2 => int_val := 2; + | end case; + | return resize(to_slv(int_val), 2); + |end; + |function to_PkgEnum(A : std_logic_vector) return PkgEnum is + |begin + | case to_integer(unsigned(A)) is + | when 0 => return PkgEnum_P0; + | when 1 => return PkgEnum_P1; + | when 2 => return PkgEnum_P2; + | when others => + | assert false report "Unknown state detected!" severity error; + | return PkgEnum_P0; + | end case; + |end; + |function bool_sel(C : boolean; T : PkgEnum; F : PkgEnum) return PkgEnum is + |begin + | if C then + | return T; + | else + | return F; + | end if; + |end; + |function to_PkgOpaque(A : std_logic_vector) return PkgOpaque is + | variable A0 : std_logic_vector(A'length - 1 downto 0); + |begin + | A0 := A; + | return A0; + |end; + |pure function pkgCalc(arg : unsigned(7 downto 0)) return unsigned is + |begin + | return arg + 8d"1"; + |end function; + |end package body typespkg1; + | + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + |use work.PkgTop_pkg.all; + |use work.typespkg1.all; + | + |package typespkg2 is + |type PkgWrap is record + | s : PkgStruct; + | n : unsigned(7 downto 0); + |end record; + |function bitWidth(A: PkgWrap) return integer; + |function to_slv(A: PkgWrap) return std_logic_vector; + |function to_PkgWrap(A: std_logic_vector) return PkgWrap; + |function bool_sel(C : boolean; T : PkgWrap; F : PkgWrap) return PkgWrap; + |constant PkgWide : unsigned(7 downto 0) := pkgCalc(PkgDerived); + |end package typespkg2; + | + |package body typespkg2 is + |function bitWidth(A : PkgWrap) return integer is + | variable width : integer; + |begin + | width := 0; + | width := width + bitWidth(A.s); + | width := width + bitWidth(A.n); + | return width; + |end; + |function to_slv(A : PkgWrap) return std_logic_vector is + | variable hi : integer; + | variable lo : integer; + | variable ret : std_logic_vector(bitWidth(A) - 1 downto 0); + |begin + | lo := bitWidth(A); + | hi := lo - 1; lo := hi - bitWidth(A.s) + 1; ret(hi downto lo) := to_slv(A.s); + | hi := lo - 1; lo := hi - bitWidth(A.n) + 1; ret(hi downto lo) := to_slv(A.n); + | return ret; + |end; + |function to_PkgWrap(A : std_logic_vector) return PkgWrap is + | variable hi : integer; + | variable lo : integer; + | variable ret : PkgWrap; + |begin + | lo := A'length; + | hi := lo - 1; lo := hi - bitWidth(ret.s) + 1; ret.s := to_PkgStruct(A(hi downto lo)); + | hi := lo - 1; lo := hi - bitWidth(ret.n) + 1; ret.n := unsigned(A(hi downto lo)); + | return ret; + |end; + |function bool_sel(C : boolean; T : PkgWrap; F : PkgWrap) return PkgWrap is + |begin + | if C then + | return T; + | else + | return F; + | end if; + |end; + |end package body typespkg2; + | + | + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + |use work.PkgTop_pkg.all; + |use work.typespkg1.all; + |use work.typespkg2.all; + | + |entity PkgTop is + |port ( + | sp : in PkgStruct; + | so : out PkgStruct + |); + |end PkgTop; + | + |architecture PkgTop_arch of PkgTop is + | signal e : PkgEnum; + | signal o : PkgOpaque; + | signal w : PkgWrap; + | signal u : unsigned(7 downto 0) := PkgWide; + |begin + | so <= sp; + |end PkgTop_arch; + |""".stripMargin + ) end PrintVHDLCodeSpec From f67d54238f84747dfedea35fd472a8219d8cb738 Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 05:39:41 +0300 Subject: [PATCH 52/57] packages: DropPackages flattening + per-package name scoping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two backend-facing completions of the namespace-derived packages feature. `DropPackages` (new stage, last in BackendPrepStage) serves verilog.v95/v2001, which have no packages: it folds each packaged declaration's package name into its own name (`typespkg1_PkgEnum`, `typespkg1_pkgCalc`) and clears its namespace, so everything lands in the single global defs header under names that still say where they came from and cannot collide across packages. It covers exactly what the packaged emission places: named types, global constants, and the global HDL methods — whose placement analysis moves out of `Printer` into `analysis.HDLMethodAnalysis` so the stage and the printers cannot drift apart. `UniqueNames` now scopes the uniqueness of a global declaration by the package it is emitted into, rather than across all of them: every printer references a packaged declaration through its package, so two packages may hold the same simple name. The general defs group is uniquified first and reserved for every package (a package's content sits alongside the general globals), while two packages never see each other unqualified. It needs no backend flag — a backend without packages reaches this stage with the namespaces already flattened away by `DropPackages`, leaving the single general scope it wants. VHDL switches from `use work..all` to selected names (`work..`) for packaged types, enum literals, conversion functions, constants and method calls — the same collision-proofing SystemVerilog gets from `pkg::`, and what makes package-scoped uniqueness safe there. Verified to analyze under ghdl (--std=93 and --std=08) and nvc, case choices included. Also fixes `ComposedDFTypeReplacement` dropping a struct's non-matching fields when rewriting the matching ones. Known residual: a global HDL method's name is still globally unique, since same-named design blocks are enumerated by elaboration, which is not package-aware. --- .claude/commands/new-stage.md | 27 +++ .../compiler/analysis/DFValAnalysis.scala | 12 +- .../compiler/analysis/HDLMethodAnalysis.scala | 105 ++++++++++ .../main/scala/dfhdl/compiler/ir/DFType.scala | 20 +- .../dfhdl/compiler/printing/Namespacing.scala | 20 ++ .../dfhdl/compiler/printing/Printer.scala | 103 ++------- .../compiler/stages/BackendPrepStage.scala | 7 +- .../dfhdl/compiler/stages/DropPackages.scala | 195 ++++++++++++++++++ .../dfhdl/compiler/stages/UniqueNames.scala | 113 +++++++--- .../stages/vhdl/VHDLDataPrinter.scala | 4 +- .../stages/vhdl/VHDLOwnerPrinter.scala | 3 +- .../compiler/stages/vhdl/VHDLPrinter.scala | 14 +- .../stages/vhdl/VHDLTypePrinter.scala | 31 ++- .../compiler/stages/vhdl/VHDLValPrinter.scala | 12 +- .../scala/StagesSpec/DropPackagesSpec.scala | 74 +++++++ .../test/scala/StagesSpec/PkgFixtures.scala | 21 ++ .../StagesSpec/PrintCodeStringSpec.scala | 38 ++++ .../scala/StagesSpec/PrintVHDLCodeSpec.scala | 168 +++++++++++++-- .../StagesSpec/PrintVerilogCodeSpec.scala | 103 +++++++++ .../scala/StagesSpec/UniqueNamesSpec.scala | 35 ++++ 20 files changed, 946 insertions(+), 159 deletions(-) create mode 100644 compiler/ir/src/main/scala/dfhdl/compiler/analysis/HDLMethodAnalysis.scala create mode 100644 compiler/stages/src/main/scala/dfhdl/compiler/stages/DropPackages.scala create mode 100644 compiler/stages/src/test/scala/StagesSpec/DropPackagesSpec.scala diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index cf161634d..dff43d45a 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -1534,6 +1534,25 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) implicitly. Watch for token-free case classes holding a `Meta` (e.g. `DesignLoadKey`): their derived equality composes that loosened notion silently, unlike IR members, whose unique ref tokens keep distinct members unequal regardless. +41. **A design block cannot be renamed with a `Patch`** — it is its sub-DB's TOP, and its + `ownerRef` is the hierarchy key rather than a refTable entry, so a `Patch.Replace` keyed on it + never reaches the member list. Swap the block yourself in EVERY sub-DB: replace it in + `members` and in every `refTable` VALUE that resolves to it (a member's `ownerRef` resolves + through the refTable, so missing this leaves the member list and the owner lookup disagreeing). + `UniqueDesigns.canonicalReplace` and `DropPackages` both do exactly this. Build the replacement + ONCE and reuse the same instance everywhere. +42. **A `GlobalStage` runs on the hierarchical ROOT, whose `members` is empty** — so any analysis + written against a flat member list (everything in `analysis.HDLMethodAnalysis`, and most + printer-facing analyses) returns nothing there and fails SILENTLY, as "no results". Run it on + `designDB.newToOld`: the flat DB's design blocks are the SAME objects as the sub-DB tops, so + its answers map straight back onto the hierarchy. The same asymmetry bites in tests: + `StageSpec.assertCodeString` prints from the DB you hand it, so a root DB's printout omits + whatever the printer derives from flat members (global HDL method declarations, notably) — + pin those in a backend print spec and say so at the stage test. +43. **Anonymous members carry meta too** — a stage keyed on `meta` must decide what an ANONYMOUS + member does, not just filter it out. An anonymous global (an intermediate of a global + constant's expression) has no name to act on but still carries a `namespace`, and leaving it + behind kept `DropPackages` emitting an empty package for a package it had just flattened away. --- @@ -1731,6 +1750,14 @@ Mirror `plantClonedMembers`'s per-member mechanics when a custom per-ref remap i `dfc.mutableDB.newRefFor(cloned.ownerRef, dfc.owner.asIR)` → zip `m.getRefs` with `cloned.getRefs` and `newRefFor` each cloned ref to the (remapped) original target. +### `ComposedDFTypeReplacement` rewrites a composed type in place + +`preCheck` selects the types to rewrite and `updateFunc` produces the replacement; the extractor +recurses into struct fields, vector cell types and opaque actual types first, so a nested match +rewrites the enclosing type too. Non-matching parts are PRESERVED (a struct keeps the fields the +extractor does not apply to) — `UniqueNames` and `DropPackages` rename named types through it, and +`DropOpaques` erases opaques with it. + ### Materializing a type's width parameter as a standalone member To replace a member with the VALUE behind an `IntParamRef` (e.g. folding a `width`/`length` 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 34d2769ba..1efb2b4bf 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala @@ -686,10 +686,16 @@ class ComposedDFTypeReplacement[H]( def unapply(dfType: DFType): Option[DFType] = val composed = dfType match case dt: DFStruct => - val updatedMap = ListMap.from(dt.fieldMap.view.collect { case (name, Extractor(dfType)) => - (name, dfType) + // every field is kept — only the matching ones are replaced. Collecting just the + // matches here would DROP the fields the extractor does not apply to. + var anyUpdated = false + val updatedMap = ListMap.from(dt.fieldMap.view.map { + case (name, Extractor(dfType)) => + anyUpdated = true + (name, dfType) + case entry => entry }) - if (updatedMap.nonEmpty) Some(dt.copy(fieldMap = updatedMap)) + if (anyUpdated) Some(dt.copy(fieldMap = updatedMap)) else None case dt: DFOpaque => dt.actualType match diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/HDLMethodAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/HDLMethodAnalysis.scala new file mode 100644 index 000000000..6f15b4c15 --- /dev/null +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/HDLMethodAnalysis.scala @@ -0,0 +1,105 @@ +package dfhdl.compiler +package analysis +import ir.* +import scala.collection.mutable + +/** Which HDL-method blocks (ED methods / static functions) are emitted ONCE in the shared globals + * area (a VHDL package / a Verilog defs header) instead of inlined in each using design. + * + * This is a PLACEMENT decision computed purely from the IR, and both the printers (which emit the + * shared area) and `DropPackages` (which flattens the namespaces of everything placed there when + * the backend has no packages) must agree on it, so it lives here as one definition rather than in + * either of them. A backend may only WIDEN it (VHDL additionally globalizes a static function read + * by a port declaration, since the entity is elaborated before the architecture) by overriding + * `Printer.globalHDLMethods`. + * + * These read the design members directly, so they expect a FLAT DB (`newToOld`): the printers are + * fed one, and a stage running on the hierarchical root must flatten first. + */ +extension (designDB: DB) + /** The body members of an HDL-method block: the members it owns. */ + def methodBodyMembers(m: DFDesignBlock): List[DFMember] = + designDB.designMemberTable.getOrElse(m, Nil) + + /** HDL-method blocks mapped to the set of NON-method designs that use them. A method call is + * owned by the design (or method) whose body makes the call (`designBlockOwnershipMap`); a + * method-to-method call is resolved transitively, so the resulting users are always real + * designs. + */ + private def hdlMethodDesignUsers: Map[DFDesignBlock, Set[DFDesignBlock]] = + val ownership = designDB.designBlockOwnershipMap + def realUsersOf(block: DFDesignBlock, seen: Set[DFDesignBlock]): Set[DFDesignBlock] = + ownership.getOrElse(block, Set.empty).flatMap { owner => + if (!owner.isHDLMethod) Set(owner) + else if (seen(owner)) Set.empty[DFDesignBlock] + else realUsersOf(owner, seen + owner) + } + ownership.keysIterator.filter(_.isHDLMethod) + .map(m => m -> realUsersOf(m, Set(m))).toMap + + /** An HDL method is emittable in a shared package/header only if its body references no value + * captured from a single design. Captures materialize as PHANTOM input ports (globals are never + * captured — they are reachable everywhere and referenced directly), so a method with any + * phantom input is inherently design-local and stays inlined there. + */ + def methodIsGlobalEligible(m: DFDesignBlock)(using MemberGetSet): Boolean = + // every call of `m`, global-scope calls included (`members` covers the globals) + def callSitesOf(m: DFDesignBlock): List[DFVal.Func] = + designDB.members.collect { + case DFVal.Func.Call(call, key) if key.getDesignBlock == m => call + } + val formals = designDB.methodBodyMembers(m).collect { + case dcl: DFVal.Dcl if dcl.isPortIn => dcl + } + val phantomIdxs = formals.view.zipWithIndex.collect { case (f, i) if f.isPhantom => i }.toList + // A capture materializes as a PHANTOM input port, whose actual is bound POSITIONALLY at + // each call site. A GLOBAL actual is reachable from the shared package/header, so it keeps + // the method eligible; a design-local one pins the method to its design. An actual that + // cannot be lined up with the formals is treated as design-local (the conservative answer). + phantomIdxs.isEmpty || callSitesOf(m).forall { call => + val actuals = call.args.map(_.get) + actuals.length == formals.length && phantomIdxs.forall { i => + actuals(i) match + case dfVal: DFVal.CanBeGlobal => dfVal.isGlobal + case _ => false + } + } + end methodIsGlobalEligible + + /** Expand a set of HDL-method blocks to include everything they transitively call: an emitted + * method's body calls them, and a shared package/header function cannot call one that is + * declared inside a single design (or, for a method reached only from global scope, not declared + * at all). + */ + def methodCallClosure(seeds: Set[DFDesignBlock])(using MemberGetSet): Set[DFDesignBlock] = + val result = mutable.Set.empty[DFDesignBlock] + def visit(m: DFDesignBlock): Unit = + if (result.add(m)) + designDB.methodBodyMembers(m).foreach { + case DFVal.Func.Call(_, key) => + val callee = key.getDesignBlock + if (callee.isHDLMethod) visit(callee) + case _ => + } + seeds.foreach(visit) + result.toSet + + /** HDL-method blocks referenced by a GLOBAL `Func` call (a static function called at global + * scope, e.g. to compute a global constant). Such a method has no design user, but must still be + * emitted once in the shared globals area alongside the global value it computes. + */ + private def globalCallMethods(using MemberGetSet): Set[DFDesignBlock] = + designDB.membersGlobals.view.collect { + case DFVal.Func.Call(_, key) => key.getDesignBlock + }.filter(_.isHDLMethod).toSet + + /** HDL-method blocks emitted once in the shared globals area: used by more than one design, or + * called from global scope; and package-eligible. + */ + def globalHDLMethods(using MemberGetSet): Set[DFDesignBlock] = + val byUsage = designDB.hdlMethodDesignUsers.iterator.collect { + case (m, users) if users.sizeIs > 1 => m + } + designDB.methodCallClosure(byUsage.toSet ++ designDB.globalCallMethods) + .filter(designDB.methodIsGlobalEligible) +end extension diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala index 46ff62be3..714afe8ab 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala @@ -85,7 +85,9 @@ sealed trait NamedDFType extends DFType: // composes `Meta`'s equality (name + namespace + annotations), never position/doc val meta: Meta final def name: String = meta.name - def updateName(newName: String)(using MemberGetSet): this.type + def updateMeta(metaFunc: Meta => Meta)(using MemberGetSet): this.type + final def updateName(newName: String)(using MemberGetSet): this.type = + updateMeta(_.setName(newName)) object NamedDFTypes: def unapply(dfVal: DFVal)(using MemberGetSet): Option[ListSet[NamedDFType]] = Flatten.unapply(dfVal.dfType) @@ -341,8 +343,8 @@ final case class DFEnum( entries: ListMap[String, BigInt] ) extends NamedDFType derives ReadWriter: type Data = Option[BigInt] - def updateName(newName: String)(using MemberGetSet): this.type = - copy(meta = meta.setName(newName)).asInstanceOf[this.type] + def updateMeta(metaFunc: Meta => Meta)(using MemberGetSet): this.type = + copy(meta = metaFunc(meta)).asInstanceOf[this.type] def widthIntOpt(using MemberGetSet): Option[Int] = Some(widthParam) def createBubbleData(using MemberGetSet): Data = None def isDataBubble(data: Data): Boolean = data.isEmpty @@ -432,8 +434,8 @@ final case class DFOpaque( actualType: DFType ) extends NamedDFType, ComposedDFType derives ReadWriter: type Data = Any - def updateName(newName: String)(using MemberGetSet): this.type = - copy(meta = meta.setName(newName)).asInstanceOf[this.type] + def updateMeta(metaFunc: Meta => Meta)(using MemberGetSet): this.type = + copy(meta = metaFunc(meta)).asInstanceOf[this.type] def widthIntOpt(using MemberGetSet): Option[Int] = actualType.widthIntOpt def isMagnet: Boolean = kind match case _: DFOpaque.Kind.Magnet => true @@ -492,8 +494,8 @@ final case class DFStruct( fieldMap: ListMap[String, DFType] ) extends NamedDFType, ComposedDFType derives ReadWriter: type Data = List[Any] - def updateName(newName: String)(using MemberGetSet): this.type = - copy(meta = meta.setName(newName)).asInstanceOf[this.type] + def updateMeta(metaFunc: Meta => Meta)(using MemberGetSet): this.type = + copy(meta = metaFunc(meta)).asInstanceOf[this.type] def getNameForced: String = name def widthIntOpt(using MemberGetSet): Option[Int] = val fieldWidthsOpt = fieldMap.values.map(_.widthIntOpt) @@ -658,8 +660,8 @@ final case class DFView( def dataToBitsData(data: Data)(using MemberGetSet): (BitVector, BitVector) = noTypeErr def bitsDataToData(data: (BitVector, BitVector))(using MemberGetSet): Data = noTypeErr def defaultData(using MemberGetSet): Data = noTypeErr - def updateName(newName: String)(using MemberGetSet): this.type = - copy(meta = meta.setName(newName)).asInstanceOf[this.type] + def updateMeta(metaFunc: Meta => Meta)(using MemberGetSet): this.type = + copy(meta = metaFunc(meta)).asInstanceOf[this.type] // The full, directed field map of this view: `interfaceType`'s structure with the // resolved directions merged in (leaf dirs from `dirMap`; nested fields replaced by // their chosen sub-view). Derived on demand, so nothing is stored redundantly. diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Namespacing.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Namespacing.scala index a5af337c4..a553dcda9 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Namespacing.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Namespacing.scala @@ -34,4 +34,24 @@ object Namespacing: def placementOf(ns: String, topNs: String): Option[String] = if (isGlobalPlaced(ns, topNs)) None else Some(packageNameOf(ns, topNs)) + + /** Placement of a named type. Shared by the printers (`Printer.typePlacementOf`, which first + * checks that the backend has packages at all) and by the `DropPackages` stage, so the magnet + * exclusion below cannot drift between them. + */ + def typePlacementOf(dfType: NamedDFType, topNs: String): Option[String] = + dfType match + // Clk/Rst/Magnet opaques are language-level (the DFHDL printer shows them as builtins and + // the backends drop them), so they are never packaged even though their declaring namespace + // is a DFHDL-internal one + case t: DFOpaque if t.isMagnet => None + case _ => placementOf(dfType.meta.namespace, topNs) + + /** The name a packaged declaration takes when the backend has no packages and everything + * collapses into the general global defs file: its package name and its own name, joined with + * `_` (`typespkg1` + `PkgEnum` -> `typespkg1_PkgEnum`). This mirrors what a qualified reference + * shows in a package-bearing backend (`typespkg1::PkgEnum`), so the same declaration is + * recognizable across dialects. Applied by the `DropPackages` stage. + */ + def flattenedNameOf(pkgName: String, name: String): String = s"${pkgName}_$name" end Namespacing 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 a6c672abf..b6d7d35e3 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala @@ -237,92 +237,19 @@ trait Printer // ── HDL-method global-emission decision ───────────────────────────────────── // Which HDL-method blocks (ED methods / static functions) are emitted ONCE in the shared // globals area (a VHDL package / a Verilog defs header) instead of inlined in each using - // design. This is a PLACEMENT decision computed purely from the IR, and it is - // BACKEND-SPECIFIC (VHDL additionally globalizes a static function read by a port - // declaration, since the entity is elaborated before the architecture), so it lives in the - // printer as an overridable `def` rather than in the IR. The compiler pipeline feeds the - // printer a flat DB, so these read the design members directly (no sub-DB routing). - - // HDL-method blocks mapped to the set of NON-method designs that use them. A method call is - // owned by the design (or method) whose body makes the call (`designBlockOwnershipMap`); a - // method-to-method call is resolved transitively, so the resulting users are always real - // designs. - private def hdlMethodDesignUsers: Map[DFDesignBlock, Set[DFDesignBlock]] = - val ownership = getSet.designDB.designBlockOwnershipMap - def realUsersOf(block: DFDesignBlock, seen: Set[DFDesignBlock]): Set[DFDesignBlock] = - ownership.getOrElse(block, Set.empty).flatMap { owner => - if (!owner.isHDLMethod) Set(owner) - else if (seen(owner)) Set.empty[DFDesignBlock] - else realUsersOf(owner, seen + owner) - } - ownership.keysIterator.filter(_.isHDLMethod) - .map(m => m -> realUsersOf(m, Set(m))).toMap - - // The body members of an HDL-method block: the members it owns. + // design. The decision itself is a pure IR analysis, shared with the `DropPackages` stage + // (see `analysis.HDLMethodAnalysis`); a backend may only WIDEN it by overriding + // `globalHDLMethods` (VHDL additionally globalizes a static function read by a port + // declaration, since the entity is elaborated before the architecture). The compiler + // pipeline feeds the printer a flat DB, so these read the design members directly (no + // sub-DB routing). protected final def methodBodyMembers(m: DFDesignBlock): List[DFMember] = - getSet.designDB.designMemberTable.getOrElse(m, Nil) - - // An HDL method is emittable in a shared package/header only if its body references no value - // captured from a single design. Captures materialize as PHANTOM input ports (globals are - // never captured — they are reachable everywhere and referenced directly), so a method with - // any phantom input is inherently design-local and stays inlined there. - // every call of `m`, global-scope calls included (`members` covers the globals) - private def callSitesOf(m: DFDesignBlock): List[DFVal.Func] = - getSet.designDB.members.collect { - case DFVal.Func.Call(call, key) if key.getDesignBlock == m => call - } + getSet.designDB.methodBodyMembers(m) protected final def methodIsGlobalEligible(m: DFDesignBlock): Boolean = - val formals = methodBodyMembers(m).collect { - case dcl: DFVal.Dcl if dcl.isPortIn => dcl - } - val phantomIdxs = formals.view.zipWithIndex.collect { case (f, i) if f.isPhantom => i }.toList - // A capture materializes as a PHANTOM input port, whose actual is bound POSITIONALLY at - // each call site. A GLOBAL actual is reachable from the shared package/header, so it keeps - // the method eligible; a design-local one pins the method to its design. An actual that - // cannot be lined up with the formals is treated as design-local (the conservative answer). - phantomIdxs.isEmpty || callSitesOf(m).forall { call => - val actuals = call.args.map(_.get) - actuals.length == formals.length && phantomIdxs.forall { i => - actuals(i) match - case dfVal: DFVal.CanBeGlobal => dfVal.isGlobal - case _ => false - } - } - end methodIsGlobalEligible - - // HDL-method blocks referenced by a GLOBAL `Func` call (a static function called at global - // scope, e.g. to compute a global constant). Such a method has no design user, but must still - // be emitted once in the shared globals area alongside the global value it computes. - private def globalCallMethods: Set[DFDesignBlock] = - getSet.designDB.membersGlobals.view.collect { - case DFVal.Func.Call(_, key) => key.getDesignBlock - }.filter(_.isHDLMethod).toSet - - // HDL-method blocks emitted once in the shared globals area: used by more than one design, or - // called from global scope; and package-eligible. Overridable per backend (VHDL adds static - // functions read by a port declaration). - def globalHDLMethods: Set[DFDesignBlock] = - val byUsage = hdlMethodDesignUsers.iterator.collect { - case (m, users) if users.sizeIs > 1 => m - } - methodCallClosure(byUsage.toSet ++ globalCallMethods).filter(methodIsGlobalEligible) - - // Expand a set of HDL-method blocks to include everything they transitively call: an emitted - // method's body calls them, and a shared package/header function cannot call one that is - // declared inside a single design (or, for a method reached only from global scope, not - // declared at all). + getSet.designDB.methodIsGlobalEligible(m) protected final def methodCallClosure(seeds: Set[DFDesignBlock]): Set[DFDesignBlock] = - val result = mutable.Set.empty[DFDesignBlock] - def visit(m: DFDesignBlock): Unit = - if (result.add(m)) - methodBodyMembers(m).foreach { - case DFVal.Func.Call(_, key) => - val callee = key.getDesignBlock - if (callee.isHDLMethod) visit(callee) - case _ => - } - seeds.foreach(visit) - result.toSet + getSet.designDB.methodCallClosure(seeds) + def globalHDLMethods: Set[DFDesignBlock] = getSet.designDB.globalHDLMethods // ---- namespace-based type packaging ---- // Whether this backend emits namespace-derived package files. Backends without @@ -333,14 +260,8 @@ trait Printer final def topNamespace: String = getSet.designDB.rootDB.top.dclMeta.namespace // Some(packageName) when `dfType` is emitted into a dedicated package file final def typePlacementOf(dfType: NamedDFType): Option[String] = - dfType match - // Clk/Rst/Magnet opaques are language-level (the DFHDL printer shows them as - // builtins and the backends drop them), so they are never packaged even though - // their declaring namespace is a DFHDL-internal one - case t: DFOpaque if t.isMagnet => None - case _ => - if (printer.supportPackages) Namespacing.placementOf(dfType.meta.namespace, topNamespace) - else None + if (printer.supportPackages) Namespacing.typePlacementOf(dfType, topNamespace) + else None // the package whose file is currently being rendered: its own declarations (and // same-package references) print unqualified var currentPackage: Option[String] = None diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala index 47066b549..30b64985f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala @@ -33,5 +33,10 @@ case object BackendPrepStage SimpleOrderMembers, LocalToDesignParams, DropDesignParamDeps, - ViaConnection + ViaConnection, + // LAST: it flattens the namespace-derived packages into names for a backend that has no + // packages, so it should see only what actually survives to the emission (v95/v2001 drop + // structs and opaques on the way here), and the names it produces must reach + // `UniqueNames`, which runs after this bundle + DropPackages ) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropPackages.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropPackages.scala new file mode 100644 index 000000000..1523d6b64 --- /dev/null +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropPackages.scala @@ -0,0 +1,195 @@ +package dfhdl.compiler.stages + +import dfhdl.compiler.analysis.* +import dfhdl.compiler.ir.* +import dfhdl.compiler.patching.* +import dfhdl.compiler.printing.Namespacing +import dfhdl.compiler.stages.verilog.VerilogDialect +import dfhdl.options.CompilerOptions +import scala.collection.mutable +import scala.collection.immutable.ListMap + +//format: off +/** Flattens the namespace-derived packages away, for a backend that has none (verilog.v95 and + * verilog.v2001), by folding each packaged declaration's package name into its own name and + * dropping its namespace. Everything then lands in the single global defs file, under names that + * still say where the declaration came from and that cannot collide across packages. + * + * The affected declarations are exactly the ones a package-bearing backend emits into a package + * (see `printing.Namespacing`): named types, global constants, and the global static functions / + * ED methods emitted in the shared globals area (`analysis.HDLMethodAnalysis.globalHDLMethods`). + * A design-local method keeps its name: it is printed inside its own design and its namespace is + * incidental. + * + * ==Rule 1: a packaged named type takes its package name== + * {{{ + * // Before (`PkgEnum` declared in the Scala package `StagesSpec.typespkg1`, top under + * // `StagesSpec`, so the packaged emission would name it `typespkg1::PkgEnum`) + * val e = typespkg1.PkgEnum <> VAR + * + * // After + * val e = typespkg1_PkgEnum <> VAR + * }}} + * + * ==Rule 2: a packaged global constant / static function takes its package name== + * {{{ + * // Before + * val PkgConst: UInt[8] <> CONST = d"8'42" + * def pkgCalc(arg: UInt[8] <> CONST): UInt[8] <> CONSTRET = arg + d"8'1" + * + * // After + * val typespkg1_PkgConst: UInt[8] <> CONST = d"8'42" + * def typespkg1_pkgCalc(arg: UInt[8] <> CONST): UInt[8] <> CONSTRET = arg + d"8'1" + * }}} + * + * The namespace of a flattened declaration is cleared along with the rename, so its placement + * becomes the general global defs file and nothing is flattened twice. + */ +//format: on +case object DropPackages extends GlobalStage: + override def runCondition(using co: CompilerOptions): Boolean = + co.backend match + case be: dfhdl.backends.verilog => + be.dialect match + case VerilogDialect.v95 | VerilogDialect.v2001 => true + case _ => false + case _ => false + def dependencies: List[Stage] = List() + def nullifies: Set[Stage] = Set() + + def transformGlobal(designDB: DB)(using co: CompilerOptions, refGen: RefGen): DB = + val topNamespace = designDB.top.dclMeta.namespace + // the flattened name of a declaration placed in a package, `None` when it stays in the + // general global defs file (its namespace equals or is an ancestor of the top's) + def flattenedNameOf(ns: String, name: String): Option[String] = + Namespacing.placementOf(ns, topNamespace).map(Namespacing.flattenedNameOf(_, name)) + def flattenMeta(meta: Meta, newName: String): Meta = + meta.setName(newName).copy(namespace = "") + + // ---- collect the renames (deterministically, over the ordered member lists) ---- + // named types, wherever they are used (a type declared in a package is emitted into that + // package even when a single design uses it, so design-local ones count too) + val typeRenames = mutable.LinkedHashMap.empty[NamedDFType, String] + // global constants + val memberRenames = mutable.LinkedHashMap.empty[DFMember, DFMember] + designDB.subDBs.values.foreach { sub => + sub.atGetSet { + sub.members.foreach { + case dfVal: DFVal => + dfVal.dfType.decompose { case dt: NamedDFType => dt }.foreach { dt => + Namespacing.typePlacementOf(dt, topNamespace).foreach { pkg => + typeRenames.getOrElseUpdate(dt, Namespacing.flattenedNameOf(pkg, dt.name)) + } + } + case _ => + } + sub.membersGlobals.foreach { global => + Namespacing.placementOf(global.meta.namespace, topNamespace).foreach { pkg => + memberRenames.getOrElseUpdate( + global, + // an ANONYMOUS global (an intermediate of a global constant's expression) has no + // name to flatten, but its namespace still has to go: it would otherwise keep + // declaring a package of its own + if (global.isAnonymous) global.setMeta(_.copy(namespace = "")) + else global.setMeta(flattenMeta(_, Namespacing.flattenedNameOf(pkg, global.getName))) + ) + } + } + } + } + // the HDL methods emitted in the shared globals area. The placement analysis reads design + // members directly, so it runs on the FLAT view — whose design blocks are the same objects + // as the sub-DB tops, and therefore map straight back onto the hierarchy. + val flatDB = designDB.newToOld + val globalMethods = flatDB.atGetSet(flatDB.globalHDLMethods) + val designRenames = mutable.LinkedHashMap.empty[DFDesignBlock, DFDesignBlock] + designDB.subDBs.values.foreach { sub => + val method = sub.top + if (globalMethods.contains(method)) + flattenedNameOf(method.dclMeta.namespace, method.dclName).foreach { newName => + designRenames(method) = method.copy(meta = flattenMeta(method.meta, newName)) + } + } + + if (typeRenames.isEmpty && memberRenames.isEmpty && designRenames.isEmpty) designDB + else + // ---- phase 1: the member and design-block renames, per sub-DB ---- + // A global member lives (by identity) in several sub-DB closures, so its replacement is + // built ONCE above and reused in each of them — `newToOld` then dedups it to one member. + // A design block is not patchable (it is its sub-DB's top and its `ownerRef` is the + // hierarchy key, which the rename does not touch), so it is swapped in the member list + // and in every refTable entry that resolves to it. + val firstStepSubs: ListMap[StaticRef, DB] = ListMap.from( + designDB.subDBs.iterator.map { (key, sub) => + val patched = sub.patch(sub.members.collect { + case m if memberRenames.contains(m) => + m -> Patch.Replace(memberRenames(m), Patch.Replace.Config.FullReplacement) + }) + if (designRenames.isEmpty) key -> patched + else + val newMembers = patched.members.map { + case d: DFDesignBlock if designRenames.contains(d) => designRenames(d) + case m => m + } + val newRefTable = patched.refTable.view.mapValues { + case d: DFDesignBlock if designRenames.contains(d) => designRenames(d) + case t => t + }.toMap + key -> patched.update(members = newMembers, refTable = newRefTable) + } + ) + val firstStep = designDB.update(subDBs = firstStepSubs) + + // ---- phase 2: the named-type renames, per sub-DB ---- + if (typeRenames.isEmpty) firstStep + else + val typeUpdates = typeRenames.toMap + // built once (type rewriting reads only type structure + renames by type), keyed by + // member so a shared global member's update is reused across every sub-DB holding it + val typeUpdatePatches: mutable.LinkedHashMap[DFMember, (DFMember, Patch)] = + firstStep.topDB.atGetSet { + object ComposedNamedDFTypeReplacement + extends ComposedDFTypeReplacement( + preCheck = { + case dt: NamedDFType => typeUpdates.get(dt) + case _ => None + }, + updateFunc = { case (dt: NamedDFType, name) => + dt.updateMeta(flattenMeta(_, name)) + } + ) + val patches = mutable.LinkedHashMap.empty[DFMember, (DFMember, Patch)] + firstStep.subDBs.values.foreach { sub => + sub.members.foreach { + case dfVal: DFVal => + dfVal.dfType match + case ComposedNamedDFTypeReplacement(updatedDFType) => + patches.getOrElseUpdate( + dfVal, + dfVal -> Patch.Replace( + dfVal.updateDFType(updatedDFType), + Patch.Replace.Config.FullReplacement + ) + ) + case _ => + case _ => + } + } + patches + } + val secondStepSubs: ListMap[StaticRef, DB] = ListMap.from( + firstStep.subDBs.iterator.map { (key, sub) => + key -> sub.patch(sub.members.collect { + case m if typeUpdatePatches.contains(m) => typeUpdatePatches(m) + }) + } + ) + firstStep.update(subDBs = secondStepSubs) + end if + end if + end transformGlobal +end DropPackages + +extension [T: HasDB](t: T) + def dropPackages(using CompilerOptions): DB = + StageRunner.run(DropPackages)(t.db) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueNames.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueNames.scala index e4211c12c..cb7df7915 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueNames.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueNames.scala @@ -3,6 +3,7 @@ package dfhdl.compiler.stages import dfhdl.compiler.analysis.* import dfhdl.compiler.ir.* import dfhdl.compiler.patching.* +import dfhdl.compiler.printing.Namespacing import dfhdl.options.CompilerOptions import dfhdl.internals.* import scala.collection.mutable @@ -51,38 +52,92 @@ private abstract class UniqueNames(reservedNames: Set[String], caseSensitive: Bo // prefixes, type and value identifiers share one HDL namespace, so every value // renamer must reserve them var globalTypeNamesFinalLC: Set[String] = Set.empty + // the names a PACKAGED declaration must avoid: the general defs group's names (which sit + // alongside every package) plus the design and given reserved names — but NOT the names of + // the other packages, which it can never be confused with + var generalReservedNamesLC: Set[String] = Set.empty + // the FINAL global type names per scope: a design-local packaged type must not collide with + // the global types of its OWN package + val scopeGlobalTypeNamesLC = mutable.LinkedHashMap.empty[Option[String], Set[String]] + // The package a global declaration is emitted into (`None` = the general global defs file), + // which is also its uniqueness SCOPE: every printer references a packaged declaration + // through its package (`pkg::name` in SystemVerilog, `work.pkg.name` in VHDL, + // `.name` in DFHDL code), so two packages holding the same simple name can never + // be confused at a use site. A backend WITHOUT packages has no packaged declarations to + // scope by the time this runs: `DropPackages` folds their package names into their own and + // clears their namespaces, leaving everything in the single `None` scope, which is the + // across-the-board uniqueness such a backend needs. + val topNamespace = designDB.top.dclMeta.namespace + def typeScopeOf(dfType: NamedDFType): Option[String] = + Namespacing.typePlacementOf(dfType, topNamespace) + def memberScopeOf(m: DFMember): Option[String] = + Namespacing.placementOf(m.meta.namespace, topNamespace) // ---- global named types + members (cross-design, computed once) ---- // names resolve from member meta only, so any sub-DB getSet works; use the top's. val globalReservedTypeNamesLC: Set[String] = designDB.topDB.atGetSet { // the existing design (class) names — one per sub-DB - val designNames = designDB.subDBs.values.map(_.top.dclName) + val designNames = designDB.subDBs.values.map(_.top.dclName).toList // the global named types across the whole hierarchy - val globalNamedTypes = designDB.hierGlobalNamedDFTypes + val globalNamedTypes = designDB.hierGlobalNamedDFTypes.toList // the global named members, de-duplicated across the sub-DB closures that // share them by identity (member equality is effectively identity — every // distinct member carries unique refs) val globalNamedMembers = designDB.subDBs.values.iterator .flatMap(_.membersGlobals).filterNot(_.isAnonymous).toList.distinct - // global type map for unique renamed names - val globalTypeUpdateMap = - renamer(globalNamedTypes, reservedNamesLC)(_.name, (e, n) => e -> n).toMap - typeUpdateMap ++= globalTypeUpdateMap - globalTypeNamesFinalLC = lowerCases( - globalNamedTypes.map(t => globalTypeUpdateMap.getOrElse(t, t.name)).toSet - ) - // the global reserved type names, after unique global type renaming - val globalReservedTypeNames: Set[String] = - (globalNamedTypes.map(e => e.name) ++ globalTypeUpdateMap.values ++ designNames ++ + // The uniqueness scopes, general defs group (`None`) first and the packages after it in + // first-appearance order. The general group is uniquified first and then reserved for + // every package group, because a package's content sits ALONGSIDE the general globals + // rather than apart from them (a SystemVerilog package includes the global defs header, + // a VHDL package uses the general package). Two DIFFERENT packages, on the other hand, + // never see each other unqualified, so each starts from the same clean slate. + val typeGroups = globalNamedTypes.groupByOrdered(typeScopeOf) + val memberGroups = globalNamedMembers.groupByOrdered(memberScopeOf) + val typesOfScope = typeGroups.toMap + val membersOfScope = memberGroups.toMap + val scopes = (None :: typeGroups.map(_._1) ::: memberGroups.map(_._1)).distinct + val globalTypeFinalNames = mutable.ListBuffer.empty[String] + // the general group's final names, reserved by every package group (empty while the + // general group itself is being processed, since `scopes` leads with it) + var generalNamesLC: Set[String] = Set.empty + scopes.foreach { scope => + val scopeTypes = typesOfScope.getOrElse(scope, Nil) + val scopeTypeUpdateMap = + renamer(scopeTypes, reservedNamesLC ++ generalNamesLC)(_.name, (e, n) => e -> n).toMap + typeUpdateMap ++= scopeTypeUpdateMap + val scopeTypeFinalNames = scopeTypes.map(t => scopeTypeUpdateMap.getOrElse(t, t.name)) + globalTypeFinalNames ++= scopeTypeFinalNames + scopeGlobalTypeNamesLC(scope) = lowerCases(scopeTypeFinalNames.toSet) + // the names reserved for this scope's global members: its own type names (before and + // after renaming), the design names, and the general group's names + val memberReservedLC = lowerCases( + (scopeTypes.map(_.name) ++ scopeTypeFinalNames ++ designNames ++ reservedNames).toSet + ) ++ generalNamesLC + val scopeMembers = membersOfScope.getOrElse(scope, Nil) + val scopeMemberRenames = + renamer(scopeMembers, memberReservedLC)(_.getName, (m, n) => m -> n).toMap + // global named member patching + scopeMembers.foreach { m => + scopeMemberRenames.get(m).foreach { n => + localReservedNamesLCMutable += lowerCase(n) + memberRenamePatches(m) = + m -> Patch.Replace(m.setName(n), Patch.Replace.Config.FullReplacement) + } + } + if (scope.isEmpty) + generalNamesLC = lowerCases( + (scopeTypeFinalNames ++ + scopeMembers.map(m => scopeMemberRenames.getOrElse(m, m.getName))) + .toSet + ) + } + globalTypeNamesFinalLC = lowerCases(globalTypeFinalNames.toSet) + generalReservedNamesLC = generalNamesLC ++ lowerCases(designNames.toSet ++ reservedNames) + // the global reserved type names, after unique global type renaming — every package's + // types included, which is what a design-local type in the GENERAL scope must avoid + lowerCases( + (globalNamedTypes.map(_.name) ++ globalTypeFinalNames ++ designNames ++ reservedNames).toSet - val resultLC = lowerCases(globalReservedTypeNames) - // global named member patching - renamer(globalNamedMembers, resultLC)( - _.getName, - (m, n) => - localReservedNamesLCMutable += lowerCase(n) - m -> Patch.Replace(m.setName(n), Patch.Replace.Config.FullReplacement) - ).foreach(entry => memberRenamePatches(entry._1) = entry) - resultLC + ) } // the reserved names for local (design) values: the given reservedNames, the // renamed global member names, and the (post-rename) global TYPE names (types and @@ -103,12 +158,24 @@ private abstract class UniqueNames(reservedNames: Set[String], caseSensitive: Bo // a single sub-DB may otherwise mis-classify a cross-design type as local val localTypes = sub.getLocalNamedDFTypes(design) .filterNot(designDB.hierGlobalNamedDFTypes.contains) - renamer(localTypes, globalReservedTypeNamesLC)(_.name, (e, n) => e -> n) - .foreach(entry => typeUpdateMap(entry._1) = entry._2) + // A design-local type declared in a package is still EMITTED into that package + // (and referenced qualified), so it is uniquified in that package's scope: only + // the general group's names and its own package's global type names are in its + // way. A type in the general scope avoids every global type name, packaged ones + // included, since nothing qualifies IT. + localTypes.toList.groupByOrdered(typeScopeOf).foreach { (scope, scopeLocalTypes) => + val reservedLC = scope match + case None => globalReservedTypeNamesLC + case Some(_) => + generalReservedNamesLC ++ scopeGlobalTypeNamesLC.getOrElse(scope, Set.empty) + renamer(scopeLocalTypes, reservedLC)(_.name, (e, n) => e -> n) + .foreach(entry => typeUpdateMap(entry._1) = entry._2) + } designLocalTypeNamesLC = lowerCases( localTypes.map(t => typeUpdateMap.getOrElse(t, t.name)).toSet ) case _ => + end match renamer( members.view.flatMap { // ignore iterator declarations that can repeat the same name wihtout collision diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLDataPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLDataPrinter.scala index f6846f1fd..f24c48203 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLDataPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLDataPrinter.scala @@ -60,7 +60,9 @@ protected trait VHDLDataPrinter extends AbstractDataPrinter: data match case Some(value) => val entryName = dfType.entries.find(_._2 == value).get._1 - s"${dfType.name}_${entryName}" + // the entry is an enumeration literal DECLARED IN the type's package, so it takes the + // same selected-name qualifier as the type itself + s"${printer.pkgQualifier(dfType)}${dfType.name}_${entryName}" case None => "?" def csDFVectorElemCS(elemCS: List[String]): String = elemCS.view.zipWithIndex.map((x, i) => 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 a32152be4..dc4f782f7 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 @@ -25,8 +25,7 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: |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 ""} - |${printer.packagedContents.map((p, _, _) => s"use work.$p.all;").mkString("\n")}""" + |${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 75a78bd4d..1f987a054 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 @@ -169,13 +169,16 @@ class VHDLPrinter(val dialect: VHDLDialect)(using // def csTimer(timer: Timer): String = unsupported override def supportPackages: Boolean = true override def packageFileName(pkgName: String): String = s"$pkgName.vhd" + // a packaged global constant / method call is referenced by SELECTED NAME, like the packaged + // types (see `VHDLTypePrinter.pkgQualifier`), rather than through a `use work..all` + override def csGlobalMemberQualifier(ns: String, pkgName: String): String = s"work.$pkgName." // A namespace-derived package: spec (type dcls + conv-func protos, then constants and - // method protos in dependency order) and body (conv-func and method bodies). VHDL has - // no reference qualification here: visibility comes from `use` clauses, and a package - // uses the general package plus every package PRECEDING it in the cross-package - // topological order (its dependencies are guaranteed to precede it). + // method protos in dependency order) and body (conv-func and method bodies). Only the + // general package is `use`d: everything a package takes from a SIBLING package it names + // by selected name, so no cross-package use clause (and no ordering-sensitive visibility) + // is needed. Analysis order still follows the cross-package topological order of + // `packagedContents`, which `printedDB` preserves. override def csPackageFileContent(pkgName: String, namespace: String, typeDcls: String): String = - val precedingPkgs = packagedContents.map(_._1).takeWhile(_ != pkgName) val typeEntries = packagedTypeEntries.collectFirst { case (`pkgName`, _, entries) => entries }.getOrElse(Nil) @@ -209,7 +212,6 @@ class VHDLPrinter(val dialect: VHDLDialect)(using |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; |${if (hasGlobalContent) s"use work.${printer.packageName}.all;" else ""} - |${precedingPkgs.map(p => s"use work.$p.all;").mkString("\n")} | |package $pkgName is |$typeSpecDcls diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala index 768f359d8..8c542b8a0 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLTypePrinter.scala @@ -39,6 +39,22 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: end csDFDecimal def csDFString(dfType: DFString, typeCS: Boolean): String = "string" + // The selected-name qualifier of a packaged type's reference (`work..`), dropped inside + // its own package (where its declarations are directly visible). VHDL references packaged + // declarations by SELECTED NAME rather than through a blanket `use work..all`, exactly as + // SystemVerilog qualifies with `::`, so the same simple name may live in two packages + // without becoming an ambiguous homograph at the use site. + // NOTE: it prefixes the type's REFERENCE forms only. Anything that builds an IDENTIFIER out of + // a type name (an array type name, a conversion function name) must use the simple `dfType.name` + // and place this qualifier at the front of the identifier it forms. + def pkgQualifier(dfType: NamedDFType): String = + printer.typePlacementOf(dfType) match + case Some(pkg) if !printer.currentPackage.contains(pkg) => s"work.$pkg." + case _ => "" + // a conversion function of a packaged type is declared IN that package, so its call site takes + // the package qualifier ahead of the function name (`work.pkg.to_Foo(...)`) + def csConvFuncName(dfType: NamedDFType, funcName: String): String = + s"${pkgQualifier(dfType)}$funcName" def csNamedDFTypeConvFuncsDcl(dfType: NamedDFType): String = val typeName = dfType match case dt: DFEnum => csDFEnumTypeName(dt) @@ -69,7 +85,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: case dt: DFEnum => csDFEnumConvFuncsBody(dt) case dt: DFStruct => csDFStructConvFuncsBody(dt) case dt: DFOpaque => csDFOpaqueConvFuncsBody(dt) - def csDFEnumTypeName(dfType: DFEnum): String = dfType.name + def csDFEnumTypeName(dfType: DFEnum): String = s"${pkgQualifier(dfType)}${dfType.name}" def csDFEnumDcl(dfType: DFEnum, global: Boolean): String = val enumName = dfType.name val entries = @@ -194,7 +210,10 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: ): (String, (DFVector, Int)) = dfType.cellType match case dfType: DFVector => getVecDepthAndCellTypeName(dfType, depth + 1) - case cellType => (csDFType(cellType, true), (dfType, depth)) + // the cell type name becomes part of the array type IDENTIFIER, so a packaged named + // cell type contributes its simple name (never its `work..` qualified reference) + case cellType: NamedDFType => (cellType.name, (dfType, depth)) + case cellType => (csDFType(cellType, true), (dfType, depth)) def getVecDepthAndCellTypeName(dfType: DFVector): (String, (DFVector, Int)) = if (supportUnconstrainedArrays) getVecDepthAndCellTypeName(dfType, 1) else (csDFVectorDclName(dfType), (dfType, 1)) @@ -208,9 +227,9 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: s"slv${csIntParamRef(dt.widthParamRef)}$lowSuffix" case DFUInt(widthParamRef) => s"unsigned${csIntParamRef(widthParamRef)}" case DFSInt(widthParamRef) => s"signed${csIntParamRef(widthParamRef)}" - case dt: DFOpaque => csDFOpaqueTypeName(dt) + case dt: DFOpaque => dt.name case dt: DFVector => csDFVectorDclName(dt) - case dt: DFStruct => csDFStructTypeName(dt) + case dt: DFStruct => dt.name case _ => printer.unsupported // Wrapper used to uniquely track parameter values and index them @@ -398,7 +417,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: inVector = false desc end csDFVector - def csDFOpaqueTypeName(dfType: DFOpaque): String = dfType.name + def csDFOpaqueTypeName(dfType: DFOpaque): String = s"${pkgQualifier(dfType)}${dfType.name}" def csDFOpaqueDcl(dfType: DFOpaque): String = s"subtype ${csDFOpaqueTypeName(dfType)} is ${csDFType(dfType.actualType)};" def csDFOpaque(dfType: DFOpaque, typeCS: Boolean): String = csDFOpaqueTypeName(dfType) @@ -410,7 +429,7 @@ protected trait VHDLTypePrinter extends AbstractTypePrinter: | A0 := A; | return ${printer.csBitsToType(dfType.actualType, "A0")}; |end;""".stripMargin - def csDFStructTypeName(dfType: DFStruct): String = dfType.name + def csDFStructTypeName(dfType: DFStruct): String = s"${pkgQualifier(dfType)}${dfType.name}" def csDFStructDcl(dfType: DFStruct): String = val fields = dfType.fieldMap.view .map((n, t) => s"${n} : ${csDFType(t)};") diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala index e3dd2abaf..a8ca248de 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLValPrinter.scala @@ -10,10 +10,11 @@ protected trait VHDLValPrinter extends AbstractValPrinter: def csMethodCall(call: Func, designKey: StaticRef): String = val design = designKey.getDesignBlock val args = csMethodCallArgs(call, design).mkString(", ") + // a packaged global method is called by selected name (`work..`), like every + // other packaged reference in VHDL + val name = s"${printer.globalMethodQualifier(design)}${design.dclName}" // parameterless VHDL method calls have no parentheses - val callCS = - if (args.isEmpty) design.dclName - else s"${design.dclName}($args)" + val callCS = if (args.isEmpty) name else s"$name($args)" // a procedural (Unit-return) call is a procedure call statement if (call.dfType == DFUnit) s"$callCS;" else callCS end csMethodCall @@ -225,7 +226,8 @@ protected trait VHDLValPrinter extends AbstractValPrinter: desc = desc + finale inVector = false s"$desc)" - case dfType: DFStruct => s"to_${printer.csDFStructTypeName(dfType)}($csArg)" + case dfType: DFStruct => + s"${printer.csConvFuncName(dfType, s"to_${dfType.name}")}($csArg)" case dfType: DFOpaque => csBitsToType(dfType.actualType, csArg) case _ => printer.unsupported @@ -276,7 +278,7 @@ protected trait VHDLValPrinter extends AbstractValPrinter: case (DFBool, DFBit | DFEnum(widthParam = 1)) => s"to_bool($relValStr)" case (toType @ DFEnum(widthParam = 1), DFBit | DFBool) => - s"to_${printer.csDFEnumTypeName(toType)}($relValStr)" + s"${printer.csConvFuncName(toType, s"to_${toType.name}")}($relValStr)" case (DFUInt(tWidthRef), DFInt32) => s"to_unsigned($relValStr, ${tWidthRef.refCodeString})" case (DFSInt(tWidthRef), DFInt32) => diff --git a/compiler/stages/src/test/scala/StagesSpec/DropPackagesSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropPackagesSpec.scala new file mode 100644 index 000000000..20955b6d0 --- /dev/null +++ b/compiler/stages/src/test/scala/StagesSpec/DropPackagesSpec.scala @@ -0,0 +1,74 @@ +package StagesSpec + +import dfhdl.* +import dfhdl.compiler.stages.{dropPackages, getCodeString} +// scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} + +class DropPackagesSpec extends StageSpec: + // the stage only runs for a backend that has no packages + given options.CompilerOptions.Backend = _.verilog.v95 + + // `GlbNsStruct`/`GlbNsConst` share the top design's namespace, so they are already + // general-placed and keep their names; everything declared under `typespkg1`/`typespkg2` + // takes its package name. + // NOTE: the declaration of the flattened static function is not in this printout — the DFHDL + // printer renders global HDL methods from the flat DB only — but its call sites carry the + // flattened name. The declaration is pinned end-to-end in `PrintVerilogCodeSpec`'s + // "Namespace-derived declarations flattened under verilog.v95". + val flattenedCS = + """|val GlbNsConst: UInt[8] <> CONST = d"8'3" + |val typespkg1_PkgConst: UInt[8] <> CONST = GlbNsConst + d"8'39" + |val typespkg1_PkgDerived: UInt[8] <> CONST = typespkg1_pkgCalc(typespkg1_PkgConst) + |val typespkg2_PkgWide: UInt[8] <> CONST = typespkg1_pkgCalc(typespkg1_PkgDerived) + | + |class PkgTop extends DFDesign: + | final case class GlbNsStruct( + | g: Bits[2] <> VAL + | ) extends Struct + | final case class typespkg1_PkgStruct( + | a: Bits[8] <> VAL + | b: Bit <> VAL + | g: GlbNsStruct <> VAL + | ) extends Struct + | enum typespkg1_PkgEnum(val value: UInt[2] <> CONST) extends Encoded.Manual(2): + | case P0 extends typespkg1_PkgEnum(d"2'0") + | case P1 extends typespkg1_PkgEnum(d"2'1") + | case P2 extends typespkg1_PkgEnum(d"2'2") + | case class typespkg1_PkgOpaque() extends Opaque(Bits(4)) + | final case class typespkg2_PkgWrap( + | s: typespkg1_PkgStruct <> VAL + | n: UInt[8] <> VAL + | ) extends Struct + | + | val s = typespkg1_PkgStruct <> VAR + | val e = typespkg1_PkgEnum <> VAR + | val o = typespkg1_PkgOpaque <> VAR + | val w = typespkg2_PkgWrap <> VAR + | val u = UInt(8) <> VAR init typespkg2_PkgWide + | e := typespkg1_PkgEnum.P0 + |end PkgTop + |""".stripMargin + + class PkgTop extends DFDesign: + val s = typespkg1.PkgStruct <> VAR + val e = typespkg1.PkgEnum <> VAR + val o = typespkg1.PkgOpaque <> VAR + val w = typespkg2.PkgWrap <> VAR + val u = UInt(8) <> VAR init typespkg2.PkgWide + e := typespkg1.PkgEnum.P0 + + test("packaged types, constants and static functions take their package name") { + assertCodeString((new PkgTop).dropPackages, flattenedCS) + } + + test("re-running changes nothing (the namespace goes with the rename)") { + assertCodeString((new PkgTop).dropPackages.dropPackages, flattenedCS) + } + + test("nothing is flattened for a backend that has packages") { + given options.CompilerOptions.Backend = _.verilog.sv2009 + val top = (new PkgTop).dropPackages + assert(clue(top.getCodeString).contains("package StagesSpec.typespkg1:")) + } + +end DropPackagesSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PkgFixtures.scala b/compiler/stages/src/test/scala/StagesSpec/PkgFixtures.scala index 7546d9c3b..a12610524 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PkgFixtures.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PkgFixtures.scala @@ -25,3 +25,24 @@ package typespkg2 { case class PkgWrap(s: typespkg1.PkgStruct <> VAL, n: UInt[8] <> VAL) extends Struct val PkgWide: UInt[8] <> CONST = typespkg1.pkgCalc(typespkg1.PkgDerived) } + +// Cross-package homographs: two sibling packages declaring the SAME simple names (and, for +// `Shared`, structurally different types under that name). Every packaged reference is emitted +// qualified, so name uniqueness is scoped per package and neither side is renamed. +// NOTE: the static functions are deliberately named apart. A method is a design block, and +// same-named design blocks are enumerated (`f_0`, `f_1`) by elaboration, which is not +// package-aware — so a packaged method name is still globally unique, unlike a type or a +// constant name. +package dualpkg1 { + case class Shared(v: Bits[4] <> VAL) extends Struct + val SharedConst: UInt[8] <> CONST = 1 + def calc1(arg: UInt[8] <> CONST): UInt[8] <> CONSTRET = arg + 10 + val SharedDerived: UInt[8] <> CONST = calc1(SharedConst) +} + +package dualpkg2 { + case class Shared(v: Bits[8] <> VAL) extends Struct + val SharedConst: UInt[8] <> CONST = 2 + def calc2(arg: UInt[8] <> CONST): UInt[8] <> CONSTRET = arg + 20 + val SharedDerived: UInt[8] <> CONST = calc2(SharedConst) +} diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index a9cb5fd28..d1b26ccdf 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -4011,4 +4011,42 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |end PkgTop |""".stripMargin ) + test("Same-named declarations across packages"): + class DualTop extends DFDesign: + val a = dualpkg1.Shared <> VAR + val b = dualpkg2.Shared <> VAR + val c = UInt(8) <> VAR init dualpkg1.SharedDerived + val d = UInt(8) <> VAR init dualpkg2.SharedDerived + val top = (new DualTop).getCodeString + assertNoDiff( + top, + """|package StagesSpec.dualpkg1: + | final case class Shared( + | v: Bits[4] <> VAL + | ) extends Struct + | val SharedConst: UInt[8] <> CONST = d"8'1" + | def calc1(arg: UInt[8] <> CONST): UInt[8] <> CONSTRET = + | arg + d"8'10" + | end calc1 + | val SharedDerived: UInt[8] <> CONST = calc1(SharedConst) + | + |package StagesSpec.dualpkg2: + | final case class Shared( + | v: Bits[8] <> VAL + | ) extends Struct + | val SharedConst: UInt[8] <> CONST = d"8'2" + | def calc2(arg: UInt[8] <> CONST): UInt[8] <> CONSTRET = + | arg + d"8'20" + | end calc2 + | val SharedDerived: UInt[8] <> CONST = calc2(SharedConst) + | + | + |class DualTop extends DFDesign: + | val a = StagesSpec.dualpkg1.Shared <> VAR + | val b = StagesSpec.dualpkg2.Shared <> VAR + | val c = UInt(8) <> VAR init StagesSpec.dualpkg1.SharedDerived + | val d = UInt(8) <> VAR init StagesSpec.dualpkg2.SharedDerived + |end DualTop + |""".stripMargin + ) end PrintCodeStringSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index efbb66927..a2c07ce08 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -4052,18 +4052,17 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; |use work.PkgTop_pkg.all; - |use work.typespkg1.all; | |package typespkg2 is |type PkgWrap is record - | s : PkgStruct; + | s : work.typespkg1.PkgStruct; | n : unsigned(7 downto 0); |end record; |function bitWidth(A: PkgWrap) return integer; |function to_slv(A: PkgWrap) return std_logic_vector; |function to_PkgWrap(A: std_logic_vector) return PkgWrap; |function bool_sel(C : boolean; T : PkgWrap; F : PkgWrap) return PkgWrap; - |constant PkgWide : unsigned(7 downto 0) := pkgCalc(PkgDerived); + |constant PkgWide : unsigned(7 downto 0) := work.typespkg1.pkgCalc(work.typespkg1.PkgDerived); |end package typespkg2; | |package body typespkg2 is @@ -4091,7 +4090,7 @@ class PrintVHDLCodeSpec extends StageSpec: | variable ret : PkgWrap; |begin | lo := A'length; - | hi := lo - 1; lo := hi - bitWidth(ret.s) + 1; ret.s := to_PkgStruct(A(hi downto lo)); + | hi := lo - 1; lo := hi - bitWidth(ret.s) + 1; ret.s := work.typespkg1.to_PkgStruct(A(hi downto lo)); | hi := lo - 1; lo := hi - bitWidth(ret.n) + 1; ret.n := unsigned(A(hi downto lo)); | return ret; |end; @@ -4111,24 +4110,169 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; |use work.PkgTop_pkg.all; - |use work.typespkg1.all; - |use work.typespkg2.all; | |entity PkgTop is |port ( - | sp : in PkgStruct; - | so : out PkgStruct + | sp : in work.typespkg1.PkgStruct; + | so : out work.typespkg1.PkgStruct |); |end PkgTop; | |architecture PkgTop_arch of PkgTop is - | signal e : PkgEnum; - | signal o : PkgOpaque; - | signal w : PkgWrap; - | signal u : unsigned(7 downto 0) := PkgWide; + | signal e : work.typespkg1.PkgEnum; + | signal o : work.typespkg1.PkgOpaque; + | signal w : work.typespkg2.PkgWrap; + | signal u : unsigned(7 downto 0) := work.typespkg2.PkgWide; |begin | so <= sp; |end PkgTop_arch; |""".stripMargin ) + test("Same-named declarations across packages"): + class DualTop extends EDDesign: + val a = dualpkg1.Shared <> IN + val b = dualpkg2.Shared <> OUT + val c = UInt(8) <> VAR init dualpkg1.SharedDerived + val d = UInt(8) <> VAR init dualpkg2.SharedDerived + b.v <> a.v.resize(8) + val top = (new DualTop).getCompiledCodeString + assertNoDiff( + top, + """|library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + | + |package dualpkg1 is + |type Shared_0 is record + | v : std_logic_vector(3 downto 0); + |end record; + |function bitWidth(A: Shared_0) return integer; + |function to_slv(A: Shared_0) return std_logic_vector; + |function to_Shared_0(A: std_logic_vector) return Shared_0; + |function bool_sel(C : boolean; T : Shared_0; F : Shared_0) return Shared_0; + |constant SharedConst : unsigned(7 downto 0) := 8d"1"; + |pure function calc1(arg : unsigned(7 downto 0)) return unsigned; + |constant SharedDerived : unsigned(7 downto 0) := calc1(SharedConst); + |end package dualpkg1; + | + |package body dualpkg1 is + |function bitWidth(A : Shared_0) return integer is + | variable width : integer; + |begin + | width := 0; + | width := width + bitWidth(A.v); + | return width; + |end; + |function to_slv(A : Shared_0) return std_logic_vector is + | variable hi : integer; + | variable lo : integer; + | variable ret : std_logic_vector(bitWidth(A) - 1 downto 0); + |begin + | lo := bitWidth(A); + | hi := lo - 1; lo := hi - bitWidth(A.v) + 1; ret(hi downto lo) := A.v; + | return ret; + |end; + |function to_Shared_0(A : std_logic_vector) return Shared_0 is + | variable hi : integer; + | variable lo : integer; + | variable ret : Shared_0; + |begin + | lo := A'length; + | hi := lo - 1; lo := hi - bitWidth(ret.v) + 1; ret.v := A(hi downto lo); + | return ret; + |end; + |function bool_sel(C : boolean; T : Shared_0; F : Shared_0) return Shared_0 is + |begin + | if C then + | return T; + | else + | return F; + | end if; + |end; + |pure function calc1(arg : unsigned(7 downto 0)) return unsigned is + |begin + | return arg + 8d"10"; + |end function; + |end package body dualpkg1; + | + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + | + |package dualpkg2 is + |type Shared_0 is record + | v : std_logic_vector(7 downto 0); + |end record; + |function bitWidth(A: Shared_0) return integer; + |function to_slv(A: Shared_0) return std_logic_vector; + |function to_Shared_0(A: std_logic_vector) return Shared_0; + |function bool_sel(C : boolean; T : Shared_0; F : Shared_0) return Shared_0; + |constant SharedConst : unsigned(7 downto 0) := 8d"2"; + |pure function calc2(arg : unsigned(7 downto 0)) return unsigned; + |constant SharedDerived : unsigned(7 downto 0) := calc2(SharedConst); + |end package dualpkg2; + | + |package body dualpkg2 is + |function bitWidth(A : Shared_0) return integer is + | variable width : integer; + |begin + | width := 0; + | width := width + bitWidth(A.v); + | return width; + |end; + |function to_slv(A : Shared_0) return std_logic_vector is + | variable hi : integer; + | variable lo : integer; + | variable ret : std_logic_vector(bitWidth(A) - 1 downto 0); + |begin + | lo := bitWidth(A); + | hi := lo - 1; lo := hi - bitWidth(A.v) + 1; ret(hi downto lo) := A.v; + | return ret; + |end; + |function to_Shared_0(A : std_logic_vector) return Shared_0 is + | variable hi : integer; + | variable lo : integer; + | variable ret : Shared_0; + |begin + | lo := A'length; + | hi := lo - 1; lo := hi - bitWidth(ret.v) + 1; ret.v := A(hi downto lo); + | return ret; + |end; + |function bool_sel(C : boolean; T : Shared_0; F : Shared_0) return Shared_0 is + |begin + | if C then + | return T; + | else + | return F; + | end if; + |end; + |pure function calc2(arg : unsigned(7 downto 0)) return unsigned is + |begin + | return arg + 8d"20"; + |end function; + |end package body dualpkg2; + | + | + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + | + |entity DualTop is + |port ( + | a : in work.dualpkg1.Shared_0; + | b : out work.dualpkg2.Shared_0 + |); + |end DualTop; + | + |architecture DualTop_arch of DualTop is + | signal c : unsigned(7 downto 0) := work.dualpkg1.SharedDerived; + | signal d : unsigned(7 downto 0) := work.dualpkg2.SharedDerived; + |begin + | b.v <= eby(a.v, 4); + |end DualTop_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 64eba2a35..054e3be18 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -4043,4 +4043,107 @@ class PrintVerilogCodeSpec extends StageSpec: |endmodule |""".stripMargin ) + test("Same-named declarations across packages"): + class DualTop extends EDDesign: + val a = dualpkg1.Shared <> IN + val b = dualpkg2.Shared <> OUT + val c = UInt(8) <> VAR init dualpkg1.SharedDerived + val d = UInt(8) <> VAR init dualpkg2.SharedDerived + b.v <> a.v.resize(8) + val top = (new DualTop).getCompiledCodeString + assertNoDiff( + top, + """|package dualpkg1; + |typedef struct packed { + | logic [3:0] v; + |} Shared; + |parameter logic [7:0] SharedConst = 8'd1; + |function automatic logic [7:0] calc1(input logic [7:0] arg); + |begin + | calc1 = arg + 8'd10; + |end + |endfunction + |parameter logic [7:0] SharedDerived = calc1(SharedConst); + |endpackage + | + |package dualpkg2; + |typedef struct packed { + | logic [7:0] v; + |} Shared; + |parameter logic [7:0] SharedConst = 8'd2; + |function automatic logic [7:0] calc2(input logic [7:0] arg); + |begin + | calc2 = arg + 8'd20; + |end + |endfunction + |parameter logic [7:0] SharedDerived = calc2(SharedConst); + |endpackage + | + | + |`default_nettype none + |`timescale 1ns/1ps + | + |module DualTop( + | input wire dualpkg1::Shared a, + | output dualpkg2::Shared b + |); + | `include "dfhdl_defs.svh" + | logic [7:0] c = dualpkg1::SharedDerived; + | logic [7:0] d = dualpkg2::SharedDerived; + | assign b.v = `EBY_U(a.v, 4); + |endmodule + |""".stripMargin + ) + test("Namespace-derived declarations flattened under verilog.v95"): + // v95 has no packages, so `DropPackages` folds each packaged declaration's package name + // into its own name and everything lands in the single global defs header + given options.CompilerOptions.Backend = _.verilog.v95 + class PkgTop extends EDDesign: + val e = typespkg1.PkgEnum <> VAR + val u = UInt(8) <> OUT + u <> typespkg2.PkgWide + val top = (new PkgTop).getCompiledCodeString + assertNoDiff( + top, + """|`define GlbNsConst_def parameter [7:0] GlbNsConst = 8'd3; + |`define typespkg1_PkgConst_def parameter [7:0] typespkg1_PkgConst = GlbNsConst + 8'd39; + |function [7:0] typespkg1_pkgCalc; + | input [7:0] arg; + |begin + | typespkg1_pkgCalc = arg + 8'd1; + |end + |endfunction + |`define typespkg1_PkgDerived_def parameter [7:0] typespkg1_PkgDerived = typespkg1_pkgCalc(typespkg1_PkgConst); + |`define typespkg2_PkgWide_def parameter [7:0] typespkg2_PkgWide = typespkg1_pkgCalc(typespkg1_PkgDerived); + | + |`default_nettype none + |`timescale 1ns/1ps + |`include "PkgTop_defs.vh" + | + |module PkgTop( + | u + |); + | `include "dfhdl_defs.vh" + | `include "PkgTop_defs.vh" + | `typespkg2_PkgWide_def + | `define typespkg1_PkgEnum_P0 0 + | `define typespkg1_PkgEnum_P1 1 + | `define typespkg1_PkgEnum_P2 2 + | function [8*20:1] typespkg1_PkgEnum_to_string; + | /* verilator lint_off UNUSEDSIGNAL */ + | input [1:0] value; + | case (value) + | `typespkg1_PkgEnum_P0: typespkg1_PkgEnum_to_string = "typespkg1_PkgEnum_P0"; + | `typespkg1_PkgEnum_P1: typespkg1_PkgEnum_to_string = "typespkg1_PkgEnum_P1"; + | `typespkg1_PkgEnum_P2: typespkg1_PkgEnum_to_string = "typespkg1_PkgEnum_P2"; + | default: typespkg1_PkgEnum_to_string = "?"; + | endcase + | /* verilator lint_on UNUSEDSIGNAL */ + | endfunction + | output wire [7:0] u; + | reg [1:0] e; + | assign u = typespkg2_PkgWide; + |endmodule + |""".stripMargin + ) end PrintVerilogCodeSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/UniqueNamesSpec.scala b/compiler/stages/src/test/scala/StagesSpec/UniqueNamesSpec.scala index a40a03deb..141dbc0a3 100644 --- a/compiler/stages/src/test/scala/StagesSpec/UniqueNamesSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/UniqueNamesSpec.scala @@ -164,4 +164,39 @@ class UniqueNamesSpec extends StageSpec: ) } + // `dualpkg1`/`dualpkg2` (see PkgFixtures) declare the same simple names, and the top design's + // namespace is `StagesSpec`, so both land in packages of their own. + class DualTop extends DFDesign: + val a = dualpkg1.Shared <> VAR + val b = dualpkg2.Shared <> VAR + val c = UInt(8) <> VAR init dualpkg1.SharedConst + val d = UInt(8) <> VAR init dualpkg2.SharedConst + + test("same-named declarations in different packages, scoped per package") { + val top = (new DualTop).uniqueNames(Set(), true) + assertCodeString( + top, + """|package StagesSpec.dualpkg1: + | final case class Shared( + | v: Bits[4] <> VAL + | ) extends Struct + | val SharedConst: UInt[8] <> CONST = d"8'1" + | + |package StagesSpec.dualpkg2: + | final case class Shared( + | v: Bits[8] <> VAL + | ) extends Struct + | val SharedConst: UInt[8] <> CONST = d"8'2" + | + | + |class DualTop extends DFDesign: + | val a = StagesSpec.dualpkg1.Shared <> VAR + | val b = StagesSpec.dualpkg2.Shared <> VAR + | val c = UInt(8) <> VAR init StagesSpec.dualpkg1.SharedConst + | val d = UInt(8) <> VAR init StagesSpec.dualpkg2.SharedConst + |end DualTop + |""".stripMargin + ) + } + end UniqueNamesSpec From e9d163e0a97d51533177f633f012cb17c4d7ed05 Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 06:09:07 +0300 Subject: [PATCH 53/57] devdocs: packages feature implementation --- devdocs/packages.md | 238 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 devdocs/packages.md diff --git a/devdocs/packages.md b/devdocs/packages.md new file mode 100644 index 000000000..35b9ee97b --- /dev/null +++ b/devdocs/packages.md @@ -0,0 +1,238 @@ +# Packages + +How a declaration's Scala package becomes an HDL package: `veer_types::lsu_pkt_t` in SystemVerilog, +`work.veer_types.lsu_pkt_t` in VHDL, one file per package, instead of everything in a single global +defs file per top design. + +The motivating case is translation review. `cav`'s `interface_precheck` string-compares port types +against a reference design, so a struct port emitted as `t_struct_lsu_pkt_t` never matched the gold +`veer_types::lsu_pkt_t`, and every struct-port module was blocked. Matching that form needs two +things: the declaring package must survive elaboration into the IR, and the emitted type name must be +exactly what the user wrote. + +Related: [methods.md](methods.md) for the static functions and ED methods that are packaged +alongside types, and [elaboration-caching.md](elaboration-caching.md) for the `Meta` identity rules +this feature had to work within. + +## 1. Terminology + +| Term | Means | +|---|---| +| **namespace** | the Scala package path of a DECLARATION, carried in `ir.Meta.namespace` (`""` for the root package). Package level only: an enclosing `object` or `class` is scoping, not namespacing | +| **package** | the emitted HDL unit: an SV `package`, a VHDL `package`/`package body` pair, a `package :` section in DFHDL code | +| **placement** | the decision that maps a namespace to either the general global defs file or one dedicated package | +| **general defs file** | the pre-existing per-top globals file (`_defs.svh`, `_pkg.vhd`), which still holds everything not placed in a package | +| **packaged declaration** | one whose placement is a package: a named type, a global constant, or a global HDL method | + +## 2. Where the namespace comes from + +`Meta.namespace` is captured at declaration time by whichever mechanism already builds that +declaration's `Meta`. There is no annotation and nothing for a user to write. + +| Declaration | Captured by | +|---|---| +| design class, design/method `def` | compiler plugin, `CommonPhase.mkNamespace` (via `genDclMeta`) → `enclosingPackageClass` | +| `case class ... extends Struct`, `enum ... extends Encoded` | derivation macro, [TypeMetaGen](../core/src/main/scala/dfhdl/core/TypeMetaGen.scala) | +| `case class ... extends Opaque(...)` | `ClassEv` (`dclNamespace`, captured in the same macro) | +| global value (`<> CONST` at global scope) | plugin `genMeta` → `DFC.namespace` | +| anything reaching neither | runtime `getClass.getPackageName` fallback in `DFStruct`/`DFEnum`/`DFOpaque` | + +A name that starts with `<` (the compiler's empty-package marker) becomes `""`. + +Two gates matter: + +- **`DFC.getMeta` keeps the namespace only at global scope** (`ownerOption.isEmpty`). A + design-scoped value's namespace is its design, not the Scala package its file happens to be in, so + it must not turn into a package. `getDclMeta` is the *ungated* variant and is what `Design.Block` + uses, because a design block's namespace is a genuine declaration property. +- **Namespaces are package-level.** `object Internal { case class Foo(...) }` in package `p` gives + `Foo` the namespace `p`, not `p.Internal`. This was implemented both ways and deliberately settled + on packages only: object nesting is a Scala scoping device with no HDL counterpart. + +`NamedDFType` (`DFStruct`, `DFEnum`, `DFOpaque`, `DFView`) carries a full `meta: Meta` rather than a +bare name, which is what makes a type's namespace, position and doc comment available to the +printers (the doc comment is why a named type's declaration can carry its ScalaDoc). + +### `Meta` identity + +Adding a field to `Meta` forced its equality to be stated explicitly. `Meta` has **no `CanEqual`**; +a call site names the comparison it means: + +- `sameIdentityAs`: name + namespace + annotations. Excludes `position` and `docOpt`, which are + invisible to the code digest and so can drift while an elaboration-cache entry stays valid. This + is what `equals`/`hashCode` implement, so member and type equality compose it implicitly. +- `sameDclAs`: all fields. "Same declaration", anchored on position: `DesignLoadKey`'s intra-run + gate and `UniqueDesigns`' grouping need same-named designs from *different* declarations to stay + apart. + +The namespace participates in identity in both (a package clause is in the typed tree, hence in the +digest), which is what keeps `p1.Foo` and `p2.Foo` distinct types. + +## 3. Placement + +[Namespacing.scala](../compiler/ir/src/main/scala/dfhdl/compiler/printing/Namespacing.scala) is the +whole rule, shared by every printer and by the `DropPackages` stage: + +``` +isGlobalPlaced(ns, topNs) = ns.isEmpty || ns == topNs || topNs.startsWith(s"$ns.") +packageNameOf(ns, topNs) = ns relative to topNs, remaining segments joined with `_` +``` + +A declaration whose namespace equals the top design's, or is an **ancestor** package of it, stays in +the general defs file. Anything else gets a package named by its namespace *relative* to the top's: + +| top namespace | declaration namespace | placement | +|---|---|---| +| `veer` | `veer` | general defs file | +| `veer` | `` (root) | general defs file | +| `veer` | `veer.veer_types` | package `veer_types` | +| `mydesign` | `dfhdl.lib.crypto.aes` | package `dfhdl_lib_crypto_aes` | + +The ancestor rule is what keeps a design that merely *lives* in a package from pushing its +neighbours and parents into separate files. Magnet-kind opaques (`Clk`, `Rst`) are excluded by +`Namespacing.typePlacementOf`: they are language-level and their declaring namespace is a +DFHDL-internal one. + +Distinct namespaces map to distinct package names, with one residual clash: a top under `top` with +declarations in `top.x` and in a root-level `x` would produce two `x` packages. The emission detects +it and fails loudly rather than merging. + +## 4. Emission + +The shared machinery is in [Printer.scala](../compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala) +and [DFTypePrinter.scala](../compiler/ir/src/main/scala/dfhdl/compiler/printing/DFTypePrinter.scala); +each backend supplies the syntax. + +| Hook | Role | +|---|---| +| `supportPackages` | does this backend emit package files at all (false for verilog.v95/v2001) | +| `topNamespace` | `rootDB.top.dclMeta.namespace`, the reference point of every placement | +| `typePlacementOf` / `memberPlacementOf` | placement of a named type / of a global constant or method | +| `currentPackage` | the package being rendered; its own declarations and same-package references print unqualified | +| `packagedTypeEntries` | per-package type declarations, in cross-package dependency order | +| `packagedGlobalDeclEntries` | per-package constants and methods, in the same dependency order the general globals use | +| `packagedContents` | the two merged per package: what `printedDB` and `csDB` iterate | +| `csGlobalMemberQualifier` | the backend's spelling of a packaged reference's qualifier | + +Three details are less obvious than they look: + +- **Design-local types are packaged too.** Placement overrides design-locality: a type declared in a + package but used by a single design is still emitted into that package, not into that design's + declarative region. `packagedTypeEntries` collects the per-design local types for exactly this. +- **Hoisting.** A package file cannot reference a type declared inside a design, so a design-local, + *general-placed* type that packaged content references (a global-placed struct used as a field of + a packaged struct) is hoisted into the general defs file by `packagedHoistedTypes`, and excluded + from the design's own local declarations. +- **Order.** Packages are emitted in topological order (a package precedes any package referencing + it) and, in `printedDB`, ahead of the design files. Both VHDL analysis order and SV compilation + order need this. A package name colliding with a design name is a hard error. + +### Qualification, not imports + +Every backend references a packaged declaration **through its package**. No backend emits a +blanket import, so two packages can hold the same simple name and no use site is ever ambiguous. + +| Backend | Type / constant / call reference | Package unit | +|---|---|---| +| SystemVerilog | `pkg::Name` | `package pkg; ... endpackage` in `pkg.sv`, including the global defs header | +| VHDL | `work.pkg.Name` (types, enum literals, conversion functions, constants, method calls) | `package` + `package body` in `pkg.vhd`, `use`ing only ieee, `dfhdl_pkg` and the general package | +| DFHDL code | `.Name` | `package :` section | + +VHDL selected names replaced an earlier `use work..all` design. The blanket use clause makes +two same-named packaged types an ambiguous homograph at every use site, whereas a selected name is +unambiguous by construction, and it is what allows package-scoped name uniqueness (§5). Verified to +analyze under `ghdl --std=93`, `ghdl --std=08` and `nvc`, `case` choices on selected-name enum +literals included. + +One asymmetry to keep in mind when editing the VHDL printer: `pkgQualifier` prefixes a type's +**reference** forms. Anything that builds an *identifier* out of a type name (an array type +name `t_arrX1_Foo`, a conversion function name `to_Foo`) must use the simple `dfType.name` and place +the qualifier ahead of the identifier it forms (`work.pkg.to_Foo`), which is what `csConvFuncName` +is for. + +## 5. Names + +Two changes to naming came with the feature. + +**The `t_struct_` / `t_enum_` / `t_opaque_` prefixes are gone**, in every backend. The emitted type +name is the declared name, which is the point (`veer_types::lsu_pkt_t`, not +`veer_types::t_struct_lsu_pkt_t`). Without a prefix, type and value identifiers share one HDL +namespace (and VHDL is case-insensitive), so +[UniqueNames](../compiler/stages/src/main/scala/dfhdl/compiler/stages/UniqueNames.scala) now +reserves the final global type names against every value renamer, and each design's local type names +against that design's values. + +**Uniqueness is scoped per package.** A name only has to be unique within the package it is emitted +into, since every reference to it is qualified. The scopes are processed general-group-first, and +the general group's final names are then reserved for every package group: a package's content sits +*alongside* the general globals (an SV package includes the global defs header; a VHDL package uses +the general package), while two different packages never see each other unqualified. This applies to +named types, global constants, and design-local packaged types. + +There is no backend flag for this. A backend without packages reaches `UniqueNames` with no packaged +declarations left to scope, because `DropPackages` ran first (§6), leaving the single general scope, +which is exactly the across-the-board uniqueness such a backend needs. + +## 6. Backends without packages: `DropPackages` + +verilog.v95 and v2001 have no packages, so +[DropPackages](../compiler/stages/src/main/scala/dfhdl/compiler/stages/DropPackages.scala) folds each +packaged declaration's package name into its own name and **clears its namespace**: + +``` +typespkg1.PkgEnum -> typespkg1_PkgEnum +typespkg1.pkgCalc -> typespkg1_pkgCalc +typespkg2.PkgWide -> typespkg2_PkgWide +``` + +Everything then lands in the single global defs header, under names that still say where they came +from and that cannot collide across packages. The mirror of `pkg::Name` is deliberate: the same +declaration stays recognizable across dialects. + +Clearing the namespace is what makes the stage idempotent *and* printable: the flattened declaration +is now general-placed, so a re-run finds nothing to do, and re-elaborating the printout reconstructs +the same state. Renaming while keeping the namespace would flatten again on every pass. + +It is the last entry in `BackendPrepStage`, so it sees only what survives to the emission (v95/v2001 +drop structs and opaques on the way there) and its names reach `UniqueNames`, which runs +after that bundle. + +The stage covers exactly what the packaged emission places, which for methods means "emitted in the +shared globals area". That decision used to live in `Printer`; it now lives in +[HDLMethodAnalysis](../compiler/ir/src/main/scala/dfhdl/compiler/analysis/HDLMethodAnalysis.scala) so +the stage and the printers cannot drift apart. A backend may only WIDEN it (VHDL globalizes a static +function read by a port declaration) by overriding `Printer.globalHDLMethods`. + +Two implementation notes worth carrying: a design block cannot be renamed with a `Patch` (it is its +sub-DB's top, and its `ownerRef` is the hierarchy key), so the block is swapped in every sub-DB's +member list *and* in every refTable value resolving to it; and an anonymous global carries a +namespace even though it has no name to flatten, so it gets its namespace cleared too, or it keeps +declaring a package of its own. + +## 7. Where it is pinned + +| Test | Covers | +|---|---| +| `StagesSpec.MetaSpec` | namespace capture per declaration kind, the placement rules, the two `Meta` comparisons | +| `StagesSpec.PkgFixtures` | the shared fixture: general-scope globals, `typespkg1`/`typespkg2` (with a cross-package dependency), and `dualpkg1`/`dualpkg2` (same simple names in both) | +| "Namespace-derived type packages" | the same design pinned in `PrintCodeStringSpec`, `PrintVerilogCodeSpec` and `PrintVHDLCodeSpec` | +| "Same-named declarations across packages" | per-package uniqueness, in the same three specs | +| "Namespace-derived declarations flattened under verilog.v95" | `DropPackages` end-to-end, in `PrintVerilogCodeSpec` | +| `StagesSpec.DropPackagesSpec` | the stage alone, its idempotency, and its no-op for a backend with packages | +| `StagesSpec.UniqueNamesSpec` | package-scoped uniqueness of types and global constants | + +## 8. Known gaps + +- **A global HDL method's name is still globally unique.** Same-named design blocks are enumerated + (`f_0`, `f_1`) by elaboration in `MutableDB.dclNameEnumeration`, which is not package-aware, and + `UniqueDesigns.scopedDclNameKey` scopes a method by its owning design rather than by its package. + So two packages may each declare a type `Foo`, but not each a function `calc`. Correct output, + just not package-named. +- **A global-scope `def` called both globally and from within a design mints two IR blocks.** The + printed declaration is deduped (`globalDeclsDeduped`, by `sameDclAs`), so it is invisible in the + output; the real fix is improvement #11 in [elaboration-caching.md](elaboration-caching.md). +- **A global vector type whose cell type is packaged** would be declared in the general package, + which has no visibility of that package. Pre-existing and untested; it needs the vector type + declaration to follow its cell type's placement. +- **`DFView` / interfaces** are a work in progress and were left out of scope, though `DFView` is a + `NamedDFType` and so already carries a namespace. From 8ee7169f232c296e1a91bc9fc4663d35af25646e Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 17:04:56 +0300 Subject: [PATCH 54/57] devdocs: the packed/unpacked array decision, and the clk-port note was wrong The devdoc covers the Verilog-backend representation choice for a DFVector: why packed is the default and unpacked the exception, the two halves of the decision (supportsPackedVector is a TYPE property, hasMemAccessPattern a usage one) and why that split is what makes it sound, the rendering consequences (dimension placement, aggregate order, the streaming-reversal cast, part-selects), and a standalone argument for why the two forms can never meet in one operation. The user-facing rules stay in the type-system guide; this is the "why it is shaped this way" companion. Two things measured while writing it rather than taken from the source: - An index-labeled aggregate (`'{0: e0, 1: e1, ...}`) IS legal on a packed array and would have been representation-independent, needing no reversal and no `unpackedOrder` flag. slang and verilator accept it; iverilog -g2012 and yosys's read_verilog reject it. So the positional-reversed form buys portability across the partial frontends, which is the same trade as avoiding `'{default:}` on unpacked arrays (yosys#6120). Recorded as the reason, since the source does not say why. - The namespace-derived package file is emitted with no `default_nettype / `timescale header while every other file has both, and slang refuses the mix, so any design using a type package fails to elaborate as a file set. Listed under open issues; it is not this feature's, but it lands on the same output and blocks the flow that consumes it. Also corrects the verilog-to-dfhdl skill: it claimed a port literally named `clk` collides with the magnet. It does not. `val clk = Clk <> IN` in a design that also has registers emits one `clk` port, the registers still clock on it, and `clk.actual` reads it as a Bit -- which is how lsu_clkdomain drives a derived clock from the root clock the way an ungated ICG does. What collides is a non-magnet port whose name shadows the magnet's. The benchmarks bump carries lsu_clkdomain, lsu_trigger, the veer_types package and the clock-domain traits. Co-Authored-By: Claude Opus 5 --- .claude/commands/verilog-to-dfhdl.md | 7 +- benchmarks | 2 +- devdocs/packed-unpacked-arrays.md | 236 +++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 devdocs/packed-unpacked-arrays.md diff --git a/.claude/commands/verilog-to-dfhdl.md b/.claude/commands/verilog-to-dfhdl.md index 662cf258d..63f19924a 100644 --- a/.claude/commands/verilog-to-dfhdl.md +++ b/.claude/commands/verilog-to-dfhdl.md @@ -108,8 +108,11 @@ automatically (the emitted parent shows `child_i_clk = wb_clk`). Beyond the per- cnt.din := 0 init_done.din := 0 ``` -- A port literally named **`rst`/`clk`** collides with the magnet ("Unsupported read-to-read - connection"). Use the baseline's real name (`i_rst`, `wb_clk`, ...). +- **Declaring the baseline's own `clk` port is fine and unifies with the magnet.** `val clk = Clk <> + IN` in a design that also has registers emits one `clk` port, the registers still clock on it, and + `clk.actual` reads it as a `Bit` (needed to drive a derived clock from the root clock, as an + ungated ICG does). What does collide is a *non-magnet* port whose name shadows the magnet's, so + keep a data port off the names `clk`/`rst` ("Unsupported read-to-read connection"). ## Memories and `initFile` diff --git a/benchmarks b/benchmarks index f60c52f6d..8c99bf94d 160000 --- a/benchmarks +++ b/benchmarks @@ -1 +1 @@ -Subproject commit f60c52f6d13e0594b3ed9787c7b7d32472036018 +Subproject commit 8c99bf94dd4deb2ff98b074182ce727e0c7dba6d diff --git a/devdocs/packed-unpacked-arrays.md b/devdocs/packed-unpacked-arrays.md new file mode 100644 index 000000000..7c94e4bae --- /dev/null +++ b/devdocs/packed-unpacked-arrays.md @@ -0,0 +1,236 @@ +# Packed and Unpacked Arrays (Verilog backends) + +How a DFHDL `DFVector` chooses between a SystemVerilog **packed** array (`logic [3:0][7:0] v`) +and an **unpacked** one (`logic [7:0] v [0:3]`), and everything that follows from the choice: +the type rendering, the aggregate-literal order, the whole-vector casts, and the part-selects. + +The user-facing side is +[docs/user-guide/type-system/index.md](../docs/user-guide/type-system/index.md#DFVector-verilog-representation) +(the seven placement rules as a reader needs them, plus the two consequences). This document +covers why the decision is shaped the way it is, where each half lives, and what keeps the two +representations from ever meeting in one operation. + +The VHDL backends are unaffected throughout: they emit named array types with ascending ranges, +and none of the machinery below is reachable from them. + +## 1. Why there is a choice at all + +Packed is the better default and unpacked is the necessary exception. + +**Packed is what the language wants for data.** A packed array is a vector: it can be sliced, +cast, compared, concatenated, and passed through a port with a type a Verilog author would +recognise. Crucially it is the form a *hand-written* baseline uses for a bus-like array +(`el_t [3:0] channels`), and SystemVerilog will not connect a packed port to an unpacked one at +all, so an unpacked port is not merely a stylistic difference from such a baseline, it is an +incompatible interface. + +**Unpacked is what synthesis wants for memory.** Block-RAM and ROM inference keys off the +unpacked-array-with-dynamic-index shape. Emitting a 512 KB ICCM as a packed vector would produce +a correct but unsynthesisable flop farm. + +So the representation is chosen per declaration, from its **shape and usage**, and the rules +exist to guarantee the two forms never have to interoperate. + +## 2. The two halves of the decision + +The decision splits into a **type** question and a **declaration** question, and the split is +what makes it sound. + +| | question | answer depends on | where | +|---|---|---|---| +| type | *can* this vector be packed? | the cell type alone | `supportsPackedVector` | +| declaration | *should* this one stay unpacked? | shape and usage | `hasMemAccessPattern` | + +### 2.1 `supportsPackedVector`: a type property + +[VerilogTypePrinter.scala](../compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogTypePrinter.scala) + +Packed dimensions apply only to single-bit types, enums, packed structs/unions and other packed +arrays (IEEE 1800-2017 7.4.1), so a vector over an integer atom (`Int`), `real`, `String` or a +time value can never pack. Two further exclusions: + +- **Signed cells** (`SInt`, signed fixed-point) stay unpacked. An element select of a packed + array is a part-select, which is always unsigned, so the cell's signedness would be silently + lost. Lifting this needs a named signed element type (IEEE 1800-2017 7.4.3), i.e. a dedicated + stage. +- **Pre-SystemVerilog dialects** (`v95`, `v2001`) have no packed arrays at all, so + `supportPackedArrays` is false and everything is unpacked. This is also why + [DropWholeVecAssign](../compiler/stages/src/main/scala/dfhdl/compiler/stages/DropWholeVecAssign.scala) + exists for those dialects. + +Being a **type** property is the load-bearing part: every value of a given vector type agrees on +it, so a mixed-representation *connection* can never print. + +### 2.2 `hasMemAccessPattern`: a usage property + +[DFValAnalysis.scala](../compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala) + +Answers "does this declaration look like a memory?" for a `DFVal.Dcl` or a named constant: + +- a **port** never does, being the design interface (§1) +- an **alias-bound constant** (`val b = a`) never does; its value is a whole-vector read +- any **whole-vector use** (assignment, connection, cast, slice, function argument) or any + **constant-index access** disqualifies it +- otherwise a **`VAR.SHARED`** qualifies (multi-ported RAM), and so does a declaration whose + dynamic-index accesses include **exactly one read** (the single-read RAM/ROM shape, constants + included, which covers `localparam` ROMs) + +An **`init` reference is representation-neutral**: it neither disqualifies the initialized +declaration nor counts as a whole-vector read of the init value. Without that carve-out every +initialized memory would be forced packed by its own initializer. + +The read test recurses through `DFVal.Alias.Partial`, so `mem(addr)(7, 0)` still counts as one +read of `mem` rather than a whole-vector use. + +### 2.3 Putting them together + +[VerilogPrinter.scala](../compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala) +computes `unpackedVectorDcls` once per design DB: + +```scala +if (!supportsPackedVector(vecType)) Some(dfVal) // type says it cannot pack +else if (dfVal.isGlobal) None // globals are always packed +else Option.when(dfVal.hasMemAccessPattern)(dfVal) // usage says memory +``` + +**A global is always packed** even if its usage looks memory-shaped, because +`hasMemAccessPattern` is a design-local analysis while a global's usage spans designs. A global +ROM that would qualify in one design and not another must not print two ways. + +Two queries sit on top: + +| | meaning | +|---|---| +| `isUnpackedDcl(dfVal)` | this *declaration* prints unpacked | +| `isUnpackedVal(dfVal)` | this *value* is of the unpacked representation | + +`isUnpackedVal` is deliberately narrow: only a direct reference to an unpacked declaration, or +an **outer-dimension slice** of one, is unpacked. Every expression value, element select (an +inner dimension) and cast result is packed. + +## 3. Rendering + +### 3.1 Type and declaration + +A packed vector carries its dimensions **in the type**, descending and outermost-first: + +```scala +val vin = Bits(8) X 4 <> IN +``` +```verilog +input wire logic [3:0][7:0] vin +``` + +An unpacked declaration puts its **outermost** dimension after the name (ascending) while the +cell keeps its packed type form. That mixed shape is `csDclTypeAndRange`: + +```verilog +logic [7:0] mem [0:3]; // outer unpacked, cell packed +``` + +Under the pre-SystemVerilog dialects and for non-integral cell types, *all* dimensions go after +the name, which is what `csDFVectorRanges` yields (it returns nothing for a packed-capable +vector under SystemVerilog). + +`csDFVectorPacked` assembles the packed form from the innermost non-vector cell type +(`vectorScalarCellType`) plus the accumulated dimensions, with a branch per cell kind +(`DFBoolOrBit`, `DFBitsWL`, unsigned `DFDecimal`, `DFEnum`, `DFStruct`, `DFOpaque`). Signed +decimals never reach it, per §2.1. + +### 3.2 Aggregate literals + +The two forms need different aggregate syntax, and the packed one is **positional and reversed**: + +```verilog +'{e3, e2, e1, e0} // packed: leftmost position binds the HIGHEST index +'{0: e0, 1: e1, 2: e2} // unpacked: index-labeled, ascending +``` + +`csDFVectorElemCS(elemCS, unpackedOrder)` picks between them, and `unpackedOrder` applies to the +**outermost dimension only**: nested dimensions are always packed, so the cell recursion drops +the flag. + +`csUnpackedInitValue` handles the one place a value's order must be adapted to its target: an +unpacked declaration's `init`/default. Only an anonymous constant-data value or a vector literal +(a `Func.Op.++`) has an order-sensitive aggregate; everything else prints its regular form. + +### 3.3 The whole-vector cast is a streaming reversal + +This is the subtle consequence. DFHDL's own bit order puts **element 0 in the most-significant +bits**; a packed descending array holds **element 0 at the least-significant end**. So a +whole-vector ⇄ `Bits` cast is not a reinterpretation; it is a scalar-cell-granular reversal, +emitted with the streaming operator: + +```verilog +{<<8{v}} // cell width 8 +``` + +Both directions use it (`DFVector` ← `DFBitsWL` and `DFBitsWL` ← `DFVector`), grouped by +`vectorScalarCellType`'s width. Note the restriction recorded in the source: **a streaming +concatenation is only legal in an assignment-like context**, not as a general subexpression: +the same restriction the element-enumerated `'{...}` form already had. + +### 3.4 Part-selects + +A packed vector's range descends, so an outer-dimension slice prints `[high:low]`, guarded on +the value not being unpacked: + +```scala +case vec: DFVector if supportsPackedVector(vec) && !isUnpackedVal(relVal) => + s"$relVal[$idxHigh:$idxLow]" +``` + +## 4. Why the two forms never meet + +The soundness argument is worth stating explicitly, because the whole design rests on it: + +1. `supportsPackedVector` is a **type** property, so a connection between two values of the same + vector type can never straddle the two forms. +2. `hasMemAccessPattern` admits only declarations accessed **element-by-element through dynamic + indexes** (or through their representation-neutral `init`). A whole-vector use or a + constant-index access disqualifies the declaration outright. +3. `isUnpackedVal` propagates unpacked-ness only through an outer-dimension slice, so no + expression result is ever unpacked. + +Together: an unpacked declaration is only ever touched one element at a time, and an element +select yields a packed cell. There is no operation in which one form has to be converted to the +other. + +## 5. Consequences elsewhere + +- **Interface fidelity against a Verilog baseline.** A packed port is what a hand-written + baseline declares, so a ported module's port list now matches by type rather than merely by + width. This is what makes per-module equivalence checking possible for array ports at all; + see [../private-plans/logic-eq-plan.md](../private-plans/logic-eq-plan.md) §3 for why an + unpacked port against a packed baseline port is not bit-comparable (SystemVerilog cannot + connect them, so no bit correspondence is defined). +- **Element indexing is representation-independent.** DFHDL element `i` is Verilog element `[i]` + in both forms. Only the *flat bit layout* differs, and only casts (§3.3) expose it. +- **Reference HDL churn.** The packed default rewrote all the vector-bearing reference outputs + under `lib/src/test/resources/ref/`; the diffs are mechanical (type-position dimensions, + reversed aggregates, streaming casts). + +## 6. Tests + +`PrintVerilogCodeSpec` pins the decision table directly: + +| test | pins | +|---|---| +| `unpacked vector representation for RAM/ROM shapes` | the memory patterns of §2.2 | +| `packed vector representation overrides` | port / constant-index / whole-vector use forcing packed | +| `vector nested in a struct is packed` | the cell-type recursion | +| `SInt-cell vectors keep the unpacked representation` | the signedness exclusion of §2.1 | + +## 7. Open issues + +- **Signed cells cannot pack** (§2.1). Lifting it needs named signed element types per IEEE + 1800-2017 7.4.3, i.e. a dedicated stage rather than a printer change. +- **Streaming casts are assignment-context-only** (§3.3). A whole-vector cast used as a general + subexpression has no legal packed rendering today. +- **`hasMemAccessPattern` is design-local**, which is why globals are excluded (§2.3). A + cross-design usage analysis would let a global ROM stay unpacked. +- **Namespace-derived package files carry no `` `default_nettype ``/`` `timescale `` header** + while every other emitted file does. slang rejects the mix ("design element does not have a + time scale defined but others in the design do"), so any design using a type package fails to + elaborate as a file set. Not strictly part of this feature, but it lands on the same emitted + output and blocks the equivalence flow that consumes it. From 1296f96a505e5fdd14c732e0cb812ad73933f975 Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 17:22:00 +0300 Subject: [PATCH 55/57] printers: packed arrays as the default SystemVerilog vector representation A DFHDL vector now prints as a packed descending array (element 0 at the LSB end, DFHDL index i = SV index i) under the SystemVerilog dialects, matching what hand-written baselines declare for bus-like array ports (SystemVerilog cannot connect a packed port to an unpacked one, so the old unpacked-only form was an incompatible interface). Unpacked survives only where required: the pre-SV dialects, cell types that cannot form a packed array (integer atoms, real, string, time, and signed cells, whose element part-select would silently lose signedness), and declarations whose usage follows the memory access pattern, so block-RAM/ROM inference is preserved. The usage classification is DFVal.hasMemAccessPattern in DFValAnalysis; the printer combines it with the type-level packability into unpackedVectorDcls (init references are representation-neutral on both sides). Bits<->vector casts preserve the DFHDL element-0-at-MSB bit order via a scalar-cell-granular streaming reversal ({< --- .claude/commands/new-stage.md | 14 ++ .../compiler/analysis/DFValAnalysis.scala | 51 ++++ .../stages/verilog/VerilogDataPrinter.scala | 20 +- .../stages/verilog/VerilogPrinter.scala | 52 +++++ .../stages/verilog/VerilogTypePrinter.scala | 62 ++++- .../stages/verilog/VerilogValPrinter.scala | 64 ++++- .../StagesSpec/PrintVerilogCodeSpec.scala | 219 ++++++++++++++++-- devdocs/packed-unpacked-arrays.md | 45 +++- docs/user-guide/type-system/index.md | 36 ++- .../hdl/CipherNoOpaques_defs.svh | 92 ++++---- .../verilog.sv2009/hdl/addRoundKey.sv | 8 +- .../verilog.sv2009/hdl/cipher.sv | 22 +- .../verilog.sv2009/hdl/keyExpansion.sv | 62 ++--- .../verilog.sv2009/hdl/mixColumns.sv | 38 +-- .../verilog.sv2009/hdl/rotWord.sv | 2 +- .../verilog.sv2009/hdl/shiftRows.sv | 8 +- .../verilog.sv2009/hdl/subBytes.sv | 8 +- .../verilog.sv2009/hdl/subWord.sv | 2 +- .../verilog.sv2009/hdl/Cipher_defs.svh | 92 ++++---- .../verilog.sv2009/hdl/addRoundKey.sv | 8 +- .../verilog.sv2009/hdl/cipher_0.sv | 22 +- .../verilog.sv2009/hdl/keyExpansion.sv | 62 ++--- .../verilog.sv2009/hdl/mixColumns.sv | 38 +-- .../verilog.sv2009/hdl/rotWord.sv | 2 +- .../verilog.sv2009/hdl/shiftRows.sv | 8 +- .../verilog.sv2009/hdl/subBytes.sv | 8 +- .../verilog.sv2009/hdl/subWord.sv | 2 +- .../verilog.sv2009/hdl/RegFile.sv | 2 +- 28 files changed, 766 insertions(+), 283 deletions(-) diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md index dff43d45a..dad06b28e 100644 --- a/.claude/commands/new-stage.md +++ b/.claude/commands/new-stage.md @@ -1553,6 +1553,20 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) member does, not just filter it out. An anonymous global (an intermediate of a global constant's expression) has no name to act on but still carries a `namespace`, and leaving it behind kept `DropPackages` emitting an empty package for a package it had just flattened away. +44. **A backend representation choice belongs in the printer, not in a stage or tag** — when a + decision only changes how one backend SPELLS the same IR (e.g. the Verilog packed-vs-unpacked + vector representation), an IR tag would violate printability (nothing in the printout + regenerates it) and a stage would leak one backend's concern into the shared IR. Split it in + two (`VerilogPrinter.unpackedVectorDcls` is the model): the backend-agnostic USAGE + classification goes to `compiler/ir`'s `analysis` package (`DFVal.hasMemAccessPattern`), + while the backend-specific parts (dialect gates, target-language type rules) stay on the + printer as a `lazy val` — printers are constructed per sub-DB `getSet`, so a design-local + analysis is self-contained, and the DB is immutable so laziness is safe. Two constraints: + the printer object may be shared, so NO mutable printer state — thread context flags as + extra parameters (add a printer-specific overload beside the shared abstract signature + rather than widening it); and if the representation must agree across values that meet in + one operation, the analysis rules themselves must guarantee the agreement (there is no + checker to catch a mismatch — the output is simply illegal HDL). --- 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 1efb2b4bf..6329562e0 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala @@ -315,6 +315,57 @@ object BlockRamVar: case _ => false case _ => false +extension (dfVal: DFVal) + //format: off + /** True for a vector-typed declaration (a variable or a named constant) whose shape and usage + * follow a MEMORY (RAM/ROM) access pattern, so a backend may keep it in a dedicated memory + * representation (e.g., a Verilog unpacked array, preserving block-RAM/ROM inference): + * + * - a port never does (it is part of the design interface) + * - an alias-bound constant (`val b = a`) never does (its value is a whole-vector read of + * its source) + * - any whole-vector use (an assignment/connection of the vector itself, a cast, a slice, + * a function argument) or any CONSTANT-index access disqualifies it + * - otherwise, a `VAR.SHARED` follows the pattern (a multi-ported RAM), and so does a + * declaration whose dynamic-index accesses include exactly one READ (a single-read + * RAM/ROM, constants included) + * + * An init reference is representation-neutral: it neither disqualifies the initialized + * declaration nor counts as a whole-vector read of the init value. + */ + //format: on + def hasMemAccessPattern(using MemberGetSet): Boolean = + def isReadAccess(dfVal: DFVal): Boolean = + dfVal.getReadDeps.exists { + case partial: DFVal.Alias.Partial => isReadAccess(partial) + case _ => true + } + def usageQualifies(isShared: Boolean): Boolean = + var wholeUse = false + var constIdx = false + var dynReads = 0 + dfVal.originMembersNoTypeRef.foreach { + case idx: DFVal.Alias.ApplyIdx if idx.relValRef.get == dfVal => + if (idx.relIdx.get.isConst) constIdx = true + else if (isReadAccess(idx)) dynReads += 1 + // an init reference is representation-neutral + case dcl: DFVal.Dcl if dcl.initRefList.exists(_.get == dfVal) => // skip + case _ => wholeUse = true + } + if (wholeUse || constIdx) false + else isShared || dynReads == 1 + dfVal.dfType match + case _: DFVector => + dfVal match + case DclPort() => false + case _: DFVal.Alias => false + case dcl: DFVal.Dcl => usageQualifies(dcl.modifier.isShared) + case DclConst() => usageQualifies(isShared = false) + case _ => false + case _ => false + end hasMemAccessPattern +end extension + extension (dcl: DFVal.Dcl) /** True when the declaration is emitted as an HDL VARIABLE (updated where it is written) rather * than an HDL SIGNAL (updated only once the enclosing process suspends). The classification is 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 884012b31..9e84b969b 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 @@ -59,12 +59,24 @@ protected trait VerilogDataPrinter extends AbstractDataPrinter: s"$verilogDefine$pkgQualifier${dfType.name}_${entryName}" case None => "?" val maxElementsPerLine = 64 - def csDFVectorElemCS(elemCS: List[String]): String = - elemCS.view.zipWithIndex.map((x, i) => + // Vector aggregates are index-keyed (`idx: value`), so the keys bind element indexes and the + // spelling is semantically order-free; the listing order follows the declared range direction: + // DESCENDING (`'{3: e3, ..., 0: e0}`) for the packed representation and ascending + // (`'{0: e0, ..., 3: e3}`) for the unpacked one. NOTE: vanilla yosys's own SV parser accepts + // neither index-keyed patterns nor any assignment pattern on a packed target; flows that read + // DFHDL output through yosys must use its slang frontend (which accepts both). + def csDFVectorElemCS(elemCS: List[String], unpackedOrder: Boolean): String = + val keyed = elemCS.view.zipWithIndex.map((x, i) => s"${i.toPaddedString(elemCS.length - 1, padWithZeros = false)}: $x" - ).toList.csList("'{", ",", "}") + ).toList + val ordered = if (unpackedOrder || !printer.supportPackedArrays) keyed else keyed.reverse + ordered.csList("'{", ",", "}") + // `unpackedOrder` applies to the OUTERMOST dimension only (the one that may be unpacked); + // nested dimensions are always packed, so the cell recursion drops the flag. + def csDFVectorData(dfType: DFVector, data: Vector[Any], unpackedOrder: Boolean): String = + csDFVectorElemCS(data.view.map(csConstData(dfType.cellType, _)).toList, unpackedOrder) def csDFVectorData(dfType: DFVector, data: Vector[Any]): String = - csDFVectorElemCS(data.view.map(csConstData(dfType.cellType, _)).toList) + csDFVectorData(dfType, data, unpackedOrder = false) def csDFOpaqueData(dfType: DFOpaque, data: Any): String = csConstData(dfType.actualType, data) def csDFStructData(dfType: DFStruct, data: List[Any]): 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 5f29076f3..238f544e4 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 @@ -23,6 +23,58 @@ class VerilogPrinter(val dialect: VerilogDialect)(using "Unsupported member for this VerilogPrinter." ) val tupleSupportEnable: Boolean = false + // the pre-SystemVerilog dialects have no packed (multi-dimensional) arrays + val supportPackedArrays: Boolean = + dialect match + case VerilogDialect.v95 | VerilogDialect.v2001 => false + case _ => true + //format: off + /** The vector-typed declarations (variables and constants) that keep the UNPACKED array + * representation under the SystemVerilog dialects, where a DFHDL vector prints as a PACKED + * (descending-range) array by default. A declaration stays unpacked in two cases: + * + * - a cell type this printer cannot express in a packed array (see + * [[supportsPackedVector]]) forces unpacked, unconditionally: a TYPE property, so every + * value of such a vector type agrees + * - otherwise, a non-global declaration whose shape/usage follows a memory access pattern + * (see [[dfhdl.compiler.analysis.hasMemAccessPattern]]) stays unpacked, so + * block-RAM/ROM inference is preserved; globals are always packed (their usage spans + * designs, while this analysis is design-local) + * + * The memory-pattern rules only admit declarations that are accessed element-by-element + * through dynamic indexes (or via their init, which is representation-neutral), which is + * what keeps the two representations from ever meeting in one operation. + */ + //format: on + lazy val unpackedVectorDcls: Set[DFVal] = + if (!supportPackedArrays) Set.empty + else + getSet.designDB.members.view.flatMap { + case dfVal: DFVal => + dfVal.dfType match + case vecType: DFVector => + dfVal match + case (_: DFVal.Dcl) | DclConst() => + if (!supportsPackedVector(vecType)) Some(dfVal) + else if (dfVal.isGlobal) None + else Option.when(dfVal.hasMemAccessPattern)(dfVal) + case _ => None + case _ => None + case _ => None + }.toSet + end unpackedVectorDcls + + /** Is this vector-typed declaration printed as an UNPACKED array? */ + def isUnpackedDcl(dfVal: DFVal): Boolean = unpackedVectorDcls.contains(dfVal) + + /** Is this vector-typed VALUE of the unpacked representation? Only a direct reference to an + * unpacked declaration (or an outer-dimension slice of one) is unpacked; every expression value, + * element select (an inner dimension), and cast result is packed. + */ + def isUnpackedVal(dfVal: DFVal): Boolean = + dfVal match + case alias: DFVal.Alias.ApplyRange => isUnpackedVal(alias.relValRef.get) + case _ => unpackedVectorDcls.contains(dfVal) def csViaConnectionSep: String = "," def csAssignment(lhsStr: String, rhsStr: String, lhsDcl: DFVal.Dcl): String = s"$lhsStr = $rhsStr;" 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 78cc97b0c..02d049d20 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 @@ -100,14 +100,72 @@ protected trait VerilogTypePrinter extends AbstractTypePrinter: end csDFEnumDcl def csDFEnum(dfType: DFEnum, typeCS: Boolean): String = csDFEnumTypeName(dfType) + // Whether this vector prints as a PACKED array. Requires a SystemVerilog dialect, and an + // integral scalar cell type: packed dimensions apply only to single-bit types, enums, packed + // structs/unions, and other packed arrays (IEEE 1800-2017 7.4.1). Integer atom types (`int`), + // `real`, `string`, and time values cannot form packed arrays, so vectors over them keep the + // unpacked representation regardless of usage. SIGNED cells (`SInt`, signed fixed-point) are + // also kept unpacked: an element select of an (anonymous-typed) packed array is a part-select, + // which is always unsigned, so the cell signedness would be lost (a future dedicated stage may + // lift this restriction, e.g. via named signed element types per IEEE 1800-2017 7.4.3). This + // is a TYPE property, so every value of the same vector type agrees on it and + // mixed-representation connections can never print. + def supportsPackedVector(dfType: DFVector): Boolean = + printer.supportPackedArrays && { + def packable(cellType: DFType): Boolean = cellType match + case _: DFBoolOrBit | _: DFBitsWL | _: DFEnum => true + case dec: DFDecimal => !dec.isDFInt32 && !dec.signed + case _: DFStruct => true + case vec: DFVector => packable(vec.cellType) + case op: DFOpaque => packable(op.actualType) + case _ => false + packable(dfType.cellType) + } + // the innermost non-vector cell type, whose width is the packed<->DFHDL bit-order reversal + // grouping of the streaming casts + def vectorScalarCellType(dfType: DFVector): DFType = + dfType.cellType match + case vec: DFVector => vectorScalarCellType(vec) + case cellType => cellType + // The after-the-name array ranges of the UNPACKED representation (ascending). Under the + // SystemVerilog dialects a packed-capable vector carries its dimensions in the type itself + // (see `csDFVector`), so this yields nothing for it; the pre-SystemVerilog dialects (and + // non-integral cell types) keep all dimensions here. def csDFVectorRanges(dfType: DFType): String = dfType match - case vec: DFVector => + case vec: DFVector if !supportsPackedVector(vec) => s" [0:${vec.cellDimParamRefs.head.uboundCS}]${csDFVectorRanges(vec.cellType)}" case _ => "" + // the descending packed dimensions of this vector, outermost first (`[N-1:0][M-1:0]...`) + private def csDFVectorPackedDims(dfType: DFType): String = + dfType match + case vec: DFVector => + s"[${vec.cellDimParamRefs.head.uboundCS}:0]${csDFVectorPackedDims(vec.cellType)}" + case _ => "" + // The complete packed-array type: the scalar cell's base keyword/name, then the vector + // dimensions (descending, outermost first), then the cell's own packed dimensions. Only + // unsigned decimal cells reach the DFDecimal branch: signed cells never pack (see + // `supportsPackedVector`). + private def csDFVectorPacked(dfType: DFVector): String = + val dims = csDFVectorPackedDims(dfType) + vectorScalarCellType(dfType) match + case _: DFBoolOrBit => s"logic $dims" + case cell: DFBitsWL => + s"logic $dims[${cell.widthParamRef.hboundCS(cell.lowIdxRef)}:${cell.lowIdxRef.refCodeString}]" + case cell: DFDecimal => + import cell.* + if (fractionWidth != 0) + s"logic $dims`ufix(${magnitudeWidthParamRef.refCodeString}, $fractionWidth)" + else s"logic $dims[${magnitudeWidthParamRef.uboundCS}:0]" + case cell: DFEnum => s"${csDFEnumTypeName(cell)} $dims" + case cell: DFStruct => s"${csDFStructTypeName(cell)} $dims" + case cell: DFOpaque => s"${csDFOpaqueTypeName(cell)} $dims" + case _ => printer.unsupported + end csDFVectorPacked def csDFVector(dfType: DFVector, typeCS: Boolean): String = import dfType.* - s"${csDFType(cellType, typeCS)}" + if (supportsPackedVector(dfType)) csDFVectorPacked(dfType) + else s"${csDFType(cellType, typeCS)}" def csDFOpaqueTypeName(dfType: DFOpaque): String = s"${pkgQualifier(dfType)}${dfType.name}" def csDFOpaqueDcl(dfType: DFOpaque): String = 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 e0d3eb044..3866371c5 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 @@ -57,17 +57,40 @@ protected trait VerilogValPrinter extends AbstractValPrinter: // yields the applied value in both cases. printer.csConstData(param.dfType, param.appliedOrDefaultVal.getConstDataOrDefault[Any]) + // The type-part and the after-the-name array-range of a declaration. A packed vector carries + // its dimensions in the type itself; an unpacked declaration unpacks its OUTERMOST dimension + // after the name (ascending) while the cell keeps its (packed) type form; the pre-SystemVerilog + // dialects and non-integral cell types unpack all dimensions after the name. + private def csDclTypeAndRange(dfVal: DFVal): (String, String) = + dfVal.dfType match + case vec: DFVector if printer.isUnpackedDcl(dfVal) && printer.supportsPackedVector(vec) => + (printer.csDFType(vec.cellType), s" [0:${vec.cellDimParamRefs.head.uboundCS}]") + case t => (printer.csDFType(t), printer.csDFVectorRanges(t)) + // The ascending rendering of an unpacked declaration's init/default value. Only an anonymous + // constant-data or vector-literal value has an order-sensitive aggregate to adapt; everything + // else prints its regular form. + private def csUnpackedInitValue(value: DFVal, csRegular: => String): String = + value match + case const: DFVal.Const => + const.dfType match + case dt: DFVector => + printer.csDFVectorData(dt, const.data.asInstanceOf[Vector[Any]], unpackedOrder = true) + case _ => csRegular + case func @ DFVal.Func(dfType = _: DFVector, op = Func.Op.++) => + printer.csDFVectorElemCS(func.args.map(_.refCodeString), unpackedOrder = true) + case _ => csRegular def csDFValDclConst(dfVal: DFVal.CanBeExpr): String = - val arrRange = printer.csDFVectorRanges(dfVal.dfType) + val (csRawType, arrRange) = csDclTypeAndRange(dfVal) val endOfStatement = if (dfVal.isGlobal) ";" else "" val default = dfVal match // for non-top-level design parameters, we fetch the default value if it is defined. // for all other cases, we get the parameter constant data and use that as default value. // using the constant data only happens in verilog.v95, since parameters are declared in // the body and must have defaults. - case param: DesignParam => csDesignParamDefault(param) - case _ => csDFValExpr(dfVal) - val csType = printer.csDFType(dfVal.dfType).emptyOr(_ + " ") + case param: DesignParam => csDesignParamDefault(param) + case _ if printer.isUnpackedDcl(dfVal) => csUnpackedInitValue(dfVal, csDFValExpr(dfVal)) + case _ => csDFValExpr(dfVal) + val csType = csRawType.emptyOr(_ + " ") val csTypeNoLogic = if (supportLogicType) csType else csType.replace("logic ", "") val keyword = if (supportLocalParam && !dfVal.isDesignParam && !dfVal.isGlobal) "localparam" @@ -80,7 +103,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: end csDFValDclConst def csDFValDclWithoutInit(dfVal: Dcl): String = - val dfTypeStr = printer.csDFType(dfVal.dfType) + val (dfTypeStr, arrRange) = csDclTypeAndRange(dfVal) val modifier = dfVal.modifier.dir match case Modifier.IN => "input wire " case Modifier.OUT => "output " @@ -94,7 +117,6 @@ protected trait VerilogValPrinter extends AbstractValPrinter: val fixedDFTypeStr = if (supportLogicType) dfTypeStr else dfTypeStr.replace("logic ", regOrWireRep).replace("logic", regOrWireRep.trim) - val arrRange = printer.csDFVectorRanges(dfVal.dfType) s"$modifier${fixedDFTypeStr.emptyOr(_ + " ")}${dfVal.getName}$arrRange" end csDFValDclWithoutInit def csInitKeyword: String = "=" @@ -103,7 +125,11 @@ protected trait VerilogValPrinter extends AbstractValPrinter: case VerilogDialect.v95 | VerilogDialect.v2001 => false case _ => true override val supportOutputInlineInit: Boolean = false - def csInitSingle(ref: Dcl.InitRef): String = ref.refCodeString + def csInitSingle(ref: Dcl.InitRef): String = + ref.originMember match + case dcl: Dcl if printer.isUnpackedDcl(dcl) => + csUnpackedInitValue(ref.get, ref.refCodeString) + case _ => ref.refCodeString def csInitSeq(refs: List[Dcl.InitRef]): String = printer.unsupported def csDFValDclEnd(dfVal: Dcl): String = "" // The `initial` block an output port's init needs (no Verilog dialect can inline one). A @@ -249,7 +275,9 @@ protected trait VerilogValPrinter extends AbstractValPrinter: case DFVal.Func.Op.++ => dfVal.dfType match case DFVector(_, _) => - printer.csDFVectorElemCS(args.map(_.refCodeString)) + // a vector-literal EXPRESSION is always packed: an unpacked declaration's only + // aggregate is its init, which prints through `csUnpackedInitValue` + printer.csDFVectorElemCS(args.map(_.refCodeString), unpackedOrder = false) case DFStruct(_, _) => args.map(_.refCodeString).csList(literalGroupOpen, ",", "}") // all args are the same ==> repeat function @@ -409,6 +437,14 @@ protected trait VerilogValPrinter extends AbstractValPrinter: else relValStr case (toStruct: DFStruct, _: DFBitsWL) => s"${toStruct.name}'($relValStr)" + // A packed vector and the DFHDL bits form differ exactly by a scalar-cell-granular + // reversal: DFHDL element 0 holds the MSBs of the bits form, while a packed (descending) + // array holds element 0 at the LSB end. The streaming operator with the scalar cell width + // as the slice size is precisely that reversal, in both directions. + case (toVector: DFVector, _: DFBitsWL) if printer.supportsPackedVector(toVector) => + val csCellWidth = + printer.csInlinedWidth(printer.vectorScalarCellType(toVector)).applyBrackets() + s"{<<$csCellWidth{$relValStr}}" case (toVector: DFVector, _: DFBitsWL) => def to_vector_conv(vectorType: DFVector, relHighIdx: Int): String = val vecLength = vectorType.lengthUNSAFE @@ -449,6 +485,14 @@ protected trait VerilogValPrinter extends AbstractValPrinter: end from_vector_conv assert(tWidth == fromType.widthUNSAFE) from_vector_conv(fromVector, "") + // a parametric-length packed vector cannot enumerate its elements, so the cast is the + // scalar-cell-granular streaming reversal (see the to-vector case above). NOTE: a + // streaming concatenation is only legal in an assignment-like context, not as a general + // subexpression, which is the same restriction the element-enumerated `'{...}` form has. + case (_: DFBitsWL, fromVector: DFVector) if printer.supportsPackedVector(fromVector) => + val csCellWidth = + printer.csInlinedWidth(printer.vectorScalarCellType(fromVector)).applyBrackets() + s"{<<$csCellWidth{$relValStr}}" case (DFBitsWL(tWidthRef, _), DFBit | DFBool) => if (printer.allowWidthCastSyntax) s"${tWidthRef.refCodeString.applyBrackets()}'($relValStr)" @@ -486,6 +530,10 @@ protected trait VerilogValPrinter extends AbstractValPrinter: dfVal.dfType match case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) => s"${dfVal.relValCodeString}[${dfVal.idxHighRef.refCodeString}:${dfVal.idxLowRef.refCodeString}]" + // a packed vector's range is descending, so its part-select is [high:low] + case vec: DFVector + if printer.supportsPackedVector(vec) && !printer.isUnpackedVal(dfVal.relValRef.get) => + s"${dfVal.relValCodeString}[${dfVal.idxHighRef.refCodeString}:${dfVal.idxLowRef.refCodeString}]" case _ => s"${dfVal.relValCodeString}[${dfVal.idxLowRef.refCodeString}:${dfVal.idxHighRef.refCodeString}]" end match diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 054e3be18..b267f469b 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -392,11 +392,11 @@ class PrintVerilogCodeSpec extends StageSpec: | localparam logic [7:0] c13 = 8'hxx; | localparam logic signed [7:0] c14 = $signed(8'hxx); | localparam DFTuple2 c15 = '{3'h0, 1'b1}; - | localparam logic [7:0] c16 [0:6] [0:4] = '{ - | 0: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, 1: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, - | 2: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, 3: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, - | 4: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, 5: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44}, - | 6: '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33, 4: 8'h44} + | localparam logic [6:0][4:0][7:0] c16 = '{ + | 6: '{4: 8'h44, 3: 8'h33, 2: 8'h22, 1: 8'h11, 0: 8'h00}, 5: '{4: 8'h44, 3: 8'h33, 2: 8'h22, 1: 8'h11, 0: 8'h00}, + | 4: '{4: 8'h44, 3: 8'h33, 2: 8'h22, 1: 8'h11, 0: 8'h00}, 3: '{4: 8'h44, 3: 8'h33, 2: 8'h22, 1: 8'h11, 0: 8'h00}, + | 2: '{4: 8'h44, 3: 8'h33, 2: 8'h22, 1: 8'h11, 0: 8'h00}, 1: '{4: 8'h44, 3: 8'h33, 2: 8'h22, 1: 8'h11, 0: 8'h00}, + | 0: '{4: 8'h44, 3: 8'h33, 2: 8'h22, 1: 8'h11, 0: 8'h00} | }; | localparam real c17 = 3.14159; | localparam real c18 = -2.71828; @@ -926,7 +926,7 @@ class PrintVerilogCodeSpec extends StageSpec: |`timescale 1ns/1ps | |module Foo( - | output logic [9:0] matrix [0:7] [0:7] + | output logic [7:0][7:0][9:0] matrix |); | `include "dfhdl_defs.svh" | @@ -1599,14 +1599,14 @@ class PrintVerilogCodeSpec extends StageSpec: |`timescale 1ns/1ps | |module Foo( - | input wire logic i1 [0:7], + | input wire logic [7:0] i1, | output logic [7:0] o1, | input wire logic [7:0] i2, - | output logic o2 [0:7] + | output logic [7:0] o2 |); | `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]}; + | assign o2 = {<<1{i2}}; |endmodule""".stripMargin ) } @@ -3421,7 +3421,7 @@ class PrintVerilogCodeSpec extends StageSpec: | parameter int WID = N * W, | parameter int LEN = N |)( - | input wire logic [W - 1:0] vec [0:N - 1], + | input wire logic [N - 1:0][W - 1:0] vec, | input wire logic [LI - 1:0] din, | output logic [LI - 1:0] dout, | output logic [WID - 1:0] flat, @@ -3429,7 +3429,7 @@ class PrintVerilogCodeSpec extends StageSpec: |); | `include "dfhdl_defs.svh" | assign dout = din; - | assign flat = {vec}; + | assign flat = {< IN + val we = Bit <> IN + val addr = Bits(2) <> IN + val din = Bits(8) <> IN + val q1 = Bits(8) <> OUT + val q2 = Bits(8) <> OUT + val q3 = Bits(8) <> OUT + val sh = Bits(8) X 4 <> VAR.SHARED + val ram = Bits(8) X 4 <> VAR + val rom: Bits[8] X 4 <> CONST = Vector(h"00", h"11", h"22", h"33") + process(clk.rising): + if (we) sh(addr) :== din + process(clk.rising): + q1 :== sh(addr) + process(clk.rising): + if (we) ram(addr) :== din + else q2 :== ram(addr) + q3 <> rom(addr) + end Mems + val top = (new Mems).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module Mems( + | input wire logic clk, + | input wire logic we, + | input wire logic [1:0] addr, + | input wire logic [7:0] din, + | output logic [7:0] q1, + | output logic [7:0] q2, + | output logic [7:0] q3 + |); + | `include "dfhdl_defs.svh" + | localparam logic [7:0] rom [0:3] = '{0: 8'h00, 1: 8'h11, 2: 8'h22, 3: 8'h33}; + | /* verilator lint_off MULTIDRIVEN */ + | logic [7:0] sh [0:3]; + | /* verilator lint_on MULTIDRIVEN */ + | logic [7:0] ram [0:3]; + | always @(posedge clk) + | begin + | if (we) sh[addr] <= din; + | end + | always_ff @(posedge clk) + | begin + | q1 <= sh[addr]; + | end + | always_ff @(posedge clk) + | begin + | if (we) ram[addr] <= din; + | else q2 <= ram[addr]; + | end + | assign q3 = rom[addr]; + |endmodule""".stripMargin + ) + } + // the packed-representation overrides: a whole-vector use (rule e) and a constant-index access + // (rule d) force the packed form even in the presence of a single dynamic read, and a + // multi-read memory (rule h) is packed as well; aggregates keep the index-keyed form (the + // keys bind element indexes), listed descending to match the packed range direction + test("packed vector representation overrides") { + given options.CompilerOptions.Backend = _.verilog.sv2009 + class Packed extends EDDesign: + val clk = Bit <> IN + val we = Bit <> IN + val addr = Bits(2) <> IN + val addrB = Bits(2) <> IN + val din = Bits(8) <> IN + val vin = Bits(8) X 4 <> IN + val vout = Bits(8) X 4 <> OUT + val q1 = Bits(8) <> OUT + val q2 = Bits(8) <> OUT + val q3 = Bits(8) <> OUT + val pk: Bits[8] X 4 <> CONST = Vector(h"00", h"11", h"22", h"33") + val w = Bits(8) X 4 <> VAR + val t = Bits(8) X 4 <> VAR + val m = Bits(8) X 4 <> VAR + w <> vin + q1 <> w(addr) + vout <> pk + process(clk.rising): + t(0) :== din + q2 :== t(addr) + process(clk.rising): + if (we) m(addr) :== din + q3 :== m(addr) | m(addrB) + end Packed + val top = (new Packed).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module Packed( + | input wire logic clk, + | input wire logic we, + | input wire logic [1:0] addr, + | input wire logic [1:0] addrB, + | input wire logic [7:0] din, + | input wire logic [3:0][7:0] vin, + | output logic [3:0][7:0] vout, + | output logic [7:0] q1, + | output logic [7:0] q2, + | output logic [7:0] q3 + |); + | `include "dfhdl_defs.svh" + | localparam logic [3:0][7:0] pk = '{3: 8'h33, 2: 8'h22, 1: 8'h11, 0: 8'h00}; + | logic [3:0][7:0] w; + | logic [3:0][7:0] t; + | logic [3:0][7:0] m; + | assign w = vin; + | assign q1 = w[addr]; + | assign vout = pk; + | always_ff @(posedge clk) + | begin + | t[0] <= din; + | q2 <= t[addr]; + | end + | always_ff @(posedge clk) + | begin + | if (we) m[addr] <= din; + | q3 <= m[addr] | m[addrB]; + | end + |endmodule""".stripMargin + ) + } + // a vector nested in a struct is always packed (an unpacked array cannot be a packed-struct + // member), and its field selection chains index the packed dimensions directly + test("vector nested in a struct is packed") { + given options.CompilerOptions.Backend = _.verilog.sv2009 + case class Pkt(v: Bits[8] X 2 <> VAL, ok: Bit <> VAL) extends Struct + class StructVec extends EDDesign: + val x = Pkt <> IN + val y = Bits(8) <> OUT + y <> x.v(1) + end StructVec + val top = (new StructVec).getCompiledCodeString + assertNoDiff( + top, + """|typedef struct packed { + | logic [1:0][7:0] v; + | logic ok; + |} Pkt; + | + |`default_nettype none + |`timescale 1ns/1ps + |`include "StructVec_defs.svh" + | + |module StructVec( + | input wire Pkt x, + | output logic [7:0] y + |); + | `include "dfhdl_defs.svh" + | assign y = x.v[1]; + |endmodule""".stripMargin + ) + } + // a signed-cell vector never packs: an element select of an (anonymous-typed) packed array is + // an unsigned part-select, so the cell signedness would be lost; the unpacked element select + // keeps the declared (signed) cell type + test("SInt-cell vectors keep the unpacked representation") { + given options.CompilerOptions.Backend = _.verilog.sv2009 + class SignedVec extends EDDesign: + val iv = SInt(8) X 4 <> IN + val o = SInt(8) <> OUT + val b = Bit <> OUT + o <> iv(0) + b <> (iv(1) < iv(2)) + end SignedVec + val top = (new SignedVec).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module SignedVec( + | input wire logic signed [7:0] iv [0:3], + | output logic signed [7:0] o, + | output logic b + |); + | `include "dfhdl_defs.svh" + | assign o = iv[0]; + | assign b = iv[1] < iv[2]; + |endmodule""".stripMargin + ) + } end PrintVerilogCodeSpec diff --git a/devdocs/packed-unpacked-arrays.md b/devdocs/packed-unpacked-arrays.md index 7c94e4bae..923db8b0c 100644 --- a/devdocs/packed-unpacked-arrays.md +++ b/devdocs/packed-unpacked-arrays.md @@ -75,6 +75,10 @@ Answers "does this declaration look like a memory?" for a `DFVal.Dcl` or a named dynamic-index accesses include **exactly one read** (the single-read RAM/ROM shape, constants included, which covers `localparam` ROMs) +Only a *constant*-index access disqualifies; dynamic-index accesses are the memory pattern +itself. Reading "individual index access" as covering dynamic indexes too would make the +single-read rule unreachable, since every RAM write is such an access. + An **`init` reference is representation-neutral**: it neither disqualifies the initialized declaration nor counts as a whole-vector read of the init value. Without that carve-out every initialized memory would be forced packed by its own initializer. @@ -139,17 +143,27 @@ decimals never reach it, per §2.1. ### 3.2 Aggregate literals -The two forms need different aggregate syntax, and the packed one is **positional and reversed**: +Both forms use the **index-keyed** aggregate (`idx: value`), whose keys bind element indexes and +make the spelling semantically order-free; the listing order follows the declared range +direction: ```verilog -'{e3, e2, e1, e0} // packed: leftmost position binds the HIGHEST index -'{0: e0, 1: e1, 2: e2} // unpacked: index-labeled, ascending +'{3: e3, 2: e2, 1: e1, 0: e0} // packed: listed descending, like its range +'{0: e0, 1: e1, 2: e2, 3: e3} // unpacked: listed ascending ``` -`csDFVectorElemCS(elemCS, unpackedOrder)` picks between them, and `unpackedOrder` applies to the +`csDFVectorElemCS(elemCS, unpackedOrder)` picks the order, and `unpackedOrder` applies to the **outermost dimension only**: nested dimensions are always packed, so the cell recursion drops the flag. +Tool support for index keys on a *packed* target was verified empirically: verilator executes +them with the correct element binding (keys are honored regardless of listing order) and slang +accepts them. Vanilla yosys's own SV parser is the outlier, and not because of the keys on +packed specifically: it accepts **no** assignment pattern on a packed target and no index keys +even on unpacked ones (so DFHDL's pre-existing unpacked ROM form was already unreadable there). +Any flow reading DFHDL output through yosys must use its slang frontend, which the equivalence +flow already does. + `csUnpackedInitValue` handles the one place a value's order must be adapted to its target: an unpacked declaration's `init`/default. Only an anonymous constant-data value or a vector literal (a `Func.Op.++`) has an order-sensitive aggregate; everything else prints its regular form. @@ -165,10 +179,19 @@ emitted with the streaming operator: {<<8{v}} // cell width 8 ``` -Both directions use it (`DFVector` ← `DFBitsWL` and `DFBitsWL` ← `DFVector`), grouped by -`vectorScalarCellType`'s width. Note the restriction recorded in the source: **a streaming -concatenation is only legal in an assignment-like context**, not as a general subexpression: -the same restriction the element-enumerated `'{...}` form already had. +The grouping width comes from `vectorScalarCellType`. The two directions differ, and the +difference is deliberate: + +- `DFVector` ← `DFBitsWL` (to-vector) always streams. **A streaming concatenation is only legal + in an assignment-like context**, not as a general subexpression, but that is the same + restriction the element-enumerated `'{...}` form already had, so nothing is lost. +- `DFBitsWL` ← `DFVector` (from-vector) keeps the element-enumerated concatenation + (`{v[0], v[1], ..., v[N-1]}`, element 0 at the MSB end, correct for both representations) + whenever the length is a literal, precisely because a plain concatenation *is* a general + expression (`v.bits | x` must print). Only a parametric-length source, which cannot enumerate + its elements, falls back to the streaming form and inherits its context restriction (it + replaces the previous `{v}` spelling, which was not legal SystemVerilog over an unpacked + array either). ### 3.4 Part-selects @@ -225,8 +248,10 @@ other. - **Signed cells cannot pack** (§2.1). Lifting it needs named signed element types per IEEE 1800-2017 7.4.3, i.e. a dedicated stage rather than a printer change. -- **Streaming casts are assignment-context-only** (§3.3). A whole-vector cast used as a general - subexpression has no legal packed rendering today. +- **Streaming casts are assignment-context-only** (§3.3). A bits-to-vector cast (or a + parametric-length vector-to-bits cast) used as a general subexpression has no legal packed + rendering today; the literal-length vector-to-bits direction is covered by the + element-enumerated concatenation. - **`hasMemAccessPattern` is design-local**, which is why globals are excluded (§2.3). A cross-design usage analysis would let a global ROM stay unpacked. - **Namespace-derived package files carry no `` `default_nettype ``/`` `timescale `` header** diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index b9b88963f..58fff674b 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -573,7 +573,7 @@ The `#!scala for` runs over a Scala range in a concurrent scope, so it is an ela ```verilog module LaneConcat( /* the four input lanes */ - input wire logic [7:0] lanes [0:3], + input wire logic [3:0][7:0] lanes, /* the packed word */ output logic [31:0] word ); @@ -594,7 +594,7 @@ Three things are worth noticing in the generated code: 2. **Only two of the bindings kept a name.** `acc` names the *first* binding (`lanes[0]`), which is where the Scala name was introduced; the intermediate concatenations are anonymous and were folded away. `allLanes` is the `#!scala val` that froze the result. -3. **The vector port survives as a vector.** `lanes` is emitted as an unpacked array (`input wire logic [7:0] lanes [0:3]`) and indexed with constants, since every index was resolved during elaboration. +3. **The vector port survives as a vector.** `lanes` is emitted as a packed array (`input wire logic [3:0][7:0] lanes`, see [vector representation][DFVector-verilog-representation]) and indexed with constants, since every index was resolved during elaboration. /// /// tab | Generated VHDL @@ -1019,7 +1019,7 @@ val b6: Bits[6] <> CONST = all(0) /// details | Transitioning from Verilog type: verilog -* __Specifying a width instead of an index range:__ In Verilog bit vectors are declared with an index range that enables outliers like non-zero index start, negative indexing or changing bit order. These use-cases are rare and they are better covered using different language constructs. Therefore, DFHDL simplifies things by only requiring a single width/length argument which yields a `[width-1:0]` sized vector (for [generic vectors][DFVector] the element order the opposite). For the rare designs that genuinely need a non-zero low index, DFHDL provides the dedicated [`BitsHL`][DFBitsHL] constructor. +* __Specifying a width instead of an index range:__ In Verilog bit vectors are declared with an index range that enables outliers like non-zero index start, negative indexing or changing bit order. These use-cases are rare and they are better covered using different language constructs. Therefore, DFHDL simplifies things by only requiring a single width/length argument which yields a `[width-1:0]` sized vector ([generic vectors][DFVector] follow the same descending `[length-1:0]` convention in their default packed form, and only their unpacked memory form uses the ascending `[0:length-1]` order; see [vector representation][DFVector-verilog-representation]). For the rare designs that genuinely need a non-zero low index, DFHDL provides the dedicated [`BitsHL`][DFBitsHL] constructor. * __Additional constructors:__ DFHDL provides additional constructs to simplify some common Verilog bit vector declaration. For example, instead of declaring `reg [$clog2(DEPTH)-1:0] addr` in Verilog, in DFHDL simply declare `val addr = Bits.until(DEPTH) <> VAR`. /// @@ -1560,6 +1560,36 @@ matrix(1)(2) := 42 matrix := all(all(0)) // All elements to 0 ``` +#### Verilog Representation: Packed vs. Unpacked Arrays {#DFVector-verilog-representation} + +Under the SystemVerilog backends (`verilog.sv2005` and newer), a DFHDL vector is emitted as a **packed** array by default, with a *descending* dimension range: + +```scala +val vin = Bits(8) X 4 <> IN +``` +```verilog +input wire logic [3:0][7:0] vin +``` + +Element indexing is independent of the representation: DFHDL element `i` is Verilog element `[i]` in both forms (in the packed form, element 0 occupies the least-significant bits). + +A vector declaration keeps the **unpacked** ascending form (`logic [7:0] mem [0:3]`) only when it matches a memory shape, so block-RAM/ROM inference is preserved. The decision follows the declaration's shape and usage: + +1. A vector of a non-integral cell type (`Int`, `Double`, `String`, time values) is always unpacked, for every value of that type; SystemVerilog forbids packed arrays over such types (IEEE 1800-2017 7.4.1). A vector of *signed* cells (`SInt`, signed fixed-point) is always unpacked as well: an element select of a packed array is an unsigned part-select, so the cell signedness would be lost, while an unpacked element select keeps the declared (signed) cell type. +2. A **port** is packed. +3. A **constant-index access** (`vec(3)`) forces packed. +4. A **whole-vector use** (assigned, connected, or read as a whole; sliced; compared; cast) forces packed. An `init` is representation-neutral: it neither forces the initialized declaration packed nor counts as a whole-vector read of a named init value. +5. A **`VAR.SHARED`** declaration is unpacked (the multi-ported RAM template). +6. A declaration whose dynamic-index accesses include exactly **one read** (`vec(addr)`) is unpacked; this is the single-read RAM/ROM shape, and applies to constants as well (`localparam` ROMs). +7. Anything else is packed. + +The `verilog.v95`/`verilog.v2001` backends have no packed (multi-dimensional) arrays, so under them every vector remains unpacked. The VHDL backends are unaffected: they emit named array types with ascending ranges as before. + +Two consequences of the packed form are worth knowing: + +* A whole-vector ⇄ `Bits` cast preserves the DFHDL bit order, where element 0 holds the *most*-significant bits. Since a packed array stores element 0 at the *least*-significant end, such casts emit an explicit element-order reversal (a streaming `{< Date: Mon, 17 Aug 2026 17:25:18 +0300 Subject: [PATCH 56/57] verilog: file header on the namespace-derived package files `default_nettype` and `timescale` are compilation-unit state, not file state, so a file that omits them inherits whatever the previously compiled file left behind. The package files are compiled ahead of the designs and carried no header of their own, which left their compilation dependent on ordering. They now emit the same `csLibrary` header a design file does, which also subsumes the global defs include they were emitting by hand. --- .../stages/verilog/VerilogPrinter.scala | 13 ++++++++----- .../StagesSpec/PrintVerilogCodeSpec.scala | 18 +++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) 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 238f544e4..21d241e26 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 @@ -278,11 +278,14 @@ class VerilogPrinter(val dialect: VerilogDialect)(using case _ => true override def packageFileName(pkgName: String): String = s"$pkgName.sv" override def csPackageFileContent(pkgName: String, namespace: String, typeDcls: String): String = - // the global defs header may be referenced by packaged type declarations - // (e.g. a struct field of a global-placed named type); its include guard makes - // the include harmless otherwise - sn"""|package $pkgName; - |${if (hasGlobalContent) s"""`include "$globalFileName"""" else ""} + // A package file carries the same file header as a design file. The directives are + // COMPILATION-UNIT state rather than file state, so a file that omits them inherits + // whatever the previously compiled file left behind, and packages compile ahead of the + // designs. `csLibrary` also emits the global defs include, which packaged type + // declarations may reference (e.g. a struct field of a global-placed named type). + sn"""|${csLibrary(getSet.designDB.inSimulation, minTimeUnitGlobalOpt)} + | + |package $pkgName; |$typeDcls |endpackage |""" diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index b267f469b..c31084dc0 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -3994,8 +3994,11 @@ class PrintVerilogCodeSpec extends StageSpec: | logic [1:0] g; |} GlbNsStruct; |parameter logic [7:0] GlbNsConst = 8'd3; - |package typespkg1; + |`default_nettype none + |`timescale 1ns/1ps |`include "PkgTop_defs.svh" + | + |package typespkg1; |typedef struct packed { | logic [7:0] a; | logic b; @@ -4016,8 +4019,11 @@ class PrintVerilogCodeSpec extends StageSpec: |parameter logic [7:0] PkgDerived = pkgCalc(PkgConst); |endpackage | - |package typespkg2; + |`default_nettype none + |`timescale 1ns/1ps |`include "PkgTop_defs.svh" + | + |package typespkg2; |typedef struct packed { | typespkg1::PkgStruct s; | logic [7:0] n; @@ -4053,7 +4059,10 @@ class PrintVerilogCodeSpec extends StageSpec: val top = (new DualTop).getCompiledCodeString assertNoDiff( top, - """|package dualpkg1; + """|`default_nettype none + |`timescale 1ns/1ps + | + |package dualpkg1; |typedef struct packed { | logic [3:0] v; |} Shared; @@ -4066,6 +4075,9 @@ class PrintVerilogCodeSpec extends StageSpec: |parameter logic [7:0] SharedDerived = calc1(SharedConst); |endpackage | + |`default_nettype none + |`timescale 1ns/1ps + | |package dualpkg2; |typedef struct packed { | logic [7:0] v; From c85c18ffc835d092e6fe84a4f2c3132ed2a4d58a Mon Sep 17 00:00:00 2001 From: Oron Date: Mon, 17 Aug 2026 18:57:48 +0300 Subject: [PATCH 57/57] core: inject a global operand's context before SimplifyFunc dereferences it A global value's refTable bindings live in its own DesignContext, injected into the run's DB only at first reference (refTW -> injectGlobalCtx). The SimplifyFunc extractors run on the raw IR args before any refTW, so a never-yet-referenced global alias operand (an object-scoped Int <> CONST whose value is another const) crashed with `Missing ref` the moment an extractor stripped it, e.g. as the left operand of `-` (SelfCancelling) or as a max operand against a literal (MaxMinChainAbsorb). Injecting each operand's global context up front is exactly what refTW does moments later, idempotent, and covers every extractor including the global-scope ones. Fixes #494 Co-Authored-By: Claude Fable 5 --- .claude/commands/bugfix.md | 31 ++++++++++++ .../StagesSpec/PrintCodeStringSpec.scala | 50 +++++++++++++++++++ .../main/scala/dfhdl/core/SimplifyFunc.scala | 7 +++ 3 files changed, 88 insertions(+) diff --git a/.claude/commands/bugfix.md b/.claude/commands/bugfix.md index dd323b1f1..0c7091b86 100644 --- a/.claude/commands/bugfix.md +++ b/.claude/commands/bugfix.md @@ -338,6 +338,37 @@ all three call shapes (nested operand, standalone statement, `val` RHS) and chan the forwarded argument's name is overwritten by `setName`'s own argument anyway. Check the naming-sensitive suites before assuming that holds for another op. +### Front-end analysis over raw IR operands must inject a global operand's context first + +A `Missing ref "TW_..."` thrown DURING ELABORATION (from `MutableDB.getMember`, not from a +stage's `SanityCheck` or `originMemberTable`) is its own species: a **global** member (a +top-level or object-scoped `Int <> CONST` and friends) carries its refTable bindings in its own +`DesignContext`, and the current run can resolve them only after `injectGlobalCtx()` merges that +context in — which `refTW` performs at the member's first REFERENCE. Any front-end analysis that +dereferences an operand's refs BEFORE minting a ref therefore crashes on a never-yet-referenced +global. `SimplifyFunc` was the case (issue #494): `DFVal.Func.applyFromIR` runs the extractors on +the raw `ir.DFVal` args before any `refTW`, and `SelfCancelling`'s guard strips the operand's +type-preserving aliases. The fix shape is to inject each operand's global context at the top of +the analysis — exactly what `refTW` does moments later, idempotent (`injectedCtx` set), and it +covers every extractor including the ones that run in global scope. + +Three things about the trigger set generalize: + +- **The reported trigger is far narrower than the defect.** The issue said "left operand of + `-`", because only extractors that strip an alias operand on that op's path crash; `+ 1` + survives since `AdditiveCancellation` only strips sign-opposed pairs. `max` against a + **literal** crashes too, via `MaxMinChainAbsorb`, which strips the chain operand before + checking its shape. Enumerate which extractors dereference and probe one per family. +- **A DFHDL-value RHS defuses the reproducer.** The RHS type-conversion of a two-DFHDL-operand + op references (and thereby injects) the operand before any extractor runs, so `V max V` + cannot reproduce while `V max 5` does. When a "first materialization" bug refuses to fire, + check whether an operand adaptation referenced the member first. +- **Test-local DFHDL globals are still globals.** A `val`/`object` declared inside a munit test + body elaborates with no design context and is a global for the DB, so per-test globals + reproduce the species without file-level state. Keep the object first *touched* inside the + design body (Scala object init is lazy), one object per test so tests cannot defuse each + other, and remember the crash needs the first use to be the analyzed position. + ### Changing a type-level algebra: pick the mechanism by when it costs `IntP` decides widths at the type level, and there are three mechanisms for such a rule. They diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index d1b26ccdf..efce7f1dc 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -4049,4 +4049,54 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true): |end DualTop |""".stripMargin ) + // issue #494: the reported shape, where the object-scoped global const alias's first + // materialization is the left operand of `-`, which runs SimplifyFunc's self-cancellation + // check; that check dereferences the operand's refs BEFORE any `refTW` has injected the + // global's own context into the run's DB, so it used to crash with `Missing ref`. The + // globals are declared per test (a const whose value is another const, in an object that + // is deliberately first touched only inside the design body). + test("Object-scoped global const alias first used as the left operand of `-`"): + val TOP_CONST_I494: Int <> CONST = 16 + object i494SubConsts: + val W: Int <> CONST = TOP_CONST_I494 + class Repro extends RTDesign: + val x = Bits(32) <> IN + val o = Bits(16) <> OUT + o <> x(i494SubConsts.W - 1, 0) + assertCodeString( + new Repro, + """|val TOP_CONST_I494: Int <> CONST = 16 + |val W: Int <> CONST = TOP_CONST_I494 + | + |class Repro extends RTDesign: + | val x = Bits(32) <> IN + | val o = Bits(16) <> OUT + | o <> x(W - 1, 0) + |end Repro + |""".stripMargin + ) + // issue #494, second direction: `MaxMinChainAbsorb` (which runs even in global context) + // strips the chain operand before checking its shape, so a first use as a `max` operand + // against a literal hits the same dereference. A literal RHS is essential: a DFHDL-value + // RHS is adapted through a TC conversion that references (and thereby injects) the global + // before any extractor runs. The `max` itself is then legitimately folded away by + // `MaxMinWithOffset` (a global const's value is fixed, so `V max 5` is provably `V`). + test("Object-scoped global const alias first used as a max operand against a literal"): + val TOP_CONST_I494: Int <> CONST = 16 + object i494SubConstsMaxMin: + val V: Int <> CONST = TOP_CONST_I494 + class Repro extends RTDesign: + val o = Bits(i494SubConstsMaxMin.V max 5) <> OUT + o <> all(0) + assertCodeString( + new Repro, + """|val TOP_CONST_I494: Int <> CONST = 16 + |val V: Int <> CONST = TOP_CONST_I494 + | + |class Repro extends RTDesign: + | val o = Bits(V) <> OUT + | o <> b"0".repeat(V) + |end Repro + |""".stripMargin + ) end PrintCodeStringSpec diff --git a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala index 8f5f8323c..1d9299168 100644 --- a/core/src/main/scala/dfhdl/core/SimplifyFunc.scala +++ b/core/src/main/scala/dfhdl/core/SimplifyFunc.scala @@ -10,6 +10,12 @@ private object SimplifyFunc: // are skipped in that mode. if (dfc.inMetaProgramming) None else + // A global operand (e.g. an object-scoped `Int <> CONST` alias) may be seen here before + // its first `refTW`, which is what injects the operand's own global context into this + // run's DB (`injectGlobalCtx`). The extractors below dereference operand refs + // (`stripTypePreservingAliases`, arg walks), so the injection must happen up front, or + // the first dereference dies with `Missing ref` (issue #494). + opArgs._3.foreach(_.injectGlobalCtx()) opArgs match // These three run even in global context (no owner). case ConstFoldAddSubChain(v) => Some(v) @@ -27,6 +33,7 @@ private object SimplifyFunc: case CompareAgainstMaxMin(v) => Some(v) case AdditiveCancellation(v) => Some(v) case _ => None + end match // Checks if an intermediate Func can be merged into the current one. // + and * are only merged when the intermediate has the same dfType (non-carry).