logo Spade Blog

Spade 0.10.0

Posted 2024-09-18 by The Spade Developers

After an extended summer break, it is time for us to release a new Spade version! This release includes several new exciting features, most notably: trait bounds, much improved support for methods, as well as pipelines with inferred depth.

Traits🔗

You can now specify trait requirements on generic parameters, for example, you can now describe a UART driver that can send any type as long as it has a method to convert it into a byte

trait AsByte {
  fn as_byte(self) -> uint<8>;
}

entity uart<T>(clk: clock, rst: bool, to_send: Option<T>)
  where T: AsByte
{ ... }

This feature was contributed by Fabian Bleck, Alex Pichler and Rene Wimmer, as part of their bachelor thesis at Johannes Kepler University. Thanks!

Multiple non-overlaping methods on generic types🔗

It is now possible to have more than one method on a type, as long as the generic parameters do not overlap. For example, the standard library now includes to_be_bytes and to_le_bytes for 16, 24 and 32 bit integers

impl uint<16> {
    /// Converts `self` into an array of bytes in big-endian order. 0x1234
    /// becomes [0x12, 0x34].
    fn to_be_bytes(self) -> [uint<8>; 2] {
        [trunc(self >> 8), trunc(self)]
    }

    /// Converts `self` into an array of bytes in little-endian order. 0x1234
    /// becomes [0x34, 0x12].
    fn to_le_bytes(self) -> [uint<8>; 2] {
        std::conv::flip_array(self.to_be_bytes())
    }
}

Turbofishes on method🔗

You can now also specify type parameters on methods using turbofishes. This is required if a method has type parameters that cannot be inferred, such as the size of a FIFO.

stream
  .inst into_fifo::<1024>(...) // FIFO with 1024 elements

Turbofish wildcards🔗

If you only want to specify some type parameter and infer the rest, you can now use wildcards (_). These can be placed anywhere in a type, for example, if you want to specify that a type parameter is an unsigned integer of an inferred size, you can use

inst method::<_, uint<_>>(...)

Const generics in turbofishes🔗

The final change to the turbofishes and type specifications in this release allows using const generics inside the type specification.

let x: uint<{10 + 12}> = ...;

Pipelines with a type-inferred depth🔗

Generic parameters can now be used in pipeline depths, allowing pipelines whose depth depends on the type of their inputs. For now, this is hard to make use of in practice, but in the future it will open up more possibilities, and making the change required a significant change in the compiler.

An actual use case that is supported today is to define a pipeline which delays its inputs a configurable amount

pipeline({N}) delay<#uint N>(clk: clock, t: T) -> T {
  reg * {N};
    t
}

More type expressions🔗

You can now use -, * and uint_bits_to_fit in const generics. The latter is extra exciting as you can now write a counter whose internal size will be inferred based on the maximum value

entity blink<#uint Period>(clk: clock, rst: bool) -> bool {
  reg(clk) counter: uint<{uint_bits_to_fit(Period)}> =
    if counter == Period {
      0
    } else {
      trunc(counter + 1)
    };
  counter > Period/2
}

Pattern matching on arrays🔗

Arrays can now be pattern-matched in match expressions, or unpacked in let bindings.

let [x0, x1] = array;

let xor = match array {
  [true, false] => true,
  [false, true] => true,
  _ => false,
}

Array shorthand syntax🔗

Arrays with N identical elements can now be constructed using [x; N], for example

let all_false = [false; 8];

Change syntax of generic numbers to use #uint or #int instead of🔗

As part of the changes made in this release, we had to change the syntax of units taking generic methods from #N to #uint N or #int N depending on the use case. Existing code can be easily ported by replacing # with #uint, #int N was not expressible in the language before.