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.
Summary
When one operand of a bitwise operation has a symbolic width (
Bits(LEN), from anInt <> CONSTdesign parameter) and the other has a concrete literal width (aBits[Int] <> CONSTdesign 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-bodyvalinstead 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'slib/maths/lfsr.sv(runstarfield-1), whoseparameter LEN/parameter TAPSpair is exactly this shape.Minimal reproduction (verified)
Run:
Actual behavior
Elaboration and compilation succeed with no error and no warning:
Emitted
Inner.sv(verbatim) —iis 16 bits,TAPSis 8 bits:Emitted
WidthHole.svconfirms the mismatched instantiation: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
.selalready does for the identical width pair, orLENis applied (which would flagLEN = 16against an 8-bitTAPS).Silently emitting an implicit extension is the one outcome that should not happen.
Contrast cases (both verified, same version)
1.
.selrejects the identical symbolic-vs-literal pair.2. The same
^, with the constant as a design-bodyvalrather than a parameter, is rejected correctly.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
.selmessageThe 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.TAPS, which is the first argument of.sel; the actual receiver is theBitconditionc. "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: .selwithThe 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-SNAPSHOTRelated
The documentation side of the
LEN/TAPSparameter pair is already tracked inDFiantHDL/dfhdl_by_agents#88and#120. This ticket is only about the missing compiler check.