Spade 0.9.0
Posted 2024-07-04 by The Spade Developers
Happy summer everyone, today we're releasing v0.9.0 of Spade. This is one of our biggest releases yet and packs several really exciting improvements, especially around writing generic code.
As always, this blog post highlights the most exciting changes and you can read the whole change log here.
Generic impl blocks🔗
Spade has had impl
blocks for a while, but until now they have only been usable
on non-generic types limiting their usefulness. With 0.9.0 you can now write
generic impl
blocks such as the new standard library addition is_some
impl<T> Option<T> {
fn is_some(self) -> bool {
match self {
Some(_) => true,
None => false,
}
}
}
Thanks to Alex, Rene and Fabian for implementing this as part of their bachelor project at JKU!
Where clauses🔗
You can now use where
clauses to constrain integer parameters in generic units. For example, it is now possible to define a function with the same type signature as the +
operator for integers:
fn add<#In, #Out>(x: int<In>, y: int<In>) -> int<Out>
where Out: In + 1
Fixed point library🔗
Combining generic impl
blocks and where clauses, it is now possible to start working on a fixed point math library in Spade
As an example, here are the implementations of various arithmetic operators from that library
struct Fp<#Size, #FracBits> {
inner: int<Size>
}
impl<#Size, #FracBits> Fp<Size, FracBits> {
fn add<#OutSize>(self, other: Fp<Size, FracBits>) -> Fp<OutSize, FracBits>
where OutSize: Size + 1,
{
Fp(self.inner + other.inner)
}
fn sub<#OutSize>(self, other: Fp<Size, FracBits>) -> Fp<OutSize, FracBits>
where OutSize: Size + 1,
{
Fp(self.inner - other.inner)
}
fn mul<#OutSize, #OutBits>(self, other: Fp<Size, FracBits>) -> Fp<OutSize, OutBits>
where OutSize: Size+Size,
OutBits: FracBits+FracBits
{
Fp(self.inner * other.inner)
}
fn abs<#OutSize>(self) -> Fp<OutSize, FracBits>
where OutSize: Size + 1
{
if self.inner < 0 {
Fp(0 - self.inner)
} else {
Fp(sext(self.inner))
}
}
}
Named turbofish arguments🔗
The final improvements to generics in this release is named turbofish arguments. The turbofish (::<>
) is used to specify generic parameters if they cannot be inferred, however, previously you had to specify them positionally which gets confusing if you have a complex generic, for example1:
fn mul_fp<#Size, #FracBits, #OutSize, #OutBits>(self, other: Fp<Size, FracBits>) -> Fp<OutSize, OutBits>
where OutSize: Size+Size,
OutBits: FracBits+FracBits
{
Fp(self.inner * other.inner)
}
fp_mul::<16, 8, 20, 16>(...)
// ^^^^^^^^^^^^^ Hard to know what these numbers mean
With named turbofish parameters (::$<>
), you can specify the arguments by name, similar to specifying unit arguments
fp_mul::$<Size: 16, FracBits: 8, OutSize: 20, OutBits: 16>(...)
The keen reader may wonder why we don't use new fancy mul
method on
our FixedPoint
type. Unfortunately, methods don't support turbofish yet
:(
impl
blocks in modules🔗
The final change to generics is a fix for impl
blocks being broken when used in a mod
, for example
mod {
impl X {
... // Oh no, compiler bug! 😱
}
}
Thanks to DasLixou for fixing this and other issues after looking at the code around namespacing and realizing it was terrible in general!
Test improvements🔗
Moving on from generics and impl blocks, the cocotb test bench API has seen 2 nice convenience improvements.
Native python type support🔗
First, it is now possible to assign integers, booleans and lists directly to Spade types, resolving the need for some annoying stringifying
# Before you had to do this if you wanted to assign a python bool to a Spade input
s.i.bool_value = "true" if value else "false"
# Now you can just do
s.i.bool_value = value
# You also no longer need to stringify integers
s.i.int_value = 5
# And you can use python lists natively
s.i.some_integers = [i for i in range(0, 8)]
== operator🔗
This is a smaller change, but fixes an anoying issue where if you wanted to compare the output of a module with a Spade expression, you had to do
s.o.value_eq("[1,2,3]")
Now you can simply write
s.o == "[1,2,3]"
or combining both changes
s.o == [1,2,3]
Small fixes🔗
This release also includes several small convenience changes
- You can now suffix int literals with a signedness and size to help the type
inferer. For example
512u32
or123i64
- Added
/
and%
operators. These work for dividing by constant powers of two. For other values, the resulting hardware is likely too slow to be what you want so the compiler gives an error suggesting a different approach. If you do want to do combinational division or mod, you can usestd::ops::comb_div
orstd::ops::comb_mod
which is suggested by the compiler - Fixed incorrect code gen for enums with a single variant
- Swim cocotb tests now work on more operating systems