Skip to content

[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

@soronpo

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions