Skip to content

[Bug] Bitwise ^/&/| skips the width check when one operand's width is a symbolic parameter and the other is a literal (.sel rejects the same pair) #474

Description

@soronpo

Summary

When one operand of a bitwise operation has a symbolic width (Bits(LEN), from an Int <> CONST design parameter) and the other has a concrete literal width (a Bits[Int] <> CONST design parameter, whose width is fixed by its applied argument), DFHDL performs no width check at all — not at compile time, not at elaboration. A genuinely mismatched design elaborates silently and emits Verilog with a real width mismatch.

The same undecidable width pair is correctly rejected by .sel, and the same operation ^ is correctly rejected when the constant is a design-body val instead of a parameter. So the check exists; it is only missing on this one path, and it fails in the unsound direction.

This contradicts the documented rule for Bitwise Operations — "The RHS must match the LHS type and width" — and the documented policy for undecidable width relations, which is to reject conservatively.

Found while translating projf-explore's lib/maths/lfsr.sv (run starfield-1), whose parameter LEN / parameter TAPS pair is exactly this shape.

Minimal reproduction (verified)

import dfhdl.*

class Inner(
    val LEN: Int <> CONST = 8,
    val TAPS: Bits[Int] <> CONST = b"10111000"
) extends EDDesign:
  val i = Bits(LEN) <> IN
  val o = Bits(LEN) <> OUT
  process(all):
    o := i ^ TAPS          // Bits(LEN) ^ Bits[8] -- accepted, no diagnostic

class WidthHole extends EDDesign:
  val i = Bits(16) <> IN
  val o = Bits(16) <> OUT
  val u = new Inner(LEN = 16, TAPS = b"10111000")   // 16 vs 8: real mismatch
  u.i <> i
  u.o <> o

Run:

scala run src/ --scala 3.8.4 --dep ... --compiler-plugin ... -O -deprecation -M WidthHole -- --nocache commit

Actual behavior

Elaboration and compilation succeed with no error and no warning:

Welcome to DFiant HDL (DFHDL) v0.22.0+72-ec89141c-SNAPSHOT !!!
Elaborating design...
Compiling design...
Committing backend files to disk...

Emitted Inner.sv (verbatim) — i is 16 bits, TAPS is 8 bits:

module Inner#(
    parameter int LEN = 8,
    parameter logic [7:0] TAPS = 8'hb8
)(
  input  wire logic [LEN - 1:0] i,
  output      logic [LEN - 1:0] o
);
  `include "dfhdl_defs.svh"
  always_comb
  begin
    o = i ^ TAPS;
  end
endmodule

Emitted WidthHole.sv confirms the mismatched instantiation:

  Inner #(
    .LEN (16),
    .TAPS (8'hb8)
  ) u(
    .i /*<--*/ (u_i),
    .o /*-->*/ (u_o)
  );

The result is only correct because Verilog silently zero-extends TAPS. DFHDL's whole bit-accuracy contract is that this is the case it catches.

Expected behavior

Either

  • reject the operation, the way .sel already does for the identical width pair, or
  • accept it deliberately and require the widths to agree at elaboration once LEN is applied (which would flag LEN = 16 against an 8-bit TAPS).

Silently emitting an implicit extension is the one outcome that should not happen.

Contrast cases (both verified, same version)

1. .sel rejects the identical symbolic-vs-literal pair.

import dfhdl.*

class Probe2(
    val LEN: Int <> CONST = 8,
    val TAPS: Bits[Int] <> CONST = b"10111000"
) extends EDDesign:
  val c = Bit       <> IN
  val o = Bits(LEN) <> OUT
  process(all):
    o := c.sel(TAPS, b"0".repeat(LEN))
DFiant HDL elaboration error!
Position:  src/Probe2.scala:10:10 - 10:39
Hierarchy: Probe2
Operation: `apply`
Message:   The argument width (LEN) is different than the receiver width (8).
Consider applying `.resize` to resolve this issue.

2. The same ^, with the constant as a design-body val rather than a parameter, is rejected correctly.

import dfhdl.*

class Probe4 extends EDDesign:
  val i = Bits(16) <> IN
  val o = Bits(16) <> OUT
  val K: Bits[Int] <> CONST = b"10111000"   // 8 bits
  process(all):
    o := i ^ K                              // 16-bit ^ 8-bit
DFiant HDL elaboration error!
Position:  src/Probe4.scala:8:10 - 8:15
Hierarchy: Probe4
Operation: `apply`
Message:   Cannot apply this operation between a value of 16 bits width (LHS) and a value of 8 bits width (RHS).
An explicit conversion must be applied.

So the elaboration-time width check for bitwise ops fires for a concrete-vs-concrete pair, and .sel's fires for a symbolic-vs-concrete pair — only bitwise-op-with-symbolic-operand falls through.

Secondary observation on the .sel message

The message quoted above is the one that does fire, and it is hard to act on:

  • Operation: apply`` names compiler-internal machinery. The operation written in the source is .sel.
  • "receiver width (8)" is TAPS, which is the first argument of .sel; the actual receiver is the Bit condition c. "argument width (LEN)" is the second argument. Neither term maps onto what the user wrote, so it is not obvious which of the two operands to resize.

Suggested wording: Operation: .sel with The two selected values have different widths: 8 and LEN. Apply .resize to one of them so both match.

DFHDL version

v0.22.0+72-ec89141c-SNAPSHOT

Related

The documentation side of the LEN/TAPS parameter pair is already tracked in DFiantHDL/dfhdl_by_agents#88 and #120. This ticket is only about the missing compiler check.

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