diff --git a/.claude/commands/compile-perf.md b/.claude/commands/compile-perf.md new file mode 100644 index 000000000..506810d56 --- /dev/null +++ b/.claude/commands/compile-perf.md @@ -0,0 +1,207 @@ +# DFHDL Compile Time Performance Guide + +## Quick Setup for Debugging Compile Times + +```bash +sbtn.bat corePlayground # Only compiles core/src/test/scala/Playground.scala +sbtn.bat libPlayground # Only compiles lib/src/test/scala/Playground.scala +sbtn.bat compile # Compile dependencies (run after clean or code changes) +sbtn.bat "core/Test/compile" # Compile just the Playground test file +``` + +Enable profiling in `build.sbt` (in `pluginTestUseSettings`): +```scala +"-Yprofile-enabled", +// "-Yprofile-trace:compiler.trace" +``` + +Always use `sbtn.bat` (sbt client), never `sbt`. + +## Test Methodology + +### Measuring Compile Time + +1. Run `corePlayground` or `libPlayground` once per session (persists until `shutdown`) +2. Run `clean` then `compile` to build dependencies +3. Edit `Playground.scala` with the test code +4. Run `core/Test/compile` — this compiles ONLY the Playground file +5. To force recompile without changing code, add/change a comment (e.g., `// v2`) +6. Compare `Total time` or `posttyper` phase timing with `-Yprofile-enabled` + +When running the full test suite, always separate compilation from execution: +```bash +sbtn.bat Test/compile # compile all tests first — measure compile workload +sbtn.bat test # then run tests — measure runtime workload +``` +This avoids conflating compile time with test runtime in measurements. + +### When to Run `clean` + +- **After modifying `internals/` or `plugin/`** — the incremental compiler doesn't always detect changes in macro code or plugin classes. A stale compiled macro will produce the OLD tree even though the source changed. Always `clean` after touching: + - `internals/src/main/scala/dfhdl/internals/Exact.scala` + - `internals/src/main/scala/dfhdl/internals/Checked.scala` + - `plugin/src/main/scala/plugin/*.scala` + - Any file that defines macros or compiler plugin phases +- **After modifying `core/`** — changes to `DFVal.scala`, `DFDecimal.scala`, etc. affect inline definitions that downstream code depends on +- **NOT needed** when only editing `Playground.scala` — incremental compilation handles this correctly + +### OOM and JVM Issues + +The sbt JVM can run out of memory when compiling large test suites or after many incremental compilations. Symptoms: +- `java.lang.OutOfMemoryError: Java heap space` +- `GC overhead limit exceeded` +- Compile times suddenly spike to 5+ minutes +- sbt becomes unresponsive + +Recovery: +```bash +taskkill //F //IM java.exe # Kill all Java processes (Windows) +sleep 2 # Wait for cleanup +sbtn.bat clean # Start fresh +sbtn.bat compile # Rebuild +``` + +The `sbtn` client will auto-start a new JVM server. Always kill java BEFORE starting sbtn again, otherwise the old server may still hold ports/locks. + +When running the full test suite (`sbtn.bat test`), watch for OOM — the test compilation of all spec files plus Playground can exhaust 1GB heap. If the `lib/src/test/scala/Playground.scala` or `core/src/test/scala/Playground.scala` contain heavy test code (many operands, design hierarchies), comment them out before running the full suite. + +### Printing Trees for Debugging + +In the `FlattenInlinedPhase` plugin, use `prepareForUnit` / `transformUnit` overrides to print trees before/after transformation: +```scala +override def prepareForUnit(tree: Tree)(using Context): Context = + if tree.source.path.toString.contains("Playground.scala") then + println(tree.show) + ctx + +override def transformUnit(tree: Tree)(using Context): Tree = + val result = treeMap.transform(tree) + if tree.source.path.toString.contains("Playground.scala") then + println(result.show) + result +``` + +Use `System.err.println` for debug output that needs to bypass sbt's output buffering. + +### Upstream Scala 3 Bug + +The root cause is a performance bug in `PostTyper.scala` (line ~568 in current nightly): +```scala +case tree @ Inlined(call, bindings, expansion) if !tree.inlinedFromOuterScope => + ... + withMode(Mode.NoInline)(transform(call)) // expensive, result DISCARDED + val callTrace = Inlines.inlineCallTrace(call.symbol, pos)(...) + cpy.Inlined(tree)(callTrace, ...) // callTrace replaces call entirely +``` +`transform(call)` re-transforms the full call tree (running `checkBounds`, `normalizeTypeArgs`, etc. on any TypeApply inside it), but the result is thrown away — the next line computes `callTrace` (a minimal `Ident`) and uses that instead. With N chained transparent inlines producing O(N) Inlined nodes with increasingly complex call trees, this causes O(N²) or worse behavior. + +**Fix**: Either remove `withMode(Mode.NoInline)(transform(call))` entirely, or replace it with just the side-effecting checks needed (which `CrossVersionChecks.checkRef` already covers on the line above). See https://github.com/scala/scala3/blob/main/CONTRIBUTING.md for contribution guide. + +A Scala 3 clone is available at `C:\Users\OronPort\IdeaProjects\forclaude\scala3` for building a minimal reproducer and preparing the upstream PR. + +## Root Cause: Transparent Inline Expansion + +The primary compile time bottleneck comes from Scala 3's `transparent inline` mechanism used extensively in DFHDL for type-level computation. The key file is `internals/src/main/scala/dfhdl/internals/Exact.scala`. + +### How It Works + +Operations like `+`, `-`, `<>`, `apply` are defined as: +```scala +transparent inline def +(inline rhs: SupportedValue)(using DFCG): DFValAny = + exactOp2[FuncOp.+.type, DFC, DFValAny](lhs, rhs) +``` + +`exactOp2` is a `transparent inline` macro that: +1. Extracts the exact types of `lhs` and `rhs` via `exactInfo` +2. Summons an `ExactOp2` typeclass instance for those exact types +3. Returns the result with the precise output type + +### The Exponential Problem + +When chaining operations like `a + b + c + d`, each `+` is `transparent inline`, and: +- The `inline lhs` parameter causes the entire previous expansion to be substituted +- The compiler's `posttyper` phase re-processes the growing tree +- Tree sizes grow linearly but `posttyper` time grows super-linearly + +### What We Fixed + +1. **`cleanTypeHack` removal** — Was an extra `transparent inline` + `inline match` layer around every `exactOp*` call. Replaced with `ascribeWidenedType` using `.dealias` type ascription. See `Exact.scala`. + +2. **`flattenInlined` in macros** — The macros now flatten nested `Inlined` nodes from subexpressions by extracting val bindings into a flat `Block`. + +3. **`ascribeWidenedType`** — Adds a `Typed` node with `.dealias` type to prevent unreduced type projections (`ExactOp2Aux[...].Out`) from leaking into error messages. + +4. **Non-transparent Check/CTName/DualSummonTrapError givens** — Removed unnecessary `transparent` from `inline given` instances that don't need type narrowing. + +5. **`FlattenInlinedPhase` compiler plugin** — Runs between `typer` and `posttyper`. Flattens nested `Inlined` trees and pre-minimizes the `call` field of `Inlined` nodes to a simple `Ident` pointing to the top-level class. This prevents PostTyper from re-transforming complex call trees (which contain TypeApply with heavy type arguments) for every `Inlined` node. This is the single biggest compile time fix — it reduced 8-addition chains from 33s to 2s and eliminated the exponential growth entirely (20 additions: previously impossible, now 7s). + +## Key Files + +| File | Role | +|---|---| +| `internals/src/main/scala/dfhdl/internals/Exact.scala` | ExactOp1/2/3 macros, cleanTypeHack, flattenInlined | +| `internals/src/main/scala/dfhdl/internals/Checked.scala` | Check1/Check2 constraint macros | +| `plugin/src/main/scala/plugin/FlattenInlinedPhase.scala` | Compiler plugin phase for tree optimization | +| `plugin/src/main/scala/plugin/Plugin.scala` | Plugin registration | + +## Profiling Results + +The `posttyper` phase is the main bottleneck. Use `-Yprofile-enabled` to measure. + +### Compiler Phase Breakdown (10-operand `a + b + c + ... + j`) +- `typer`: ~1-2s (macros run here, very fast) +- `posttyper`: 20-70s depending on optimizations (the bottleneck) +- `inlining`: <1s +- All other phases: <1s each + +### What PostTyper Does + +PostTyper performs post-typing checks on inline-expanded code: +- Overriding checks +- Type bound verification +- Import resolution +- Re-traverses the expanded tree multiple times + +### What Drives PostTyper Time + +**The `call` field of `Inlined` nodes** — PostTyper's `Inlined` handler calls `withMode(Mode.NoInline)(transform(call))` on every Inlined node. The result is then immediately discarded and replaced by `inlineCallTrace(call.symbol, pos)`, which is just an `Ident` to the top-level class. With chained inlines producing 100+ Inlined nodes, each with a complex call tree containing TypeApply, PostTyper re-transforms all of these call trees for nothing. Pre-minimizing the call to the same Ident that PostTyper would produce eliminates this entirely. + +**NOT type lambda complexity** — replacing complex `[LS, RS] =>> LS || !RS` with trivial `[LS, RS] =>> Boolean` had zero impact on posttyper time. + +**NOT node count alone** — removing 200 nodes from a 2300-node tree gave marginal improvement. + +**NOT InferredTypeTree spans** — PostTyper's `checkInferredWellFormed` gates on `span.isZeroExtent` for TypeTree nodes. Marking all 457 zero-extent TypeTrees with non-zero spans had zero impact — this check is not the bottleneck. + +**NOT TypeApply type arg dealiasing** — dealiasing all TypeApply type arguments had negligible impact. The types are already small individually; the issue was the call-tree re-transformation, not type complexity. + +### What We Tried That Didn't Work + +1. **Replacing TypeLambda args with `Any`** — Violates HK upper bounds (`[T <: Int] =>> Boolean` can't accept `Any`). + +2. **Replacing TypeLambda args with `Nothing`** — Crashes Check macro when it tries to interpret `Nothing` as a lambda body for runtime checks. + +3. **Replacing TypeLambda args with upper-bound lambdas** — Works for compile-time checks but breaks `CheckNUBLP` runtime fallback in `lib`. + +4. **Simplifying `asInstanceOf` type args** — The `CheckOK.asInstanceOf[Check[...]]` pattern doesn't appear in trees because Check givens are now non-transparent (the `asInstanceOf` is inside the macro, not in expanded code). + +5. **Replacing `CheckNUB.ok[...](...)(...)` with `CheckNUBOK`** — Pattern matching failed because these calls are inside nested Inlined nodes that the TreeMap doesn't visit in the right order. + +6. **Removing Check type signatures from trees** — User confirmed no performance impact. The Check types are not the bottleneck. + +### Resolved + +The compile time issue is resolved. The `minimizeCall` optimization in `FlattenInlinedPhase` eliminates the exponential growth in PostTyper. This is also a general Scala 3 compiler bug — PostTyper's `Inlined` handler re-transforms call trees whose results are immediately discarded. A minimal reproducer and upstream patch should be filed against the Scala 3 compiler. + +## Checked.scala Architecture + +`Check1` / `Check2` are type-level constraint systems: +- **Check**: Trait with `Cond` and `Msg` type lambdas, macro-generated +- **CheckNUB**: "Not Upper Bounded" variant, handles unknown types at compile time +- **CheckNUB.ok**: Fast path when condition is statically true (returns `CheckNUBOK`) +- **CheckNUBLP**: Fallback when condition needs runtime evaluation (summons `Check` which triggers the macro) +- **CheckOK / CheckNUBOK**: Singleton no-op objects + +The macro (`checkMacro`) checks `condOpt`: +- `Some(true)` → returns `CheckOK.asInstanceOf[Check[...]]` (no runtime check needed) +- `Some(false)` → compile error +- `None` → generates runtime check code (`if (!cond) throw ...`) diff --git a/CLAUDE.md b/CLAUDE.md index 0eabac6c4..0f6a87bd0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,17 +16,21 @@ Outputs: Verilog, SystemVerilog, VHDL. ## Build System -**Tool**: SBT 1.12.2 — **Scala**: 3.8.1 (nightly resolver enabled) +**Tool**: SBT 1.12.9 — **Scala**: 3.8.3 (nightly resolver enabled) ```bash -sbt compile # compile all subprojects -sbt test # run all unit tests -sbt testApps # run simulation/app tests (requires OSS CAD tools) -sbt quickTestSetup # limit test scope to lib/Playground.scala only (fast iteration) -sbt clearSandbox # delete sandbox/ directory -sbt docExamplesRefUpdate # copy generated HDL from sandbox/ to lib/src/test/resources/ref/ +sbtn compile # compile all subprojects +sbtn Test/compile # compile all tests (separate from running them) +sbtn test # run all unit tests +sbtn testApps # run simulation/app tests (requires OSS CAD tools) +sbtn corePlayground # limit test scope to core/Playground.scala only (fast iteration) +sbtn libPlayground # limit test scope to lib/Playground.scala only (fast iteration) +sbtn clearSandbox # delete sandbox/ directory +sbtn docExamplesRefUpdate # copy generated HDL from sandbox/ to lib/src/test/resources/ref/ ``` +Always use `sbtn` (sbt client) instead of `sbt` for faster startup. On Windows use `sbtn.bat`. + ## Subproject Structure Dependencies flow left to right: @@ -109,6 +113,7 @@ CI installs these via OSS CAD Suite: ## Claude Instructions - When asked to **create a new compiler stage** or **modify an existing compiler stage**, always invoke the `/new-stage` skill before doing any work. +- When working on **compile time performance**, invoke the `/compile-perf` skill to review the methodology, known bottlenecks, and what has already been tried. ## Licenses diff --git a/build.sbt b/build.sbt index 64e17d8a4..c8e567bb9 100755 --- a/build.sbt +++ b/build.sbt @@ -1,11 +1,12 @@ -commands += DFHDLCommands.quickTestSetup +commands += DFHDLCommands.libPlayground +commands += DFHDLCommands.corePlayground commands += DFHDLCommands.clearSandbox commands += DFHDLCommands.testApps commands += DFHDLCommands.docExamplesRefUpdate // format: off val projectName = "dfhdl" -val compilerVersion = "3.8.1" +val compilerVersion = "3.8.3" inThisBuild( List( 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 f645fbb32..cdd003ef1 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -582,6 +582,12 @@ object DFVal: def hasNonBubbleInit(using MemberGetSet): Boolean = dcl.initRefList match case DFRef(dfVal) :: _ => !dfVal.isBubble case _ => false + // Funcs with associative ops (&, |, ^, ++) may have more than 2 args when consecutive + // same-op anonymous Funcs are merged during elaboration (e.g., `a ^ b ^ c` produces + // a single `Func(^, [a, b, c])` instead of nested binary Funcs). + // +, -, * are NOT merged because carry promotion assumes binary (2-arg) Funcs. + // ++ is only merged for flat DFBits concatenation, not struct/vector/string. + // The position spans from the first operand to the last in the merged chain. final case class Func( dfType: DFType, op: Func.Op, diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala index 95291920f..8a6b206b6 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala @@ -21,6 +21,8 @@ case object ExtendTag extends DFTag type ExtendTag = ExtendTag.type case object TruncateTag extends DFTag type TruncateTag = TruncateTag.type +case object ImplicitlyFromIntTag extends DFTag +type ImplicitlyFromIntTag = ImplicitlyFromIntTag.type case class DFHDLVersionTag(version: String) extends DFTag opaque type DFTags = Map[String, DFTag] 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 7660baf37..5eb4bdc52 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala @@ -249,11 +249,12 @@ final case class DFDecimal( end DFDecimal object DFDecimal extends DFType.Companion[DFDecimal, Option[BigInt]]: - enum NativeType derives CanEqual, ReadWriter: - case BitAccurate, Int32 + type NativeType = Boolean object NativeType: - type BitAccurate = BitAccurate.type - type Int32 = Int32.type + type BitAccurate = false + type Int32 = true + val BitAccurate: false = false + val Int32: true = true end DFDecimal import DFDecimal.NativeType 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 dccd5bd58..4b5c07875 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/NamedAliases.scala @@ -31,9 +31,14 @@ private abstract class NamedAliases extends Stage: .filterNot(_.isAllowedMultipleReferences) // tuple with the suggested name .map(m => (m, m.suggestName.getOrElse("anon"))) - // group dfhdl-equivalent values, as long as they are in the same scope + // group dfhdl-equivalent values, as long as they are in the same scope. + // conditional headers are excluded from grouping because their =~ comparison + // does not account for block contents (conditions/branches), so structurally + // different conditionals could be incorrectly merged. .groupByCompare( - (l, r) => l._1 =~ r._1 && l._1.isInsideOwner(r._1.getOwner), + (l, r) => + !l._1.isInstanceOf[DFConditional.Header] && + l._1 =~ r._1 && l._1.isInsideOwner(r._1.getOwner), _._2.hashCode() ) // split to list of aliases and list of suggested names for each group @@ -112,7 +117,8 @@ case object NamedVerilogSelection extends NamedAliases: case alias: DFVal.Alias.ApplyIdx => List(alias.relValRef.get) case func @ DFVal.Func(op = op, args = DFRef(lhs) :: _ :: Nil) - if !lhs.hasVerilogName && carryOps.contains(op) && func.width > lhs.width => + if isBasicVerilog && !lhs.hasVerilogName && carryOps.contains(op) && + func.width > lhs.width => List(lhs) case func: DFVal.Func => func.getReadDeps.headOption match diff --git a/compiler/stages/src/test/scala/StagesSpec/DropStructsVecsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropStructsVecsSpec.scala index d2fabdd74..c8c308d8a 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropStructsVecsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropStructsVecsSpec.scala @@ -239,7 +239,7 @@ class DropStructsVecsSpec extends StageSpec(stageCreatesUnrefAnons = true): | val v2 = Bits(32) X 4 <> VAR init arg2.as(Bits(32) X 4) | val v3 = Bits(8) X 4 <> VAR.REG | val sel = UInt(2) <> IN - | o := ((arg(31, 24) ^ arg(23, 16)) ^ v(sel.toInt)) ^ v3(sel.toInt) + | o := arg(31, 24) ^ arg(23, 16) ^ v(sel.toInt) ^ v3(sel.toInt) | o2 := arg2(127, 96) | v3(sel.toInt).din := v(sel.toInt) | o3 := v2(sel.toInt) diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala index 07286e397..c35aaafc5 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala @@ -263,4 +263,27 @@ class ExplicitNamedVarsSpec extends StageSpec: |end ID""".stripMargin ) } + + // TODO: this causes an exception + // test("AES regression test") { + // case class AESByte() extends Opaque(Bits(8)) + + // class xtime extends DFDesign: + // val lhs = AESByte <> IN + // val o = AESByte <> OUT + // val shifted = lhs.bits << 1 + // val anon: Bits[8] <> VAL = + // if (lhs.bits(7)) + // val o: Bits[8] <> CONST = h"1b" + // shifted ^ o + // else shifted + // o <> anon.as(AESByte) + // end xtime + + // val top = (new xtime).explicitNamedVars + // assertCodeString( + // top, + // """|""".stripMargin + // ) + // } end ExplicitNamedVarsSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/NamedSelectionSpec.scala b/compiler/stages/src/test/scala/StagesSpec/NamedSelectionSpec.scala index e3f34021e..094d396c2 100644 --- a/compiler/stages/src/test/scala/StagesSpec/NamedSelectionSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/NamedSelectionSpec.scala @@ -134,9 +134,10 @@ class NamedSelectionSpec extends StageSpec(stageCreatesUnrefAnons = true): } test("Named selection with functions under system verilog") { class ID extends DFDesign: - val x = UInt(6) <> IN - val y: UInt[5] <> VAL = (x min x).truncate - val z: UInt[5] <> VAL = (x + x).truncate + val x = UInt(6) <> IN + val y: UInt[5] <> VAL = (x min x).truncate + val z: UInt[5] <> VAL = (x + x).truncate + val w: UInt[20] <> VAL = (x + x) + x val id = (new ID).verilogNamedSelection assertCodeString( @@ -145,15 +146,17 @@ class NamedSelectionSpec extends StageSpec(stageCreatesUnrefAnons = true): | val x = UInt(6) <> IN | val y = (x min x).resize(5) | val z = (x + x).resize(5) + | val w = ((x + x) +^ x).resize(20) |end ID""".stripMargin ) } test("Named selection with functions under basic verilog") { given options.CompilerOptions.Backend = backends.verilog.v95 class ID extends DFDesign: - val x = UInt(6) <> IN - val y: UInt[5] <> VAL = (x min x).truncate - val z: UInt[5] <> VAL = (x + x).truncate + val x = UInt(6) <> IN + val y: UInt[5] <> VAL = (x min x).truncate + val z: UInt[5] <> VAL = (x + x).truncate + val w: UInt[20] <> VAL = (x + x) + x val id = (new ID).verilogNamedSelection assertCodeString( @@ -164,6 +167,8 @@ class NamedSelectionSpec extends StageSpec(stageCreatesUnrefAnons = true): | val y = y_part.resize(5) | val z_part = x + x | val z = z_part.resize(5) + | val w_part = x + x + | val w = (w_part +^ x).resize(20) |end ID""".stripMargin ) } diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index fab327b6e..a6fe13b4e 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -654,7 +654,7 @@ class PrintCodeStringSpec extends StageSpec: top, """|class BigXor extends DFDesign: | val sum = Bits(4) <> OUT - | sum := ((((((h"0" ^ h"1") ^ h"2") ^ h"3") ^ h"4") ^ h"5") ^ h"6") ^ h"7" + | sum := h"0" ^ h"1" ^ h"2" ^ h"3" ^ h"4" ^ h"5" ^ h"6" ^ h"7" |end BigXor |""".stripMargin ) @@ -669,7 +669,7 @@ class PrintCodeStringSpec extends StageSpec: top, """|class BigXor(val c: Bits[4] <> CONST) extends DFDesign: | val sum = Bits(4) <> OUT - | sum := (((((((c | h"0") ^ (c | h"1")) ^ (c | h"2")) ^ (c | h"3")) ^ (c | h"4")) ^ (c | h"5")) ^ (c | h"6")) ^ (c | h"7") + | sum := (c | h"0") ^ (c | h"1") ^ (c | h"2") ^ (c | h"3") ^ (c | h"4") ^ (c | h"5") ^ (c | h"6") ^ (c | h"7") |end BigXor | |class BigXorContainer extends DFDesign: @@ -829,7 +829,7 @@ class PrintCodeStringSpec extends StageSpec: | y1 <> (x1 | (x1 & x1)) | val x2 = Bits(8) <> IN | val y2 = Bits(8) <> OUT - | y2 <> ((x2 ^ x2) ^ x2) + | y2 <> (x2 ^ x2 ^ x2) | val x3 = Bit <> IN | val y3 = Bit <> OUT | y3 <> ((x3 && x3) || x3) @@ -1266,7 +1266,7 @@ class PrintCodeStringSpec extends StageSpec: val param4 = d"22" val param5 = h"abc123" val param6 = b"101010" - val param7 = d"-11" + val param7 = sd"-11" val param8: Bit <> CONST = 1 val param9: Boolean <> CONST = false enum MyEnum extends Encoded: @@ -1807,4 +1807,46 @@ class PrintCodeStringSpec extends StageSpec: |end Foo""".stripMargin ) } + + test("assigned design def name regression") { + def bar(lhs: Bits[8] <> VAL): Bits[8] <> DFRET = lhs ^ h"1b" + class Foo extends DFDesign: + val x = Bits(8) <> IN + val o = bar(x) + end Foo + val top = (new Foo).getCodeString + assertNoDiff( + top, + """|def bar(lhs: Bits[8] <> VAL): Bits[8] <> DFRET = + | lhs ^ h"1b" + |end bar + | + |class Foo extends DFDesign: + | val x = Bits(8) <> IN + | val o = bar(x) + |end Foo""".stripMargin + ) + } + + // Regression test for exponential compile time with chained transparent inline operations. + // Before the FlattenInlinedPhase.minimizeCall optimization, 9 additions took 130s and + // 20 additions were impossible (hours/OOM). If this test causes compilation to hang or + // OOM, the optimization in FlattenInlinedPhase has regressed. + test("long chain of chained transparent inline operations compiles successfully") { + class LongChain extends DFDesign: + val a = UInt(8) <> IN + val o = UInt(8) <> OUT + o <> (a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a) + end LongChain + val top = (new LongChain).getCodeString + assertNoDiff( + top, + """|class LongChain extends DFDesign: + | val a = UInt(8) <> IN + | val o = UInt(8) <> OUT + | o <> ((((((((((((((((((((a + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + a) + |end LongChain""".stripMargin + ) + } + end PrintCodeStringSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index 89e21aec3..79343d619 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -1136,7 +1136,7 @@ class PrintVHDLCodeSpec extends StageSpec: val param4 = d"22" val param5 = h"abc123" val param6 = b"101010" - val param7 = d"-11" + val param7 = sd"-11" val param8: Bit <> CONST = 1 val param9: Boolean <> CONST = false enum MyEnum extends Encoded: diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 4cf1700c3..4514be515 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -250,7 +250,7 @@ class PrintVerilogCodeSpec extends StageSpec: |); | `include "dfhdl_defs.svh" | localparam logic lp = 1'b1; - | assign y = ((x | gp) | dp) | lp; + | assign y = x | gp | dp | lp; |endmodule |""".stripMargin ) @@ -1065,7 +1065,7 @@ class PrintVerilogCodeSpec extends StageSpec: val param4 = d"22" val param5 = h"abc123" val param6 = b"101010" - val param7 = d"-11" + val param7 = sd"-11" val param8: Bit <> CONST = 1 val param9: Boolean <> CONST = false enum MyEnum extends Encoded: diff --git a/core/src/main/scala/dfhdl/core/DFBits.scala b/core/src/main/scala/dfhdl/core/DFBits.scala index be1f3a998..6c0ae7e50 100644 --- a/core/src/main/scala/dfhdl/core/DFBits.scala +++ b/core/src/main/scala/dfhdl/core/DFBits.scala @@ -670,6 +670,19 @@ object DFBits: new ExactOp2[Op, DFC, DFValAny, L, R]: type Out = DFValTP[LT, LP | RP] def apply(lhs: L, rhs: R)(using DFC): Out = trydf { + import dfc.getSet + // Check B: shift amount is self-determined in Verilog, + // so only warn if the LHS chain itself contains a tagged operand + if DFXInt.Val.Ops.containsNarrowNonCarryArithWithTaggedOperand( + lhs.asIR + ) + then + dfc.logEvent( + DFWarning( + op.value.toString, + DFXInt.Val.Ops.verilogSemanticsWarnMsg + ) + ) val shiftVal = ub(lhs.widthIntParam.asInstanceOf[IntParam[LW]], rhs) DFVal.Func(lhs.dfType, op.value, List(lhs, shiftVal)) } diff --git a/core/src/main/scala/dfhdl/core/DFC.scala b/core/src/main/scala/dfhdl/core/DFC.scala index baf8f12db..eb09fe9dc 100644 --- a/core/src/main/scala/dfhdl/core/DFC.scala +++ b/core/src/main/scala/dfhdl/core/DFC.scala @@ -76,10 +76,13 @@ final case class DFC( def setAnnotations(annotations: List[HWAnnotation]): this.type = copy(annotations = annotations).asInstanceOf[this.type] def anonymize: this.type = copy(nameOpt = None).asInstanceOf[this.type] - def logError(err: DFError): Unit = mutableDB.logger.logError(err) + def logEvent(event: LogEvent): Unit = mutableDB.logger.logEvent(event) + def injectEvents(newEvents: List[LogEvent]): Unit = mutableDB.logger.injectEvents(newEvents) def getErrors: List[DFError] = mutableDB.logger.getErrors + def getWarnings: List[DFWarning] = mutableDB.logger.getWarnings + def getEvents: List[LogEvent] = mutableDB.logger.getEvents def inMetaProgramming: Boolean = mutableDB.inMetaProgramming - def clearErrors(): Unit = mutableDB.logger.clearErrors() + def clearEvents(): Unit = mutableDB.logger.clearEvents() end DFC object DFC: import java.util.concurrent.atomic.AtomicInteger diff --git a/core/src/main/scala/dfhdl/core/DFDecimal.scala b/core/src/main/scala/dfhdl/core/DFDecimal.scala index 42def4adb..cf8a281bb 100644 --- a/core/src/main/scala/dfhdl/core/DFDecimal.scala +++ b/core/src/main/scala/dfhdl/core/DFDecimal.scala @@ -3,7 +3,7 @@ import dfhdl.compiler.ir import dfhdl.internals.* import ir.DFVal.Func.{Op => FuncOp} import ir.DFDecimal.NativeType -import NativeType.{valueOf => _, *} +import NativeType.* import scala.quoted.* import scala.annotation.targetName @@ -93,15 +93,6 @@ object DFDecimal: " bits width (LHS) to a value of " + RW + " bits width (RHS).\nAn explicit conversion must be applied." ] - object `ValW >= ArgW` - extends Check2[ - Int, - Int, - [ValW <: Int, ArgW <: Int] =>> ValW >= ArgW, - [ValW <: Int, ArgW <: Int] =>> "Cannot compare a DFHDL value (width = " + ValW + - ") with a Scala `Int` argument that is wider (width = " + ArgW + - ").\nAn explicit conversion must be applied." - ] object `LS >= RS` extends Check2[ Boolean, @@ -112,6 +103,25 @@ object DFDecimal: ITE[RS, "a signed", "an unsigned"] + " value (RHS).\nAn explicit conversion must be applied." ] + object `BaS >= WcS` + extends Check2[ + Boolean, + Boolean, + [BaS <: Boolean, WcS <: Boolean] =>> BaS || ![WcS], + [BaS <: Boolean, WcS <: Boolean] =>> + "Cannot apply a signed wildcard `Int` value to " + + ITE[BaS, "a signed", "an unsigned"] + + " bit-accurate value.\nUse an explicit conversion or `sd\"\"` interpolation." + ] + object `BaW >= WcW` + extends Check2[ + Int, + Int, + [BaW <: Int, WcW <: Int] =>> BaW >= WcW, + [BaW <: Int, WcW <: Int] =>> + "The wildcard `Int` value width (" + WcW + + ") is larger than the bit-accurate value width (" + BaW + ")." + ] type SignStr[S <: Boolean] = ITE[S, "a signed", "an unsigned"] object `LS == RS` extends Check2[ @@ -153,7 +163,7 @@ object DFDecimal: ValW <: IntP, ArgS <: Boolean, ArgW <: IntP, - ArgIsInt <: Boolean, // argument is from a Scala Int or DFHDL Int + ArgIsInt <: Boolean, // argument is a wildcard (Int32 NativeType) Castle <: Boolean // castling of dfVal and arg ]: def apply( @@ -185,7 +195,7 @@ object DFDecimal: )(using checkS: `LS == RS`.Check[ls.Out, rs.Out], checkW: `LW == RW`.Check[lw.Out, rw.Out], - checkVAW: `ValW >= ArgW`.Check[ValWI, ITE[ArgIsInt, argWFix.Out, 0]], + checkVAW: `BaW >= WcW`.Check[ValWI, ITE[ArgIsInt, argWFix.Out, 0]], argIsInt: ValueOf[ArgIsInt], castle: ValueOf[Castle] ): CompareCheck[ValS, ValW, ArgS, ArgW, ArgIsInt, Castle] with @@ -215,59 +225,46 @@ object DFDecimal: LS <: Boolean, LW <: IntP, LN <: NativeType, - LSM <: Boolean, - LWM <: IntP, - LI <: Boolean, RS <: Boolean, RW <: IntP, - RN <: NativeType, - RSM <: Boolean, - RWM <: IntP, - RI <: Boolean + RN <: NativeType ]: def apply( lhs: DFValOf[DFXInt[LS, LW, LN]], rhs: DFValOf[DFXInt[RS, RW, RN]] )(using DFC): Unit end ArithCheck - inline given [ + given [ LS <: Boolean, LW <: IntP, LN <: NativeType, - LSM <: Boolean, - LWM <: IntP, - LI <: Boolean, LWI <: Int, RS <: Boolean, RW <: IntP, RN <: NativeType, - RSM <: Boolean, - RWM <: IntP, - RI <: Boolean, RWI <: Int ](using // forcing Int upper-bound - ubL: UBound.Aux[Int, LWM, LWI], + ubL: UBound.Aux[Int, LW, LWI], // forcing Int upper-bound - ubR: UBound.Aux[Int, RWM, RWI], + ubR: UBound.Aux[Int, RW, RWI], // the RHS width is increased by 1 if the LHS is signed and the RHS is unsigned, // because the RHS will be converted to signed for the arithmetic operation - signedRW: Id[ITE[LSM && ![RSM], RWI + 1, RWI]] - // skip sign checks if the RHS is a Scala Int and the LHS is unsigned - // skipSignChecks: Id[RI && (RS || ![LS])] + signedRW: Id[ITE[LS && ![RS], RWI + 1, RWI]] )(using - checkS: `LS >= RS`.Check[LSM, RSM], - checkW: `LW >= RW`.Check[LWI, signedRW.Out], - isIntL: ValueOf[LI], - isIntR: ValueOf[RI] - ): ArithCheck[LS, LW, LN, LSM, LWM, LI, RS, RW, RN, RSM, RWM, RI] with + // When LHS is a wildcard (LN=Int32), bypass sign/width checks by comparing + // the value against itself (always passes). Wildcards adapt at runtime. + checkS: `LS >= RS`.Check[ITE[LN, LS, LS], ITE[LN, LS, RS]], + checkW: `LW >= RW`.Check[ITE[LN, LWI, LWI], ITE[LN, LWI, signedRW.Out]], + isWildcardL: ValueOf[LN] + ): ArithCheck[LS, LW, LN, RS, RW, RN] with def apply( lhs: DFValOf[DFXInt[LS, LW, LN]], rhs: DFValOf[DFXInt[RS, RW, RN]] )(using dfc: DFC): Unit = - import dfc.getSet - import DFXInt.Val.getActualSignedWidth - if (!lhs.dfType.asIR.isDFInt32 || !(rhs.dfType.asIR.isDFInt32 || isIntR)) + if (!isWildcardL.value) + import dfc.getSet + import DFXInt.Val.getActualSignedWidth val (lhsSigned, lhsWidth) = lhs.getActualSignedWidth val (rhsSigned, rhsWidth) = rhs.getActualSignedWidth val rhsSignedWidth: Int = @@ -281,7 +278,7 @@ object DFDecimal: trait SignCheck[ ValS <: Boolean, ArgS <: Boolean, - ArgIsInt <: Boolean, // argument is from a Scala Int + ArgIsInt <: Boolean, // argument is a wildcard (Int32 NativeType) Castle <: Boolean // castling of dfVal and arg ]: def apply( @@ -316,9 +313,9 @@ object DFDecimal: end apply end given - type NativeCheck[LN <: NativeType, RN <: NativeType, RI <: Boolean] = + type NativeCheck[LN <: NativeType, RN <: NativeType] = AssertGiven[ - (RI =:= true) | ((LN =:= RN) | (LN =:= BitAccurate)), + (RN =:= Int32) | ((LN =:= RN) | (LN =:= BitAccurate)), "Cannot implicitly convert to DFHDL Int type." ] end Constraints @@ -379,7 +376,11 @@ object DFDecimal: fullTerm match case Literal(StringConstant(t)) => fromDecString(t, signedForced) match - case Right((signed, width, fractionWidth, _)) => + case Right((signed, width, fractionWidth, value)) => + if (!signedForced && value < 0) + report.errorAndAbort( + s"Negative value in unsigned `d\"\"` interpolation. Use `sd\"\"` for signed values." + ) explicitWidthTpeOption match case Some(ConstantType(IntConstant(explicitWidth))) => val actualWidth = fromIntDecString(t, signedForced)._2 @@ -690,9 +691,6 @@ object DFXInt: type OutW <: IntP type OutN <: NativeType type OutP - type OutSMask <: Boolean - type OutWMask <: IntP - type IsScalaInt <: Boolean type Out = DFValTP[DFXInt[OutS, OutW, OutN], OutP] def conv(from: R)(using DFC): Out = apply(from) def apply(arg: R)(using DFC): Out @@ -704,9 +702,6 @@ object DFXInt: type OutW = W type OutN = BitAccurate type OutP = P - type OutSMask = false - type OutWMask = W - type IsScalaInt = false def apply(arg: R)(using dfc: DFC): Out = import DFBits.Val.Ops.uint val dfVal = ic(arg)(using dfc.anonymize) @@ -731,47 +726,25 @@ object DFXInt: type OutN = N type OutP = P } - type AuxM[R, S <: Boolean, W <: IntP, N <: NativeType, P, SMask <: Boolean, WMask <: IntP] = - Aux[R, S, W, N, P] { - type OutSMask = SMask - type OutWMask = WMask - } - type AuxMI[ - R, - S <: Boolean, - W <: IntP, - N <: NativeType, - P, - SMask <: Boolean, - WMask <: IntP, - I <: Boolean - ] = AuxM[R, S, W, N, P, SMask, WMask] { - type IsScalaInt = I - } given fromInt[R <: Int, OS <: Boolean, OW <: Int](using info: IntInfo.Aux[R, OS, OW] ): Candidate[R] with type OutS = OS type OutW = OW - type OutN = BitAccurate + type OutN = Int32 type OutP = CONST - type OutSMask = OS - type OutWMask = OW - type IsScalaInt = true def apply(arg: R)(using dfc: DFC): Out = val dfType = DFXInt(info.signed(arg), info.width(arg), BitAccurate) - DFVal.Const(dfType, Some(BigInt(arg)), named = true) - // when the candidate is a DFInt32 we remove the signed and width tags - // from the type to allow for elaboration (runtime) check where values - // are constant and known. + DFVal.Const(dfType, Some(BigInt(arg)), named = true)(using + dfc.tag(ir.ImplicitlyFromIntTag) + ).asInstanceOf[Out] + // DFInt32 acts as a wildcard in operations: it adapts to the + // bit-accurate value's sign and width. OutN = Int32 (true) signals wildcard status. given fromDFConstInt32[P, R <: DFValTP[DFInt32, P]]: Candidate[R] with - type OutS = true - type OutW = 32 + type OutS = Boolean + type OutW = Int type OutN = Int32 type OutP = P - type OutSMask = Boolean - type OutWMask = Int - type IsScalaInt = false def apply(arg: R)(using DFC): Out = arg given fromDFXIntVal[S <: Boolean, W <: IntP, N <: NativeType, P, R <: DFValTP[ DFXInt[S, W, N], @@ -781,9 +754,6 @@ object DFXInt: type OutW = W type OutN = N type OutP = P - type OutSMask = S - type OutWMask = W - type IsScalaInt = false def apply(arg: R)(using DFC): Out = arg inline given errDFEncoding[E <: DFEncoding]: Candidate[E] = compiletime.error( @@ -807,9 +777,6 @@ object DFXInt: type OutW = TW type OutN = TN type OutP = TP | FP - type OutSMask = TS - type OutWMask = TW - type IsScalaInt = false def apply(value: R)(using DFC): Out = value.unwrap end fromIf end Candidate @@ -841,8 +808,8 @@ object DFXInt: given [LS <: Boolean, LW <: IntP, LN <: NativeType, R, RP, IC <: Candidate[R]](using ic: IC { type OutP = RP } )(using - check: TCCheck[LS, LW, ic.OutSMask, ic.OutWMask], - nativeCheck: NativeCheck[LN, ic.OutN, ic.IsScalaInt] + check: TCCheck[LS, LW, ic.OutS, ic.OutW], + nativeCheck: NativeCheck[LN, ic.OutN] ): DFVal.TC[DFXInt[LS, LW, LN], R] with type OutP = RP def conv(dfType: DFXInt[LS, LW, LN], value: R)(using dfc: DFC): Out = @@ -860,7 +827,7 @@ object DFXInt: given DFXIntFromCandidateConv[LS <: Boolean, R, RP, IC <: Candidate[R]](using ic: IC { type OutP = RP } )(using - checkS: `LS >= RS`.Check[LS, ic.OutSMask], + checkS: `LS >= RS`.Check[LS, ic.OutS], lsigned: OptionalGiven[ValueOf[LS]] ): DFVal.TCConv[DFXInt[LS, Int, BitAccurate], R] with type OutP = RP @@ -887,10 +854,8 @@ object DFXInt: ](using ic: IC { type OutP = RP } )(using - isInt32: IsGiven[ic.OutN =:= NativeType.Int32] - )(using - check: CompareCheck[LS, LW, ic.OutSMask, ic.OutWMask, ic.IsScalaInt || isInt32.Out, C], - nativeCheck: NativeCheck[LN, ic.OutN, ic.IsScalaInt] + check: CompareCheck[LS, LW, ic.OutS, ic.OutW, ic.OutN, C], + nativeCheck: NativeCheck[LN, ic.OutN] ): Compare[DFXInt[LS, LW, LN], R, Op, C] with type OutP = RP def conv(dfType: DFXInt[LS, LW, LN], arg: R)(using dfc: DFC): Out = @@ -1011,6 +976,24 @@ object DFXInt: op = op @ (FuncOp.+ | FuncOp.- | FuncOp.*) ) if func.isAnonymous && !dt.isDFInt32 && dfType.widthInt > func.width => + // Check B: warn if sub-expressions contain implicit Int with + // narrow non-carry arith. Check args (not func itself, since + // the func is about to be carry-promoted). + if (func.args.exists(ref => + hasImplicitlyFromIntTag(ref.get) + ) && func.args.exists(ref => + containsNarrowNonCarryArith(ref.get) + )) || + func.args.exists(ref => + containsNarrowNonCarryArithWithTaggedOperand(ref.get) + ) + then + dfc.logEvent( + DFWarning( + op.toString, + verilogSemanticsWarnMsg + ) + ) val cw: IntParam[Int] = func.op.runtimeChecked match case FuncOp.+ | FuncOp.- => funcWidth + 1 case FuncOp.* => funcWidth + funcWidth @@ -1025,7 +1008,7 @@ object DFXInt: case Int32 => lhsCarryPromo.toInt.asIR case BitAccurate => - DFVal.Alias.AsIs(dfType, lhsCarryPromo).asIR + DFVal.Alias.AsIs(dfType, lhsCarryPromo)(using dfc.tag(ir.ImplicitlyFromIntTag)).asIR else if ( !dfType.asIR.widthParamRef.isSimilarTo( lhsCarryPromo.dfType.asIR.widthParamRef @@ -1067,6 +1050,85 @@ object DFXInt: end resize end extension + private[core] val verilogSemanticsWarnMsg = + """|Implicit Scala/DFHDL Int conversion may produce different results than Verilog. + |In Verilog, integer literals are 32-bit, which can widen intermediate arithmetic. + |In DFHDL, Int literals are converted to minimum bit-accurate width. + |Use carry operations (+^, -^, *^) or explicit bit-accurate literals (d"W'V").""".stripMargin + + // Check if a value is tagged with ImplicitlyFromIntTag + private[core] def hasImplicitlyFromIntTag(dfVal: ir.DFVal): Boolean = + dfVal.tags.hasTagOf[ir.ImplicitlyFromIntTag] + + // Check if an anonymous sub-tree contains non-carry +/-/* with width < 32. + // Carry ops have func.width > max(arg widths) and are excluded. + private[core] def containsNarrowNonCarryArith( + dfVal: ir.DFVal + )(using ir.MemberGetSet): Boolean = + dfVal match + case func: ir.DFVal.Func if func.isAnonymous => + func.op match + case FuncOp.+ | FuncOp.- | FuncOp.* => + val maxArgWidth = func.args.map(_.get.width).max + val isNonCarry = func.width <= maxArgWidth + (isNonCarry && func.width < 32) || + func.args.exists(ref => containsNarrowNonCarryArith(ref.get)) + case _ => + func.args.exists(ref => containsNarrowNonCarryArith(ref.get)) + case _ => false + + // Check if an anonymous sub-tree contains narrow non-carry arith that + // also has an ImplicitlyFromIntTag operand (Verilog "Forcing Larger + // Evaluation" pattern, or implicit Int in a chain assigned to wider target). + // Carry ops (func.width > max arg width) are excluded. + private[core] def containsNarrowNonCarryArithWithTaggedOperand( + dfVal: ir.DFVal + )(using ir.MemberGetSet): Boolean = + dfVal match + case func: ir.DFVal.Func if func.isAnonymous => + func.op match + case FuncOp.+ | FuncOp.- | FuncOp.* => + val maxArgWidth = func.args.map(_.get.width).max + val isNonCarry = func.width <= maxArgWidth + val isNarrowNonCarry = isNonCarry && func.width < 32 + (isNarrowNonCarry && + func.args.exists(ref => hasImplicitlyFromIntTag(ref.get))) || + func.args.exists(ref => + containsNarrowNonCarryArithWithTaggedOperand(ref.get) + ) + case _ => + func.args.exists(ref => + containsNarrowNonCarryArithWithTaggedOperand(ref.get) + ) + case _ => false + + // Check that a wildcard `Int` value fits in the bit-accurate value's type. + // Produces an elaboration error if it doesn't. + private def checkWildcardFit( + wildcard: DFValOf[DFXInt[Boolean, Int, NativeType]], + counterPartType: DFTypeAny + )(using dfc: DFC): Unit = + counterPartType.asIR match + case d: ir.DFDecimal => + import dfc.getSet + import DFXInt.Val.getActualSignedWidth + val (wSigned, wWidth) = wildcard.getActualSignedWidth + val baTypeName = + if (d.signed) s"SInt[${d.width}]" else s"UInt[${d.width}]" + // Unsigned wildcard adapting to signed bit-accurate value needs an extra bit + val effectiveWidth = + if (d.signed && !wSigned) wWidth + 1 else wWidth + if (!d.signed && wSigned) + throw new IllegalArgumentException( + s"Wildcard `Int` value is negative and cannot adapt to unsigned bit-accurate value $baTypeName." + ) + else if (effectiveWidth > d.width) + throw new IllegalArgumentException( + s"Wildcard `Int` value width ($effectiveWidth) is larger than the bit-accurate value width (${d.width})." + ) + case _ => + end checkWildcardFit + private def arithOp[ OS <: Boolean, OW <: IntP, @@ -1086,12 +1148,25 @@ object DFXInt: rhs: DFValTP[DFXInt[RS, RW, RN], RP] )(using dfc: DFC): DFValTP[DFXInt[OS, OW, ON], LP | RP] = val rhsFix = rhs.toDFXIntOf(lhs.dfType)(using dfc.anonymize) + import dfc.getSet + // Check A: / and % — both operands are context-determined in Verilog + val shouldWarn = op match + case FuncOp./ | FuncOp.% => + (hasImplicitlyFromIntTag(rhsFix.asIR) && + containsNarrowNonCarryArith(lhs.asIR)) || + (hasImplicitlyFromIntTag(lhs.asIR) && + containsNarrowNonCarryArith(rhsFix.asIR)) + case _ => false + if shouldWarn then + dfc.logEvent(DFWarning(op.toString, verilogSemanticsWarnMsg)) DFVal.Func(dfType, op, List(lhs, rhsFix)) end arithOp - type ArithOp = - FuncOp.+.type | FuncOp.-.type | FuncOp.*.type | FuncOp./.type | FuncOp.%.type | - FuncOp.max.type | FuncOp.min.type + type CommutativeArithOp = + FuncOp.+.type | FuncOp.*.type | FuncOp.max.type | FuncOp.min.type + type NonCommutativeArithOp = + FuncOp.-.type | FuncOp./.type | FuncOp.%.type + type ArithOp = CommutativeArithOp | NonCommutativeArithOp given evOpArithIntDFInt32[ Op <: ArithOp, L <: Int, @@ -1107,41 +1182,181 @@ object DFXInt: DFVal.Func(DFInt32, op, List(lhsVal, rhs)).asValTP[DFInt32, RP] }(using dfc, CTName(op.value.toString)) end evOpArithIntDFInt32 - given evOpArithDFXInt[ - Op <: ArithOp, + given evOpCommutativeArithDFXInt[ + Op <: CommutativeArithOp, L, LS <: Boolean, LW <: IntP, LN <: NativeType, LP, - LSM <: Boolean, - LWM <: IntP, - LI <: Boolean, R, RS <: Boolean, RW <: IntP, RN <: NativeType, - RP, - RSM <: Boolean, - RWM <: IntP, - RI <: Boolean + RP ](using - icL: Candidate.AuxMI[L, LS, LW, LN, LP, LSM, LWM, LI], - icR: Candidate.AuxMI[R, RS, RW, RN, RP, RSM, RWM, RI], - op: ValueOf[Op] + icL: Candidate.Aux[L, LS, LW, LN, LP], + icR: Candidate.Aux[R, RS, RW, RN, RP], + op: ValueOf[Op], + isWildcardL: ValueOf[LN], + isWildcardR: ValueOf[RN], + // Type-level wildcard detection: when exactly one operand is a wildcard + // (Int32 NativeType), adapt to the bit-accurate value's sign and width. + // When both are wildcards, use LS || RS and Max (both-wildcard = DFInt32-like). + resultSign: Id[ITE[LN && ![RN], RS, ITE[RN && ![LN], LS, ITE[LN && RN, LS, LS || RS]]]], + resultWidth: Id[ITE[LN && ![RN], RW, ITE[RN && ![LN], LW, + ITE[LN && RN, LW, + IntP.Max[ + ITE[![LS] && RS, LW + 1, LW], + ITE[![RS] && LS, RW + 1, RW] + ] + ] + ]]], + resultNative: Id[ITE[LN && ![RN], RN, LN]], + // Compile-time wildcard fit: when one operand is a literal wildcard, + // verify its sign and width fit in the bit-accurate value's type. + ubLW: UBound.Aux[Int, LW, ? <: Int], + ubRW: UBound.Aux[Int, RW, ? <: Int], + checkWS: `BaS >= WcS`.Check[ + ITE[RN && ![LN], LS, ITE[LN && ![RN], RS, LS]], + ITE[RN && ![LN], RS, ITE[LN && ![RN], LS, LS]] + ], + checkWW: `BaW >= WcW`.Check[ + ITE[RN && ![LN], ubLW.Out, ITE[LN && ![RN], ubRW.Out, ubLW.Out]], + ITE[RN && ![LN], + ITE[LS && ![RS], ubRW.Out + 1, ubRW.Out], + ITE[LN && ![RN], + ITE[RS && ![LS], ubLW.Out + 1, ubLW.Out], + ubLW.Out + ] + ] + ] + ): ExactOp2Aux[Op, DFC, DFValAny, L, R, DFValTP[ + DFXInt[resultSign.Out, resultWidth.Out, resultNative.Out], + LP | RP + ]] = + new ExactOp2[Op, DFC, DFValAny, L, R]: + type Out = DFValTP[DFXInt[resultSign.Out, resultWidth.Out, resultNative.Out], LP | RP] + def apply(lhs: L, rhs: R)(using DFC): Out = trydf { + val dfcAnon = dfc.anonymize + val lhsVal = icL(lhs)(using dfcAnon) + val rhsVal = icR(rhs)(using dfcAnon) + import IntParam.{+, max} + val lhsIsWildcard = isWildcardL.value + val rhsIsWildcard = isWildcardR.value + if (lhsIsWildcard && !rhsIsWildcard) + // LHS is wildcard: adapt to RHS type + checkWildcardFit( + lhsVal.asValOf[DFXInt[Boolean, Int, NativeType]], + rhsVal.dfType + ) + arithOp(rhsVal.dfType, op.value, rhsVal, lhsVal) + .asInstanceOf[Out] + else if (rhsIsWildcard && !lhsIsWildcard) + // RHS is wildcard: adapt to LHS type + checkWildcardFit( + rhsVal.asValOf[DFXInt[Boolean, Int, NativeType]], + lhsVal.dfType + ) + arithOp(lhsVal.dfType, op.value, lhsVal, rhsVal) + .asInstanceOf[Out] + else + // Both concrete (or both wildcards): use max width, max signed + val resultSigned = lhsVal.dfType.signed || rhsVal.dfType.signed + val lhsEffWidth: Int = + if (resultSigned && !lhsVal.dfType.signed) lhsVal.dfType.widthInt + 1 + else lhsVal.dfType.widthInt + val rhsEffWidth: Int = + if (resultSigned && !rhsVal.dfType.signed) rhsVal.dfType.widthInt + 1 + else rhsVal.dfType.widthInt + if (lhsEffWidth >= rhsEffWidth) + if (resultSigned && !lhsVal.dfType.signed) + val lhsSigned = lhsVal.asValOf[DFUInt[Int]].signed(using dfcAnon) + val rhsAdj = rhsVal.toDFXIntOf(lhsSigned.dfType)(using dfcAnon) + DFVal.Func(lhsSigned.dfType, op.value, List(lhsSigned, rhsAdj)) + .asInstanceOf[Out] + else + arithOp(lhsVal.dfType, op.value, lhsVal, rhsVal) + .asInstanceOf[Out] + else + if (resultSigned && !rhsVal.dfType.signed) + val rhsSigned = rhsVal.asValOf[DFUInt[Int]].signed(using dfcAnon) + val lhsAdj = lhsVal.toDFXIntOf(rhsSigned.dfType)(using dfcAnon) + DFVal.Func(rhsSigned.dfType, op.value, List(rhsSigned, lhsAdj)) + .asInstanceOf[Out] + else + arithOp(rhsVal.dfType, op.value, rhsVal, lhsVal) + .asInstanceOf[Out] + }(using dfc, CTName(op.value.toString)) + end evOpCommutativeArithDFXInt + + given evOpNonCommutativeArithDFXInt[ + Op <: NonCommutativeArithOp, + L, + LS <: Boolean, + LW <: IntP, + LN <: NativeType, + LP, + R, + RS <: Boolean, + RW <: IntP, + RN <: NativeType, + RP + ](using + icL: Candidate.Aux[L, LS, LW, LN, LP], + icR: Candidate.Aux[R, RS, RW, RN, RP], + op: ValueOf[Op], + isWildcardL: ValueOf[LN], + isWildcardR: ValueOf[RN] )(using - check: ArithCheck[LS, LW, LN, LSM, LWM, LI, RS, RW, RN, RSM, RWM, RI] - ): ExactOp2Aux[Op, DFC, DFValAny, L, R, DFValTP[DFXInt[LS, LW, LN], LP | RP]] = + check: ArithCheck[LS, LW, LN, RS, RW, RN], + // Wildcard LHS adapts to RHS type; otherwise LHS-dominant + resultSign: Id[ITE[LN && ![RN], RS, LS]], + resultWidth: Id[ITE[LN && ![RN], RW, LW]], + resultNative: Id[ITE[LN && ![RN], RN, LN]], + // Compile-time wildcard fit: when LHS is a literal wildcard, + // verify its sign and width fit in the RHS (bit-accurate value) type. + ubLW: UBound.Aux[Int, LW, ? <: Int], + ubRW: UBound.Aux[Int, RW, ? <: Int], + checkWS: `BaS >= WcS`.Check[ + ITE[LN && ![RN], RS, LS], + ITE[LN && ![RN], LS, LS] + ], + checkWW: `BaW >= WcW`.Check[ + ITE[LN && ![RN], ubRW.Out, ubLW.Out], + ITE[LN && ![RN], + ITE[RS && ![LS], ubLW.Out + 1, ubLW.Out], + ubLW.Out + ] + ] + ): ExactOp2Aux[Op, DFC, DFValAny, L, R, DFValTP[ + DFXInt[resultSign.Out, resultWidth.Out, resultNative.Out], + LP | RP + ]] = new ExactOp2[Op, DFC, DFValAny, L, R]: - type Out = DFValTP[DFXInt[LS, LW, LN], LP | RP] + type Out = DFValTP[DFXInt[resultSign.Out, resultWidth.Out, resultNative.Out], LP | RP] def apply(lhs: L, rhs: R)(using DFC): Out = trydf { val dfcAnon = dfc.anonymize val lhsVal = icL(lhs)(using dfcAnon) val rhsVal = icR(rhs)(using dfcAnon) - check(lhsVal, rhsVal) - arithOp(lhsVal.dfType, op.value, lhsVal, rhsVal) + val lhsIsWildcard = isWildcardL.value + val rhsIsWildcard = isWildcardR.value + if (lhsIsWildcard && !rhsIsWildcard) + // LHS is wildcard, RHS is concrete: adapt LHS to RHS type, keep operand order + checkWildcardFit( + lhsVal.asValOf[DFXInt[Boolean, Int, NativeType]], + rhsVal.dfType + ) + val lhsAdj = lhsVal.toDFXIntOf(rhsVal.dfType)(using dfcAnon) + DFVal.Func(rhsVal.dfType, op.value, List(lhsAdj, rhsVal)) + .asInstanceOf[Out] + else + // Both concrete, both wildcards, or only RHS is wildcard: LHS-dominant + check(lhsVal, rhsVal) + arithOp(lhsVal.dfType, op.value, lhsVal, rhsVal) + .asInstanceOf[Out] }(using dfc, CTName(op.value.toString)) - end evOpArithDFXInt + end evOpNonCommutativeArithDFXInt import DFVal.Ops.CarryOp given evOpCarryAddSubDFXInt[ @@ -1151,37 +1366,35 @@ object DFXInt: LW <: IntP, LN <: NativeType, LP, - LSM <: Boolean, - LWM <: IntP, - LI <: Boolean, R, RS <: Boolean, RW <: IntP, RN <: NativeType, - RP, - RSM <: Boolean, - RWM <: IntP, - RI <: Boolean + RP ](using - icL: Candidate.AuxMI[L, LS, LW, LN, LP, LSM, LWM, LI], - icR: Candidate.AuxMI[R, RS, RW, RN, RP, RSM, RWM, RI], + icL: Candidate.Aux[L, LS, LW, LN, LP], + icR: Candidate.Aux[R, RS, RW, RN, RP], op: ValueOf[Op] - )(using - check: SignCheck[LS, RS, RI, false] ): ExactOp2Aux[CarryOp[Op], DFC, DFValAny, L, R, DFValTP[ - DFXInt[LS, IntP.+[IntP.Max[LW, RW], 1], BitAccurate], + DFXInt[LS || RS, IntP.+[IntP.Max[LW, RW], 1], BitAccurate], LP | RP ]] = new ExactOp2[CarryOp[Op], DFC, DFValAny, L, R]: - type Out = DFValTP[DFXInt[LS, IntP.+[IntP.Max[LW, RW], 1], BitAccurate], LP | RP] + type Out = DFValTP[DFXInt[LS || RS, IntP.+[IntP.Max[LW, RW], 1], BitAccurate], LP | RP] def apply(lhs: L, rhs: R)(using DFC): Out = trydf { val dfcAnon = dfc.anonymize val lhsVal = icL(lhs)(using dfcAnon) val rhsVal = icR(rhs)(using dfcAnon) - check(lhsVal.dfType.signed, rhsVal.dfType.signed) + val resultSigned = lhsVal.dfType.signed || rhsVal.dfType.signed import IntParam.{+, max} - val width = lhsVal.widthIntParam.max(rhsVal.widthIntParam) + 1 - val dfType = DFXInt(lhsVal.dfType.signed, width, BitAccurate) - arithOp(dfType, op.value, lhsVal, rhsVal) + val commonWidth = lhsVal.widthIntParam.max(rhsVal.widthIntParam) + val width = commonWidth + 1 + val dfType = DFXInt(resultSigned, width, BitAccurate) + // Resize both operands to common width, converting to signed if needed + val commonType = DFXInt(resultSigned, commonWidth, BitAccurate) + val lhsFix = lhsVal.toDFXIntOf(commonType)(using dfcAnon) + val rhsFix = rhsVal.toDFXIntOf(commonType)(using dfcAnon) + DFVal.Func(dfType, op.value, List(lhsFix, rhsFix)) + .asInstanceOf[Out] }(using dfc, CTName(op.value.toString + "^")) end evOpCarryAddSubDFXInt @@ -1192,36 +1405,38 @@ object DFXInt: LW <: IntP, LN <: NativeType, LP, - LSM <: Boolean, - LWM <: IntP, - LI <: Boolean, R, RS <: Boolean, RW <: IntP, RN <: NativeType, - RP, - RSM <: Boolean, - RWM <: IntP, - RI <: Boolean + RP ](using - icL: Candidate.AuxMI[L, LS, LW, LN, LP, LSM, LWM, LI], - icR: Candidate.AuxMI[R, RS, RW, RN, RP, RSM, RWM, RI] - )(using - check: SignCheck[LS, RS, RI, false] + icL: Candidate.Aux[L, LS, LW, LN, LP], + icR: Candidate.Aux[R, RS, RW, RN, RP] ): ExactOp2Aux[CarryOp[Op], DFC, DFValAny, L, R, DFValTP[ - DFXInt[LS, IntP.+[LW, RW], BitAccurate], + DFXInt[LS || RS, IntP.+[LW, RW], BitAccurate], LP | RP ]] = new ExactOp2[CarryOp[Op], DFC, DFValAny, L, R]: - type Out = DFValTP[DFXInt[LS, IntP.+[LW, RW], BitAccurate], LP | RP] + type Out = DFValTP[DFXInt[LS || RS, IntP.+[LW, RW], BitAccurate], LP | RP] def apply(lhs: L, rhs: R)(using DFC): Out = trydf { val dfcAnon = dfc.anonymize val lhsVal = icL(lhs)(using dfcAnon) val rhsVal = icR(rhs)(using dfcAnon) - check(lhsVal.dfType.signed, rhsVal.dfType.signed) + val resultSigned = lhsVal.dfType.signed || rhsVal.dfType.signed import IntParam.+ val width = lhsVal.widthIntParam + rhsVal.widthIntParam - val dfType = DFXInt(lhsVal.dfType.signed, width, BitAccurate) - DFVal.Func(dfType, FuncOp.`*`, List(lhsVal, rhsVal)) + val dfType = DFXInt(resultSigned, width, BitAccurate) + // Convert unsigned operand to signed if needed + val lhsFix = + if (resultSigned && !lhsVal.dfType.signed) + lhsVal.toDFXIntOf(DFXInt(true, lhsVal.widthIntParam + 1, BitAccurate))(using dfcAnon) + else lhsVal + val rhsFix = + if (resultSigned && !rhsVal.dfType.signed) + rhsVal.toDFXIntOf(DFXInt(true, rhsVal.widthIntParam + 1, BitAccurate))(using dfcAnon) + else rhsVal + DFVal.Func(dfType, FuncOp.`*`, List(lhsFix, rhsFix)) + .asInstanceOf[Out] }(using dfc, CTName("*^")) end evOpCarryMulDFXInt @@ -1245,7 +1460,7 @@ object DFXInt: // RWM <: IntP, // RI <: Boolean // ](using - // icR: Candidate.AuxMI[R, RS, RW, RN, RP, RSM, RWM, RI], + // icR: Candidate.AuxM[R, RS, RW, RN, RP, RSM, RWM], // op: ValueOf[Op] // )(using // check: AssertGiven[ @@ -1360,14 +1575,12 @@ object DFUInt: S <: Boolean, W <: IntP, N <: NativeType, - P, - SMask <: Boolean, - WMask <: IntP + P ](using - ic: DFXInt.Val.Candidate.AuxM[R, S, W, N, P, SMask, WMask] + ic: DFXInt.Val.Candidate.Aux[R, S, W, N, P] )(using - unsignedCheck: Unsigned.Check[SMask], - widthCheck: `UBW == RW`.CheckNUB[IntP.CLog2[UB], WMask] + unsignedCheck: Unsigned.Check[S], + widthCheck: `UBW == RW`.CheckNUB[IntP.CLog2[UB], W] ): UBArg[UB, R] with type OutP = P def apply(ub: IntParam[UB], arg: R)(using DFC): Out = @@ -1484,7 +1697,7 @@ end DFSInt //a native Int32 decimal has no explicit Scala compile-time width, since the //actual value determines its width. type DFInt32 = - DFType[ir.DFDecimal, Args4[true, 32, 0, Int32]] // This means: DFDecimal[true, 32, 0, Int32] (could not be defined this way because of type recursion) + DFType[ir.DFDecimal, Args4[Boolean, Int, 0, Int32]] // This means: DFDecimal[Boolean, Int, 0, Int32] (could not be defined this way because of type recursion) final val DFInt32 = ir.DFInt32.asFE[DFInt32] type DFConstInt32 = DFConstOf[DFInt32] object DFConstInt32: diff --git a/core/src/main/scala/dfhdl/core/DFError.scala b/core/src/main/scala/dfhdl/core/DFError.scala index 9b0e1ceba..7b1de058e 100644 --- a/core/src/main/scala/dfhdl/core/DFError.scala +++ b/core/src/main/scala/dfhdl/core/DFError.scala @@ -5,9 +5,12 @@ import dfhdl.internals.* import scala.annotation.targetName import dfhdl.options.OnError +sealed trait LogEvent derives CanEqual: + val dfMsg: String + sealed abstract class DFError( val dfMsg: String -) extends Exception(dfMsg) derives CanEqual +) extends Exception(dfMsg), LogEvent object DFError: class Basic( @@ -54,14 +57,37 @@ object DFError: inline def asOwner: DFOwnerAny = DFOwner[ir.DFOwner](dfErr) end DFError +class DFWarning( + val opName: String, + val dfMsg: String +)(using dfc: DFC) + extends LogEvent derives CanEqual: + import dfc.getSet + val designName = dfc.ownerOption match + case Some(owner) => owner.asIR.getThisOrOwnerDesign.getFullName + case None => "" + val fullName = + if (dfc.isAnonymous) designName + else if (designName.nonEmpty) s"$designName.${dfc.name}" + else dfc.name + val position = dfc.position + override def toString: String = + s"""|DFiant HDL elaboration warning! + |Position: ${position} + |Hierarchy: ${fullName} + |Operation: `${opName}` + |Message: ${dfMsg}""".stripMargin +end DFWarning + class Logger: - private[Logger] var errors: List[DFError] = Nil - def logError(err: DFError): Unit = - errors = err :: errors - def injectErrors(fromLogger: Logger): Unit = - errors = fromLogger.errors ++ errors - def getErrors: List[DFError] = errors.reverse - def clearErrors(): Unit = errors = Nil + private[Logger] var events: List[LogEvent] = Nil + def logEvent(event: LogEvent): Unit = events = event :: events + def injectEvents(fromLogger: Logger): Unit = events = fromLogger.events ++ events + def injectEvents(newEvents: List[LogEvent]): Unit = events = events ++ newEvents + def getErrors: List[DFError] = events.reverse.collect { case e: DFError => e } + def getWarnings: List[DFWarning] = events.reverse.collect { case w: DFWarning => w } + def getEvents: List[LogEvent] = events.reverse + def clearEvents(): Unit = events = Nil def trydfSpecific[T]( block: => T @@ -77,7 +103,7 @@ def trydfSpecific[T]( case e => throw e if (dfc.ownerOption.isEmpty) exitWithError(dfErr.toString()) - dfc.logError(dfErr) + dfc.logEvent(dfErr) finale(dfErr) @targetName("tryDFType") diff --git a/core/src/main/scala/dfhdl/core/DFIf.scala b/core/src/main/scala/dfhdl/core/DFIf.scala index 3bad4e43d..b70024767 100644 --- a/core/src/main/scala/dfhdl/core/DFIf.scala +++ b/core/src/main/scala/dfhdl/core/DFIf.scala @@ -152,7 +152,7 @@ object DFIf: |""".stripMargin ) ) - dfc.logError(err) + dfc.logEvent(err) err.asVal[DFTypeAny, ModifierAny].asInstanceOf[R] end if catch case e: DFError => DFVal(DFError.Derived(e)).asInstanceOf[R] diff --git a/core/src/main/scala/dfhdl/core/DFMatch.scala b/core/src/main/scala/dfhdl/core/DFMatch.scala index 9f1c6bf2b..dd55a048b 100644 --- a/core/src/main/scala/dfhdl/core/DFMatch.scala +++ b/core/src/main/scala/dfhdl/core/DFMatch.scala @@ -100,7 +100,7 @@ object DFMatch: |""".stripMargin ) ) - dfc.logError(err) + dfc.logEvent(err) err.asVal[DFTypeAny, ModifierAny].asInstanceOf[R] end if catch case e: DFError => DFVal(DFError.Derived(e)).asInstanceOf[R] diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index 5984877c7..c9cf18468 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -289,7 +289,7 @@ sealed protected trait DFValLP: import dfc.getSet x.asIR.getConstData.get.asInstanceOf[ir.RateNumber] // lower priority than other evidence because this is more generic - export DFXInt.Val.Ops.evOpArithDFXInt + export DFXInt.Val.Ops.{evOpCommutativeArithDFXInt, evOpNonCommutativeArithDFXInt} export DFOpaque.Val.Ops.{evOpAsDFOpaqueTFE, evOpAsDFOpaqueComp} export DFBits.Val.Ops.{evLogicOpDFBits, evConcatOpDFBits} end DFValLP @@ -797,17 +797,71 @@ object DFVal extends DFValLP: (dfType.asIR.dropUnreachableRefs, op, args.map(_.getReachableMember)) match case SimplifyFunc(func) => func.asValTP[T, P] case (dfType, op, args) => + import dfc.getSet + // Merge consecutive same-op anonymous Funcs for associative operations. + // E.g., `a + b + c` becomes Func(+, [a, b, c]) instead of nested binary Funcs. + // Only merge args that appear once (multi-referenced Funcs must stay as-is). + var mergedPositionStart: Option[Position] = None + val mergedArgs = + if (ir.DFVal.Func.Op.associativeSet.contains(op)) + // Count how many times each arg appears to avoid merging multi-referenced nodes + val argCounts = args.groupBy(identity).view.mapValues(_.length) + args.flatMap { + case prevFunc: ir.DFVal.Func + if prevFunc.op == op + && prevFunc.isAnonymous + && argCounts(prevFunc) == 1 + && canMergeFunc(dfType, op, prevFunc) => + // Track the earliest start position from absorbed Funcs + val prevPos = prevFunc.meta.position + mergedPositionStart = Some( + mergedPositionStart.fold(prevPos)(existing => + if prevPos.lineStart < existing.lineStart || + (prevPos.lineStart == existing.lineStart && + prevPos.columnStart < existing.columnStart) + then prevPos + else existing + ) + ) + // Don't remove prevFunc here — it becomes unreferenced and + // DropUnreferencedAnons will clean it up with proper ref cleanup. + prevFunc.args.map(_.get) + case arg => List(arg) + } + else args + // Merge position: start from absorbed LHS, end from current (RHS) + val meta = mergedPositionStart match + case Some(lhsPos) => + val currentMeta = dfc.getMeta + val currentPos = currentMeta.position + val merged = Position( + lhsPos.file, lhsPos.lineStart, lhsPos.columnStart, + currentPos.lineEnd, currentPos.columnEnd + ) + currentMeta.copy(position = merged) + case None => dfc.getMeta val func: ir.DFVal = ir.DFVal.Func( dfType, op, - args.map(_.refTW[ir.DFVal](knownReachable = true)), + mergedArgs.map(_.refTW[ir.DFVal](knownReachable = true)), dfc.ownerOrEmptyRef, - dfc.getMeta, + meta, dfc.tags ) func.addMember.asValTP[T, P] end match end apply + // Checks if an intermediate Func can be merged into the current one. + // +, -, * are excluded because carry promotion assumes binary (2-arg) Funcs. + // ++ is only merged for flat bits concatenation, not struct/vector/string. + private def canMergeFunc( + resultType: ir.DFType, op: FuncOp, prevFunc: ir.DFVal.Func + )(using ir.MemberGetSet): Boolean = + op match + case FuncOp.++ => + resultType.isInstanceOf[ir.DFBits] && prevFunc.dfType.isInstanceOf[ir.DFBits] + case FuncOp.+ | FuncOp.- | FuncOp.`*` => false + case _ => true // &, |, ^, max, min — no carry concept end Func object Alias: @@ -1793,6 +1847,19 @@ object ConnectOps: )(using dfc: DFC): Unit = trydf { (dualSummon.valueL, dualSummon.valueR) match case (Some(tcL), Some(tcR)) => + import dfc.getSet + import dfhdl.compiler.analysis.* + val currentDesign = dfc.owner.asIR.getThisOrOwnerDesign + enum Write: + case Left, Right + val writeDir = (lhs.asIR, rhs.asIR) match + case (lhs @ DclOut(), rhs @ DclIn()) => + if (lhs.isSameOwnerDesignAs(rhs)) Write.Left + else Write.Right + case (lhs @ DclIn(), rhs @ DclOut()) => + if (rhs.isSameOwnerDesignAs(lhs)) Write.Right + else Write.Left + case _ => Write.Left try tcL.connect(lhs, rhs) catch case eL: Throwable => diff --git a/core/src/main/scala/dfhdl/core/Design.scala b/core/src/main/scala/dfhdl/core/Design.scala index 1b167dda4..989949b9f 100644 --- a/core/src/main/scala/dfhdl/core/Design.scala +++ b/core/src/main/scala/dfhdl/core/Design.scala @@ -125,8 +125,22 @@ trait Design extends Container, HasClsMetaArgs: else dfc.exitOwner() import dfc.getSet - // At the end of the top-level instance we check for errors + // At the end of the top-level instance we check for warnings and errors if (containedOwner.asIR.isTop && thisOwner.isEmpty) + val warnings = dfc.getWarnings + if (warnings.nonEmpty) + System.err.println( + warnings.map(_.toString).mkString("\n\n") + ) + if (dfc.elaborationOptions.Werror.toBoolean) + dfc.logEvent( + DFError.Basic( + "Werror", + new IllegalArgumentException( + "Warnings found with -Werror enabled. Fix the warnings or disable the Werror flag." + ) + ) + ) val errors = dfc.getErrors // If we have errors, then we print them to stderr and exit if (errors.nonEmpty) diff --git a/core/src/test/scala/CoreSpec/DFDecimalSpec.scala b/core/src/test/scala/CoreSpec/DFDecimalSpec.scala index 5e0d73b55..0b01c7ad4 100644 --- a/core/src/test/scala/CoreSpec/DFDecimalSpec.scala +++ b/core/src/test/scala/CoreSpec/DFDecimalSpec.scala @@ -1,5 +1,6 @@ package CoreSpec import dfhdl.* +import dfhdl.core.DFInt32 import munit.* class DFDecimalSpec extends DFSpec: @@ -372,9 +373,7 @@ class DFDecimalSpec extends DFSpec: value <= u8 } assertDSLErrorLog( - """Cannot compare a DFHDL value (width = 8) with a Scala `Int` argument that is wider (width = 10). - |An explicit conversion must be applied. - |""".stripMargin + "The wildcard `Int` value width (10) is larger than the bit-accurate value width (8)." )( """u8 > 1000""" ) { @@ -389,16 +388,12 @@ class DFDecimalSpec extends DFSpec: u8 == negOne } assertRuntimeErrorLog( - """|Cannot compare a DFHDL value (width = 8) with a Scala `Int` argument that is wider (width = 10). - |An explicit conversion must be applied. - |""".stripMargin + "The wildcard `Int` value width (10) is larger than the bit-accurate value width (8)." ) { u8 == big } assertRuntimeErrorLog( - """|Cannot compare a DFHDL value (width = 8) with a Scala `Int` argument that is wider (width = 11). - |An explicit conversion must be applied. - |""".stripMargin + "The wildcard `Int` value width (11) is larger than the bit-accurate value width (8)." ) { s8 == big } @@ -417,58 +412,22 @@ class DFDecimalSpec extends DFSpec: assertEquals(sd"9'255" +^ sd"8'1", sd"10'256") assertEquals(200 + d"8'1", d"8'201") assertEquals(200 +^ d"8'1", d"9'201") - assertEquals(-200 + sd"8'1", sd"9'-199") - assertCompileError( - "The applied RHS value width (9) is larger than the LHS variable width (8)." - )( - """sd"8'22" + d"8'22"""" - ) - assertCompileError( - """|Cannot apply this operation between an unsigned value (LHS) and a signed value (RHS). - |An explicit conversion must be applied. - |""".stripMargin - )( - """d"8'22" + sd"8'22"""" - ) - assertCompileError( - """|Cannot apply this operation between an unsigned value (LHS) and a signed value (RHS). - |An explicit conversion must be applied. - |""".stripMargin - )( - """h"8'22" + sd"8'22"""" - ) - assertCompileError( - """|Cannot apply this operation between an unsigned value (LHS) and a signed value (RHS). - |An explicit conversion must be applied. - |""".stripMargin - )( - """d"8'22" + (-22)""" - ) - assertCompileError( - "The applied RHS value width (9) is larger than the LHS variable width (8)." - )( - """d"8'22" + d"9'22"""" - ) + // -200 is a wildcard that adapts to SInt[8], but -200 doesn't fit → error + // assertEquals(-200 + sd"8'1", sd"9'-199") // now an error with wildcard rules + assertEquals(-100 + sd"8'1", sd"8'-99") + // Commutative + allows mixed width and signedness (DFVal + DFVal) + assertEquals(sd"8'22" + d"8'22", sd"9'44") + assertEquals(d"8'22" + sd"8'22", sd"9'44") + assertEquals(h"8'22" + sd"8'22", sd"9'56") + assertEquals(d"8'22" + d"9'22", d"9'44") + // Wildcard Int literals adapt to bit-accurate value assertEquals(d"8'22" + 200, d"8'222") assertEquals(sd"8'-1" + 1, sd"8'0") - assertDSLErrorLog( - "The applied RHS value width (9) is larger than the LHS variable width (8)." - )( - """sd"8'22" + 200""" - ) { - val value = 200 - sd"8'22" + value - } - assertDSLErrorLog( - """|Cannot apply this operation between an unsigned value (LHS) and a signed value (RHS). - |An explicit conversion must be applied. - |""".stripMargin - )( - """22 + sd"8'22"""" - ) { - val value = 22 - value + sd"8'22" - } + assertEquals(22 + sd"8'22", sd"8'44") + assertEquals(sd"8'22" + 100, sd"8'122") + // These are now errors with wildcard rules (literal doesn't fit bit-accurate value): + // assertEquals(d"8'22" + (-22), sd"9'0") // -22 negative for UInt + // assertEquals(sd"8'22" + 200, sd"9'222") // 200 exceeds SInt[8] assertEquals(d"8'22" - d"8'22", d"8'0") assertEquals(sd"8'22" - sd"8'22", sd"8'0") assertEquals(sd"8'22" - 22, sd"8'0") @@ -514,23 +473,16 @@ class DFDecimalSpec extends DFSpec: val value = 200 sd"8'22" - value } - assertDSLErrorLog( - """|Cannot apply this operation between an unsigned value (LHS) and a signed value (RHS). - |An explicit conversion must be applied. - |""".stripMargin - )( - """22 - sd"8'22"""" - ) { - val value = 22 - value - sd"8'22" - } + // Wildcard adapts: 22 adapts to SInt[8] bit-accurate value + val subWild = 22 - sd"8'22" assertEquals(d"8'22" * d"8'2", d"8'44") assertEquals(d"8'22" / d"8'2", d"8'11") assertEquals(d"8'22" % d"8'2", d"8'0") assertEquals(100 * d"7'2", d"7'72") assertEquals(100 / d"7'2", d"7'50") - assertEquals(17 % d"3'2", d"5'1") + // 5 % d"3'2": wildcard 5 adapts to UInt[3], 5 % 2 = 1 + assertEquals(5 % d"3'2", d"3'1") assertEquals(d"8'22" *^ d"8'2", d"16'44") assertEquals(100 *^ d"7'2", d"14'200") @@ -572,11 +524,10 @@ class DFDecimalSpec extends DFSpec: val t10 = 100 *^ u8 t10.verifyValOf[UInt[15]] } - assertCompileError( - "The applied RHS value width (9) is larger than the LHS variable width (8)." - )( - """s8 + d"8'22"""" - ) + // Commutative + and * allow mixed width/signedness + val t11 = s8 + d"8'22" + val t12 = b8 * s8 + // Non-commutative -, /, % keep strict constraints assertCompileError( """|Cannot apply this operation between an unsigned value (LHS) and a signed value (RHS). |An explicit conversion must be applied. @@ -584,13 +535,6 @@ class DFDecimalSpec extends DFSpec: )( """u8 - sd"8'22"""" ) - assertCompileError( - """|Cannot apply this operation between an unsigned value (LHS) and a signed value (RHS). - |An explicit conversion must be applied. - |""".stripMargin - )( - """b8 * s8""" - ) assertCompileError( """|Cannot apply this operation between an unsigned value (LHS) and a signed value (RHS). |An explicit conversion must be applied. @@ -604,6 +548,225 @@ class DFDecimalSpec extends DFSpec: """u8 % d"9'22"""" ) } + test("Wildcard Int operands") { + val u8 = UInt(8) <> VAR + val s8 = SInt(8) <> VAR + val b8 = Bits(8) <> VAR + val param: Int <> CONST = 10 + val i42: Int = 42 + + // === Commutative + with Scala Int literals === + val t1 = u8 + 5; t1.verifyValOf[UInt[8]] + val t2 = 5 + u8; t2.verifyValOf[UInt[8]] + val t3 = s8 + 5; t3.verifyValOf[SInt[8]] + val t4 = 5 + s8; t4.verifyValOf[SInt[8]] + val t5 = (-5) + s8; t5.verifyValOf[SInt[8]] + val t6 = s8 + (-5); t6.verifyValOf[SInt[8]] + + // === Commutative * with Scala Int literals === + val t7 = u8 * 3; t7.verifyValOf[UInt[8]] + val t8 = 3 * u8; t8.verifyValOf[UInt[8]] + val t9 = s8 * 3; t9.verifyValOf[SInt[8]] + val t10 = 3 * s8; t10.verifyValOf[SInt[8]] + + // === Commutative + with Scala non-literal Int === + val t11 = u8 + i42; t11.verifyValOf[UInt[8]] + val t12 = i42 + u8; t12.verifyValOf[UInt[8]] + val t13 = s8 + i42; t13.verifyValOf[SInt[8]] + val t14 = i42 + s8; t14.verifyValOf[SInt[8]] + + // === Commutative * with Scala non-literal Int === + val t15 = u8 * i42; t15.verifyValOf[UInt[8]] + val t16 = i42 * u8; t16.verifyValOf[UInt[8]] + val t17 = s8 * i42; t17.verifyValOf[SInt[8]] + val t18 = i42 * s8; t18.verifyValOf[SInt[8]] + + // === Commutative + with DFHDL Int param === + val t19 = u8 + param; t19.verifyValOf[UInt[8]] + val t20 = param + u8; t20.verifyValOf[UInt[8]] + val t21 = s8 + param; t21.verifyValOf[SInt[8]] + val t22 = param + s8; t22.verifyValOf[SInt[8]] + + // === Commutative * with DFHDL Int param === + val t23 = u8 * param; t23.verifyValOf[UInt[8]] + val t24 = param * u8; t24.verifyValOf[UInt[8]] + val t25 = s8 * param; t25.verifyValOf[SInt[8]] + val t26 = param * s8; t26.verifyValOf[SInt[8]] + + // === Bits (implicit UInt) with wildcards === + val t27 = b8 + 5 + val t28 = 5 + b8 + val t29 = b8 + param + val t30 = param + b8 + + // === Commutative max/min with wildcards === + val t27b = u8 max 5; t27b.verifyValOf[UInt[8]] + val t27c = 5 max u8; t27c.verifyValOf[UInt[8]] + val t27d = s8 max (-5); t27d.verifyValOf[SInt[8]] + val t27e = (-5) max s8; t27e.verifyValOf[SInt[8]] + val t27f = u8 min i42; t27f.verifyValOf[UInt[8]] + val t27g = i42 min u8; t27g.verifyValOf[UInt[8]] + val t27h = s8 min i42; t27h.verifyValOf[SInt[8]] + val t27i = i42 min s8; t27i.verifyValOf[SInt[8]] + val t27j = u8 max param; t27j.verifyValOf[UInt[8]] + val t27k = param max u8; t27k.verifyValOf[UInt[8]] + val t27l = s8 min param; t27l.verifyValOf[SInt[8]] + val t27m = param min s8; t27m.verifyValOf[SInt[8]] + + // === Param + Param: stays Int (DFInt32) === + val param2: Int <> CONST = 20 + val t31 = param + param2; t31.verifyValOf[DFInt32] + val t32 = param * param2; t32.verifyValOf[DFInt32] + val t33b = param max param2; t33b.verifyValOf[DFInt32] + val t33c = param min param2; t33c.verifyValOf[DFInt32] + + // === Non-commutative ops: wildcard adapts to LHS === + val t33 = u8 - 3; t33.verifyValOf[UInt[8]] + val t34 = u8 - i42; t34.verifyValOf[UInt[8]] + val t35 = u8 - param; t35.verifyValOf[UInt[8]] + val t36 = u8 / 3; t36.verifyValOf[UInt[8]] + val t37 = u8 / i42; t37.verifyValOf[UInt[8]] + val t38 = u8 / param; t38.verifyValOf[UInt[8]] + val t39 = s8 - 3; t39.verifyValOf[SInt[8]] + val t40 = s8 - i42; t40.verifyValOf[SInt[8]] + val t41 = s8 - param; t41.verifyValOf[SInt[8]] + val t42 = s8 % 3; t42.verifyValOf[SInt[8]] + val t43 = s8 % i42; t43.verifyValOf[SInt[8]] + val t44 = s8 % param; t44.verifyValOf[SInt[8]] + + // === Non-commutative with wildcard LHS === + // Wildcard always adapts, even in non-commutative ops + val t45 = 200 - u8; t45.verifyValOf[UInt[8]] + val t46 = i42 - u8; t46.verifyValOf[UInt[8]] + val t47 = param - u8; t47.verifyValOf[UInt[8]] + val t48 = param / u8; t48.verifyValOf[UInt[8]] + + // === Constant propagation === + // All-constant expressions produce CONST results + val c1: UInt[8] <> CONST = d"8'22" + 5 + val c2: UInt[8] <> CONST = 5 + d"8'22" + val c3: SInt[8] <> CONST = sd"8'22" + (-5) + val c4: UInt[8] <> CONST = d"8'22" * d"8'2" + val c5: UInt[8] <> CONST = 3 * d"8'22" + // Param + literal is also CONST (both are CONST) + val c6: UInt[8] <> CONST = d"8'5" + param + val c7: SInt[8] <> CONST = sd"8'5" + param + // Param + VAR is NOT const (verified by type — no CONST annotation) + val nc1 = u8 + param // result is not CONST + val nc2 = param + u8 // result is not CONST + + // === Comparisons: wildcard adapts === + val cmp1 = u8 == 200 + // 200 == u8 not supported (Scala primitive LHS with ==) + val cmp3 = s8 < (-5) + val cmp4 = (-5) < s8 + val cmp5 = u8 == param + val cmp6 = param == u8 + val cmp7 = s8 < param + val cmp8 = param < s8 + val cmp9 = u8 == i42 + // i42 == u8 not supported (Scala primitive LHS with ==) + val cmp11 = s8 < i42 + val cmp12 = i42 < s8 + + // Compile-time errors for literal value-fit checking + assertCompileError( + "The wildcard `Int` value width (10) is larger than the bit-accurate value width (8)." + )("""u8 + 1000""") + assertCompileError( + "Cannot apply a signed wildcard `Int` value to an unsigned bit-accurate value.\nUse an explicit conversion or `sd\"\"` interpolation." + )("""u8 + (-1)""") + assertCompileError( + "The wildcard `Int` value width (11) is larger than the bit-accurate value width (8)." + )("""s8 + 1000""") + // Non-commutative: literal wildcard LHS that doesn't fit + assertCompileError( + "The wildcard `Int` value width (10) is larger than the bit-accurate value width (8)." + )("""1000 - u8""") + assertCompileError( + "Cannot apply a signed wildcard `Int` value to an unsigned bit-accurate value.\nUse an explicit conversion or `sd\"\"` interpolation." + )("""(-1) - u8""") + // Unsigned wildcard adapting to signed bit-accurate value needs extra bit + assertCompileError( + "The wildcard `Int` value width (9) is larger than the bit-accurate value width (8)." + )("""255 + s8""") + assertCompileError( + "The wildcard `Int` value width (9) is larger than the bit-accurate value width (8)." + )("""s8 + 255""") + assertCompileError( + "The wildcard `Int` value width (9) is larger than the bit-accurate value width (8)." + )("""255 - s8""") + + // Elaboration-time errors for non-literal value-fit checking + assertDSLErrorLog( + "Wildcard `Int` value width (10) is larger than the bit-accurate value width (8)." + )( + "" + ) { + val bigVal: Int <> CONST = 1000 + u8 + bigVal + } + assertDSLErrorLog( + "Wildcard `Int` value is negative and cannot adapt to unsigned bit-accurate value UInt[8]." + )( + "" + ) { + val negVal: Int <> CONST = -1 + u8 + negVal + } + // Unsigned wildcard adapting to signed bit-accurate value at elaboration time + assertDSLErrorLog( + "Wildcard `Int` value width (9) is larger than the bit-accurate value width (8)." + )( + "" + ) { + val bigUnsigned: Int <> CONST = 255 + s8 + bigUnsigned + } + } + test("d\"\" unsigned-only interpolation") { + // d"" produces unsigned UInt constants + assertEquals(d"0", d"1'0") + assertEquals(d"255", d"8'255") + assertEquals(d"8'42", d"8'42") + + // d"" rejects negative values at compile time + assertCompileError( + """Negative value in unsigned `d""` interpolation. Use `sd""` for signed values.""" + )( + """d"-1"""" + ) + assertCompileError( + """Negative value in unsigned `d""` interpolation. Use `sd""` for signed values.""" + )( + """d"8'-1"""" + ) + + // sd"" produces signed SInt constants (positive and negative) + assertEquals(sd"0", sd"2'0") + assertEquals(sd"-1", sd"2'-1") + assertEquals(sd"8'42", sd"8'42") + assertEquals(sd"8'-1", sd"8'-1") + + // d"" with runtime negative value is a runtime error + assertRuntimeErrorLog( + """|Unexpected negative value found for unsigned decimal string interpolation: -5 + |To Fix: Use the signed decimal string interpolator `sd` instead.""".stripMargin + ) { + val negVal = -5 + d"$negVal" + } + } + test("Arithmetic position") { + val u8 = UInt(8) <> VAR + val b8 = Bits(8) <> VAR + // single binary op: position spans the full expression + val t1 = u8 + u8; t1.assertPosition(0, 1, 14, 21) + // chained +: NOT merged (carry promotion requires binary), position is the last op + val t2 = u8 + u8 + u8; t2.assertPosition(0, 1, 14, 26) + // chained ^: merged into multi-arg, position spans from first to last operand + val t3 = b8 ^ b8 ^ b8; t3.assertPosition(0, 1, 14, 26) + } test("Arithmetic auto-carry promotion") { val u8 = UInt(8) <> VAR val u5 = UInt(5) <> VAR @@ -684,4 +847,91 @@ class DFDecimalSpec extends DFSpec: val r9: Int <> CONST = 4 assertEquals(t9, r9) } + test("Implicit Int Verilog-semantics warning") { + val warnMsg = + """|Implicit Scala/DFHDL Int conversion may produce different results than Verilog. + |In Verilog, integer literals are 32-bit, which can widen intermediate arithmetic. + |In DFHDL, Int literals are converted to minimum bit-accurate width. + |Use carry operations (+^, -^, *^) or explicit bit-accurate literals (d"W'V").""".stripMargin + val u8 = UInt(8) <> VAR + val a = UInt(8) <> VAR + val b = UInt(8) <> VAR + val c = UInt(8) <> VAR + val d = UInt(8) <> VAR + + // Should warn: (a + b + c + d) / 4 + assertRuntimeWarningLog(warnMsg) { + val t1 = (a + b + c + d) / 4 + } + + // Should warn: (a * 3 + b) / 4 + assertRuntimeWarningLog(warnMsg) { + val t2 = (a * 3 + b) / 4 + } + + // Should warn: (a + b) % 3 + assertRuntimeWarningLog(warnMsg) { + val t2b = (a + b) % 3 + } + + // Should warn: (a + b + 0) >> 1 — "Forcing Larger Evaluation" Verilog pattern + assertRuntimeWarningLog(warnMsg) { + val t2c = (a + b + 0) >> 1 + } + + // Should NOT warn: (a + b) >> 2 — no implicit Int in the + chain + val t2d = (a + b) >> 2 + + // Should warn: wider target with implicit Int in chain + val sum = UInt(10) <> VAR + assertRuntimeWarningLog(warnMsg) { + sum := a + b + c + d + 1 + } + + // Should NOT warn: wider target but explicit literal + sum := a + b + c + d + d"1" + + // Should NOT warn: wider target but single op, carry promotion handles it + sum := u8 + 1 + + // Should warn: wider target with chain, intermediate overflow + assertRuntimeWarningLog(warnMsg) { + sum := u8 + u8 + 1 + } + + // Should NOT warn: target width == expression width + u8 := u8 + 1 + + // Should NOT warn: a / 4 (no anonymous arith chain) + val t3 = a / 4 + + // Should NOT warn: carry ops used + val t4 = (a +^ b +^ c +^ d) / 4 + + // Should NOT warn: explicit bit-accurate literal + val t5 = (a + b + c + d) / d"3'4" + + // Should warn: DFHDL Int <> CONST used as divisor + val p: Int <> CONST = 4 + assertRuntimeWarningLog(warnMsg) { + val t6 = (a + b + c + d) / p + } + + // Should NOT warn: operands are 32-bit or wider + val w32 = UInt(32) <> VAR + val w32b = UInt(32) <> VAR + val t7 = (w32 + w32b) / 4 + + assertNoWarnings() + } + val sint8 = SInt(8) <> VAR + test("Clean type in error messages (no ExactOp2Aux leakage)") { + val errors = compiletime.testing.typeCheckErrors("(sint8 + sint8).sint") + assert(errors.nonEmpty, "Expected a compile error for .sint on SInt") + val allMessages = errors.map(_.message).mkString("\n") + assert( + !allMessages.contains("ExactOp2Aux"), + s"Error message should not contain ExactOp2Aux projection types:\n$allMessages" + ) + } end DFDecimalSpec diff --git a/core/src/test/scala/DFSpec.scala b/core/src/test/scala/DFSpec.scala index 88a5f02ba..353acc061 100644 --- a/core/src/test/scala/DFSpec.scala +++ b/core/src/test/scala/DFSpec.scala @@ -38,10 +38,12 @@ abstract class DFSpec extends NoDFCSpec, HasTypeName, HasDFC: private val noErrMsg = "No error found" inline def assertRuntimeErrorLog(expectedErr: String)(runTimeCode: => Unit): Unit = - dfc.clearErrors() + val currentEvents = dfc.getEvents + dfc.clearEvents() runTimeCode val err = dfc.getErrors.headOption.map(_.dfMsg).getOrElse(noErrMsg) - dfc.clearErrors() + dfc.clearEvents() + dfc.injectEvents(currentEvents) assertNoDiff(err, expectedErr) def assertEquals[T <: DFType, L <: DFConstOf[T], R <: DFConstOf[T]](l: L, r: R)(using DFC): Unit = @@ -54,12 +56,28 @@ abstract class DFSpec extends NoDFCSpec, HasTypeName, HasDFC: inline if (compileTimeCode != "") assertCompileError(expectedErr)(compileTimeCode) else () - dfc.clearErrors() + val currentEvents = dfc.getEvents + dfc.clearEvents() runTimeCode val err = dfc.getErrors.headOption.map(_.dfMsg).getOrElse(noErrMsg) - dfc.clearErrors() + dfc.clearEvents() + dfc.injectEvents(currentEvents) assertNoDiff(err, expectedErr) + inline def assertRuntimeWarningLog(expectedWarn: String)(runTimeCode: => Unit): Unit = + val currentEvents = dfc.getEvents + dfc.clearEvents() + runTimeCode + val warn = dfc.getWarnings.headOption.map(_.dfMsg).getOrElse(noErrMsg) + dfc.clearEvents() + dfc.injectEvents(currentEvents) + assertNoDiff(warn, expectedWarn) + + def assertNoWarnings(): Unit = + val warns = dfc.getWarnings + if (warns.nonEmpty) + assert(false, s"Expected no warnings, but found:\n${warns.mkString("\n")}") + def getCodeStringFrom(block: => Unit): String = import dfc.getSet val startIdx = dfc.mutableDB.DesignContext.getMembersNum @@ -75,12 +93,12 @@ abstract class DFSpec extends NoDFCSpec, HasTypeName, HasDFC: println(getCodeStringFrom(block)) inline def assertCodeString(expectedCS: String)(block: => Unit): Unit = - dfc.clearErrors() + dfc.clearEvents() val cs = getCodeStringFrom(block) assertNoDiff(cs, expectedCS) - val errors = dfc.getErrors - if (errors.nonEmpty) - assert(false, errors.mkString("\n")) + val events = dfc.getEvents + if (events.nonEmpty) + assert(false, events.mkString("\n")) def assertLatestDesignDclPosition( lineOffset: Int, diff --git a/core/src/test/scala/Playground.scala b/core/src/test/scala/Playground.scala new file mode 100644 index 000000000..723e77aad --- /dev/null +++ b/core/src/test/scala/Playground.scala @@ -0,0 +1,3 @@ +package dfhdl + +class Foo extends EDDesign diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 6736fbd3c..c44fcadc0 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -292,7 +292,7 @@ class Sum(val MAX: Int <> CONST = 16) -`.width` works on any DFHDL value. Use it to derive widths from existing declarations: +`.width` works on any DFHDL value (a port, variable, or constant — anything declared with `<>`; see [logic/reg/wire](#logicregwire) above). Use it to derive widths from existing declarations: ```scala val a = UInt.until(MAX) <> IN @@ -642,6 +642,39 @@ val fill_one = b"1".repeat(W) `.repeat` works on any `Bits` value, including single-bit literals. /// +/// admonition | Bit Concatenation (`{a, b, ...}`) + type: verilog +Verilog's concatenation operator `{a, b}` maps to the `++` operator or tuple `.toBits` in DFHDL: + +
+ +```sv linenums="0" title="Verilog" +logic [7:0] a, b; +logic [15:0] concat = {a, b}; + +// Parametric: {1'b1, {(N-1){1'b0}}} +parameter N = 8; +logic [N-1:0] half = {1'b1, {(N-1){1'b0}}}; +``` + +```scala linenums="0" title="DFHDL" +val a, b = Bits(8) <> VAR +val concat = a ++ b // Bits[16] + +// Parametric: MSB=1, rest zeros +val N: Int <> CONST = 8 +val half = b"1" ++ b"0".repeat(N - 1) +``` + +
+ +Alternative: use tuple `.toBits` syntax for multi-value concatenation: + +```scala +val concat = (a, b, c).toBits // equivalent to a ++ b ++ c +``` +/// + /// admonition | Part-Select Notation (`-:` and `+:`) type: verilog Verilog's descending and ascending part-select notation maps to DFHDL's `(hi, lo)` range slice, or use the convenience methods `.msbits(n)` and `.lsbits(n)`: @@ -672,11 +705,19 @@ val bit5 = data(5) // single bit -Bit-slicing and single-bit access work on `Bits`, `UInt`, and `SInt` values with the same syntax. You do **not** need to convert `SInt` to `Bits` before slicing: +Bit-slicing and single-bit access work on `Bits`, `UInt`, and `SInt` values with the same syntax. Slicing preserves the source type: + +| Source type | Slice result | +|---|---| +| `Bits[N]` | `Bits` | +| `UInt[N]` | `UInt` | +| `SInt[N]` | `SInt` | + +You do **not** need to convert `SInt` to `Bits` before slicing — the result is already `SInt`: ```scala val prod = SInt(16) <> VAR -val top8 = prod(15, 8) // 8-bit slice, returns Bits +val top8 = prod(15, 8) // 8-bit SInt slice val sign = prod(15) // single bit access ``` /// @@ -684,35 +725,46 @@ val sign = prod(15) // single bit access /// admonition | Arithmetic with Signed Values and Constants type: verilog **Arithmetic operand compatibility:** -DFHDL enforces sign and width constraints at compile time. The LHS must be at least as wide and at least as signed as the RHS. When the LHS is signed and the RHS is unsigned, the RHS is implicitly widened by 1 bit (for the sign bit), so the LHS must be wide enough to accommodate that. - -| LHS | RHS | Result | Constraints | Valid | Invalid | -|-----|-----|--------|-------------|-------|---------| -| `UInt[W1]` | `UInt[W2]` | `UInt[W1]` | `W1 >= W2` | `d"8'5" + d"4'3"` | `d"4'5" + d"8'3"` | -| `UInt[W]` | `Int` | `UInt[W]` | `Int` >= 0, fits in W bits | `d"8'5" + 3` | `d"8'5" + (-1)` | -| `SInt[W1]` | `SInt[W2]` | `SInt[W1]` | `W1 >= W2` | `sd"8'1" + sd"4'3"` | `sd"4'5" + sd"8'3"` | -| `SInt[W]` | `Int` | `SInt[W]` | `Int` fits in W bits | `sd"8'5" + (-3)` | | -| `SInt[W1]` | `UInt[W2]` | `SInt[W1]` | `W1 >= W2 + 1` | `sd"8'5" + d"4'3"` | `sd"4'5" + d"4'3"` | -| `UInt[W]` | `SInt[W2]` | **Error** | Unsigned cannot accept signed RHS | | `d"8'5" + sd"4'3"` | -| `Int` | `Int` | `Int` | Elaboration-time arithmetic | `3 + 5` | | -| `Int` (>= 0) | `UInt[W]` | `UInt[Int]` | `W` fits in `Int`'s width | `180 - d"4'3"` | `2 - d"8'200"` | -| `Int` (< 0) | `SInt[W]` | `SInt[Int]` | `W` fits in `Int`'s width | `(-5) + sd"4'3"` | | - -The constraint is on **width**, not value. A small value in a wide type is valid as LHS: `d"8'1" + d"4'15"` works because `W1=8 >= W2=4`. +DFHDL enforces sign and width constraints at compile time. **Commutative operations** (`+`, `*`, `max`, `min`) produce the widest, most signed result -- operand order does not matter. **Non-commutative operations** (`-`, `/`, `%`) require the LHS to be at least as wide and signed as the RHS. When mixing signed and unsigned, the unsigned operand is implicitly sign-extended by 1 bit. + +Both Scala `Int` values and DFHDL `Int` parameters act as [wildcards][wildcard-ops] -- the wildcard `Int` value adapts to the bit-accurate value's sign and width. If the wildcard `Int` value does not fit, an error is generated. + +```scala +// Commutative: result is widest, most signed +d"8'5" + d"4'3" // UInt[8] (max(8,4) = 8) +d"4'5" + d"8'3" // UInt[8] (commutative, same result) +sd"8'5" + d"4'3" // SInt[8] (max(8, 4+1) = 8, signed) +d"8'5" + sd"4'3" // SInt[9] (max(8+1, 4) = 9, signed) +d"4'5" + d"8'200" // UInt[8] (larger operand widens the result) + +// Wildcard `Int` values adapt to bit-accurate values +d"8'5" + 3 // UInt[8] (3 adapts to UInt[8]) +sd"8'5" + (-3) // SInt[8] (-3 adapts to SInt[8]) +val param: Int <> CONST = 10 +d"8'5" + param // UInt[8] (param adapts to UInt[8]) +sd"8'5" + param // SInt[8] (param adapts to SInt[8]) +d"8'5" + 1000 // ERROR: 1000 exceeds UInt[8] range +d"8'5" + (-1) // ERROR: -1 is negative for UInt bit-accurate value + +// Non-commutative: LHS-dominant, LHS must be >= RHS +d"8'5" - d"4'3" // UInt[8] +sd"8'5" - d"4'3" // SInt[8] (RHS widened to 5 bits, 8 >= 5) +// d"4'5" - d"8'3" // ERROR: RHS width > LHS width +// d"8'5" - sd"4'3" // ERROR: unsigned LHS, signed RHS +``` **Comparison operand compatibility:** -Comparisons (`==`, `!=`, `<`, `>`, `<=`, `>=`) require both operands to have the **same signedness and width**. When comparing with an `Int`, its actual width must not exceed the DFHDL value's width. - -| LHS | RHS | Constraints | Valid | Invalid | -|-----|-----|-------------|-------|---------| -| `UInt[W]` | `UInt[W]` | Same width | `d"8'5" == d"8'3"` | `d"8'5" == d"4'3"` | -| `UInt[W]` | `Int` | `Int` >= 0, fits in W bits | `d"8'5" == 3` | `d"4'5" == 300` | -| `SInt[W]` | `SInt[W]` | Same width | `sd"8'5" < sd"8'3"` | `sd"8'5" < sd"4'3"` | -| `SInt[W]` | `Int` | `Int` fits in W bits | `sd"8'5" == (-3)` | | -| `Int` (>= 0) | `UInt[W]` | Fits in W bits | `3 == d"8'5"` | `300 == d"4'5"` | -| `Int` (< 0) | `SInt[W]` | Fits in W bits | `(-3) == sd"8'5"` | | -| `UInt[W]` | `SInt[W2]` | **Error** | | `d"8'5" == sd"8'3"` | -| `SInt[W]` | `UInt[W2]` | **Error** | | `sd"8'5" == d"8'3"` | +Comparisons (`==`, `!=`, `<`, `>`, `<=`, `>=`) require both operands to have the **same signedness and width**. `Int` values act as wildcards, adapting to the DFHDL value's type. + +```scala +d"8'5" == d"8'3" // OK (same sign, same width) +d"8'5" == 3 // OK (3 adapts to UInt[8]) +sd"8'5" < sd"8'3" // OK (same sign, same width) +sd"8'5" == (-3) // OK (-3 adapts to SInt[8]) +// d"8'5" == d"4'3" // ERROR: different widths +// d"8'5" == sd"8'3" // ERROR: different signedness +// d"4'5" == 300 // ERROR: 300 exceeds UInt[4] range +``` To compare values of different widths, use `.resize(W)` to match widths first. To compare values of different signedness, convert explicitly (e.g., `.bits.sint` or `.signed`). @@ -773,6 +825,37 @@ end MixedArith See [Arithmetic Operations][arithmetic-ops] and [Carry Arithmetic][carry-ops] for full details. /// +/// admonition | Integer Literal Width and Silent Overflow + type: verilog +In Verilog, unsized integer literals are 32-bit. When combined with narrower signals, the wider literal causes the entire expression to evaluate at 32-bit width via context-dependent propagation. This prevents intermediate overflow in expressions like `(a + b + c + d) / 4`. + +In DFHDL, Scala `Int` literals are implicitly converted to minimum-width bit-accurate types (e.g., `4` becomes `UInt[3]`). Each arithmetic operation independently uses the LHS width, so intermediate results can overflow before reaching a division or shift. + +DFHDL detects this pattern at elaboration and issues a warning. See [Implicit Scala `Int` and Verilog-semantics mismatch][arithmetic-ops] for the full list of warning triggers. + +
+ +```sv linenums="0" title="Verilog" +input logic [7:0] a, b, c, d; +output logic [7:0] result; + +// 4 is 32-bit, so (a+b+c+d) evaluates +// at 32-bit width — no overflow +assign result = (a + b + c + d) / 4; +``` + +```scala linenums="0" title="DFHDL (with carry ops)" +val a, b, c, d = UInt(8) <> IN +val result = UInt(8) <> OUT + +// Use carry ops to match Verilog's +// overflow-free semantics +result <> ((a +^ b +^ c +^ d) / 4).truncate +``` + +
+/// + ## Parametric Constants diff --git a/docs/user-guide/compilation/index.md b/docs/user-guide/compilation/index.md index 8a0b24102..03fc5af5f 100755 --- a/docs/user-guide/compilation/index.md +++ b/docs/user-guide/compilation/index.md @@ -4,3 +4,13 @@ --- ## Elaboration + +### Wildcard Arithmetic Value Checking {#wildcard-check} + +When [arithmetic operations][arithmetic-ops] involve wildcard `Int` values (Scala `Int` or DFHDL `Int` parameters), the wildcard `Int` value adapts to the bit-accurate value's sign and width. The value is then checked to ensure it fits. This check occurs at three levels, depending on when the value becomes known: + +1. **Scala compile-time** -- Literal Scala integers (e.g., `u8 + 1000`) have known values at compile time. The Scala compiler reports an error immediately if the wildcard `Int` value exceeds the bit-accurate value's range or has incompatible sign. + +2. **DFHDL elaboration-time** -- Non-literal Scala integers (e.g., `val x: Int = computeValue(); u8 + x`) and DFHDL `Int` constants whose values are resolved during elaboration. A DFHDL elaboration error (Scala runtime error) is generated if the value does not fit. + +3. **Synthesis/simulation-time** -- DFHDL `Int` parameters that are set externally or computed in complex generation loops may not be known until synthesis or simulation. Assertions must be added to verify these values at the target platform level. This is a planned future feature (TODO). diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index f3157037f..8c1b6cc50 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -955,7 +955,7 @@ DFHDL provides three decimal numeric types: - `UInt` - Unsigned bit-accurate integer values - `SInt` - Signed bit-accurate integer values -- `Int` - 32-bit integer values (used mainly for parameters) +- `Int` - 32-bit integer values (used mainly for parameters). In operations with `UInt` or `SInt`, both Scala `Int` and DFHDL `Int` act as [wildcards][wildcard-ops] that adapt to the bit-accurate value's sign and width. #### DFType Constructors @@ -983,30 +983,32 @@ DFHDL provides three decimal numeric types: #### Constant Generation -##### Decimal String-Interpolator {#d-interp} +##### Unsigned Decimal String-Interpolator {#d-interp} -The decimal string interpolator `d` creates unsigned/signed integer constants (`UInt`) from decimal values. +The unsigned decimal string interpolator `d` creates unsigned integer constants (`UInt`) from decimal values. For negative values, use the [signed decimal string-interpolator][sd-interp]. -```scala linenums="0" title="Decimal string-interpolation syntax" +```scala linenums="0" title="Unsigned decimal string-interpolation syntax" d"width'dec" ``` -- __dec__ is a sequence of decimal characters ('0'-'9') with an optional prefix `-` for negative values +- __dec__ is a sequence of decimal characters ('0'-'9'). Negative values are not allowed. - __width__ followed by a `'` is optional and specifies the exact width of the integer's bit representation - Separators `_` (underscore) and `,` (comma) within `dec` are ignored - If width is omitted, it is inferred from the value's size -- If specified, the output is padded with zeros or extended for signed numbers using two's complement -- Returns an unsigned `UInt[W]` for natural numbers and signed `SInt[W]` for negative numbers, where `W` is the width in bits +- If specified, the output is padded with zeros +- Returns `UInt[W]`, where `W` is the width in bits - An error occurs if the specified width is less than required to represent the value +- When used with a DFHDL `Int` parameter, the interpolation binds it as unsigned -Examples: ```scala -d"0" // UInt[1], value = 0 -d"-1" // SInt[2], value = -1 -d"8'-1" // SInt[8], value = -1 -d"255" // UInt[8], value = 255 -d"1,023" // UInt[10], value = 1023 -d"1_000" // UInt[10], value = 1000 +d"0" // UInt[1], value = 0 +d"255" // UInt[8], value = 255 +d"8'42" // UInt[8], value = 42 +d"1,023" // UInt[10], value = 1023 +d"1_000" // UInt[10], value = 1000 +d"$param" // UInt[Int], unsigned binding of Int parameter +d"8'$param" // UInt[8], unsigned binding with explicit width +d"${w}'$param" // UInt[w.type], unsigned binding with parametric width ``` ##### Signed Decimal String-Interpolator {#sd-interp} @@ -1023,14 +1025,17 @@ sd"width'dec" - Output is always a signed integer type `SInt[W]`, regardless of whether the value is negative or natural - Width is always at least 2 bits to accommodate the sign bit - An error occurs if the specified width is less than required to represent the value including the sign bit +- When used with a DFHDL `Int` parameter, the interpolation binds it as signed -Examples: ```scala -sd"0" // SInt[2], value = 0 (natural number represented as signed) -sd"-1" // SInt[2], value = -1 -sd"255" // SInt[9], value = 255 (natural number represented as signed) -sd"8'42" // SInt[8], value = 42 -sd"8'255" // Error: width too small to represent value with sign bit +sd"0" // SInt[2], value = 0 (natural number represented as signed) +sd"-1" // SInt[2], value = -1 +sd"255" // SInt[9], value = 255 (natural number represented as signed) +sd"8'42" // SInt[8], value = 42 +sd"8'255" // Error: width too small to represent value with sign bit +sd"$param" // SInt[Int], signed binding of Int parameter +sd"8'$param" // SInt[8], signed binding with explicit width +sd"${w}'$param" // SInt[w.type], signed binding with parametric width ``` #### Examples @@ -1504,6 +1509,20 @@ class Example extends EDDesign: ``` ## Operations +### Constant Propagation + +When all operands of an expression are constants (`CONST`), the result is also a constant. This includes Scala `Int` literals, DFHDL `Int` parameters, and bit-accurate constants created with `d""` or `sd""`. + +```scala +val param: Int <> CONST = 10 +val c1: UInt[8] <> CONST = d"8'5" + 3 // constant + literal = constant +val c2: UInt[8] <> CONST = d"8'5" + param // constant + param = constant + +val u8 = UInt(8) <> VAR +val v1 = u8 + 3 // VAR + literal = not constant +val v2 = u8 + param // VAR + param = not constant +``` + ### Conversions and Casts {#type-conversion} The diagram below shows the conversion/cast paths between DFHDL types. Solid arrows are simple casts that preserve width; dashed arrows involve width changes. @@ -1870,18 +1889,37 @@ VHDL has no equivalent to Verilog's ternary expression. The DFHDL-generated VHDL Applies to: `UInt`, `SInt`, `Bits` (via implicit conversion to `UInt`), `Int`, `Double` (`%` not available for `Double`) /// html | div.decimal_arithmetic -| Operation | Description | Returns | -| ------------ | -------------- | ---------------- | -| `lhs + rhs` | Addition | Same type as LHS | -| `lhs - rhs` | Subtraction | Same type as LHS | -| `lhs * rhs` | Multiplication | Same type as LHS | -| `lhs / rhs` | Division | Same type as LHS | -| `lhs % rhs` | Modulo | Same type as LHS | +| Operation | Description | Returns | +| ------------ | -------------- | ----------------------------------- | +| `lhs + rhs` | Addition | Commutative: widest, most signed | +| `lhs - rhs` | Subtraction | Same type as LHS | +| `lhs * rhs` | Multiplication | Commutative: widest, most signed | +| `lhs / rhs` | Division | Same type as LHS | +| `lhs % rhs` | Modulo | Same type as LHS | +| `lhs max rhs` | Maximum | Commutative: widest, most signed | +| `lhs min rhs` | Minimum | Commutative: widest, most signed | /// #### Bit-Accurate Type Constraints (`UInt`, `SInt`) -The result of a standard arithmetic operation on bit-accurate types always has the **same type as the LHS** operand. The RHS is resized to match the LHS before the operation is applied. +**Commutative operations** (`+`, `*`, `max`, `min`) produce a result that is **as wide and as signed as possible** given both operands. The narrower operand is resized to match. Operand order does not affect the result type. + +**Non-commutative operations** (`-`, `/`, `%`) use the **LHS type** as the result. The RHS is resized to match the LHS before the operation is applied. The LHS must be at least as wide and at least as signed as the RHS. + +##### Commutative result type rules (`+`, `*`, `max`, `min`) + +The result is signed if either operand is signed. When mixing signed and unsigned, the unsigned operand is implicitly sign-extended by 1 bit (gaining a `0` sign bit). + +/// html | div.operations +| LHS Type | RHS Type | Result Type | +| -------- | -------- | ----------- | +| `UInt[LW]` | `UInt[RW]` | `UInt[Max[LW, RW]]` | +| `SInt[LW]` | `SInt[RW]` | `SInt[Max[LW, RW]]` | +| `SInt[LW]` | `UInt[RW]` | `SInt[Max[LW, RW + 1]]` | +| `UInt[LW]` | `SInt[RW]` | `SInt[Max[LW + 1, RW]]` | +/// + +##### Non-commutative result type rules (`-`, `/`, `%`) **Sign rule** -- The LHS sign must be greater than or equal to the RHS sign (`signed >= unsigned`): @@ -1896,7 +1934,36 @@ The result of a standard arithmetic operation on bit-accurate types always has t **Width rule** -- The LHS width must be greater than or equal to the (effective) RHS width. When applying `SInt op UInt`, the effective RHS width is `RHS width + 1` because the unsigned value gains an implicit sign bit. -**Scala `Int` literals** are auto-promoted to a matching DFHDL type with the minimum required bit width. A negative `Int` literal on the RHS of a `UInt` operation is a compile error (unsigned LHS cannot accept a signed RHS). +### Wildcard `Int` Values {#wildcard-ops} + +Both Scala `Int` values and DFHDL `Int` parameters (`Int <> CONST`) act as **wildcards** when used in operations with bit-accurate `UInt` or `SInt` values. The wildcard `Int` value adapts to the bit-accurate value's sign and width. If the wildcard `Int` value does not fit in the bit-accurate value's range or has incompatible sign, an error is generated. + +```scala +val u8 = UInt(8) <> VAR +val s8 = SInt(8) <> VAR +val param: Int <> CONST = 10 + +// Wildcard `Int` value adapts to bit-accurate value in commutative ops +u8 + 5 // UInt[8] (5 adapts to UInt[8]) +u8 + param // UInt[8] (param adapts to UInt[8]) +s8 + param // SInt[8] (param adapts to SInt[8]) +param + u8 // UInt[8] (commutative, same result) + +// Wildcard `Int` value adapts to bit-accurate value in non-commutative ops +u8 - 3 // UInt[8] (3 adapts to UInt[8]) +u8 / param // UInt[8] (param adapts to UInt[8]) + +// Wildcard `Int` value adapts in comparisons +u8 == 200 // OK (200 fits in UInt[8]) +s8 < (-5) // OK (-5 fits in SInt[8]) + +// ERROR: wildcard `Int` value does not fit bit-accurate value +u8 + 1000 // ERROR: 1000 exceeds UInt[8] range (0..255) +u8 + (-1) // ERROR: -1 is negative for unsigned bit-accurate value +s8 + 1000 // ERROR: 1000 exceeds SInt[8] range (-128..127) +``` + +See [Wildcard Arithmetic Value Checking][wildcard-check] for details on when these checks occur (compile-time, elaboration-time, or synthesis-time). /// admonition | `Bits` values in arithmetic type: tip @@ -1917,39 +1984,36 @@ val u8 = UInt(8) <> VAR val u4 = UInt(4) <> VAR val s8 = SInt(8) <> VAR -// UInt + UInt (same width) -val r1 = u8 + u8 // UInt[8] -// SInt + SInt -val r2 = s8 + s8 // SInt[8] -// SInt + UInt: RHS widened to 5 bits (4+1); 8 >= 5, OK -val r3 = s8 + u4 // SInt[8] -// UInt + Scala Int literal (200 fits in 8 bits) -val r4 = u8 + 200 // UInt[8] -// Scala Int literal on LHS (200 is promoted to UInt[8]) -val r5 = 200 - u8 // UInt[8] - -// error: Cannot apply this operation between an unsigned -// value (LHS) and a signed value (RHS). -// An explicit conversion must be applied. -val e1 = u8 + s8 +// Commutative: result is widest, most signed +val r1 = u8 + u8 // UInt[8] (max(8,8) = 8) +val r2 = u8 + u4 // UInt[8] (max(8,4) = 8) +val r3 = u4 + u8 // UInt[8] (commutative, same as above) +val r4 = s8 + u4 // SInt[8] (max(8, 4+1) = 8, signed) +val r5 = u8 + s8 // SInt[9] (max(8+1, 8) = 9, signed) +val r6 = u8 + 200 // UInt[8] (literal adapts) +val r7 = (-5) + u8 // SInt[8] (negative literal, signed result) + +// Non-commutative: LHS-dominant +val r8 = 200 - u8 // UInt[8] // error: The applied RHS value width (8) is larger than // the LHS variable width (4). -val e2 = u4 + u8 +val e1 = u4 - u8 // error: Cannot apply this operation between an unsigned // value (LHS) and a signed value (RHS). -// An explicit conversion must be applied. -val e3 = u8 + (-22) +val e2 = u8 - s8 -// Int arithmetic +// Int parameter as wildcard in commutative ops val param: Int <> CONST = 10 -val r6 = param * 2 // Int <> CONST = 20 -val r7 = param % 3 // Int <> CONST = 1 +val r9 = u8 + param // UInt[8] (param adapts to UInt[8]) +val r10 = param + u8 // UInt[8] (commutative, same result) +val r11 = s8 + param // SInt[8] (param adapts to SInt[8]) +val r12 = param * 2 // Int <> CONST = 20 // Double arithmetic val d1 = Double <> VAR val d2 = Double <> VAR -val r8 = d1 + d2 // Double -val r9 = d1 / d2 // Double +val r13 = d1 + d2 // Double +val r14 = d1 / d2 // Double ``` /// admonition | Overflow and automatic carry promotion @@ -1973,18 +2037,90 @@ u9 := sum // resized from 8 to 9, no carry promotion ``` /// +/// admonition | Implicit Scala `Int` and Verilog-semantics mismatch + type: warning +In Verilog, unsized integer literals are 32-bit. When such a literal appears in an expression like `(a + b + c + d) / 4`, Verilog's context-dependent width propagation widens the entire expression to 32 bits, preventing intermediate overflow. + +In DFHDL, a Scala `Int` literal like `4` is implicitly converted to the minimum bit-accurate width (`UInt[3]` for value 4). Each `+` independently uses the LHS width, so intermediate additions stay at the LHS width (e.g., 8 bits) and can overflow before the `/` is applied. Similarly, the Verilog pattern of "forcing larger evaluation" by adding a zero constant (e.g., `(a + b + 0) >> 1`) does not widen the expression in DFHDL. + +DFHDL issues an **elaboration warning** when it detects these patterns: + +**1. Non-modular operation with implicit `Int` and overflowing chain:** +A `/` or `%` operation has an implicit Scala `Int` (or DFHDL `Int`) operand, and the other operand contains anonymous sub-32-bit `+`/`-`/`*` operations. +```scala +val a, b = UInt(8) <> VAR +val t1 = (a + b) / 4 // WARNING: a + b can overflow at 8-bit +val t2 = (a * 3 + b) % 3 // WARNING: a * 3 + b can overflow +val t3 = a / 4 // OK: no intermediate overflow possible +``` + +**2. Shift with implicit `Int` inside the expression chain ("forcing larger evaluation"):** +A `>>` or `<<` operation whose LHS expression contains both an implicit `Int` operand and sub-32-bit `+`/`-`/`*` operations. +```scala +val t4 = (a + b + 0) >> 1 // WARNING: + 0 forces 32-bit in Verilog, not in DFHDL +val t5 = (a + b) >> 2 // OK: no implicit Int in the + chain, Verilog also loses carry +``` + +**3. Assignment to wider target with implicit `Int` in the chain:** +An anonymous expression assigned to a wider target contains both an implicit `Int` and sub-32-bit `+`/`-`/`*` operations. +```scala +val sum = UInt(10) <> VAR +sum := a + b + 1 // WARNING: + 1 widens to 32-bit in Verilog, not in DFHDL +sum := a + b + d"1" // OK: explicit literal, no implicit Int +val cnt = UInt(8) <> VAR +cnt := cnt + 1 // OK: same-width target, modular truncation matches +``` + +**No warning** is issued when: + +- The expression uses carry operations (`+^`, `-^`, `*^`), which widen the result. +- The integer constant is an explicit bit-accurate literal (e.g., `d"3'4"`). +- The bit-accurate expression width is already 32 bits or wider. +- The implicit `Int` is only used in modular operations (`+`, `-`, `*`) assigned to a same-width target. + +**Mitigation strategies:** +```scala +val a, b, c, d = UInt(8) <> IN +val result = UInt(8) <> OUT + +// WARNING: implicit Int with non-carry chain before division +result <> (a + b + c + d) / 4 + +// Fix 1: use carry addition to prevent intermediate overflow +result <> ((a +^ b +^ c +^ d) / 4).truncate + +// Fix 2: use an explicit bit-accurate literal to accept DFHDL +// overflow semantics and silence the warning +result <> (a + b + c + d) / d"3'4" +``` +/// + ### Carry Arithmetic (`+^`, `-^`, `*^`) {#carry-ops} Applies to: `UInt`, `SInt` -Carry operations widen the result to prevent overflow. Unlike standard arithmetic, carry operations require both operands to have the **same sign**. +Carry operations widen the result to prevent overflow. Mixed signedness is allowed -- the result is signed if either operand is signed. When mixing signs, the unsigned operand is sign-extended by 1 bit. + +**Carry addition and subtraction (`+^`, `-^`):** + +/// html | div.operations +| LHS Type | RHS Type | Result Type | +| -------- | -------- | ----------- | +| `UInt[LW]` | `UInt[RW]` | `UInt[Max[LW, RW] + 1]` | +| `SInt[LW]` | `SInt[RW]` | `SInt[Max[LW, RW] + 1]` | +| `SInt[LW]` | `UInt[RW]` | `SInt[Max[LW, RW + 1] + 1]` | +| `UInt[LW]` | `SInt[RW]` | `SInt[Max[LW + 1, RW] + 1]` | +/// + +**Carry multiplication (`*^`):** /// html | div.operations -| Operation | Description | Result Width | Result Sign | -| ------------- | -------------------- | ------------------ | -------------------- | -| `lhs +^ rhs` | Carry Addition | max(LW, RW) + 1 | Same sign as operands | -| `lhs -^ rhs` | Carry Subtraction | max(LW, RW) + 1 | Same sign as operands | -| `lhs *^ rhs` | Carry Multiplication | LW + RW | Same sign as operands | +| LHS Type | RHS Type | Result Type | +| -------- | -------- | ----------- | +| `UInt[LW]` | `UInt[RW]` | `UInt[LW + RW]` | +| `SInt[LW]` | `SInt[RW]` | `SInt[LW + RW]` | +| `SInt[LW]` | `UInt[RW]` | `SInt[LW + RW + 1]` | +| `UInt[LW]` | `SInt[RW]` | `SInt[LW + 1 + RW]` | /// ```scala @@ -2009,10 +2145,6 @@ val r5 = s8 +^ s8 // SInt[9] val r6 = s8 *^ s8 // SInt[16] ``` -/// admonition | Carry vs standard sign rules - type: tip -Unlike standard arithmetic where `SInt op UInt` is allowed (the unsigned RHS is implicitly widened), carry operations require both operands to have the **same sign**. This is because carry operations produce a wider result, and mixed-sign widening semantics would be ambiguous. Scala `Int` literals are still accepted when the literal's sign matches the DFHDL value's sign. -/// ### Comparison Operations (`==`, `!=`, `<`, `>`, `<=`, `>=`) {#comparison-ops} diff --git a/internals/src/main/scala/dfhdl/internals/Exact.scala b/internals/src/main/scala/dfhdl/internals/Exact.scala index 14a9a6800..a55222d34 100644 --- a/internals/src/main/scala/dfhdl/internals/Exact.scala +++ b/internals/src/main/scala/dfhdl/internals/Exact.scala @@ -277,10 +277,28 @@ trait ExactOp1[Op, Ctx, OutUB, LHS]: type ExactOp1Aux[Op, Ctx, OutUB, LHS, O <: OutUB] = ExactOp1[Op, Ctx, OutUB, LHS] { type Out = O } transparent inline def exactOp1[Op, Ctx, OutUB](inline lhs: Any)(using ctx: Ctx): OutUB = - cleanTypeHack(exactOp1BeforeTypeHack[Op, Ctx, OutUB](lhs)) -transparent inline def exactOp1BeforeTypeHack[Op, Ctx, OutUB](inline lhs: Any)(using - ctx: Ctx -): OutUB = ${ exactOp1Macro[Op, Ctx, OutUB]('lhs)('ctx) } + ${ exactOp1Macro[Op, Ctx, OutUB]('lhs)('ctx) } +// Wraps a term in a type ascription using its widened type to prevent +// unreduced type projections from leaking into transparent inline results. +// This replaces the cleanTypeHack workaround without adding extra inline layers. +// Adds a type ascription using the widened+dealiased type to prevent +// unreduced type projections (like ExactOp2Aux[...].Out) from leaking +// into transparent inline results and polluting error messages. +private def ascribeWidenedType(using Quotes)(term: quotes.reflect.Term): quotes.reflect.Term = + import quotes.reflect.* + Typed(term, TypeTree.of(using term.tpe.dealias.asType)) + +private def flattenInlined(using Quotes)(term: quotes.reflect.Term): (List[quotes.reflect.Definition], quotes.reflect.Term) = + import quotes.reflect.* + term match + case Inlined(_, bindings, inner) => + val (innerBindings, innerTerm) = flattenInlined(inner) + (bindings ++ innerBindings, innerTerm) + case Block(stats, expr) => + val (innerBindings, innerTerm) = flattenInlined(expr) + (stats.collect { case d: Definition => d } ++ innerBindings, innerTerm) + case _ => (Nil, term) + private def exactOp1Macro[Op, Ctx, OutUB](lhs: Expr[Any])(ctx: Expr[Ctx])(using Quotes, Type[Op], @@ -289,9 +307,17 @@ private def exactOp1Macro[Op, Ctx, OutUB](lhs: Expr[Any])(ctx: Expr[Ctx])(using ): Expr[OutUB] = import quotes.reflect.* val lhsExactInfo = lhs.exactInfo + val (lhsBindings, lhsInner) = flattenInlined(lhsExactInfo.exactExpr.asTerm) Expr.summon[ExactOp1[Op, Ctx, OutUB, lhsExactInfo.Underlying]] match - case Some(expr) => '{ $expr(${ lhsExactInfo.exactExpr })(using $ctx) } - case None => + case Some(expr) => + val appTerm = ascribeWidenedType('{ $expr(${ lhsInner.asExpr })(using $ctx) }.asTerm) + if lhsBindings.isEmpty then appTerm.asExprOf[OutUB] + else + val innerTerm = appTerm match + case Inlined(_, Nil, inner) => inner + case t => t + Block(lhsBindings, innerTerm).asExprOf[OutUB] + case None => ControlledMacroError.report("Unsupported argument type for this operation.") end match end exactOp1Macro @@ -310,13 +336,6 @@ transparent inline def exactOp2[Op, Ctx, OutUB]( inline bothWays: Boolean = false )(using ctx: Ctx -): OutUB = cleanTypeHack(exactOp2BeforeTypeHack[Op, Ctx, OutUB](lhs, rhs, bothWays)) -transparent inline def exactOp2BeforeTypeHack[Op, Ctx, OutUB]( - inline lhs: Any, - inline rhs: Any, - inline bothWays: Boolean = false -)(using - ctx: Ctx ): OutUB = ${ exactOp2Macro[Op, Ctx, OutUB]('lhs, 'rhs, 'bothWays)('ctx) } private def exactOp2Macro[Op, Ctx, OutUB]( lhs: Expr[Any], @@ -357,10 +376,20 @@ private def exactOp2Macro[Op, Ctx, OutUB]( ]] end try end exactOp2ExprOrError + def buildFlattened(lhsTerm: Term, rhsTerm: Term, expr: Expr[?]): Expr[OutUB] = + val (lhsBindings, lhsInner) = flattenInlined(lhsTerm) + val (rhsBindings, rhsInner) = flattenInlined(rhsTerm) + val allBindings = lhsBindings ++ rhsBindings + val appTerm = ascribeWidenedType('{ ${ expr.asInstanceOf[Expr[ExactOp2[Op, Ctx, OutUB, lhsExactInfo.Underlying, rhsExactInfo.Underlying]]] }(${ lhsInner.asExpr }, ${ rhsInner.asExpr })(using $ctx) }.asTerm) + if allBindings.isEmpty then appTerm.asExprOf[OutUB] + else + val innerTerm = appTerm match + case Inlined(_, Nil, inner) => inner + case t => t + Block(allBindings, innerTerm).asExprOf[OutUB] exactOp2ExprOrError match - case Right(expr) => '{ - $expr(${ lhsExactInfo.exactExpr }, ${ rhsExactInfo.exactExpr })(using $ctx) - } + case Right(expr) => + buildFlattened(lhsExactInfo.exactExpr.asTerm, rhsExactInfo.exactExpr.asTerm, expr) case Left(msg) => if (bothWays.value.getOrElse(false)) Expr.summonOrError[ExactOp2[ @@ -370,9 +399,8 @@ private def exactOp2Macro[Op, Ctx, OutUB]( rhsExactInfo.Underlying, lhsExactInfo.Underlying ]] match - case Right(expr) => '{ - $expr(${ rhsExactInfo.exactExpr }, ${ lhsExactInfo.exactExpr })(using $ctx) - } + case Right(expr) => + buildFlattened(rhsExactInfo.exactExpr.asTerm, lhsExactInfo.exactExpr.asTerm, expr) case Left(msg) => ControlledMacroError.report("Unsupported argument types for this operation.") else @@ -392,11 +420,6 @@ transparent inline def exactOp3[Op, Ctx, OutUB]( inline lhs: Any, inline mhs: Any, inline rhs: Any -)(using ctx: Ctx): OutUB = cleanTypeHack(exactOp3BeforeTypeHack[Op, Ctx, OutUB](lhs, mhs, rhs)) -transparent inline def exactOp3BeforeTypeHack[Op, Ctx, OutUB]( - inline lhs: Any, - inline mhs: Any, - inline rhs: Any )(using ctx: Ctx): OutUB = ${ exactOp3Macro[Op, Ctx, OutUB]('lhs, 'mhs, 'rhs)('ctx) } private def exactOp3Macro[Op, Ctx, OutUB]( lhs: Expr[Any], @@ -413,6 +436,10 @@ private def exactOp3Macro[Op, Ctx, OutUB]( val lhsExactInfo = lhs.exactInfo val mhsExactInfo = mhs.exactInfo val rhsExactInfo = rhs.exactInfo + val (lhsBindings, lhsInner) = flattenInlined(lhsExactInfo.exactExpr.asTerm) + val (mhsBindings, mhsInner) = flattenInlined(mhsExactInfo.exactExpr.asTerm) + val (rhsBindings, rhsInner) = flattenInlined(rhsExactInfo.exactExpr.asTerm) + val allBindings = lhsBindings ++ mhsBindings ++ rhsBindings Expr.summon[ExactOp3[ Op, Ctx, @@ -421,13 +448,20 @@ private def exactOp3Macro[Op, Ctx, OutUB]( mhsExactInfo.Underlying, rhsExactInfo.Underlying ]] match - case Some(expr) => '{ + case Some(expr) => + val appTerm = ascribeWidenedType('{ $expr( - ${ lhsExactInfo.exactExpr }, - ${ mhsExactInfo.exactExpr }, - ${ rhsExactInfo.exactExpr } + ${ lhsInner.asExpr }, + ${ mhsInner.asExpr }, + ${ rhsInner.asExpr } )(using $ctx) - } + }.asTerm) + if allBindings.isEmpty then appTerm.asExprOf[OutUB] + else + val innerTerm = appTerm match + case Inlined(_, Nil, inner) => inner + case t => t + Block(allBindings, innerTerm).asExprOf[OutUB] case None => ControlledMacroError.report("Unsupported argument types for this operation.") end match diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mixColumns.sv b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mixColumns.sv index f64de2f36..983edd710 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mixColumns.sv +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mixColumns.sv @@ -585,28 +585,28 @@ module mixColumns( assign mulByte_0_inst_14_rhs = state[3][3]; assign o = '{ 0: '{ - 0: ((o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o) ^ o_part_mulByte_2_inst_o) ^ mulByte_2_inst_00_o, - 1: ((mulByte_2_inst_01_o ^ mulByte_0_inst_00_o) ^ mulByte_1_inst_00_o) ^ mulByte_2_inst_02_o, - 2: ((mulByte_2_inst_03_o ^ mulByte_2_inst_04_o) ^ mulByte_0_inst_01_o) ^ mulByte_1_inst_01_o, - 3: ((mulByte_1_inst_02_o ^ mulByte_2_inst_05_o) ^ mulByte_2_inst_06_o) ^ mulByte_0_inst_02_o + 0: o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o ^ o_part_mulByte_2_inst_o ^ mulByte_2_inst_00_o, + 1: mulByte_2_inst_01_o ^ mulByte_0_inst_00_o ^ mulByte_1_inst_00_o ^ mulByte_2_inst_02_o, + 2: mulByte_2_inst_03_o ^ mulByte_2_inst_04_o ^ mulByte_0_inst_01_o ^ mulByte_1_inst_01_o, + 3: mulByte_1_inst_02_o ^ mulByte_2_inst_05_o ^ mulByte_2_inst_06_o ^ mulByte_0_inst_02_o }, 1: '{ - 0: ((mulByte_0_inst_03_o ^ mulByte_1_inst_03_o) ^ mulByte_2_inst_07_o) ^ mulByte_2_inst_08_o, - 1: ((mulByte_2_inst_09_o ^ mulByte_0_inst_04_o) ^ mulByte_1_inst_04_o) ^ mulByte_2_inst_10_o, - 2: ((mulByte_2_inst_11_o ^ mulByte_2_inst_12_o) ^ mulByte_0_inst_05_o) ^ mulByte_1_inst_05_o, - 3: ((mulByte_1_inst_06_o ^ mulByte_2_inst_13_o) ^ mulByte_2_inst_14_o) ^ mulByte_0_inst_06_o + 0: mulByte_0_inst_03_o ^ mulByte_1_inst_03_o ^ mulByte_2_inst_07_o ^ mulByte_2_inst_08_o, + 1: mulByte_2_inst_09_o ^ mulByte_0_inst_04_o ^ mulByte_1_inst_04_o ^ mulByte_2_inst_10_o, + 2: mulByte_2_inst_11_o ^ mulByte_2_inst_12_o ^ mulByte_0_inst_05_o ^ mulByte_1_inst_05_o, + 3: mulByte_1_inst_06_o ^ mulByte_2_inst_13_o ^ mulByte_2_inst_14_o ^ mulByte_0_inst_06_o }, 2: '{ - 0: ((mulByte_0_inst_07_o ^ mulByte_1_inst_07_o) ^ mulByte_2_inst_15_o) ^ mulByte_2_inst_16_o, - 1: ((mulByte_2_inst_17_o ^ mulByte_0_inst_08_o) ^ mulByte_1_inst_08_o) ^ mulByte_2_inst_18_o, - 2: ((mulByte_2_inst_19_o ^ mulByte_2_inst_20_o) ^ mulByte_0_inst_09_o) ^ mulByte_1_inst_09_o, - 3: ((mulByte_1_inst_10_o ^ mulByte_2_inst_21_o) ^ mulByte_2_inst_22_o) ^ mulByte_0_inst_10_o + 0: mulByte_0_inst_07_o ^ mulByte_1_inst_07_o ^ mulByte_2_inst_15_o ^ mulByte_2_inst_16_o, + 1: mulByte_2_inst_17_o ^ mulByte_0_inst_08_o ^ mulByte_1_inst_08_o ^ mulByte_2_inst_18_o, + 2: mulByte_2_inst_19_o ^ mulByte_2_inst_20_o ^ mulByte_0_inst_09_o ^ mulByte_1_inst_09_o, + 3: mulByte_1_inst_10_o ^ mulByte_2_inst_21_o ^ mulByte_2_inst_22_o ^ mulByte_0_inst_10_o }, 3: '{ - 0: ((mulByte_0_inst_11_o ^ mulByte_1_inst_11_o) ^ mulByte_2_inst_23_o) ^ mulByte_2_inst_24_o, - 1: ((mulByte_2_inst_25_o ^ mulByte_0_inst_12_o) ^ mulByte_1_inst_12_o) ^ mulByte_2_inst_26_o, - 2: ((mulByte_2_inst_27_o ^ mulByte_2_inst_28_o) ^ mulByte_0_inst_13_o) ^ mulByte_1_inst_13_o, - 3: ((mulByte_1_inst_14_o ^ mulByte_2_inst_29_o) ^ mulByte_2_inst_30_o) ^ mulByte_0_inst_14_o + 0: mulByte_0_inst_11_o ^ mulByte_1_inst_11_o ^ mulByte_2_inst_23_o ^ mulByte_2_inst_24_o, + 1: mulByte_2_inst_25_o ^ mulByte_0_inst_12_o ^ mulByte_1_inst_12_o ^ mulByte_2_inst_26_o, + 2: mulByte_2_inst_27_o ^ mulByte_2_inst_28_o ^ mulByte_0_inst_13_o ^ mulByte_1_inst_13_o, + 3: mulByte_1_inst_14_o ^ mulByte_2_inst_29_o ^ mulByte_2_inst_30_o ^ mulByte_0_inst_14_o } }; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_1.sv b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_1.sv index fd8852742..d223c6170 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_1.sv +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.sv2009/hdl/mulByte_1.sv @@ -14,5 +14,5 @@ module mulByte_1#(parameter logic [7:0] lhs)( .o /*-->*/ (a_o) ); assign a_lhs = rhs; - assign o = (8'h00 ^ rhs) ^ a_o; + assign o = 8'h00 ^ rhs ^ a_o; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v2001/hdl/mixColumns.v b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v2001/hdl/mixColumns.v index b2fb7b57e..514c8a3f6 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v2001/hdl/mixColumns.v +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v2001/hdl/mixColumns.v @@ -586,28 +586,28 @@ module mixColumns( assign mulByte_0_inst_14_rhs = state[7:0]; assign o = { { - ((o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o) ^ o_part_mulByte_2_inst_o) ^ mulByte_2_inst_00_o, - ((mulByte_2_inst_01_o ^ mulByte_0_inst_00_o) ^ mulByte_1_inst_00_o) ^ mulByte_2_inst_02_o, - ((mulByte_2_inst_03_o ^ mulByte_2_inst_04_o) ^ mulByte_0_inst_01_o) ^ mulByte_1_inst_01_o, - ((mulByte_1_inst_02_o ^ mulByte_2_inst_05_o) ^ mulByte_2_inst_06_o) ^ mulByte_0_inst_02_o + o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o ^ o_part_mulByte_2_inst_o ^ mulByte_2_inst_00_o, + mulByte_2_inst_01_o ^ mulByte_0_inst_00_o ^ mulByte_1_inst_00_o ^ mulByte_2_inst_02_o, + mulByte_2_inst_03_o ^ mulByte_2_inst_04_o ^ mulByte_0_inst_01_o ^ mulByte_1_inst_01_o, + mulByte_1_inst_02_o ^ mulByte_2_inst_05_o ^ mulByte_2_inst_06_o ^ mulByte_0_inst_02_o }, { - ((mulByte_0_inst_03_o ^ mulByte_1_inst_03_o) ^ mulByte_2_inst_07_o) ^ mulByte_2_inst_08_o, - ((mulByte_2_inst_09_o ^ mulByte_0_inst_04_o) ^ mulByte_1_inst_04_o) ^ mulByte_2_inst_10_o, - ((mulByte_2_inst_11_o ^ mulByte_2_inst_12_o) ^ mulByte_0_inst_05_o) ^ mulByte_1_inst_05_o, - ((mulByte_1_inst_06_o ^ mulByte_2_inst_13_o) ^ mulByte_2_inst_14_o) ^ mulByte_0_inst_06_o + mulByte_0_inst_03_o ^ mulByte_1_inst_03_o ^ mulByte_2_inst_07_o ^ mulByte_2_inst_08_o, + mulByte_2_inst_09_o ^ mulByte_0_inst_04_o ^ mulByte_1_inst_04_o ^ mulByte_2_inst_10_o, + mulByte_2_inst_11_o ^ mulByte_2_inst_12_o ^ mulByte_0_inst_05_o ^ mulByte_1_inst_05_o, + mulByte_1_inst_06_o ^ mulByte_2_inst_13_o ^ mulByte_2_inst_14_o ^ mulByte_0_inst_06_o }, { - ((mulByte_0_inst_07_o ^ mulByte_1_inst_07_o) ^ mulByte_2_inst_15_o) ^ mulByte_2_inst_16_o, - ((mulByte_2_inst_17_o ^ mulByte_0_inst_08_o) ^ mulByte_1_inst_08_o) ^ mulByte_2_inst_18_o, - ((mulByte_2_inst_19_o ^ mulByte_2_inst_20_o) ^ mulByte_0_inst_09_o) ^ mulByte_1_inst_09_o, - ((mulByte_1_inst_10_o ^ mulByte_2_inst_21_o) ^ mulByte_2_inst_22_o) ^ mulByte_0_inst_10_o + mulByte_0_inst_07_o ^ mulByte_1_inst_07_o ^ mulByte_2_inst_15_o ^ mulByte_2_inst_16_o, + mulByte_2_inst_17_o ^ mulByte_0_inst_08_o ^ mulByte_1_inst_08_o ^ mulByte_2_inst_18_o, + mulByte_2_inst_19_o ^ mulByte_2_inst_20_o ^ mulByte_0_inst_09_o ^ mulByte_1_inst_09_o, + mulByte_1_inst_10_o ^ mulByte_2_inst_21_o ^ mulByte_2_inst_22_o ^ mulByte_0_inst_10_o }, { - ((mulByte_0_inst_11_o ^ mulByte_1_inst_11_o) ^ mulByte_2_inst_23_o) ^ mulByte_2_inst_24_o, - ((mulByte_2_inst_25_o ^ mulByte_0_inst_12_o) ^ mulByte_1_inst_12_o) ^ mulByte_2_inst_26_o, - ((mulByte_2_inst_27_o ^ mulByte_2_inst_28_o) ^ mulByte_0_inst_13_o) ^ mulByte_1_inst_13_o, - ((mulByte_1_inst_14_o ^ mulByte_2_inst_29_o) ^ mulByte_2_inst_30_o) ^ mulByte_0_inst_14_o + mulByte_0_inst_11_o ^ mulByte_1_inst_11_o ^ mulByte_2_inst_23_o ^ mulByte_2_inst_24_o, + mulByte_2_inst_25_o ^ mulByte_0_inst_12_o ^ mulByte_1_inst_12_o ^ mulByte_2_inst_26_o, + mulByte_2_inst_27_o ^ mulByte_2_inst_28_o ^ mulByte_0_inst_13_o ^ mulByte_1_inst_13_o, + mulByte_1_inst_14_o ^ mulByte_2_inst_29_o ^ mulByte_2_inst_30_o ^ mulByte_0_inst_14_o } }; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v2001/hdl/mulByte_1.v b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v2001/hdl/mulByte_1.v index c559c8272..677406492 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v2001/hdl/mulByte_1.v +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v2001/hdl/mulByte_1.v @@ -15,5 +15,5 @@ module mulByte_1#(parameter [7:0] lhs = 8'h03)( .o /*-->*/ (a_o) ); assign a_lhs = rhs; - assign o = (8'h00 ^ rhs) ^ a_o; + assign o = 8'h00 ^ rhs ^ a_o; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v95/hdl/mixColumns.v b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v95/hdl/mixColumns.v index 7c3d8bc89..4897009e6 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v95/hdl/mixColumns.v +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v95/hdl/mixColumns.v @@ -588,28 +588,28 @@ module mixColumns( assign mulByte_0_inst_14_rhs = state[7:0]; assign o = { { - ((o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o) ^ o_part_mulByte_2_inst_o) ^ mulByte_2_inst_00_o, - ((mulByte_2_inst_01_o ^ mulByte_0_inst_00_o) ^ mulByte_1_inst_00_o) ^ mulByte_2_inst_02_o, - ((mulByte_2_inst_03_o ^ mulByte_2_inst_04_o) ^ mulByte_0_inst_01_o) ^ mulByte_1_inst_01_o, - ((mulByte_1_inst_02_o ^ mulByte_2_inst_05_o) ^ mulByte_2_inst_06_o) ^ mulByte_0_inst_02_o + o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o ^ o_part_mulByte_2_inst_o ^ mulByte_2_inst_00_o, + mulByte_2_inst_01_o ^ mulByte_0_inst_00_o ^ mulByte_1_inst_00_o ^ mulByte_2_inst_02_o, + mulByte_2_inst_03_o ^ mulByte_2_inst_04_o ^ mulByte_0_inst_01_o ^ mulByte_1_inst_01_o, + mulByte_1_inst_02_o ^ mulByte_2_inst_05_o ^ mulByte_2_inst_06_o ^ mulByte_0_inst_02_o }, { - ((mulByte_0_inst_03_o ^ mulByte_1_inst_03_o) ^ mulByte_2_inst_07_o) ^ mulByte_2_inst_08_o, - ((mulByte_2_inst_09_o ^ mulByte_0_inst_04_o) ^ mulByte_1_inst_04_o) ^ mulByte_2_inst_10_o, - ((mulByte_2_inst_11_o ^ mulByte_2_inst_12_o) ^ mulByte_0_inst_05_o) ^ mulByte_1_inst_05_o, - ((mulByte_1_inst_06_o ^ mulByte_2_inst_13_o) ^ mulByte_2_inst_14_o) ^ mulByte_0_inst_06_o + mulByte_0_inst_03_o ^ mulByte_1_inst_03_o ^ mulByte_2_inst_07_o ^ mulByte_2_inst_08_o, + mulByte_2_inst_09_o ^ mulByte_0_inst_04_o ^ mulByte_1_inst_04_o ^ mulByte_2_inst_10_o, + mulByte_2_inst_11_o ^ mulByte_2_inst_12_o ^ mulByte_0_inst_05_o ^ mulByte_1_inst_05_o, + mulByte_1_inst_06_o ^ mulByte_2_inst_13_o ^ mulByte_2_inst_14_o ^ mulByte_0_inst_06_o }, { - ((mulByte_0_inst_07_o ^ mulByte_1_inst_07_o) ^ mulByte_2_inst_15_o) ^ mulByte_2_inst_16_o, - ((mulByte_2_inst_17_o ^ mulByte_0_inst_08_o) ^ mulByte_1_inst_08_o) ^ mulByte_2_inst_18_o, - ((mulByte_2_inst_19_o ^ mulByte_2_inst_20_o) ^ mulByte_0_inst_09_o) ^ mulByte_1_inst_09_o, - ((mulByte_1_inst_10_o ^ mulByte_2_inst_21_o) ^ mulByte_2_inst_22_o) ^ mulByte_0_inst_10_o + mulByte_0_inst_07_o ^ mulByte_1_inst_07_o ^ mulByte_2_inst_15_o ^ mulByte_2_inst_16_o, + mulByte_2_inst_17_o ^ mulByte_0_inst_08_o ^ mulByte_1_inst_08_o ^ mulByte_2_inst_18_o, + mulByte_2_inst_19_o ^ mulByte_2_inst_20_o ^ mulByte_0_inst_09_o ^ mulByte_1_inst_09_o, + mulByte_1_inst_10_o ^ mulByte_2_inst_21_o ^ mulByte_2_inst_22_o ^ mulByte_0_inst_10_o }, { - ((mulByte_0_inst_11_o ^ mulByte_1_inst_11_o) ^ mulByte_2_inst_23_o) ^ mulByte_2_inst_24_o, - ((mulByte_2_inst_25_o ^ mulByte_0_inst_12_o) ^ mulByte_1_inst_12_o) ^ mulByte_2_inst_26_o, - ((mulByte_2_inst_27_o ^ mulByte_2_inst_28_o) ^ mulByte_0_inst_13_o) ^ mulByte_1_inst_13_o, - ((mulByte_1_inst_14_o ^ mulByte_2_inst_29_o) ^ mulByte_2_inst_30_o) ^ mulByte_0_inst_14_o + mulByte_0_inst_11_o ^ mulByte_1_inst_11_o ^ mulByte_2_inst_23_o ^ mulByte_2_inst_24_o, + mulByte_2_inst_25_o ^ mulByte_0_inst_12_o ^ mulByte_1_inst_12_o ^ mulByte_2_inst_26_o, + mulByte_2_inst_27_o ^ mulByte_2_inst_28_o ^ mulByte_0_inst_13_o ^ mulByte_1_inst_13_o, + mulByte_1_inst_14_o ^ mulByte_2_inst_29_o ^ mulByte_2_inst_30_o ^ mulByte_0_inst_14_o } }; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v95/hdl/mulByte_1.v b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v95/hdl/mulByte_1.v index 3ee9c56b5..28af63d87 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v95/hdl/mulByte_1.v +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/verilog.v95/hdl/mulByte_1.v @@ -18,5 +18,5 @@ module mulByte_1( .o /*-->*/ (a_o) ); assign a_lhs = rhs; - assign o = (8'h00 ^ rhs) ^ a_o; + assign o = 8'h00 ^ rhs ^ a_o; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mixColumns.vhd b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mixColumns.vhd index 23723eb1f..b27678662 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mixColumns.vhd +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mixColumns.vhd @@ -591,28 +591,28 @@ begin mulByte_0_inst_14_rhs <= state(3)(3); o <= ( 0 => ( - 0 => ((o_part_mulByte_0_inst_o xor o_part_mulByte_1_inst_o) xor o_part_mulByte_2_inst_o) xor mulByte_2_inst_00_o, - 1 => ((mulByte_2_inst_01_o xor mulByte_0_inst_00_o) xor mulByte_1_inst_00_o) xor mulByte_2_inst_02_o, - 2 => ((mulByte_2_inst_03_o xor mulByte_2_inst_04_o) xor mulByte_0_inst_01_o) xor mulByte_1_inst_01_o, - 3 => ((mulByte_1_inst_02_o xor mulByte_2_inst_05_o) xor mulByte_2_inst_06_o) xor mulByte_0_inst_02_o + 0 => o_part_mulByte_0_inst_o xor o_part_mulByte_1_inst_o xor o_part_mulByte_2_inst_o xor mulByte_2_inst_00_o, + 1 => mulByte_2_inst_01_o xor mulByte_0_inst_00_o xor mulByte_1_inst_00_o xor mulByte_2_inst_02_o, + 2 => mulByte_2_inst_03_o xor mulByte_2_inst_04_o xor mulByte_0_inst_01_o xor mulByte_1_inst_01_o, + 3 => mulByte_1_inst_02_o xor mulByte_2_inst_05_o xor mulByte_2_inst_06_o xor mulByte_0_inst_02_o ), 1 => ( - 0 => ((mulByte_0_inst_03_o xor mulByte_1_inst_03_o) xor mulByte_2_inst_07_o) xor mulByte_2_inst_08_o, - 1 => ((mulByte_2_inst_09_o xor mulByte_0_inst_04_o) xor mulByte_1_inst_04_o) xor mulByte_2_inst_10_o, - 2 => ((mulByte_2_inst_11_o xor mulByte_2_inst_12_o) xor mulByte_0_inst_05_o) xor mulByte_1_inst_05_o, - 3 => ((mulByte_1_inst_06_o xor mulByte_2_inst_13_o) xor mulByte_2_inst_14_o) xor mulByte_0_inst_06_o + 0 => mulByte_0_inst_03_o xor mulByte_1_inst_03_o xor mulByte_2_inst_07_o xor mulByte_2_inst_08_o, + 1 => mulByte_2_inst_09_o xor mulByte_0_inst_04_o xor mulByte_1_inst_04_o xor mulByte_2_inst_10_o, + 2 => mulByte_2_inst_11_o xor mulByte_2_inst_12_o xor mulByte_0_inst_05_o xor mulByte_1_inst_05_o, + 3 => mulByte_1_inst_06_o xor mulByte_2_inst_13_o xor mulByte_2_inst_14_o xor mulByte_0_inst_06_o ), 2 => ( - 0 => ((mulByte_0_inst_07_o xor mulByte_1_inst_07_o) xor mulByte_2_inst_15_o) xor mulByte_2_inst_16_o, - 1 => ((mulByte_2_inst_17_o xor mulByte_0_inst_08_o) xor mulByte_1_inst_08_o) xor mulByte_2_inst_18_o, - 2 => ((mulByte_2_inst_19_o xor mulByte_2_inst_20_o) xor mulByte_0_inst_09_o) xor mulByte_1_inst_09_o, - 3 => ((mulByte_1_inst_10_o xor mulByte_2_inst_21_o) xor mulByte_2_inst_22_o) xor mulByte_0_inst_10_o + 0 => mulByte_0_inst_07_o xor mulByte_1_inst_07_o xor mulByte_2_inst_15_o xor mulByte_2_inst_16_o, + 1 => mulByte_2_inst_17_o xor mulByte_0_inst_08_o xor mulByte_1_inst_08_o xor mulByte_2_inst_18_o, + 2 => mulByte_2_inst_19_o xor mulByte_2_inst_20_o xor mulByte_0_inst_09_o xor mulByte_1_inst_09_o, + 3 => mulByte_1_inst_10_o xor mulByte_2_inst_21_o xor mulByte_2_inst_22_o xor mulByte_0_inst_10_o ), 3 => ( - 0 => ((mulByte_0_inst_11_o xor mulByte_1_inst_11_o) xor mulByte_2_inst_23_o) xor mulByte_2_inst_24_o, - 1 => ((mulByte_2_inst_25_o xor mulByte_0_inst_12_o) xor mulByte_1_inst_12_o) xor mulByte_2_inst_26_o, - 2 => ((mulByte_2_inst_27_o xor mulByte_2_inst_28_o) xor mulByte_0_inst_13_o) xor mulByte_1_inst_13_o, - 3 => ((mulByte_1_inst_14_o xor mulByte_2_inst_29_o) xor mulByte_2_inst_30_o) xor mulByte_0_inst_14_o + 0 => mulByte_0_inst_11_o xor mulByte_1_inst_11_o xor mulByte_2_inst_23_o xor mulByte_2_inst_24_o, + 1 => mulByte_2_inst_25_o xor mulByte_0_inst_12_o xor mulByte_1_inst_12_o xor mulByte_2_inst_26_o, + 2 => mulByte_2_inst_27_o xor mulByte_2_inst_28_o xor mulByte_0_inst_13_o xor mulByte_1_inst_13_o, + 3 => mulByte_1_inst_14_o xor mulByte_2_inst_29_o xor mulByte_2_inst_30_o xor mulByte_0_inst_14_o ) ); end mixColumns_arch; diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_1.vhd b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_1.vhd index d2f6448a9..26260a4ca 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_1.vhd +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v2008/hdl/mulByte_1.vhd @@ -23,5 +23,5 @@ begin o => a_o ); a_lhs <= rhs; - o <= (x"00" xor rhs) xor a_o; + o <= x"00" xor rhs xor a_o; end mulByte_1_arch; diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v93/hdl/mixColumns.vhd b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v93/hdl/mixColumns.vhd index 23723eb1f..b27678662 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v93/hdl/mixColumns.vhd +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v93/hdl/mixColumns.vhd @@ -591,28 +591,28 @@ begin mulByte_0_inst_14_rhs <= state(3)(3); o <= ( 0 => ( - 0 => ((o_part_mulByte_0_inst_o xor o_part_mulByte_1_inst_o) xor o_part_mulByte_2_inst_o) xor mulByte_2_inst_00_o, - 1 => ((mulByte_2_inst_01_o xor mulByte_0_inst_00_o) xor mulByte_1_inst_00_o) xor mulByte_2_inst_02_o, - 2 => ((mulByte_2_inst_03_o xor mulByte_2_inst_04_o) xor mulByte_0_inst_01_o) xor mulByte_1_inst_01_o, - 3 => ((mulByte_1_inst_02_o xor mulByte_2_inst_05_o) xor mulByte_2_inst_06_o) xor mulByte_0_inst_02_o + 0 => o_part_mulByte_0_inst_o xor o_part_mulByte_1_inst_o xor o_part_mulByte_2_inst_o xor mulByte_2_inst_00_o, + 1 => mulByte_2_inst_01_o xor mulByte_0_inst_00_o xor mulByte_1_inst_00_o xor mulByte_2_inst_02_o, + 2 => mulByte_2_inst_03_o xor mulByte_2_inst_04_o xor mulByte_0_inst_01_o xor mulByte_1_inst_01_o, + 3 => mulByte_1_inst_02_o xor mulByte_2_inst_05_o xor mulByte_2_inst_06_o xor mulByte_0_inst_02_o ), 1 => ( - 0 => ((mulByte_0_inst_03_o xor mulByte_1_inst_03_o) xor mulByte_2_inst_07_o) xor mulByte_2_inst_08_o, - 1 => ((mulByte_2_inst_09_o xor mulByte_0_inst_04_o) xor mulByte_1_inst_04_o) xor mulByte_2_inst_10_o, - 2 => ((mulByte_2_inst_11_o xor mulByte_2_inst_12_o) xor mulByte_0_inst_05_o) xor mulByte_1_inst_05_o, - 3 => ((mulByte_1_inst_06_o xor mulByte_2_inst_13_o) xor mulByte_2_inst_14_o) xor mulByte_0_inst_06_o + 0 => mulByte_0_inst_03_o xor mulByte_1_inst_03_o xor mulByte_2_inst_07_o xor mulByte_2_inst_08_o, + 1 => mulByte_2_inst_09_o xor mulByte_0_inst_04_o xor mulByte_1_inst_04_o xor mulByte_2_inst_10_o, + 2 => mulByte_2_inst_11_o xor mulByte_2_inst_12_o xor mulByte_0_inst_05_o xor mulByte_1_inst_05_o, + 3 => mulByte_1_inst_06_o xor mulByte_2_inst_13_o xor mulByte_2_inst_14_o xor mulByte_0_inst_06_o ), 2 => ( - 0 => ((mulByte_0_inst_07_o xor mulByte_1_inst_07_o) xor mulByte_2_inst_15_o) xor mulByte_2_inst_16_o, - 1 => ((mulByte_2_inst_17_o xor mulByte_0_inst_08_o) xor mulByte_1_inst_08_o) xor mulByte_2_inst_18_o, - 2 => ((mulByte_2_inst_19_o xor mulByte_2_inst_20_o) xor mulByte_0_inst_09_o) xor mulByte_1_inst_09_o, - 3 => ((mulByte_1_inst_10_o xor mulByte_2_inst_21_o) xor mulByte_2_inst_22_o) xor mulByte_0_inst_10_o + 0 => mulByte_0_inst_07_o xor mulByte_1_inst_07_o xor mulByte_2_inst_15_o xor mulByte_2_inst_16_o, + 1 => mulByte_2_inst_17_o xor mulByte_0_inst_08_o xor mulByte_1_inst_08_o xor mulByte_2_inst_18_o, + 2 => mulByte_2_inst_19_o xor mulByte_2_inst_20_o xor mulByte_0_inst_09_o xor mulByte_1_inst_09_o, + 3 => mulByte_1_inst_10_o xor mulByte_2_inst_21_o xor mulByte_2_inst_22_o xor mulByte_0_inst_10_o ), 3 => ( - 0 => ((mulByte_0_inst_11_o xor mulByte_1_inst_11_o) xor mulByte_2_inst_23_o) xor mulByte_2_inst_24_o, - 1 => ((mulByte_2_inst_25_o xor mulByte_0_inst_12_o) xor mulByte_1_inst_12_o) xor mulByte_2_inst_26_o, - 2 => ((mulByte_2_inst_27_o xor mulByte_2_inst_28_o) xor mulByte_0_inst_13_o) xor mulByte_1_inst_13_o, - 3 => ((mulByte_1_inst_14_o xor mulByte_2_inst_29_o) xor mulByte_2_inst_30_o) xor mulByte_0_inst_14_o + 0 => mulByte_0_inst_11_o xor mulByte_1_inst_11_o xor mulByte_2_inst_23_o xor mulByte_2_inst_24_o, + 1 => mulByte_2_inst_25_o xor mulByte_0_inst_12_o xor mulByte_1_inst_12_o xor mulByte_2_inst_26_o, + 2 => mulByte_2_inst_27_o xor mulByte_2_inst_28_o xor mulByte_0_inst_13_o xor mulByte_1_inst_13_o, + 3 => mulByte_1_inst_14_o xor mulByte_2_inst_29_o xor mulByte_2_inst_30_o xor mulByte_0_inst_14_o ) ); end mixColumns_arch; diff --git a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_1.vhd b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_1.vhd index d2f6448a9..26260a4ca 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_1.vhd +++ b/lib/src/test/resources/ref/AES.CipherSpecNoOpaques/vhdl.v93/hdl/mulByte_1.vhd @@ -23,5 +23,5 @@ begin o => a_o ); a_lhs <= rhs; - o <= (x"00" xor rhs) xor a_o; + o <= x"00" xor rhs xor a_o; end mulByte_1_arch; diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mixColumns.sv b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mixColumns.sv index f72e76419..7e9ac2838 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mixColumns.sv +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mixColumns.sv @@ -585,28 +585,28 @@ module mixColumns( assign mulByte_0_inst_14_rhs = state[3][3]; assign o = '{ 0: '{ - 0: ((o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o) ^ o_part_mulByte_2_inst_o) ^ mulByte_2_inst_00_o, - 1: ((mulByte_2_inst_01_o ^ mulByte_0_inst_00_o) ^ mulByte_1_inst_00_o) ^ mulByte_2_inst_02_o, - 2: ((mulByte_2_inst_03_o ^ mulByte_2_inst_04_o) ^ mulByte_0_inst_01_o) ^ mulByte_1_inst_01_o, - 3: ((mulByte_1_inst_02_o ^ mulByte_2_inst_05_o) ^ mulByte_2_inst_06_o) ^ mulByte_0_inst_02_o + 0: o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o ^ o_part_mulByte_2_inst_o ^ mulByte_2_inst_00_o, + 1: mulByte_2_inst_01_o ^ mulByte_0_inst_00_o ^ mulByte_1_inst_00_o ^ mulByte_2_inst_02_o, + 2: mulByte_2_inst_03_o ^ mulByte_2_inst_04_o ^ mulByte_0_inst_01_o ^ mulByte_1_inst_01_o, + 3: mulByte_1_inst_02_o ^ mulByte_2_inst_05_o ^ mulByte_2_inst_06_o ^ mulByte_0_inst_02_o }, 1: '{ - 0: ((mulByte_0_inst_03_o ^ mulByte_1_inst_03_o) ^ mulByte_2_inst_07_o) ^ mulByte_2_inst_08_o, - 1: ((mulByte_2_inst_09_o ^ mulByte_0_inst_04_o) ^ mulByte_1_inst_04_o) ^ mulByte_2_inst_10_o, - 2: ((mulByte_2_inst_11_o ^ mulByte_2_inst_12_o) ^ mulByte_0_inst_05_o) ^ mulByte_1_inst_05_o, - 3: ((mulByte_1_inst_06_o ^ mulByte_2_inst_13_o) ^ mulByte_2_inst_14_o) ^ mulByte_0_inst_06_o + 0: mulByte_0_inst_03_o ^ mulByte_1_inst_03_o ^ mulByte_2_inst_07_o ^ mulByte_2_inst_08_o, + 1: mulByte_2_inst_09_o ^ mulByte_0_inst_04_o ^ mulByte_1_inst_04_o ^ mulByte_2_inst_10_o, + 2: mulByte_2_inst_11_o ^ mulByte_2_inst_12_o ^ mulByte_0_inst_05_o ^ mulByte_1_inst_05_o, + 3: mulByte_1_inst_06_o ^ mulByte_2_inst_13_o ^ mulByte_2_inst_14_o ^ mulByte_0_inst_06_o }, 2: '{ - 0: ((mulByte_0_inst_07_o ^ mulByte_1_inst_07_o) ^ mulByte_2_inst_15_o) ^ mulByte_2_inst_16_o, - 1: ((mulByte_2_inst_17_o ^ mulByte_0_inst_08_o) ^ mulByte_1_inst_08_o) ^ mulByte_2_inst_18_o, - 2: ((mulByte_2_inst_19_o ^ mulByte_2_inst_20_o) ^ mulByte_0_inst_09_o) ^ mulByte_1_inst_09_o, - 3: ((mulByte_1_inst_10_o ^ mulByte_2_inst_21_o) ^ mulByte_2_inst_22_o) ^ mulByte_0_inst_10_o + 0: mulByte_0_inst_07_o ^ mulByte_1_inst_07_o ^ mulByte_2_inst_15_o ^ mulByte_2_inst_16_o, + 1: mulByte_2_inst_17_o ^ mulByte_0_inst_08_o ^ mulByte_1_inst_08_o ^ mulByte_2_inst_18_o, + 2: mulByte_2_inst_19_o ^ mulByte_2_inst_20_o ^ mulByte_0_inst_09_o ^ mulByte_1_inst_09_o, + 3: mulByte_1_inst_10_o ^ mulByte_2_inst_21_o ^ mulByte_2_inst_22_o ^ mulByte_0_inst_10_o }, 3: '{ - 0: ((mulByte_0_inst_11_o ^ mulByte_1_inst_11_o) ^ mulByte_2_inst_23_o) ^ mulByte_2_inst_24_o, - 1: ((mulByte_2_inst_25_o ^ mulByte_0_inst_12_o) ^ mulByte_1_inst_12_o) ^ mulByte_2_inst_26_o, - 2: ((mulByte_2_inst_27_o ^ mulByte_2_inst_28_o) ^ mulByte_0_inst_13_o) ^ mulByte_1_inst_13_o, - 3: ((mulByte_1_inst_14_o ^ mulByte_2_inst_29_o) ^ mulByte_2_inst_30_o) ^ mulByte_0_inst_14_o + 0: mulByte_0_inst_11_o ^ mulByte_1_inst_11_o ^ mulByte_2_inst_23_o ^ mulByte_2_inst_24_o, + 1: mulByte_2_inst_25_o ^ mulByte_0_inst_12_o ^ mulByte_1_inst_12_o ^ mulByte_2_inst_26_o, + 2: mulByte_2_inst_27_o ^ mulByte_2_inst_28_o ^ mulByte_0_inst_13_o ^ mulByte_1_inst_13_o, + 3: mulByte_1_inst_14_o ^ mulByte_2_inst_29_o ^ mulByte_2_inst_30_o ^ mulByte_0_inst_14_o } }; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_1.sv b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_1.sv index 688b0f0ef..b9b01e7b1 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_1.sv +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.sv2009/hdl/mulByte_1.sv @@ -14,5 +14,5 @@ module mulByte_1#(parameter logic [7:0] lhs)( .o /*-->*/ (a_o) ); assign a_lhs = rhs; - assign o = (8'h00 ^ rhs) ^ a_o; + assign o = 8'h00 ^ rhs ^ a_o; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v2001/hdl/mixColumns.v b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v2001/hdl/mixColumns.v index 3e9722b50..cf2dec8fc 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v2001/hdl/mixColumns.v +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v2001/hdl/mixColumns.v @@ -586,28 +586,28 @@ module mixColumns( assign mulByte_0_inst_14_rhs = state[7:0]; assign o = { { - ((o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o) ^ o_part_mulByte_2_inst_o) ^ mulByte_2_inst_00_o, - ((mulByte_2_inst_01_o ^ mulByte_0_inst_00_o) ^ mulByte_1_inst_00_o) ^ mulByte_2_inst_02_o, - ((mulByte_2_inst_03_o ^ mulByte_2_inst_04_o) ^ mulByte_0_inst_01_o) ^ mulByte_1_inst_01_o, - ((mulByte_1_inst_02_o ^ mulByte_2_inst_05_o) ^ mulByte_2_inst_06_o) ^ mulByte_0_inst_02_o + o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o ^ o_part_mulByte_2_inst_o ^ mulByte_2_inst_00_o, + mulByte_2_inst_01_o ^ mulByte_0_inst_00_o ^ mulByte_1_inst_00_o ^ mulByte_2_inst_02_o, + mulByte_2_inst_03_o ^ mulByte_2_inst_04_o ^ mulByte_0_inst_01_o ^ mulByte_1_inst_01_o, + mulByte_1_inst_02_o ^ mulByte_2_inst_05_o ^ mulByte_2_inst_06_o ^ mulByte_0_inst_02_o }, { - ((mulByte_0_inst_03_o ^ mulByte_1_inst_03_o) ^ mulByte_2_inst_07_o) ^ mulByte_2_inst_08_o, - ((mulByte_2_inst_09_o ^ mulByte_0_inst_04_o) ^ mulByte_1_inst_04_o) ^ mulByte_2_inst_10_o, - ((mulByte_2_inst_11_o ^ mulByte_2_inst_12_o) ^ mulByte_0_inst_05_o) ^ mulByte_1_inst_05_o, - ((mulByte_1_inst_06_o ^ mulByte_2_inst_13_o) ^ mulByte_2_inst_14_o) ^ mulByte_0_inst_06_o + mulByte_0_inst_03_o ^ mulByte_1_inst_03_o ^ mulByte_2_inst_07_o ^ mulByte_2_inst_08_o, + mulByte_2_inst_09_o ^ mulByte_0_inst_04_o ^ mulByte_1_inst_04_o ^ mulByte_2_inst_10_o, + mulByte_2_inst_11_o ^ mulByte_2_inst_12_o ^ mulByte_0_inst_05_o ^ mulByte_1_inst_05_o, + mulByte_1_inst_06_o ^ mulByte_2_inst_13_o ^ mulByte_2_inst_14_o ^ mulByte_0_inst_06_o }, { - ((mulByte_0_inst_07_o ^ mulByte_1_inst_07_o) ^ mulByte_2_inst_15_o) ^ mulByte_2_inst_16_o, - ((mulByte_2_inst_17_o ^ mulByte_0_inst_08_o) ^ mulByte_1_inst_08_o) ^ mulByte_2_inst_18_o, - ((mulByte_2_inst_19_o ^ mulByte_2_inst_20_o) ^ mulByte_0_inst_09_o) ^ mulByte_1_inst_09_o, - ((mulByte_1_inst_10_o ^ mulByte_2_inst_21_o) ^ mulByte_2_inst_22_o) ^ mulByte_0_inst_10_o + mulByte_0_inst_07_o ^ mulByte_1_inst_07_o ^ mulByte_2_inst_15_o ^ mulByte_2_inst_16_o, + mulByte_2_inst_17_o ^ mulByte_0_inst_08_o ^ mulByte_1_inst_08_o ^ mulByte_2_inst_18_o, + mulByte_2_inst_19_o ^ mulByte_2_inst_20_o ^ mulByte_0_inst_09_o ^ mulByte_1_inst_09_o, + mulByte_1_inst_10_o ^ mulByte_2_inst_21_o ^ mulByte_2_inst_22_o ^ mulByte_0_inst_10_o }, { - ((mulByte_0_inst_11_o ^ mulByte_1_inst_11_o) ^ mulByte_2_inst_23_o) ^ mulByte_2_inst_24_o, - ((mulByte_2_inst_25_o ^ mulByte_0_inst_12_o) ^ mulByte_1_inst_12_o) ^ mulByte_2_inst_26_o, - ((mulByte_2_inst_27_o ^ mulByte_2_inst_28_o) ^ mulByte_0_inst_13_o) ^ mulByte_1_inst_13_o, - ((mulByte_1_inst_14_o ^ mulByte_2_inst_29_o) ^ mulByte_2_inst_30_o) ^ mulByte_0_inst_14_o + mulByte_0_inst_11_o ^ mulByte_1_inst_11_o ^ mulByte_2_inst_23_o ^ mulByte_2_inst_24_o, + mulByte_2_inst_25_o ^ mulByte_0_inst_12_o ^ mulByte_1_inst_12_o ^ mulByte_2_inst_26_o, + mulByte_2_inst_27_o ^ mulByte_2_inst_28_o ^ mulByte_0_inst_13_o ^ mulByte_1_inst_13_o, + mulByte_1_inst_14_o ^ mulByte_2_inst_29_o ^ mulByte_2_inst_30_o ^ mulByte_0_inst_14_o } }; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v2001/hdl/mulByte_1.v b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v2001/hdl/mulByte_1.v index 713242259..213ef8299 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v2001/hdl/mulByte_1.v +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v2001/hdl/mulByte_1.v @@ -15,5 +15,5 @@ module mulByte_1#(parameter [7:0] lhs = 8'h03)( .o /*-->*/ (a_o) ); assign a_lhs = rhs; - assign o = (8'h00 ^ rhs) ^ a_o; + assign o = 8'h00 ^ rhs ^ a_o; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v95/hdl/mixColumns.v b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v95/hdl/mixColumns.v index bb5248b50..e6602feff 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v95/hdl/mixColumns.v +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v95/hdl/mixColumns.v @@ -588,28 +588,28 @@ module mixColumns( assign mulByte_0_inst_14_rhs = state[7:0]; assign o = { { - ((o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o) ^ o_part_mulByte_2_inst_o) ^ mulByte_2_inst_00_o, - ((mulByte_2_inst_01_o ^ mulByte_0_inst_00_o) ^ mulByte_1_inst_00_o) ^ mulByte_2_inst_02_o, - ((mulByte_2_inst_03_o ^ mulByte_2_inst_04_o) ^ mulByte_0_inst_01_o) ^ mulByte_1_inst_01_o, - ((mulByte_1_inst_02_o ^ mulByte_2_inst_05_o) ^ mulByte_2_inst_06_o) ^ mulByte_0_inst_02_o + o_part_mulByte_0_inst_o ^ o_part_mulByte_1_inst_o ^ o_part_mulByte_2_inst_o ^ mulByte_2_inst_00_o, + mulByte_2_inst_01_o ^ mulByte_0_inst_00_o ^ mulByte_1_inst_00_o ^ mulByte_2_inst_02_o, + mulByte_2_inst_03_o ^ mulByte_2_inst_04_o ^ mulByte_0_inst_01_o ^ mulByte_1_inst_01_o, + mulByte_1_inst_02_o ^ mulByte_2_inst_05_o ^ mulByte_2_inst_06_o ^ mulByte_0_inst_02_o }, { - ((mulByte_0_inst_03_o ^ mulByte_1_inst_03_o) ^ mulByte_2_inst_07_o) ^ mulByte_2_inst_08_o, - ((mulByte_2_inst_09_o ^ mulByte_0_inst_04_o) ^ mulByte_1_inst_04_o) ^ mulByte_2_inst_10_o, - ((mulByte_2_inst_11_o ^ mulByte_2_inst_12_o) ^ mulByte_0_inst_05_o) ^ mulByte_1_inst_05_o, - ((mulByte_1_inst_06_o ^ mulByte_2_inst_13_o) ^ mulByte_2_inst_14_o) ^ mulByte_0_inst_06_o + mulByte_0_inst_03_o ^ mulByte_1_inst_03_o ^ mulByte_2_inst_07_o ^ mulByte_2_inst_08_o, + mulByte_2_inst_09_o ^ mulByte_0_inst_04_o ^ mulByte_1_inst_04_o ^ mulByte_2_inst_10_o, + mulByte_2_inst_11_o ^ mulByte_2_inst_12_o ^ mulByte_0_inst_05_o ^ mulByte_1_inst_05_o, + mulByte_1_inst_06_o ^ mulByte_2_inst_13_o ^ mulByte_2_inst_14_o ^ mulByte_0_inst_06_o }, { - ((mulByte_0_inst_07_o ^ mulByte_1_inst_07_o) ^ mulByte_2_inst_15_o) ^ mulByte_2_inst_16_o, - ((mulByte_2_inst_17_o ^ mulByte_0_inst_08_o) ^ mulByte_1_inst_08_o) ^ mulByte_2_inst_18_o, - ((mulByte_2_inst_19_o ^ mulByte_2_inst_20_o) ^ mulByte_0_inst_09_o) ^ mulByte_1_inst_09_o, - ((mulByte_1_inst_10_o ^ mulByte_2_inst_21_o) ^ mulByte_2_inst_22_o) ^ mulByte_0_inst_10_o + mulByte_0_inst_07_o ^ mulByte_1_inst_07_o ^ mulByte_2_inst_15_o ^ mulByte_2_inst_16_o, + mulByte_2_inst_17_o ^ mulByte_0_inst_08_o ^ mulByte_1_inst_08_o ^ mulByte_2_inst_18_o, + mulByte_2_inst_19_o ^ mulByte_2_inst_20_o ^ mulByte_0_inst_09_o ^ mulByte_1_inst_09_o, + mulByte_1_inst_10_o ^ mulByte_2_inst_21_o ^ mulByte_2_inst_22_o ^ mulByte_0_inst_10_o }, { - ((mulByte_0_inst_11_o ^ mulByte_1_inst_11_o) ^ mulByte_2_inst_23_o) ^ mulByte_2_inst_24_o, - ((mulByte_2_inst_25_o ^ mulByte_0_inst_12_o) ^ mulByte_1_inst_12_o) ^ mulByte_2_inst_26_o, - ((mulByte_2_inst_27_o ^ mulByte_2_inst_28_o) ^ mulByte_0_inst_13_o) ^ mulByte_1_inst_13_o, - ((mulByte_1_inst_14_o ^ mulByte_2_inst_29_o) ^ mulByte_2_inst_30_o) ^ mulByte_0_inst_14_o + mulByte_0_inst_11_o ^ mulByte_1_inst_11_o ^ mulByte_2_inst_23_o ^ mulByte_2_inst_24_o, + mulByte_2_inst_25_o ^ mulByte_0_inst_12_o ^ mulByte_1_inst_12_o ^ mulByte_2_inst_26_o, + mulByte_2_inst_27_o ^ mulByte_2_inst_28_o ^ mulByte_0_inst_13_o ^ mulByte_1_inst_13_o, + mulByte_1_inst_14_o ^ mulByte_2_inst_29_o ^ mulByte_2_inst_30_o ^ mulByte_0_inst_14_o } }; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v95/hdl/mulByte_1.v b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v95/hdl/mulByte_1.v index 506e448e9..30401681c 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v95/hdl/mulByte_1.v +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/verilog.v95/hdl/mulByte_1.v @@ -18,5 +18,5 @@ module mulByte_1( .o /*-->*/ (a_o) ); assign a_lhs = rhs; - assign o = (8'h00 ^ rhs) ^ a_o; + assign o = 8'h00 ^ rhs ^ a_o; endmodule diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mixColumns.vhd b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mixColumns.vhd index ffb57d6ed..123c0b471 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mixColumns.vhd +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mixColumns.vhd @@ -591,28 +591,28 @@ begin mulByte_0_inst_14_rhs <= state(3)(3); o <= ( 0 => ( - 0 => ((o_part_mulByte_0_inst_o xor o_part_mulByte_1_inst_o) xor o_part_mulByte_2_inst_o) xor mulByte_2_inst_00_o, - 1 => ((mulByte_2_inst_01_o xor mulByte_0_inst_00_o) xor mulByte_1_inst_00_o) xor mulByte_2_inst_02_o, - 2 => ((mulByte_2_inst_03_o xor mulByte_2_inst_04_o) xor mulByte_0_inst_01_o) xor mulByte_1_inst_01_o, - 3 => ((mulByte_1_inst_02_o xor mulByte_2_inst_05_o) xor mulByte_2_inst_06_o) xor mulByte_0_inst_02_o + 0 => o_part_mulByte_0_inst_o xor o_part_mulByte_1_inst_o xor o_part_mulByte_2_inst_o xor mulByte_2_inst_00_o, + 1 => mulByte_2_inst_01_o xor mulByte_0_inst_00_o xor mulByte_1_inst_00_o xor mulByte_2_inst_02_o, + 2 => mulByte_2_inst_03_o xor mulByte_2_inst_04_o xor mulByte_0_inst_01_o xor mulByte_1_inst_01_o, + 3 => mulByte_1_inst_02_o xor mulByte_2_inst_05_o xor mulByte_2_inst_06_o xor mulByte_0_inst_02_o ), 1 => ( - 0 => ((mulByte_0_inst_03_o xor mulByte_1_inst_03_o) xor mulByte_2_inst_07_o) xor mulByte_2_inst_08_o, - 1 => ((mulByte_2_inst_09_o xor mulByte_0_inst_04_o) xor mulByte_1_inst_04_o) xor mulByte_2_inst_10_o, - 2 => ((mulByte_2_inst_11_o xor mulByte_2_inst_12_o) xor mulByte_0_inst_05_o) xor mulByte_1_inst_05_o, - 3 => ((mulByte_1_inst_06_o xor mulByte_2_inst_13_o) xor mulByte_2_inst_14_o) xor mulByte_0_inst_06_o + 0 => mulByte_0_inst_03_o xor mulByte_1_inst_03_o xor mulByte_2_inst_07_o xor mulByte_2_inst_08_o, + 1 => mulByte_2_inst_09_o xor mulByte_0_inst_04_o xor mulByte_1_inst_04_o xor mulByte_2_inst_10_o, + 2 => mulByte_2_inst_11_o xor mulByte_2_inst_12_o xor mulByte_0_inst_05_o xor mulByte_1_inst_05_o, + 3 => mulByte_1_inst_06_o xor mulByte_2_inst_13_o xor mulByte_2_inst_14_o xor mulByte_0_inst_06_o ), 2 => ( - 0 => ((mulByte_0_inst_07_o xor mulByte_1_inst_07_o) xor mulByte_2_inst_15_o) xor mulByte_2_inst_16_o, - 1 => ((mulByte_2_inst_17_o xor mulByte_0_inst_08_o) xor mulByte_1_inst_08_o) xor mulByte_2_inst_18_o, - 2 => ((mulByte_2_inst_19_o xor mulByte_2_inst_20_o) xor mulByte_0_inst_09_o) xor mulByte_1_inst_09_o, - 3 => ((mulByte_1_inst_10_o xor mulByte_2_inst_21_o) xor mulByte_2_inst_22_o) xor mulByte_0_inst_10_o + 0 => mulByte_0_inst_07_o xor mulByte_1_inst_07_o xor mulByte_2_inst_15_o xor mulByte_2_inst_16_o, + 1 => mulByte_2_inst_17_o xor mulByte_0_inst_08_o xor mulByte_1_inst_08_o xor mulByte_2_inst_18_o, + 2 => mulByte_2_inst_19_o xor mulByte_2_inst_20_o xor mulByte_0_inst_09_o xor mulByte_1_inst_09_o, + 3 => mulByte_1_inst_10_o xor mulByte_2_inst_21_o xor mulByte_2_inst_22_o xor mulByte_0_inst_10_o ), 3 => ( - 0 => ((mulByte_0_inst_11_o xor mulByte_1_inst_11_o) xor mulByte_2_inst_23_o) xor mulByte_2_inst_24_o, - 1 => ((mulByte_2_inst_25_o xor mulByte_0_inst_12_o) xor mulByte_1_inst_12_o) xor mulByte_2_inst_26_o, - 2 => ((mulByte_2_inst_27_o xor mulByte_2_inst_28_o) xor mulByte_0_inst_13_o) xor mulByte_1_inst_13_o, - 3 => ((mulByte_1_inst_14_o xor mulByte_2_inst_29_o) xor mulByte_2_inst_30_o) xor mulByte_0_inst_14_o + 0 => mulByte_0_inst_11_o xor mulByte_1_inst_11_o xor mulByte_2_inst_23_o xor mulByte_2_inst_24_o, + 1 => mulByte_2_inst_25_o xor mulByte_0_inst_12_o xor mulByte_1_inst_12_o xor mulByte_2_inst_26_o, + 2 => mulByte_2_inst_27_o xor mulByte_2_inst_28_o xor mulByte_0_inst_13_o xor mulByte_1_inst_13_o, + 3 => mulByte_1_inst_14_o xor mulByte_2_inst_29_o xor mulByte_2_inst_30_o xor mulByte_0_inst_14_o ) ); end mixColumns_arch; diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_1.vhd b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_1.vhd index 6952ebcda..8db42f698 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_1.vhd +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v2008/hdl/mulByte_1.vhd @@ -23,5 +23,5 @@ begin o => a_o ); a_lhs <= rhs; - o <= (x"00" xor rhs) xor a_o; + o <= x"00" xor rhs xor a_o; end mulByte_1_arch; diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v93/hdl/mixColumns.vhd b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v93/hdl/mixColumns.vhd index ffb57d6ed..123c0b471 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v93/hdl/mixColumns.vhd +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v93/hdl/mixColumns.vhd @@ -591,28 +591,28 @@ begin mulByte_0_inst_14_rhs <= state(3)(3); o <= ( 0 => ( - 0 => ((o_part_mulByte_0_inst_o xor o_part_mulByte_1_inst_o) xor o_part_mulByte_2_inst_o) xor mulByte_2_inst_00_o, - 1 => ((mulByte_2_inst_01_o xor mulByte_0_inst_00_o) xor mulByte_1_inst_00_o) xor mulByte_2_inst_02_o, - 2 => ((mulByte_2_inst_03_o xor mulByte_2_inst_04_o) xor mulByte_0_inst_01_o) xor mulByte_1_inst_01_o, - 3 => ((mulByte_1_inst_02_o xor mulByte_2_inst_05_o) xor mulByte_2_inst_06_o) xor mulByte_0_inst_02_o + 0 => o_part_mulByte_0_inst_o xor o_part_mulByte_1_inst_o xor o_part_mulByte_2_inst_o xor mulByte_2_inst_00_o, + 1 => mulByte_2_inst_01_o xor mulByte_0_inst_00_o xor mulByte_1_inst_00_o xor mulByte_2_inst_02_o, + 2 => mulByte_2_inst_03_o xor mulByte_2_inst_04_o xor mulByte_0_inst_01_o xor mulByte_1_inst_01_o, + 3 => mulByte_1_inst_02_o xor mulByte_2_inst_05_o xor mulByte_2_inst_06_o xor mulByte_0_inst_02_o ), 1 => ( - 0 => ((mulByte_0_inst_03_o xor mulByte_1_inst_03_o) xor mulByte_2_inst_07_o) xor mulByte_2_inst_08_o, - 1 => ((mulByte_2_inst_09_o xor mulByte_0_inst_04_o) xor mulByte_1_inst_04_o) xor mulByte_2_inst_10_o, - 2 => ((mulByte_2_inst_11_o xor mulByte_2_inst_12_o) xor mulByte_0_inst_05_o) xor mulByte_1_inst_05_o, - 3 => ((mulByte_1_inst_06_o xor mulByte_2_inst_13_o) xor mulByte_2_inst_14_o) xor mulByte_0_inst_06_o + 0 => mulByte_0_inst_03_o xor mulByte_1_inst_03_o xor mulByte_2_inst_07_o xor mulByte_2_inst_08_o, + 1 => mulByte_2_inst_09_o xor mulByte_0_inst_04_o xor mulByte_1_inst_04_o xor mulByte_2_inst_10_o, + 2 => mulByte_2_inst_11_o xor mulByte_2_inst_12_o xor mulByte_0_inst_05_o xor mulByte_1_inst_05_o, + 3 => mulByte_1_inst_06_o xor mulByte_2_inst_13_o xor mulByte_2_inst_14_o xor mulByte_0_inst_06_o ), 2 => ( - 0 => ((mulByte_0_inst_07_o xor mulByte_1_inst_07_o) xor mulByte_2_inst_15_o) xor mulByte_2_inst_16_o, - 1 => ((mulByte_2_inst_17_o xor mulByte_0_inst_08_o) xor mulByte_1_inst_08_o) xor mulByte_2_inst_18_o, - 2 => ((mulByte_2_inst_19_o xor mulByte_2_inst_20_o) xor mulByte_0_inst_09_o) xor mulByte_1_inst_09_o, - 3 => ((mulByte_1_inst_10_o xor mulByte_2_inst_21_o) xor mulByte_2_inst_22_o) xor mulByte_0_inst_10_o + 0 => mulByte_0_inst_07_o xor mulByte_1_inst_07_o xor mulByte_2_inst_15_o xor mulByte_2_inst_16_o, + 1 => mulByte_2_inst_17_o xor mulByte_0_inst_08_o xor mulByte_1_inst_08_o xor mulByte_2_inst_18_o, + 2 => mulByte_2_inst_19_o xor mulByte_2_inst_20_o xor mulByte_0_inst_09_o xor mulByte_1_inst_09_o, + 3 => mulByte_1_inst_10_o xor mulByte_2_inst_21_o xor mulByte_2_inst_22_o xor mulByte_0_inst_10_o ), 3 => ( - 0 => ((mulByte_0_inst_11_o xor mulByte_1_inst_11_o) xor mulByte_2_inst_23_o) xor mulByte_2_inst_24_o, - 1 => ((mulByte_2_inst_25_o xor mulByte_0_inst_12_o) xor mulByte_1_inst_12_o) xor mulByte_2_inst_26_o, - 2 => ((mulByte_2_inst_27_o xor mulByte_2_inst_28_o) xor mulByte_0_inst_13_o) xor mulByte_1_inst_13_o, - 3 => ((mulByte_1_inst_14_o xor mulByte_2_inst_29_o) xor mulByte_2_inst_30_o) xor mulByte_0_inst_14_o + 0 => mulByte_0_inst_11_o xor mulByte_1_inst_11_o xor mulByte_2_inst_23_o xor mulByte_2_inst_24_o, + 1 => mulByte_2_inst_25_o xor mulByte_0_inst_12_o xor mulByte_1_inst_12_o xor mulByte_2_inst_26_o, + 2 => mulByte_2_inst_27_o xor mulByte_2_inst_28_o xor mulByte_0_inst_13_o xor mulByte_1_inst_13_o, + 3 => mulByte_1_inst_14_o xor mulByte_2_inst_29_o xor mulByte_2_inst_30_o xor mulByte_0_inst_14_o ) ); end mixColumns_arch; diff --git a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_1.vhd b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_1.vhd index 6952ebcda..8db42f698 100644 --- a/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_1.vhd +++ b/lib/src/test/resources/ref/AES.CipherSpecWithOpaques/vhdl.v93/hdl/mulByte_1.vhd @@ -23,5 +23,5 @@ begin o => a_o ); a_lhs <= rhs; - o <= (x"00" xor rhs) xor a_o; + o <= x"00" xor rhs xor a_o; end mulByte_1_arch; diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv index 0b6f43b99..4558d8a3d 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv @@ -9,6 +9,6 @@ module FullAdder1( output logic c_out ); `include "dfhdl_defs.svh" - assign sum = (a ^ b) ^ c_in; - assign c_out = ((a & b) | (b & c_in)) | (c_in & a); + assign sum = a ^ b ^ c_in; + assign c_out = (a & b) | (b & c_in) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v index 4d3248626..571e66d33 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v @@ -9,6 +9,6 @@ module FullAdder1( output wire c_out ); `include "dfhdl_defs.vh" - assign sum = (a ^ b) ^ c_in; - assign c_out = ((a & b) | (b & c_in)) | (c_in & a); + assign sum = a ^ b ^ c_in; + assign c_out = (a & b) | (b & c_in) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v index d994fb3ff..ce77a8100 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v @@ -14,6 +14,6 @@ module FullAdder1( input wire c_in; output wire sum; output wire c_out; - assign sum = (a ^ b) ^ c_in; - assign c_out = ((a & b) | (b & c_in)) | (c_in & a); + assign sum = a ^ b ^ c_in; + assign c_out = (a & b) | (b & c_in) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd index e2be3df93..029793cb7 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd @@ -15,6 +15,6 @@ end FullAdder1; architecture FullAdder1_arch of FullAdder1 is begin - sum <= (a xor b) xor c_in; - c_out <= ((a and b) or (b and c_in)) or (c_in and a); + sum <= a xor b xor c_in; + c_out <= (a and b) or (b and c_in) or (c_in and a); end FullAdder1_arch; diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd index e2be3df93..029793cb7 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd @@ -15,6 +15,6 @@ end FullAdder1; architecture FullAdder1_arch of FullAdder1 is begin - sum <= (a xor b) xor c_in; - c_out <= ((a and b) or (b and c_in)) or (c_in and a); + sum <= a xor b xor c_in; + c_out <= (a and b) or (b and c_in) or (c_in and a); end FullAdder1_arch; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv index 0b6f43b99..4558d8a3d 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv @@ -9,6 +9,6 @@ module FullAdder1( output logic c_out ); `include "dfhdl_defs.svh" - assign sum = (a ^ b) ^ c_in; - assign c_out = ((a & b) | (b & c_in)) | (c_in & a); + assign sum = a ^ b ^ c_in; + assign c_out = (a & b) | (b & c_in) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v index 4d3248626..571e66d33 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v @@ -9,6 +9,6 @@ module FullAdder1( output wire c_out ); `include "dfhdl_defs.vh" - assign sum = (a ^ b) ^ c_in; - assign c_out = ((a & b) | (b & c_in)) | (c_in & a); + assign sum = a ^ b ^ c_in; + assign c_out = (a & b) | (b & c_in) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v index d994fb3ff..ce77a8100 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v @@ -14,6 +14,6 @@ module FullAdder1( input wire c_in; output wire sum; output wire c_out; - assign sum = (a ^ b) ^ c_in; - assign c_out = ((a & b) | (b & c_in)) | (c_in & a); + assign sum = a ^ b ^ c_in; + assign c_out = (a & b) | (b & c_in) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd index e2be3df93..029793cb7 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd @@ -15,6 +15,6 @@ end FullAdder1; architecture FullAdder1_arch of FullAdder1 is begin - sum <= (a xor b) xor c_in; - c_out <= ((a and b) or (b and c_in)) or (c_in and a); + sum <= a xor b xor c_in; + c_out <= (a and b) or (b and c_in) or (c_in and a); end FullAdder1_arch; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd index e2be3df93..029793cb7 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd @@ -15,6 +15,6 @@ end FullAdder1; architecture FullAdder1_arch of FullAdder1 is begin - sum <= (a xor b) xor c_in; - c_out <= ((a and b) or (b and c_in)) or (c_in and a); + sum <= a xor b xor c_in; + c_out <= (a and b) or (b and c_in) or (c_in and a); end FullAdder1_arch; diff --git a/lib/src/test/scala/issues/IssueSpec.scala b/lib/src/test/scala/issues/IssueSpec.scala index dce572d81..451ec1786 100644 --- a/lib/src/test/scala/issues/IssueSpec.scala +++ b/lib/src/test/scala/issues/IssueSpec.scala @@ -84,4 +84,6 @@ class IssuesSpec extends FunSuite: i146.DoubleStructDecl().compile.lint test("i147 compiles and passes Verilog linting"): i147.ClockRstConnection().compile.lint + test("i375 compiles with no exception"): + i375.draw_line().compile end IssuesSpec diff --git a/lib/src/test/scala/issues/i375.scala b/lib/src/test/scala/issues/i375.scala new file mode 100644 index 000000000..0c02bf001 --- /dev/null +++ b/lib/src/test/scala/issues/i375.scala @@ -0,0 +1,18 @@ +package issues.i375 + +import dfhdl.* + +@top(false) class draw_line(val CORDW: Int <> CONST = 16) extends EDDesign: + val clk = Bit <> IN + val x0 = SInt(CORDW) <> IN + val x1 = SInt(CORDW) <> IN + val y0 = SInt(CORDW) <> IN + val y1 = SInt(CORDW) <> IN + val swap = Bit <> VAR + val xa = SInt(CORDW) <> VAR + val xb = SInt(CORDW) <> VAR + + process(all): + swap := y0 > y1 + xa := (if (swap) x1 else x0).resize(CORDW) // <-- crash here + xb := (if (swap) x0 else x1).resize(CORDW) diff --git a/plugin/src/main/scala/plugin/FlattenInlinedPhase.scala b/plugin/src/main/scala/plugin/FlattenInlinedPhase.scala new file mode 100644 index 000000000..37c9f82e8 --- /dev/null +++ b/plugin/src/main/scala/plugin/FlattenInlinedPhase.scala @@ -0,0 +1,72 @@ +package dfhdl.plugin + +import dotty.tools.dotc.* +import plugins.* +import core.* +import Contexts.* +import Symbols.* +import Flags.* +import ast.tpd +import ast.tpd.* + +// This phase optimizes trees produced by transparent inline expansion to reduce +// compile time in the posttyper phase. It performs two key optimizations: +// +// 1. Flattens deeply nested Inlined trees from chained transparent inline +// operations (e.g., a + b + c + d). +// +// 2. Replaces call trees in Inlined nodes with minimal call traces (just a +// reference to the top-level class). This prevents posttyper from +// re-transforming complex call trees (which may contain TypeApply with +// heavy type arguments) for each of the many Inlined nodes produced +// by chained inline expansions. +class FlattenInlinedPhase(setting: Setting) extends PluginPhase: + import tpd.* + + val phaseName = "FlattenInlined" + override val runsAfter = Set("typer") + override val runsBefore = Set("posttyper") + + // Pre-compute the call trace that posttyper would produce. + // This replaces a complex call tree (e.g., TypeApply(Select(...), ...)) + // with a minimal Ident/Select pointing to the top-level class. + private def minimizeCall(call: Tree)(using Context): Tree = + if call.isEmpty then call + else + val callSym = call.symbol + if !callSym.exists then call + else + val topLevelCls = callSym.topLevelClass + if !topLevelCls.exists then call + else if callSym.is(Macro) then + ref(topLevelCls.owner).select(topLevelCls.name)(using ctx.withOwner(topLevelCls.owner)).withSpan(call.span) + else + Ident(topLevelCls.typeRef).withSpan(call.span) + + private val treeMap = new TreeMap: + override def transform(tree: Tree)(using Context): Tree = + super.transform(tree) match + case inlined @ Inlined(call, bindings, expansion) => + val (innerBindings, innerExpansion) = flattenInlined(expansion) + val newCall = minimizeCall(call) + val allBindings = bindings ++ innerBindings + if (newCall eq call) && innerBindings.isEmpty then inlined + else cpy.Inlined(inlined)(newCall, allBindings, innerExpansion) + case tree => tree + + private def flattenInlined(tree: Tree)(using Context): (List[MemberDef], Tree) = + tree match + case Inlined(_, bindings, expansion) => + val (innerBindings, innerExpansion) = flattenInlined(expansion) + (bindings ++ innerBindings, innerExpansion) + case Block(stats, expr) => + val (innerBindings, innerExpr) = flattenInlined(expr) + if innerBindings.isEmpty then (Nil, tree) + else + val allStats = stats.map(_.asInstanceOf[MemberDef]) ++ innerBindings + (Nil, cpy.Block(tree)(allStats.toList, innerExpr)) + case _ => (Nil, tree) + + override def transformUnit(tree: Tree)(using Context): Tree = + treeMap.transform(tree) +end FlattenInlinedPhase diff --git a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala index a60879f00..ff6b10b43 100755 --- a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala @@ -313,6 +313,17 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: apply match case ContextArg(_) => addToTreeOwnerMap(apply, ownerTree, inlinedSrcPos) + // When inside an inlined context, also traverse non-context args + // for nested context-arg applies. This is needed when macros + // (e.g., flattenInlined in Exact) strip Inlined wrappers that + // prepareForInlined relied on for correct srcPos propagation. + if (inlinedSrcPos.isDefined) + val ApplyFunArgs(_, argss) = apply.runtimeChecked + for + args <- argss + arg <- args + if !arg.tpe.isMetaContext + do nameValOrDef(arg, EmptyValDef, arg.tpe.simple, inlinedSrcPos) true case _ => false else false @@ -363,15 +374,6 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: } named end match - case Block(stats, _) - if stats.exists { - case vd: ValDef => vd.name.toString == "skipContextHack" - case _ => false - } => - stats.collectFirst { - case vd: ValDef if vd.name.toString.startsWith("$scrutinee") => - nameValOrDef(vd.rhs, ownerTree, typeFocus, inlinedSrcPos) - }.getOrElse(false) case block: Block => // debug("Block expr") nameValOrDef(block.expr, ownerTree, typeFocus, inlinedSrcPos) diff --git a/plugin/src/main/scala/plugin/Plugin.scala b/plugin/src/main/scala/plugin/Plugin.scala index 19d4f8611..a68c76d22 100644 --- a/plugin/src/main/scala/plugin/Plugin.scala +++ b/plugin/src/main/scala/plugin/Plugin.scala @@ -12,6 +12,7 @@ class Plugin extends StandardPlugin: PreTyperPhase(setting) :: TopAnnotPhase(setting) :: MetaContextPlacerPhase(setting) :: + FlattenInlinedPhase(setting) :: LoopFSMPhase(setting) :: CustomControlPhase(setting) :: DesignDefsPhase(setting) :: diff --git a/project/DFHDLCommands.scala b/project/DFHDLCommands.scala index 5eb12eaca..e6434b0df 100644 --- a/project/DFHDLCommands.scala +++ b/project/DFHDLCommands.scala @@ -7,7 +7,20 @@ import java.nio.charset.StandardCharsets import java.io.IOException object DFHDLCommands { - val quickTestSetup = Command.command("quickTestSetup") { state => + val corePlayground = Command.command("corePlayground") { state => + val extracted = Project.extract(state) + val newState = extracted.appendWithSession(Seq( + (LocalProject("internals") / Test / sources) := Nil, + (LocalProject("core") / Test / sources) := ((LocalProject("core") / Test / sources).value.filter(_.toString.contains("Playground.scala"))), + (LocalProject("compiler_stages") / Test / sources) := Nil, + (LocalProject("platforms") / Compile / sources) := Nil, + (LocalProject("platforms") / Test / sources) := Nil, + (LocalProject("lib") / Compile / sources) := Nil, + (LocalProject("lib") / Test / sources) := Nil + ), state) + newState + } + val libPlayground = Command.command("libPlayground") { state => val extracted = Project.extract(state) val newState = extracted.appendWithSession(Seq( (LocalProject("internals") / Test / sources) := Nil, diff --git a/project/build.properties b/project/build.properties index 8430e165c..b50d18f5c 100755 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.12.8 \ No newline at end of file +sbt.version = 1.12.9 \ No newline at end of file