Announcing Veryl 0.22.0
2026-10-02
The Veryl team has published a new release of Veryl, 0.22.0. Veryl is a new hardware description language as an alternate to SystemVerilog.
If you have a previous version of Veryl installed via verylup, you can get the latest version with:
$ verylup update
If you don't have it already, you can get verylup from release page.
Breaking Changes
Zero width and array size are rejected #3355
A bit width or an array size which evaluates to 0 is now reported as the new
zero_size error.
logic<W> with W = 0 is emitted as logic [-1:0], which some SystemVerilog
tools reject and others treat as 2 bits.
A common case is $clog2(N) with N = 1.
Clip such a width to at least 1 bit, by an if expression or by
clog2_clipped in the standard library.
module ModuleA #(
param N: u32 = 1,
) (
// error: zero_size when N is 1
i_a: input logic<$clog2(N)>,
// clipped to at least 1 bit
i_b: input logic<if N >= 2 ? $clog2(N) : 1> ,
i_c: input logic<$std::utils::clog2_clipped(N, 1)>,
) {}
New Language Features
Cast by a parenthesized expression #3356
A constant expression enclosed in () can now be used as the bit width of as.
It is emitted as a SystemVerilog size cast.
x as (W + 1)
(x - 1) as (W * 2)
(W + 1)'(x)
(W * 2)'((x - 1))
Note that the parentheses are required.
The width operand of as is a single term, so x as W + 1 is interpreted as
(x as W) + 1.
New Tool Features
Concurrent initial blocks in native tests #3363
A test module can now have multiple initial blocks, and each block runs as an
independent process like initial blocks in SystemVerilog.
While one block waits at clk.next(), the other blocks continue.
All blocks waiting for the same clock are resumed at the same edge, and blocks
resumed at the same time run in declaration order.
The initial blocks inside the instantiated modules also run as their own
processes.
This makes it easy to split a testbench into a stimulus process and a checker process.
#[test(test_multi_initial)]
module test_multi_initial {
inst clk: $tb::clock_gen;
inst rst: $tb::reset_gen ( clk );
var done: logic;
initial {
done = 0;
rst.assert();
clk.next(5);
done = 1;
}
initial {
clk.next(8);
$assert(done == 1);
$finish();
}
}
$readmemh to a hierarchical reference #3360
A hierarchical reference can now be the destination of $readmemh, so a memory
inside the DUT can be loaded from a test module.
This is useful to load a program image into the instruction memory of a SoC.
module Rom (
addr: input logic<2> ,
data: output logic<32>,
) {
#[allow(unassign_variable)]
var mem: logic<32> [4];
assign data = mem[addr];
}
#[test(test_hier_readmemh)]
module test_hier_readmemh {
var addr: logic<2> ;
var data: logic<32>;
inst dut: Rom ( addr, data );
initial {
$readmemh("rom.hex", dut.mem);
addr = 3;
$assert(dut.mem[0] == 32'h1);
$assert(data == 32'h4);
$finish();
}
}
The destination must be a whole variable, and a part of the variable such as an
element select can't be used.
Because nothing in the DUT assigns the memory, its declaration needs
#[allow(unassign_variable)].
Value::as_slice for verification components #3368
In user defined verification components,
Value::as_u64 can read a value up to 64 bits only.
The new Value::as_slice returns all bits as 64-bit words, least significant
word first, so a value wider than 64 bits can be read.
The word layout is the same as the X/Z mask returned by Value::mask_xz.
let words: &[u64] = value.as_slice();
let lower = words[0];
let upper = words[1];
Other Changes
Check out everything that changed in Release v0.22.0.