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 #bool generic parameters and logical operations on them inside const generics

  • Added inout<[T; N]>::read_write_items to access inout arrays 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 decl syntax

  • Automatically inline gen_if temporary units

  • Integer literals can now specify a type suffix with no size (e.g., 120u, 33i)

  • gen if expressions now can be written without an else block

  • Integer comparison operators <, >, <= and >= now can be used at the type level

  • Start and end bounds on .. range indexing are now optional, defaulting to 0 and the target array size