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
I already found (and filed as #469) that +/- don't widen the same way * does when both operand widths are plain Scala-literal bounded types. This report is a different dimension of the same feature: here the target width is a plain literal (UInt(16)), and the narrower operand's own width is parametric (derived from Int <> CONST via UInt.until(N), i.e. clog2(N)). This shape fails too, for * specifically — even though:
So neither #469 nor #461 covers "literal target, parametric source" — and that shape still fails.
What I ran
Minimal repro (*, parametric source width, literal target width):
import dfhdl.*
class Probe4(val N: Int <> CONST = 8) extends EDDesign:
val rbow_id = UInt.until(N) <> VAR
val vr0 = UInt(16) <> VAR
process(all):
vr0 := 5 * rbow_id
V=$(cat ~/.dfhdl_version) # 0.22.0+72-ec89141c-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 Probe4 -- --nocache commit
Control 1 — same shape, but the source width is a plain Scala Int literal (UInt.until(8), not Int <> CONST) instead of parametric: compiles cleanly, no error.
import dfhdl.*
class Probe3 extends EDDesign:
val rbow_id = UInt.until(8) <> VAR // literal 8, not a CONST param
val vr0 = SInt(16) <> VAR
process(all):
vr0 :== 180 - 5 * rbow_id
Control 2 — issue #461's exact repro (both target and source width parametric, SInt(2*WIDTH) vs SInt(WIDTH)), retested on this build: compiles cleanly, confirming #461's fix is in place for the both-parametric shape.
import dfhdl.*
class Probe6(val WIDTH: Int <> CONST = 8) extends EDDesign:
val a1, b1 = SInt(WIDTH) <> IN
val prod = SInt(2 * WIDTH) <> OUT
prod <> a1 * b1
What it produced
Probe4 (parametric source, literal target) fails:
DFiant HDL elaboration error!
Position: src/Probe4.scala:6:5 - 6:23
Hierarchy: Probe4
Operation: `:=`
Message: The applied RHS value width (clog2(N)) is undefined compared to the LHS variable width (16).
Probe3 (literal source, literal target) and Probe6 (parametric source, parametric target) both compile with no error, confirming this is specifically the "one side literal, one side parametric" combination that is unhandled.
I also hit the identical symptom in the actual translation, on the cidx assignment (target UInt(CIDXW) where CIDXW is a literal-defaulted Int <> CONST constructor parameter, source 2 + rbow_id where rbow_id's width derives from another Int <> CONST, RBOW_CNT):
DFiant HDL elaboration error!
Position: src/Probe2.scala:6:5 - 6:30
Hierarchy: Probe2
Operation: `:==`
Message: The applied RHS value width (clog2(RBOW_CNT)) is undefined compared to the LHS variable width (16).
(shown here against a literal 16 target for isolation; the real translation has CIDXW — also parametric — as the target, which is arguably closer to #461's shape, but the N-vs-16 case above proves the literal-target variant fails independently of that.)
Expected behavior
Per the documented widening rule, 5 * rbow_id assigned to vr0: UInt(16) should re-evaluate the whole anonymous expression at 16 bits, matching Verilog's assignment-context width propagation — rbow_id's width being symbolic (clog2(N)) rather than a literal shouldn't matter, since 16 is a known concrete number the elaborator could compare clog2(N) against once N is resolved (which it always is, by elaboration time, since N is a fully concrete Int <> CONST argument on this design).
Actual behavior
The compiler rejects the assignment outright with "undefined... compared to", without attempting the recursive target-context widening the docs describe, and without suggesting a fix (contrast with the other elaboration warning for implicit-Int arithmetic chains, which explicitly names +^/-^/*^ or d"W'V" literals as the fix).
Fix that worked
Explicitly resize the parametric-width operand before the arithmetic:
vr0 := 5 * rbow_id.resize(16)
This is the same workaround #461 and #469 both landed on, applied to yet another shape the automatic promotion doesn't cover.
DFHDL version
0.22.0+72-ec89141c-SNAPSHOT
Relation to #461 and #469
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":I already found (and filed as #469) that
+/-don't widen the same way*does when both operand widths are plain Scala-literal bounded types. This report is a different dimension of the same feature: here the target width is a plain literal (UInt(16)), and the narrower operand's own width is parametric (derived fromInt <> CONSTviaUInt.until(N), i.e.clog2(N)). This shape fails too, for*specifically — even though:*in [Bug] Automatic target-context widening applies to*but not+/-when a wildcard Int's minimal width exceeds the other (literal-width) operand's own width #469, both operand widths literal) works,*in [Knowledge Gap] mul.sv - automatic carry promotion doesn't apply to parametric (design-constant) widths #461, both target and source width symbolic —SInt(2*WIDTH)vsSInt(WIDTH)) also now works on this build (retested below, confirms [Knowledge Gap] mul.sv - automatic carry promotion doesn't apply to parametric (design-constant) widths #461's fix holds for that shape).So neither #469 nor #461 covers "literal target, parametric source" — and that shape still fails.
What I ran
Minimal repro (
*, parametric source width, literal target width):Control 1 — same shape, but the source width is a plain Scala Int literal (
UInt.until(8), notInt <> CONST) instead of parametric: compiles cleanly, no error.Control 2 — issue #461's exact repro (both target and source width parametric,
SInt(2*WIDTH)vsSInt(WIDTH)), retested on this build: compiles cleanly, confirming #461's fix is in place for the both-parametric shape.What it produced
Probe4(parametric source, literal target) fails:Probe3(literal source, literal target) andProbe6(parametric source, parametric target) both compile with no error, confirming this is specifically the "one side literal, one side parametric" combination that is unhandled.I also hit the identical symptom in the actual translation, on the
cidxassignment (targetUInt(CIDXW)whereCIDXWis a literal-defaultedInt <> CONSTconstructor parameter, source2 + rbow_idwhererbow_id's width derives from anotherInt <> CONST,RBOW_CNT):(shown here against a literal
16target for isolation; the real translation hasCIDXW— also parametric — as the target, which is arguably closer to #461's shape, but theN-vs-16case above proves the literal-target variant fails independently of that.)Expected behavior
Per the documented widening rule,
5 * rbow_idassigned tovr0: UInt(16)should re-evaluate the whole anonymous expression at 16 bits, matching Verilog's assignment-context width propagation —rbow_id's width being symbolic (clog2(N)) rather than a literal shouldn't matter, since16is a known concrete number the elaborator could compareclog2(N)against onceNis resolved (which it always is, by elaboration time, sinceNis a fully concreteInt <> CONSTargument on this design).Actual behavior
The compiler rejects the assignment outright with "undefined... compared to", without attempting the recursive target-context widening the docs describe, and without suggesting a fix (contrast with the other elaboration warning for implicit-Int arithmetic chains, which explicitly names
+^/-^/*^ord"W'V"literals as the fix).Fix that worked
Explicitly resize the parametric-width operand before the arithmetic:
This is the same workaround #461 and #469 both landed on, applied to yet another shape the automatic promotion doesn't cover.
DFHDL version
0.22.0+72-ec89141c-SNAPSHOTRelation to #461 and #469
SInt(2*WIDTH)/SInt(WIDTH)). Retested here as Control 2 — still fixed.*but not+/-when a wildcard Int's minimal width exceeds the other (literal-width) operand's own width #469 (open): both target and source width literal,+/-fail while*succeeds — pure operator asymmetry.*(and, going by thecidxcase above, likely+too). Neither prior issue's shape covers this combination, and it currently fails.