Announcing Veryl 0.21.0
2026-09-02
The Veryl team has published a new release of Veryl, 0.21.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
To migrate some syntax changes, veryl migrate can be used:
// Check how changes will be applied
$ veryl migrate --check
// Migrate
$ veryl migrate
Non-portable constructs need an explicit opt-in #3268
Some constructs are valid on FPGA but not on ASIC.
Two new allow items opt in to them, written on the declaration of the variable
they concern.
initial_assign allows the variable to be assigned in an initial block, which
relies on the target device initializing it at configuration time.
Assignment through an output argument of a function call, such as $readmemh
and $readmemb, is covered as well.
On the other hand, ASIC synthesizers ignore an initial block, so such an
assignment is no longer accepted on its own.
It passed without any check until Veryl 0.20.3, and is now reported as
invalid_initial_assign without the attribute.
To migrate, add the attribute to the declaration.
veryl migrate rewrites the $readmemh / $readmemb case automatically, and
the other assignments need the attribute by hand.
// before
var count: logic<16> ;
var rom : logic<32> [1024];
initial {
count = '0;
$readmemh("rom.hex", rom);
}
// after
#[allow(initial_assign)]
var count: logic<16>;
#[allow(initial_assign)]
var rom: logic<32> [1024];
initial {
count = '0;
$readmemh("rom.hex", rom);
}
multiple_assign allows the variable to be assigned from more than one process,
as true dual port SRAM inference on FPGA requires.
It relaxes the existing multiple_assignment error, so it needs no migration.
module ModuleB (
i_clk0 : input 'a clock ,
i_clk1 : input 'b clock ,
i_en0 : input 'a logic ,
i_en1 : input 'b logic ,
i_addr0: input 'a logic<5> ,
i_addr1: input 'b logic<5> ,
i_data0: input 'a logic<32>,
i_data1: input 'b logic<32>,
o_data : output 'a logic<32>,
) {
#[allow(multiple_assign)]
var ram: 'a logic<32> [32];
always_ff (i_clk0) {
if i_en0 {
ram[i_addr0] = i_data0;
}
}
unsafe (cdc) {
always_ff (i_clk1) {
if i_en1 {
ram[i_addr1] = i_data1;
}
}
}
assign o_data = ram[i_addr0];
}
SystemVerilog forbids a variable written by an always_ff from being written by
any other process, so an always_ff which writes a variable with either
attribute is emitted as a plain always.
To keep these constructs out of a project whose target does not support them,
whether they are accepted from dependencies is decided by the consumer through
the new [lint.portability] section of Veryl.toml.
[lint.portability]
allow_in_dependencies = ["initial_assign"]
In your own code the attribute is always honored: you wrote it, it is your call.
In the code of a dependency it is rejected as non_portable_dependency unless it
is listed here, and the default is to accept nothing.
So an ASIC project can't absorb an FPGA-only library without noticing, while an
FPGA user pulling an FPGA library adds one line.
New Language Features
Component namespace import #3198 #3261 #3262
A component itself, which is a package, module or interface, can now be imported under its own name. The imported name is used as a qualifier at the use site, so a component of a dependency can be referenced without repeating the project name.
import veryl_sample::sample_pkg;
import veryl_sample::sample_if;
import veryl_sample::sample_module;
module ModuleC {
const A: u32 = sample_pkg::PARAM_A;
inst u_if: sample_if;
inst u: sample_module (
o_a: u_if.a,
);
}
A generic component is imported as its definition, and the generic arguments are given at the use site.
import veryl_sample::generic_pkg;
module ModuleD {
const B: u32 = generic_pkg::<32>::PARAM_B;
}
A proto package can be imported in the same way, and the imported name can be
used as a generic bound.
The other prototypes like proto module and proto interface can't be imported.
import veryl_sample::sample_proto_pkg;
module ModuleE::<PKG: sample_proto_pkg> {
let _a: logic<PKG::WIDTH> = 0;
}
A function declared at the project scope, which means outside any module, interface and package, can be imported through the project name too.
import veryl_sample::sample_func;
module ModuleF {
let _a: logic<8> = sample_func::<8>(8'd1);
}
Generic argument inference from module ports #3197
The generic arguments of a function call can be omitted when they are inferred from the declared type of the call arguments. The inference accepted a local variable or a parameter, and now a module port drives it in the same way.
module ModuleG (
i_d: input logic<8>,
) {
function FuncId::<T: u32> (
x: input logic<T>,
) -> logic<T> {
return x;
}
// T is inferred to be 8 from the port's declared width.
let _r: logic<8> = FuncId(i_d);
}
Improvements
Combinational loop analysis #3161
The combinational loop detector has been rebuilt on statement-ordered SSA with sparse bit and array partitions.
Successive assignments to a module-scope variable inside a single always_comb
were reported as a loop, because the reads were not matched with the definition
which actually reaches them.
The block below is feed-forward under the blocking assignment semantics of
always_comb, and is no longer reported.
module ModuleH (
a: output logic,
) {
var b: logic;
always_comb {
a = 0;
b = a;
a = b;
}
}
Arrays of more than 65536 elements were skipped by the analysis entirely, since
the per-element expansion was proportional to the declared shape.
The partitions are now built sparsely, so element identity is preserved for
large and multidimensional arrays without enumerating that shape, and the size
cutoff is gone.
When a flattened width, array size, or positional offset tracked by the analysis
exceeds the supported range, it is reported as the new
combinational_loop_position_overflow error instead of being silently dropped.
This is the initial implementation of the new analysis. Remaining false positives and false negatives, richer function and interface summaries, and latch diagnostics will be improved incrementally.
Other Changes
Check out everything that changed in Release v0.21.0.