The Spade programming language

A hardware description language inspired by modern software software languages.

// A 3 stage CPU supporting Add, Sub, Set and Jump
pipeline(3) cpu(...) -> Option<int<32>> {
        let stall = stage(+1).is_jump;
        let pc = inst program_counter(
            clk, rst,
            stall,
            stage(execute).jump_target
        );
    reg;
        let insn = inst read_progmem(clk, pc);
        let is_jump = is_jump(insn);
        let insn = if stage(+1).is_jump {NOP} else {insn};
    reg;
        'execute
        let (opa, opb) = inst regfile(
            clk, insn, stage(writeback).result
        );

        let jump_target = match insn {
            Instruction::Jump(target) => Some(target),
            _ => None;
        };

        let alu_result = alu(insn, opa, opb);
    reg;
        'writeback
        let result = match insn {
            Instruction::Add(_, _, _) => Some(alu_result),
            Instruction::Sub(_, _, _) => Some(alu_result),
            Instrutcion::Set(_, _) => Some(alu_rsult),
            Instructions::Jump(_) => None
        };
        result
}
Learn More ⇩

Spade

Spade is a new hardware description language which aims to make hardware description easier and less error prone. It does this by taking lessons from software programming languages, and adding language level support for common hardware constructs, all without compromising the low level control over what hardware gets generated.

Using Spade

Spade is in its early stages, so everything is subject to change. You can build things with it but be prepared for bugs and missing features.

For an overview of what Spade is, have a look at this talk from June 2022. Documentation is very much a work in progress, but some is available in our book

The Swim build tool

The recommended way of using Spade is with its build tool Swim.

Learn more

Feel free to follow the development either on Gitlab or in our Discord community server.

An Overview of the Language

Basic Semantics

As a basic example, a counter with a runtime-selectable maximum value can be written as

entity counter(clk: clk, rst: bool, max: int<10>) -> int<10> {
    reg(clk) x reset (rst: 0) =
        if x == max {0} else {trunc(x + 1)};
}

Spade, like verilog and VHDL describe the behaviour of a circuit from one cycle to the next, there is no magic execution model with any overhead.

Most constructs in Spade are combinational. Sequential elements like registers are explicit language constructs.

Spade is also an expression based language. To conditionally assign a value to a variable, you assign the result of an if expression to the variable.

Language Level Pipelines

Pipelines are a first class construct in Spade, making retiming and re-pipelining trivial.

pipeline(4) X(clk: clk, a: int<32>, b: int<32>)
    -> int<64> {
        let product = a * b;
    reg * 3;
        let result = f(a, product);
    reg;
        result
}

Types and Enums

Spade has a powerful type system with structs, arrays, tuples and algebraic data types called enums. Unlike the enums of C and Verilog, they can have associated payload.

You can model a value which may or may not be available with the Option enum

enum Option<T> {
    Some(val: T),
    None
}

Or why not model the instruction set of a cpu?

enum Insn {
    Set { dreg: int<5>, val: int<32> },
    Add { dreg: int<5>, lhs: int<5>, rhs: int<5> },
    Sub { dreg: int<5>, lhs: int<5>, rhs: int<5> },
    Jump { target: int<32> }
}

The compiler ensures that fields can only be accessed if the value is of the correct type. For example, if the instruction is a jump, there is no way to access a dreg field

Pattern Matching

Enums work really well with pattern matching which allows you to check conditions and easily bind sub values to variables.

Easily build an ALU

fn alu(insn: Insn, rs1: int<32>, rs2: int<32>) -> int<32> {
    let adder_result = match insn {
        Insn::Add(_, _, _) => rs1 + rs2,
        Insn::Sub(_, _, _) => rs1 - rs2,
        Insn::Set(_, val) => sext(val),
        Insn::Jump(_) => 0,
    };

    trunc(adder_result)
}

Select the first of two values, and 0 if none are present

let result = match (a, b) {
    (Some (x), _) => x,
    (_, Some(x)) => x,
    _ => 0
}

Type inference

Spade has powerful type inference which gives you the benefits of static types, without all the typing.

Great Error Messages

The compiler should be your friend. Error messages give you as much information as possible

error: Match branches have incompatible type
30 +-Insn::Add(_, _, _) => rs1 + rs2,
   |                       ^^^^^^^^^
   |                       This branch has type int<33>
32 | Insn::Set(_, val) => val,
   |                      ^^^ But this one has type int<32>
   |
   = Expected: 33 in: int<33>
   =      Got: 32 in: int<32>

Bad error messages are considered a bug, please report them!

Helpful tooling

Spade comes with a great set of tools around the language

Planned features

There are also some planned features:

Development

Spade is currently being developed as an Open Source project at the Department of Electrical Engineering at Linköping university.

License

The Spade compiler and other tooling is licensed under the EUPL-1.2 license. The Spade standard library and this website is licensed under the terms of both the MIT license and the Apache license.