logo Spade Blog

Spade 0.6.0

Posted 2023-12-28 by The Spade Developers

This holiday season we're releasing 0.6.0 of Spade. The focus for this release has been fixing small annoyances in the language to make it nicer to work with and more complete.

Unsigned integers🔗

For a long time, Spade has only had one integer type: int which has been primarily used for signed integers but has been used to emulate unsigned integers where required. However, as it turns out, this results in incorrect values when additions add additional bits, as is the case for all additions in Spade.

This finally gave us the motivation to add a proper unsigned type, uint. It is a drop-in replacement for the old hacky signed-as-unsigned integer system and all the basic operators are overloaded to work with both int and uint. This uses a lot of compiler infrastructure that has been added for traits, which means that user defined operator overloading is on the horizon.


For too long, the Spade language has not had any fish in the language itself. That changes today! The "turbofish" ::<> syntax can be used to specify type parameters for units when they cannot be inferred, for example trunc::<int<8>, int<6>>(a).

Cast functions🔗

The standard library now contains several functions for casting between bit-like types such as integers and arrays of bools.

The star of the show is the new std::conv::unsafe::unsafe_cast which simply re-interprets the bits. As the name implies, the guarantees this provides for most types are pretty much "lmao, good luck", so it is primarily to be used internally in std::conv::int_to_bits and std::conv::bits_to_int which are safe to call.

For those times where you want to do cursed things with clocks, there is also std::conv::unsafe::clock_to_bool and std::conv::unsafe::bool_to_clock.

Finally, there is now a std::conv::flip_bits which reverses the bits in an array, since that is hard to express neatly in Spade without a builtin function.

Enum variant ergonomics🔗

In rust, if you instantiate an enum variant that has no parameters, like None, you don't have to call it like a function, i.e. None(). The same thing now applies in Spade, making the language feel more familiar to rust users, and a bit more consistent, since the parentheses were already not needed in match patterns.

To avoid massive breaking changes, the old style is also supported

Array range indexing🔗

You can now index arrays by ranges, as long as the ranges are statically known. For example: the following code now compiles and runs without error

let a = [1, 2, 3, 4];
assert a[1:3] == [2, 3];

Block comments🔗

3 years in, Spade finally has block comments naturally denoted by /* and */. Of course, block comments can be nested, unlike some languages

Call by name🔗

A internal change has been made to the way the compiler instantiates units. In the resulting Verilog code, arguments were previously passed positionally like this:

subunit unit_0(arg1, arg2, arg3);

In 0.6.0 they are now instead instantiated by name

subunit unit_0(.param1(arg1), .param2(arg2), .param3(arg3));

While this makes no difference for Spade code where the compiler knows the argument order, it did cause problems when instantiating external Verilog like vendor primitives, or VHDL code where argument order is less well known.

As an example, the following code now works as expected whereas it would behave incorrectly before

fn external_verilog_module(first_arg: bool, second_arg: bool, result: &mut bool) __builtin__
module external_verilog_module(input second_arg_i, input first_arg_i, output result_o);
  // ...