Summary
Translating demos/castle-drawing/render_castle.sv from projf-explore (https://github.com/projf/projf-explore).
Per docs/user-guide/type-system/index.md, "Overflow and automatic target-context widening":
an anonymous arithmetic expression (+, -, *, unary -) that is assigned or connected to a variable wider than the operation's result is re-evaluated at the target's width and sign ... every operand, recursively through the anonymous expression, is widened to the target type
All three operators (+, -, *) are documented to widen identically. In practice, * widens correctly (confirmed via generated Verilog) but + and - fail to compile in the exact same shape, with no literal/parametric-width confusion involved — both operand widths here are plain literals (SInt(16), UInt(3)), unrelated to the parametric-width limitation already tracked in #461.
What I ran
Three minimal probes, same shapes, only the operator/value differs:
import dfhdl.*
class Probe extends EDDesign:
val clk = Bit <> IN
val rbow_id = UInt(3) <> IN
val vr0 = SInt(16) <> OUT
process(clk.rising):
vr0 :== 180 - 5 * rbow_id // the original shape (nested)
import dfhdl.*
class Probe2 extends EDDesign:
val clk = Bit <> IN
val rbow_id = UInt(3) <> IN
val vr0 = SInt(16) <> OUT
process(clk.rising):
vr0 :== 5 * rbow_id // isolated: * only
import dfhdl.*
class Probe2 extends EDDesign:
val clk = Bit <> IN
val rbow_id = UInt(3) <> IN
val vr0 = SInt(16) <> OUT
process(clk.rising):
vr0 :== 180 - rbow_id // isolated: - only
// (180 + rbow_id reproduces identically)
Each run via:
V=$(cat ~/.dfhdl_version) # 0.22.0+63-477e737a-SNAPSHOT
scala run src/ --scala 3.8.4 \
--dep "io.github.dfianthdl::dfhdl::$V" \
--compiler-plugin "io.github.dfianthdl:::dfhdl-plugin::$V" \
-O -deprecation -M <Probe|Probe2> -- --nocache commit
What it produced
vr0 :== 5 * rbow_id (isolated *) compiles successfully, and the generated Verilog shows rbow_id correctly widened all the way to the target width before the multiply:
always_ff @(posedge clk)
begin
vr0 <= 16'sd5 * $signed(`EBY_U(rbow_id, 13));
end
vr0 :== 180 - rbow_id and vr0 :== 180 + rbow_id (isolated -/+, identical shape, just a different operator) fail to compile:
[error] ./src/Probe2.scala:9:13
[error] The wildcard `Int` value width (8) is larger than the bit-accurate value width (3).
vr0 :== 180 - rbow_id // single-level: subtract only, wider target
^^^^^^^^^^^^^
(180 + rbow_id gives the identical message and position, just for +.)
The original nested shape (180 - 5 * rbow_id) fails the same way, on the outer -, even though the inner 5 * rbow_id alone is fine.
Expected behavior
Per the documented rule, +/- should widen identically to *: since vr0 (SInt[16]) is wider than either operand's native width, the whole anonymous chain — including rbow_id — should be re-evaluated at 16 bits (matching Verilog's assignment-context propagation, which is exactly what this translation is trying to reproduce), and 180/5 should not need to fit within rbow_id's own 3-bit width at all.
Actual behavior
* performs the documented recursive widening. + and - instead check whether the wildcard Int operand's own minimal width fits within the other operand's native (pre-widening) width, and reject immediately if not — apparently without ever consulting the wider assignment target. This makes the three operators behave inconsistently in a shape the docs explicitly claim they handle the same way.
Secondary issue: the error message doesn't point to the fix
The message ("The wildcard Int value width (8) is larger than the bit-accurate value width (3).") gives no indication that:
- a wider assignment target exists and should apply (unlike the other elaboration warning DFHDL emits for implicit-Int chains, which explicitly says "Use carry operations (+^, -^, *^) or explicit bit-accurate literals (d"W'V")." — this one says neither),
- the identical shape with
* instead of +/- would succeed,
- an explicit fix (e.g. widening
rbow_id first with .resize/.signed.resize, or using +^) would resolve it.
Fix that worked
Explicitly widen the narrower operand to the target width before the arithmetic:
vr0 :== 180 - 5 * rbow_id.signed.resize(16) // SInt[16], value 0..7, sign-extended safely
This compiled and the resulting design (render_castle) verified correctly against the original Verilog (equivalence proven via cav).
DFHDL version
0.22.0+63-477e737a-SNAPSHOT
This is a distinct issue from #461 (parametric/Int <> CONST-derived widths not being comparable symbolically). Every width here is a plain Scala-literal bounded type (SInt(16), UInt(3)) — no design constructor parameters are involved at all, so the "undecidable for all parameter values" reasoning that applies to #461 does not apply here. This is a plain operator-implementation asymmetry between * and +/- under fully concrete, literal widths.
Summary
Translating
demos/castle-drawing/render_castle.svfrom projf-explore (https://github.com/projf/projf-explore).Per
docs/user-guide/type-system/index.md, "Overflow and automatic target-context widening":All three operators (
+,-,*) are documented to widen identically. In practice,*widens correctly (confirmed via generated Verilog) but+and-fail to compile in the exact same shape, with no literal/parametric-width confusion involved — both operand widths here are plain literals (SInt(16),UInt(3)), unrelated to the parametric-width limitation already tracked in #461.What I ran
Three minimal probes, same shapes, only the operator/value differs:
Each run via:
What it produced
vr0 :== 5 * rbow_id(isolated*) compiles successfully, and the generated Verilog showsrbow_idcorrectly widened all the way to the target width before the multiply:vr0 :== 180 - rbow_idandvr0 :== 180 + rbow_id(isolated-/+, identical shape, just a different operator) fail to compile:(
180 + rbow_idgives the identical message and position, just for+.)The original nested shape (
180 - 5 * rbow_id) fails the same way, on the outer-, even though the inner5 * rbow_idalone is fine.Expected behavior
Per the documented rule,
+/-should widen identically to*: sincevr0(SInt[16]) is wider than either operand's native width, the whole anonymous chain — includingrbow_id— should be re-evaluated at 16 bits (matching Verilog's assignment-context propagation, which is exactly what this translation is trying to reproduce), and180/5should not need to fit withinrbow_id's own 3-bit width at all.Actual behavior
*performs the documented recursive widening.+and-instead check whether the wildcardIntoperand's own minimal width fits within the other operand's native (pre-widening) width, and reject immediately if not — apparently without ever consulting the wider assignment target. This makes the three operators behave inconsistently in a shape the docs explicitly claim they handle the same way.Secondary issue: the error message doesn't point to the fix
The message ("The wildcard
Intvalue width (8) is larger than the bit-accurate value width (3).") gives no indication that:*instead of+/-would succeed,rbow_idfirst with.resize/.signed.resize, or using+^) would resolve it.Fix that worked
Explicitly widen the narrower operand to the target width before the arithmetic:
This compiled and the resulting design (
render_castle) verified correctly against the original Verilog (equivalence proven viacav).DFHDL version
0.22.0+63-477e737a-SNAPSHOTNote on #461
This is a distinct issue from #461 (parametric/
Int <> CONST-derived widths not being comparable symbolically). Every width here is a plain Scala-literal bounded type (SInt(16),UInt(3)) — no design constructor parameters are involved at all, so the "undecidable for all parameter values" reasoning that applies to #461 does not apply here. This is a plain operator-implementation asymmetry between*and+/-under fully concrete, literal widths.