Transitioning from Verilog to DFHDL
This guide helps Verilog/SystemVerilog users translate common patterns into DFHDL. For full type system details, see the Type System reference.
Design Structure
Module Definition
module _module_name_ #(
//param declarations
) (
//port declarations
);
//internal declarations
endmodule
class _design_name_(
//param declarations
) extends EDDesign:
//port & internal declarations
end _design_name_ //optional
module AndGate (
input a, b;
output o
);
assign o = a & b
endmodule
class AndGate extends EDDesign:
val a, b = Bit <> IN
val o = Bit <> OUT
o <> a && b
end AndGate
Parameter Declarations
parameter [7:0] p = 8’b1011;
val p: Bits[8] <> CONST = b"8'1011"
Inter-dependent parameters and ports example:
module Concat#(
parameter int len1 = 8,
parameter int len2 = 8,
parameter logic [7:0] midVec = 8'h55
)(
input wire logic [len1 - 1:0] i1,
input wire logic [len2 - 1:0] i2,
output logic [outlen - 1:0] o
);
localparam int midLen = 8;
localparam int outlen = len1 + midLen + len2;
assign o = {i1, midVec, i2};
endmodule
class Concat(
val len1: Int <> CONST = 8,
val len2: Int <> CONST = 8,
val midVec: Bits[Int] <> CONST = h"55"
) extends EDDesign:
val midLen = midVec.width
val outlen = len1 + midLen + len2
val i1 = Bits(len1) <> IN
val i2 = Bits(len2) <> IN
val o = Bits(outlen) <> OUT
o <> (i1, midVec, i2)
end Concat
Unconnected Output Ports
child_mod child_inst(
.clk (clk),
.din (din),
.debug (), // unconnected
.dout (dout)
);
val child_inst = child_mod()
child_inst.clk <> clk
child_inst.din <> din
child_inst.debug <> OPEN // unconnected
child_inst.dout <> dout
Use OPEN to explicitly mark an output port as unconnected. This is equivalent to Verilog’s empty port connection (.port()). See Open (Unconnected) Ports for more details.
logic/reg/wire
logic [7:0] v = 8’b1011;
wire [7:0] v = 8’b1011;
reg [7:0] v = 8’b1011;
val v = Bits(8) <> VAR init b"8’1011"
Choosing UInt vs Bits vs SInt for Verilog logic
Verilog logic and wire are untyped bit vectors. When translating to DFHDL, choose the type based on how the signal is used:
| Verilog usage pattern | DFHDL type |
|---|---|
| Signed values, subtraction below zero | SInt |
| Exact bit-width required (addresses, masks, bitwise ops) | Bits |
| Unsigned arithmetic (counters, addition, comparison with integers) | UInt |
| FSM state encoding | enum extends Encoded |
When a signal is used in both arithmetic and bitwise contexts, prefer UInt and convert with .bits for bitwise operations. When exact bit-width must be preserved (e.g., an address bus that is also incremented), prefer Bits to avoid accidental width extension.
Types and Literals
Numeric Literals
DFHDL uses string interpolators for sized literals. Each type has its own interpolator -- do not mix Verilog base prefixes (’b, ’d, ’h) inside them.
8’b1011_0000 // binary
8’hB0 // hex
8’d176 // decimal
5’d27 // 5-bit decimal
b"8’1011_0000" // binary (b"...")
h"8’B0" // hex (h"...")
d"8’176" // unsigned decimal (d"...")
d"5’27" // 5-bit unsigned decimal
b"..." accepts only binary digits (0, 1, ?). Writing b"5’d27" is an error -- use d"5’27" for decimal values.
$clog2 and Width Computation
Instead of computing widths manually with $clog2, use .until or .to constructors which set the width automatically:
| Verilog | DFHDL | Width |
|---|---|---|
$clog2(N) |
UInt.until(N) / Bits.until(N) |
clog2(N) bits, valid for N >= 2 |
$clog2(N+1) |
UInt.to(N) / Bits.to(N) |
clog2(N+1) bits, valid for N >= 1 |
UInt.until(1) is invalid (would produce 0-bit width). For counters that count 0 to N inclusive (common with $clog2(N+1)), use UInt.to(N).
parameter RATE = 5208;
localparam WIDTH = $clog2(RATE);
reg [WIDTH-1:0] counter;
// Counter 0..SCALE inclusive
reg [$clog2(SCALE+1)-1:0] cnt = 0;
val RATE: Int <> CONST = 5208
val counter = UInt.until(RATE) <> VAR
// Counter 0..SCALE inclusive
val cnt = UInt.to(SCALE) <> VAR init 0
Choosing .until(N) vs .to(N) for counters
If the Verilog counter resets when it reaches N (i.e., counter == N), the variable must be able to hold the value N, so use UInt.to(N). If the counter only ever holds values 0..N-1, use UInt.until(N). For many values of N, both produce the same bit width (clog2(N) == clog2(N+1)), so the bug is silent until formal verification catches an out-of-range comparison.
Reusing computed types and extracting widths:
You can name a DFHDL type and reuse it across multiple declarations. You can also extract the .width from an existing DFHDL value (not from a type constructor) to derive new widths:
module Sum#(parameter int MAX = 16)(
input wire logic [A_WIDTH - 1:0] a,
input wire logic [A_WIDTH - 1:0] b,
output logic [IPW - 1:0] sum
);
localparam int A_WIDTH = $clog2(MAX);
localparam int IPW = A_WIDTH + 1;
assign sum = a + b;
endmodule
class Sum(val MAX: Int <> CONST = 16)
extends EDDesign:
// Name and reuse a type
val MyUInt = UInt.until(MAX)
val a = MyUInt <> IN
val b = MyUInt <> IN
// Extract width from an existing value
val IPW = a.width + 1
val sum = UInt(IPW) <> OUT
sum <> a + b
.width works on any DFHDL value (a port, variable, or constant — anything declared with <>; see logic/reg/wire above). Use it to derive widths from existing declarations:
1 2 3 | |
Using UInt.to/UInt.until with .width on values is preferred over calling clog2 directly (see the clog2 anti-pattern warning).
See UInt/SInt constructors and Bits constructors for details.
Processes and Sequential Logic
Process Blocks (always)
Verilog always blocks map to DFHDL ED domain process(...) blocks.
// Combinational
always @(*) begin
y = a + b;
end
// Sequential (clocked)
always @(posedge clk) begin
counter <= counter + 1;
end
// Sequential with async reset
always @(posedge clk or posedge rst)
if (rst)
q <= 0;
else
q <= d;
// Combinational
process(all):
y := a + b
// Sequential (clocked)
process(clk.rising):
counter :== counter + 1
// Sequential with async reset
process(clk.rising, rst.rising):
if (rst)
q :== 0
else
q :== d
always @(*)becomesprocess(all):always @(posedge clk)becomesprocess(clk.rising):always @(posedge clk, negedge rst)becomesprocess(clk.rising, rst.falling):- Verilog blocking
=becomes DFHDL:=(use in combinational processes) - Verilog non-blocking
<=becomes DFHDL:==(use in clocked processes)
Variable Initialization (init)
In ED domain, init on a VAR generates a Verilog reg with an initial value. This maps to both initial begin blocks and reg ... = value declarations.
reg [7:0] counter = 8’d0;
// or equivalently:
// initial counter = 8’d0;
val counter = UInt(8) <> VAR init 0
An output reg with an initial value maps directly to OUT init:
module Foo(
input clk,
input din,
output reg dout
);
initial dout = 1'b1;
always @(posedge clk)
dout <= din;
endmodule
class Foo extends EDDesign:
val clk = Bit <> IN
val din = Bit <> IN
val dout = Bit <> OUT init 1
process(clk.rising):
dout :== din
FSM State Encoding
Verilog FSMs typically use parameter constants and case/if chains. In DFHDL, the idiomatic translation uses an enum extends Encoded and match:
parameter READY = 2'b00,
AIM = 2'b01,
FIRE = 2'b10;
reg [1:0] state = READY;
always @(posedge clk)
case (state)
READY: if (go) state <= AIM;
AIM: state <= FIRE;
FIRE: state <= READY;
default: state <= READY;
endcase
enum State extends Encoded:
case Ready, Aim, Fire
import State.*
val state = State <> VAR init Ready
process(clk.rising):
state match
case Ready => if (go) state :== Aim
case Aim => state :== Fire
case Fire => state :== Ready
case _ => state :== Ready
If the encoded Verilog state values follow a standard pattern (incremental, gray, one-hot), use the corresponding Encoded variant. For non-standard encodings, use Encoded.Manual with a constructor parameter:
1 2 3 4 5 6 | |
The constructor parameter (val value: UInt[N] <> CONST) is required for Encoded.Manual and the bit width N must match the argument to Encoded.Manual(N). Omitting it causes a compile error. See Enumeration for all encoding options.
When the Verilog FSM uses an explicit reset instead of initial, omit the init and handle reset inside the clocked process:
always @(posedge clk)
if (rst)
state <= READY;
else
case (state)
READY: if (go) state <= AIM;
AIM: state <= FIRE;
FIRE: state <= READY;
default: state <= READY;
endcase
val state = State <> VAR // no init
process(clk.rising):
if (rst)
state :== Ready
else
state match
case Ready => if (go) state :== Aim
case Aim => state :== Fire
case Fire => state :== Ready
case _ => state :== Ready
Avoid modeling FSM states as Bits or UInt constants -- it is an anti-pattern. When compiling to SystemVerilog (SV), the SV enums are being utilized as well.
Integer case Statements (non-enum)
When the Verilog case selector is a plain integer counter (not an FSM), use match with integer literal cases directly:
case (sel)
'd0: out <= 60;
'd1: out <= 110;
'd2 | 'd3: out <= 200;
default: out <= 0;
endcase
sel match
case 0 => out :== 60
case 1 => out :== 110
case 2 | 3 => out :== 200
case _ => out :== 0
end match
Integer literal pattern matching works with UInt and SInt selectors. Guard conditions (case _ if sel == N) also work but are less idiomatic. See Match Expressions for full details.
Operations
Shift Operators
Verilog has separate >> (logical) and >>> (arithmetic) right shift operators. DFHDL uses only >>, but the behavior depends on the operand type:
module RightShifter(
input wire logic [7:0] data,
output logic [7:0] logical,
output logic [7:0] arith
);
assign logical = data >> 1;
assign arith = data >>> 1;
endmodule
class RightShifter extends EDDesign:
val data = Bits(8) <> IN
val logical = Bits(8) <> OUT
val arith = Bits(8) <> OUT
logical <> data >> 1
arith <> (data.sint >> 1).bits
end RightShifter
There is no >>> operator in DFHDL. The type of the LHS determines the shift semantics: >> on UInt/Bits zero-fills, >> on SInt sign-extends. See Shift Operations for details.
Bit/Boolean Operators: |/& and ||/&&
In DFHDL, ||/&& and |/& are interchangeable on Bit and Boolean types. The generated Verilog operator depends on the LHS type: Bit produces bitwise |/&, Boolean produces logical ||/&&.
input a, b, c;
output o1, o2, o3;
assign o1 = a | b | c;
assign o2 = a | b | c;
assign o3 = a || b || c;
val a, b, c = Bit <> IN
val o1, o2, o3 = Bit <> OUT
o1 <> a | b | c
o2 <> a || b || c
o3 <> a.bool || b || c
See Logical Operations for the full reference and Verilog/VHDL mapping tables.
Reduction Operators (&v, |v, ^v)
Verilog's unary reduction operators have direct DFHDL equivalents using postfix .&, .|, .^ on Bits values:
logic [7:0] v;
logic all_set = &v; // AND reduce
logic any_set = |v; // OR reduce
logic parity = ^v; // XOR reduce
logic not_all = ~&v; // NAND reduce
logic none_set = ~|v; // NOR reduce
val v = Bits(8) <> VAR
val all_set = v.& // Bit: AND reduce
val any_set = v.| // Bit: OR reduce
val parity = v.^ // Bit: XOR reduce
val not_all = !v.& // Bit: NAND reduce
val none_set = !v.| // Bit: NOR reduce
See Bit Reduction Operations for full details.
Bit Replication ({N{expr}})
Verilog's replication operator {N{expr}} maps to .repeat(N) in DFHDL:
parameter W = 8;
logic [7:0] all_ones = {8{1'b1}};
logic [7:0] all_zeros = {8{1'b0}};
logic [15:0] doubled = {2{all_ones}};
// Parametric replication
logic [W-1:0] fill_one = {W{1'b1}};
val W: Int <> CONST = 8
val all_ones = b"1".repeat(8)
val all_zeros = b"0".repeat(8)
val doubled = all_ones.repeat(2)
// Parametric replication
val fill_one = b"1".repeat(W)
.repeat works on any Bits value, including single-bit literals.
Bit Concatenation ({a, b, ...})
Verilog's concatenation operator {a, b} maps to the ++ operator or tuple .toBits in DFHDL:
logic [7:0] a, b;
logic [15:0] concat = {a, b};
// Parametric: {1'b1, {(N-1){1'b0}}}
parameter N = 8;
logic [N-1:0] half = {1'b1, {(N-1){1'b0}}};
val a, b = Bits(8) <> VAR
val concat = a ++ b // Bits[16]
// Parametric: MSB=1, rest zeros
val N: Int <> CONST = 8
val half = b"1" ++ b"0".repeat(N - 1)
Alternative: use tuple .toBits syntax for multi-value concatenation:
1 | |
Part-Select Notation (-: and +:)
Verilog's descending and ascending part-select notation maps to DFHDL's (hi, lo) range slice, or use the convenience methods .msbits(n) and .lsbits(n):
| Verilog | DFHDL | Notes |
|---|---|---|
sig[base -: W] |
sig(base, base - W + 1) |
Descending slice from base, W bits |
sig[base +: W] |
sig(base + W - 1, base) |
Ascending slice from base, W bits |
sig[N-1 -: W] |
sig.msbits(W) or sig(N-1, N-W) |
Top W bits |
sig[0 +: W] |
sig.lsbits(W) or sig(W-1, 0) |
Bottom W bits |
sig[idx] |
sig(idx) |
Single bit access |
logic [15:0] data;
logic [3:0] top4 = data[15 -: 4];
logic [3:0] bot4 = data[0 +: 4];
logic bit5 = data[5];
val data = Bits(16) <> VAR
val top4 = data.msbits(4) // top 4 bits
val bot4 = data.lsbits(4) // bottom 4 bits
val bit5 = data(5) // single bit
Bit-slicing and single-bit access work on Bits, UInt, and SInt values with the same syntax. Slicing preserves the source type:
| Source type | Slice result |
|---|---|
Bits[N] |
Bits |
UInt[N] |
UInt |
SInt[N] |
SInt |
You do not need to convert SInt to Bits before slicing — the result is already SInt:
1 2 3 | |
Arithmetic with Signed Values and Constants
Arithmetic operand compatibility:
DFHDL enforces sign and width constraints at compile time. Commutative operations (+, *, max, min) produce the widest, most signed result -- operand order does not matter. Non-commutative operations (-, /, %) require the LHS to be at least as wide and signed as the RHS. When mixing signed and unsigned, the unsigned operand is implicitly sign-extended by 1 bit.
Both Scala Int values and DFHDL Int parameters act as wildcards -- the wildcard Int value adapts to the bit-accurate value's sign and width. If the wildcard Int value does not fit, an error is generated.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Comparison operand compatibility:
Comparisons (==, !=, <, >, <=, >=) require both operands to have the same signedness and width. Int values act as wildcards, adapting to the DFHDL value's type.
1 2 3 4 5 6 7 | |
To compare values of different widths, use .resize(W) to match widths first. To compare values of different signedness, convert explicitly (e.g., .bits.sint or .signed).
UInt-to-SInt conversion methods:
.signed-- convertsUInt[W]toSInt[W+1]by adding a sign bit. The value is preserved (always non-negative)..bits.sint-- convertsUInt[W]toSInt[W]by reinterpreting the bit pattern. The width stays the same, but the value may become negative if the MSB is set. Use.resize(W)to widen a narrower operand before arithmetic.
Mixed-width signed arithmetic examples:
module MixedArith #(
parameter W = 16
)(
input wire clk,
input wire [2:0] idx,
input wire signed [W-1:0] operand,
output wire signed [W-1:0] result,
output reg signed [W+1:0] acc
);
// Combinational: UInt expr assigned to SInt
assign result = 100 - 3 * idx;
// Clocked: wide accumulator, narrow operand
always @(posedge clk)
if (acc <= operand)
acc <= acc + 2 * operand + 1;
endmodule
class MixedArith(
val W: Int <> CONST = 16
) extends EDDesign:
val clk = Bit <> IN
val idx = UInt(3) <> IN
val operand = SInt(W) <> IN
val result = SInt(W) <> OUT
val acc = SInt(W + 2) <> OUT
// Forcing SInt arithmetic
result <> sd"${W}'100" - idx * 3
process(clk.rising):
// Resize narrow operand to match wider acc
if (acc <= operand.resize(W + 2))
// switch operand and literal multiplication
// order to force SInt arithmetic
acc :== acc + operand * 2 + 1
end MixedArith
See Arithmetic Operations and Carry Arithmetic for full details.
Integer Literal Width and Silent Overflow
In Verilog, unsized integer literals are 32-bit. When combined with narrower signals, the wider literal causes the entire expression to evaluate at 32-bit width via context-dependent propagation. This prevents intermediate overflow in expressions like (a + b + c + d) / 4.
In DFHDL, Scala Int literals are implicitly converted to minimum-width bit-accurate types (e.g., 4 becomes UInt[3]). Each arithmetic operation independently uses the LHS width, so intermediate results can overflow before reaching a division or shift.
DFHDL detects this pattern at elaboration and issues a warning. See Implicit Scala Int and Verilog-semantics mismatch for the full list of warning triggers.
input logic [7:0] a, b, c, d;
output logic [7:0] result;
// 4 is 32-bit, so (a+b+c+d) evaluates
// at 32-bit width — no overflow
assign result = (a + b + c + d) / 4;
val a, b, c, d = UInt(8) <> IN
val result = UInt(8) <> OUT
// Use carry ops to match Verilog's
// overflow-free semantics
result <> ((a +^ b +^ c +^ d) / 4).resize
Parametric Constants
Parametric-Width Bits Constants
Verilog parameters can be bit-vector constants whose width depends on another parameter. In DFHDL, use Bits[Int] <> CONST (unbounded width) for the parameter. The width is inferred from the default literal value, and other local values can derive their widths from it:
module MyDesign #(
parameter WIDTH = 8,
parameter [WIDTH-1:0] MASK = 8'hB8
)(
output logic [WIDTH-1:0] data
);
// ...
endmodule
class MyDesign(
val WIDTH: Int <> CONST = 8,
val MASK: Bits[Int] <> CONST = h"B8"
) extends EDDesign:
// MASK.width gives the actual width
val data = Bits(MASK.width) <> OUT
// ...
See the Parameter Declarations section above for a complete inter-dependent parameters example.
Generate Loops and Conditionals
generate for Loops
Verilog generate for loops map to Scala for loops at design scope. Each iteration is unrolled at elaboration time -- the generated HDL has no loop construct, only the unrolled instances.
genvar i;
generate
for (i = 0; i < N; i = i + 1)
begin : BLK
filter #(
.WIDTH(BASE_W - 2*i)
) u_filter (...);
end
endgenerate
for i <- 0 until N.toScalaInt do
val u_filter = filter(
WIDTH = BASE_W - 2 * i
)
// connect ports...
Note that N must be convertible to a Scala Int at elaboration time (use .toScalaInt on Int <> CONST parameters).
Important difference from Verilog: DFHDL type-checks both branches of elaboration-time if expressions, regardless of the parameter value. Both branches must be valid for all parameter values. See Loops for details and workarounds.
Common Pitfalls
Scala Reserved Keywords as DFHDL Port or Variable Names
Some Verilog port names (val, type, class, match, case, object, etc.) are reserved in Scala. Use backtick escaping:
module foo(
output reg signed [15:0] val
);
assign val = 16'sd42;
class foo extends EDDesign:
val `val` = SInt(16) <> OUT
`val` <> 42
Alternatively, use a non-keyword name with the Scala @targetName annotation to set the actual HDL name:
module foo(
output logic signed [15:0] class
);
assign class = 16'sd42;
endmodule
import scala.annotation.targetName
class foo extends EDDesign:
@targetName("class")
val class_ = SInt(16) <> OUT
class_ <> 42
Beyond Scala keywords, Verilog module names may also conflict with DFHDL built-in functions brought in by import dfhdl.* (e.g., abs, max, min) or with other class names in the same design hierarchy. See Naming for the full list of reserved names and resolution patterns (@targetName, type aliases, backtick escaping).
Bits Initialization or Assignment
Bits values cannot be initialized or assigned with plain integers. Use all(0) or a sized literal:
reg [7:0] flags = 8'd0;
reg [7:0] mask = 8'hFF;
val flags = Bits(8) <> VAR init all(0)
val mask = Bits(8) <> VAR init h"8'FF"
// NOT: Bits(8) <> VAR init 0 // error
Note: UInt and SInt can be initialized or assigned with plain integers.
Inline Conditional Expressions
Verilog's ternary operator cond ? a : b has three DFHDL equivalents:
assign out = cond ? a : b;
always @(*)
out = cond ? a : b;
// 1. Using .sel (closest to ternary)
out <> cond.sel(a, b)
// 2. Inline if/else (wrap in parentheses)
out <> (if (cond) a else b)
// 3. Statement form
if (cond) out := a
else out := b
The .sel method compiles directly to Verilog's ternary operator. For complex nested conditions, prefer if/else or match over chaining .sel calls. See Selection (.sel) for details.
When using inline if/else as the RHS of := or :==, parentheses are required. Without them, Scala 3 parses the if as a statement, not an expression:
1 2 3 4 5 6 7 8 9 10 | |
This applies to all assignment operators (:=, :==, <>). Use .sel or the parenthesized form for inline conditionals; use the statement form for multi-assignment branches.