Skip to content

[Bug] UInt(1) + 1 fails elaboration with "Signed value width must be larger than 1" #476

Description

@soronpo

Minimal reproduction (verified against v0.22.0+72-ec89141c-SNAPSHOT)

import dfhdl.*
class Probe extends EDDesign:
  val clk, rst = Bit <> IN
  val cnt = UInt(1) <> VAR
  process(clk.rising, rst.rising):
    if (rst)
      cnt :== 0
    else
      cnt :== cnt + 1

Compiled with:

scala run src/ --scala 3.8.4 \
  --dep "io.github.dfianthdl::dfhdl::0.22.0+72-ec89141c-SNAPSHOT" \
  --compiler-plugin "io.github.dfianthdl:::dfhdl-plugin::0.22.0+72-ec89141c-SNAPSHOT" \
  -O -deprecation -M Probe -- --nocache commit

Expected behavior

Per the docs (user-guide/type-system/index.md, Wildcard Int Values section), a Scala Int literal such as 1 acts as a wildcard that adapts to the bit-accurate operand's width — 1 fits comfortably inside UInt[1] (max representable value in 1 bit is exactly 1), so cnt + 1 should elaborate as an ordinary (modular) 1-bit increment, exactly as buff_cnt <= buff_cnt + 'd1; does in Verilog for a 1-bit reg.

Actual behavior

DFiant HDL elaboration error!
Position:  src/Probe.scala:9:7 - 9:22
Hierarchy: Probe
Operation: `apply`
Message:   Signed value width must be larger than 1, but found: 1

Nothing in the source uses a signed type — cnt is UInt(1), and the literal 1 on the RHS is the only other operand. The message names an internal constraint ("Signed value width must be larger than 1") that has no counterpart in the code the user wrote, suggesting the compiler's internal handling of the wildcard-Int-to-bit-accurate-value adaptation path constructs an invalid SInt[1] somewhere along the way (perhaps while computing a signed representation of the literal to check the addition's sign/width rules) rather than a message about cnt's own type.

Where I hit this in practice

This surfaced while translating a Verilog line buffer's shift-register FIFO controller (kernel_shiftreg), which declares a small counter as reg [BUFF_SIZE-1:0] buff_cnt; where BUFF_SIZE = $clog2(BLOCK_WIDTH). For BLOCK_WIDTH == 2, $clog2(2) == 1, giving a 1-bit buff_cnt, incremented with buff_cnt <= buff_cnt + 'd1;. Translating this literally as UInt.until(BLOCK_WIDTH) <> VAR (also 1 bit wide) with buff_cnt :== buff_cnt + 1 hits exactly this error. Any UInt.until/UInt.to-derived width-1 counter incremented by a plain 1 will hit it — this is not a contrived case, it's the natural output of the documented .until/.to counter-width idiom (user-guide/type-system/index.md, "Avoid using clog2 directly for widths") whenever the counter happens to land on exactly 1 bit.

Workaround used

For a 1-bit counter, a modular +1 increment is mathematically identical to a bit toggle, so cnt :== ~cnt sidesteps the bug with unchanged semantics. This is a workaround, not a fix — it only works because the specific width is 1; the underlying UInt[1] + <literal fitting in 1 bit> path itself appears broken.

Version

0.22.0+72-ec89141c-SNAPSHOT (per ~/.dfhdl_version in the training environment on 2026-08-10).

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