Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Enums and Pattern Matching

The previously discussed type system features behave very similarly to other languages, but the enums in Spade are considerably more powerful than those found in languages like C and VHDL.

Like C and VHDL enums, an enum in Spade takes on one of several values, for example it can be used to represent different colors:

enum Color {
    Red,
    Green,
    Blue
}

Unlike C, enums are namespaced and statically typed, so you cannot convert them to or from integers, and to create them, you need to use their full name, i.e. Color::Red, not just Red. (The namespacing system will be discussed in more detail in a later chapter.)

To access an enum, you use the match expression, which like if "returns" a value for every branch. For example, to convert the Color enum into RGB values, you would write

fn to_rgb(color: Color) -> (uint<8>, uint<8>, uint<8>) {
    match color {
        Color::Red => (255, 0, 0),
        Color::Green => (0, 255, 0),
        Color::Blue => (0, 0, 255),
    }
}

Payload

So far, enums do not seem to be "considerably more powerful" than what you may be used to, but that is because we haven't discussed payloads. In addition to being one of several variants (Red, Green, Blue in this case), each variant can have associated values which are only present when the enum is that particular variant. For example, we can augment our Color enum with two new variants: Grayscale and Custom like this

enum Color {
    Red,
    Green,
    Blue,
    Gray{brightness: uint<8>},
    Custom{r: uint<8>, g: uint<8>, b: uint<8>},
}

When the enum variant is Gray, there is an additional field available containing the brightness of the color, and when the variant is Custom, the full RGB value is available. However, these are only accessible when the enum is of the right variant, and this is checked by the compiler. To access these variants, we use the match block again:

fn to_rgb(color: Color) -> (uint<8>, uint<8>, uint<8>) {
    match color {
        Color::Red => (255, 0, 0),
        Color::Green => (0, 255, 0),
        Color::Blue => (0, 0, 255),
        // Grayscale has the brightness value in all three channels
        Color::Gray(br) => (br, br, br),
        // For custom colors, just map the channels directly
        Color::Custom(r, g, b) => (r, g, b)
    }
}

Initializing enum variants is done the same way as strutcs, they are simply functions and as such can be instantiated with both named and positional arguments

let red = Color::Red; // Variants with no members do not need ()

// Positional arguments
let red = Color::Custom(255, 0, 0);
let bright_gray = Color::Gray(200);

// Named arguments
let red = Color::Custom$(r: 255, g: 0, b: 0);
let bright_gray = Color::Gray$(brightness: 200);

Pattern Matching

The branches in a pattern can be much more complex than simply matching on enum variants. To showcase this, we'll define a new enum with some more interesting variants

enum Example {
    Empty,
    Int{val: int<8>},
    Tuple{value: (int<8>, bool)},
    Struct{value: IntAndBool},
    Color{color: Color},
}

struct IntAndBool { a: int<8>, b: bool }

Like the destructuring we saw of structs and tuples before, you can destructure things recursively inside the match statement. For example:

match example {
    Example::Empty => 0,
    Example::Int(val) => val,
    Example::Tuple((a, b)) => a,
    Example::Struct(IntAndBool(a, b)) => a,
    Example::Color(_) => 0,
};

In the last branch, we use _ to ignore a member we don't care about, in this case the color.

If we do care about the color, we can do pattern matching recursively on enums too:

match example {
    Example::Empty => 0,
    Example::Int(_) => 1,
    Example::Tuple(_) => 2,
    Example::Struct(_) => 3,
    Example::Color(Color::Red) => 4,
    Example::Color(Color::Green) => 5,
    Example::Color(Color::Blue) => 6,
    Example::Color(_) => 7,
};

You may notice that the last branch in this example leaves out a few of the color members we defined previously, and has a branch with Example::Color(_). This demonstrates an important aspect of the match expression: the first branch which matches a value will be taken, so in this case red, green and blue are handled by the explicit branches, while Gray and Custom are handled by the "fallback branch". If the Example::Color(_) branch was placed before the others, it would match all color values.

The compiler checks that all match expressions are complete, i.e. there are no values for which no branch will match. As an example, if the final Example::Color(_) branch is omitted, the following error is produced.

error: Non-exhaustive match: patterns
    Example::Color(color: Color::Gray(brightness: 0..255)),
    Example::Color(color: Color::Custom(r: 0..255, g: 0..255, b: 0..255)) not covered
   ┌─ src/enums.spade:86:1
   │
86 │ ╭ match example {
87 │ │     Example::Empty => 0,
88 │ │     Example::Int(_) => 1,
89 │ │     Example::Tuple(_) => 2,
   · │
93 │ │     Example::Color(Color::Blue) => 6,
94 │ │ };
   │ ╰─^
       patterns Example::Color(color: Color::Gray(brightness: 0..255)),
                Example::Color(color: Color::Custom(r: 0..255, g: 0..255, b: 0..255))
                not covered

Patterns can also contain values. For example, you can pattern match on a bool

match b {
    true => 1,
    false => 0,
};

or integers:

match 0u8 {
    0 => 0,
    1 => 0,
    2 => 0,
    _ => 1,
};

Of course, these value patterns can be used recursively in other patterns:

let is_black = match color {
    Color::Gray(0) => true,
    Color::Custom(0, 0, 0) => true,
    _ => false,
};

Example: A state machine

One common use case of enums and match statements in Spade is to write state machines. To showcase this, we will write a simple state machine that blinks an LED thrice after a button is pressed. We will have two "main states": Idle and Blinking, where idle is waiting for the button to be pressed, and blinking is doing the actual blinking. To blink, we also have to keep track of

  • How many times we have left to blink
  • How long we have blinked

We can encode this as an enum like this

enum State {
    Idle,
    Blink{blinks_left: uint<2>, duration_left: uint<16>}
}

And then use pattern matching to implement the state machine itself like this:

reg(clk) state reset(rst: State::Idle) =
    match (state, btn) {
        // If we're not blinking and the user isn't pressing the button,
        // stay in the idle state
        (State::Idle, false) => state,
        // If we're in idle, and the user clicks the button, go to the blink
        // state
        (State::Idle, true) => {
            State::Blink$(blinks_left: 2, duration_left: 5_000)
        },
        // If we have no blinks left, and are done with this blink,
        // go back to idle
        (State::Blink$(blinks_left: 0, duration_left: 0), _) => {
            State::Idle
        },
        // If we have blinks left (blinks_left != 0), but we're done
        // with this blink, start the next blink
        (State::Blink$(blinks_left, duration_left: 0), _) => {
            State::Blink$(blinks_left: trunc(blinks_left-1), duration_left: 5_000)
        },
        // Otherwise, decrement the duration
        (State::Blink$(blinks_left, duration_left), _) => {
            State::Blink$(blinks_left, duration_left: trunc(duration_left - 1))
        },
    };

We match on both the current state, and input ((state, btn)), and ignore the input while we are blinking with a wildcard (_). In the blinking state, we use integer patterns and priority to handle three cases

  • We are done blinking
  • We are done with this blink
  • None of the above

The output of the module can also be written with a match block on the current state:

match state {
    State::Idle => false,
    State::Blink$(blinks_left: _, duration_left) => {
        if duration_left < 2_500 {
            false
        } else {
            true
        }
    }
}

You can try this example ▶️ in the playground

Exercises

Modify the blink_thrice entity to

  • Blink 4 times
  • Have a cooldown of one second between button presses

Here is a link to the code on the ▶️ playground