The Spade programming language

A hardware description language inspired by modern software languages.

  • Strong type system with algebraic data types
  • Pipelines as a language feature
  • Abstractions for module ports
  • A helpful compiler with great error messages
  • Combinatorial logic by default, with explicit registers
  • Cute mascot
// 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 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, describes the behaviour of a circuit from one cycle to the next. There is no magic execution model or 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.

You can 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)
}

Or 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:

Publications

To cite Spade itself, use the zenodo record.

Development

Spade is currently being developed as an Open Source project at the Division of Computer Engineering, Department of Electrical Engineering, Linköping University, Sweden.

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.