Repository navigation
[Bug] */+/- don't widen to a literal-width target when the narrower operand's own width is parametric (Int <> CONST-derived), even though target-context widening works for the fully-literal and fully-parametric cases #475
Description
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:
- the fully-literal case (
*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, - the fully-parametric case (
*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):
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_idV=$(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 commitControl 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_idControl 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 * b1What 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
- [Knowledge Gap] mul.sv - automatic carry promotion doesn't apply to parametric (design-constant) widths #461 (closed, fixed): both target and source width parametric (
SInt(2*WIDTH)/SInt(WIDTH)). Retested here as Control 2 — still fixed. - [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 (open): both target and source width literal,+/-fail while*succeeds — pure operator asymmetry. - This report: target width literal, source width parametric, for
*(and, going by thecidxcase above, likely+too). Neither prior issue's shape covers this combination, and it currently fails.
Activity
soronpo commentedon Aug 12, 2026
Fixed in #477