Spade 0.17.0
Posted 2026-03-05 by The Spade Developers
Today we release Spade v0.17.0, a release with 22 individual changelog entries making lots of big and small improvements to the language!
Operator Overloading
You can now overload many of the operators in Spade allowing you to build better abstractions. For example, you can now define a Vec2 struct like this
struct Vec2<T> {
x: T,
y: T,
}
And then implement the WrappingAdd trait to allow using +. on the new type. By constraining the T generic to any type that also implements WrappingAdd, you can use this Vector struct on any “addable” inner type, including integers, fixed point numbers, complex number, and anything else
impl<T: WrappingAdd> WrappingAdd for Vec2<T> {
fn wrapping_add(self, other: Self) -> Self {
Vec2(
self.x +. other.x
self.y +. other.y
)
}
}
This initial version of operator overloading supports the following operators:
Eq:==and!=Ord:<,<=,>=and>Not:!And:&&Or:||Xor:^^BitNot:~BitAnd:&BitOr:|BitXor:^WrappingAdd:+.WrappingSub:-.WrappingMul:*.WrappingUsub:-.
This list will be expanded soon to support more operators, but doing so requires a few improvements to the type system.
Wrapping Operators
What is that +. operator in the previous example? It is a new family of wrapping operators that wrap around instead of growing their size. If you’ve used Spade you’ll be used to having to write trunc a lot, which hurt readability. With wrapping operators, you can now write
reg(clk) value = value +. 1;
instead of
reg(clk) value = trunc(value + 1);
impl Trait and return position type expressions
In the past, Spade has required adding some additional type parameters to generic functions just to be able to specify some additional types. What used to be
fn growing_op<#uint N, #uint Out, Op>(x: int<8>, op: Op) -> int<Out>
where Out: {N + 1},
Op: Fn(int<N>) -> int<Out>
{
// ...
}
can now be written as
fn growing_op<#uint N>(x: int<8>, op: impl Fn(int<N>) -> int<{N+1}>) -> int<{N+1}> {
// ...
}
if let
When matching on an enum where you only want to unwrap one variant, a match block becomes pretty verbose:
let unwrapped = match value {
Some(val) => {
compute(val)
},
_ => 0
}
if let allows writing this in a more readable way
let unwrapped = if let Some(val) = value {
compute(val)
} else {
0
}
name @ pattern
Sometimes you may want to both look at the content of a pattern, and refer to the whole value at once. name @ syntax allows you to bind a sub-pattern to a name. For example, you can filter tuples to select only those whose elements have the same value like this:
match maybe_tuple {
Some(tuple @ (left, right)) => if left == right {Some(tuple)} else {None},
None => None
}
Default Type Parameters
You can now specify default parameters for type expressions. This function
fn two_type_params<#uint N, #uint M: 0>()
can now be instantiated either as
two_type_params::<10>()
or
two_type_params::<10, 1>()
verilog_attrs on calls
When interacting with Verilog black boxes, it is sometimes necessary to add attributes to the instantiation. For example, when working with the ecp5 PLL block. You can now use #[verilog_attrs(...)] on instantiations place Verilog attributes on the resulting Verilog instantiation.
Type Aliases
You can now define type aliases like this:
type i32 = int<32>;
Super Traits
Super traits allow one trait to require another trait to also be implemented on a type. This is now supported in Spade as
trait Eq: PartialEq {}
which says that for a type to implement the Eq trait, PartialEq has to be implemented first.
#[inline]
You can now annotate units with #[inline] to inline them instead of generating a Verilog instance. This can be useful when doing things like operator overloading where adding additional hierarchy would make debugging harder. It is also used internally in some cases by the compiler in order to avoid some performance issues during Verilog generation and subsequent reading by synthesis tools.
Other changes
-
Added
#[deprecated = "..."]and#[deprecated(...)]attributes that allow deprecating items. -
Added support for
#boolgeneric parameters and logical operations on them inside const generics -
Added
inout<[T; N]>::read_write_itemsto accessinoutarrays element-wise -
Allow methods to be implemented on tuples
-
Fixed incorrect behaviour when capturing a variable more than once in a lambda.
-
Fixed warnings not being shown unless errors were produced too
-
Reduce the chance of seeing
Number<_> has no method <x>errors -
Fixed compiler panic when declaring variables inside pipelines using
declsyntax -
Automatically inline
gen_iftemporary units -
Integer literals can now specify a type suffix with no size (e.g.,
120u,33i) -
gen ifexpressions now can be written without anelseblock -
Integer comparison operators
<,>,<=and>=now can be used at the type level -
Start and end bounds on
..range indexing are now optional, defaulting to0and the target array size